"""Cross-platform per-OS directory helpers.

Centralises the XDG / macOS Application Support / Windows AppData
mapping so :func:`default_config_path` and any future state-/data-dir
helper share one cross-platform map. Always honours ``XDG_*`` env
vars when set so power-users keep control across all platforms.

Resolved at call time (rule CF-04 lazy resolution) so tests
monkeypatching ``HOME`` / ``APPDATA`` per test get the right path
for that test.

Pure stdlib — no other mgf.common imports.
"""

from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import Literal

PathKind = Literal["config", "state", "data"]


def platform_dir(kind: PathKind) -> Path:
    """Return the per-OS base directory for ``kind``.

    The mapping (without env overrides):

    +---------+-------------------------+-------------------------------------+----------------------------+
    | kind    | Linux                   | macOS                               | Windows                    |
    +=========+=========================+=====================================+============================+
    | config  | ``~/.config``           | ``~/Library/Application Support``   | ``%APPDATA%``              |
    +---------+-------------------------+-------------------------------------+----------------------------+
    | state   | ``~/.local/state``      | ``~/Library/Application Support``   | ``%LOCALAPPDATA%``         |
    +---------+-------------------------+-------------------------------------+----------------------------+
    | data    | ``~/.local/share``      | ``~/Library/Application Support``   | ``%LOCALAPPDATA%``         |
    +---------+-------------------------+-------------------------------------+----------------------------+

    XDG always wins when set (rule CF-09 — power users keep control
    across all platforms).
    """
    xdg_var = {
        "config": "XDG_CONFIG_HOME",
        "state": "XDG_STATE_HOME",
        "data": "XDG_DATA_HOME",
    }[kind]
    xdg = os.environ.get(xdg_var)
    if xdg:
        return Path(xdg)

    if os.name == "nt":  # pragma: no cover — Linux CI; covered by smoke
        env_var = "APPDATA" if kind == "config" else "LOCALAPPDATA"
        env_value = os.environ.get(env_var)
        if env_value:
            return Path(env_value)
        # Fall back to the canonical Roaming/Local path beneath
        # USERPROFILE; matches what %APPDATA% resolves to by default.
        suffix = "Roaming" if kind == "config" else "Local"
        return Path.home() / "AppData" / suffix

    if sys.platform == "darwin":  # pragma: no cover — Linux CI; covered by smoke
        return Path.home() / "Library" / "Application Support"

    # Linux / other Unix
    fallback = {
        "config": ".config",
        "state": ".local/state",
        "data": ".local/share",
    }[kind]
    return Path.home() / fallback


def default_config_path(app: str | None = None) -> Path:
    """Return the default ``config.yaml`` path for ``app`` (rule CF-09).

    When ``app`` is ``None``, reads from :func:`mgf.common.app_name`
    so the consumer's configured identity is used.
    """
    if app is None:
        from mgf.common._identity import app_name

        app = app_name()
    return platform_dir("config") / app / "config.yaml"


def expand_path(v: object) -> object:
    """Expand ``~`` and ``$VARS`` in a Path-typed field.

    Accepts ``str`` / ``Path`` / anything else (returns unchanged).
    Lazy resolution per rule CF-04: paths are NOT expanded at
    module import time, so tests that monkeypatch ``HOME`` per
    test get the right path for that test.

    .. warning::
       This does **not** sandbox or contain the result — it only
       runs ``expandvars`` + ``expanduser``. A value like
       ``"$BASE/../elsewhere"`` is expanded verbatim and may point
       outside any intended directory. That is fine for the
       operator-controlled config/env fields this is built for
       (the operator already runs with the app's privileges). A
       consumer deriving a path from UNTRUSTED input must contain
       it themselves (e.g. ``resolve()`` + ``is_relative_to(base)``)
       — ``expand_path`` makes no containment guarantee.

    Plug into a Pydantic ``BaseSettings`` subclass via::

        @field_validator(*_PATH_FIELDS, mode="before")
        @classmethod
        def _expand_paths(cls, v: object) -> object:
            return expand_path(v)
    """
    if isinstance(v, str):
        v = Path(v)
    if isinstance(v, Path):
        return Path(os.path.expandvars(str(v))).expanduser()
    return v


__all__ = [
    "PathKind",
    "default_config_path",
    "expand_path",
    "platform_dir",
]
