"""Access to the canonical scaffolding template (in ``mgf-standard/templates/``).

The template is the single source of truth for the shared scaffolding: the drift policy,
byte-exact shared files (e.g. ``verify_codeberg.py``), and the
``cookiecutter-mgf-project`` generator. ``mgf-fed drift-check`` verifies against it;
``mgf-fed scaffold`` applies it.

Policy namespace (M2 §3.3 — one namespace, documented precedence)
-----------------------------------------------------------------
The fleet policy lives in the ``[tool.mgf-fed]`` namespace: the CANONICAL source is
the ``[tool.mgf-fed.policy]`` table in **mgf-standard's own** ``pyproject.toml`` (the
policy-owning repo), carrying exactly the keys ``templates/policy.toml`` used to carry
(``present`` / ``py_typed`` / ``woodpecker_generated`` / ``woodpecker_required_steps``
/ ``[exact]`` / ``[pyproject]`` / ``[exempt]``). The standalone ``templates/policy.toml``
is the DEPRECATED fallback, read **iff** the pyproject section is absent — loading it
prints a one-line deprecation note (per policy path, to stderr) so the fleet hears
about the pending move without any gate weakening.
"""

from __future__ import annotations

import sys
import tomllib
from pathlib import Path
from typing import Any

from mgf.fed._roster import federation_root

# Policy paths whose deprecation note already printed (once per path per process —
# a drift sweep loads the policy once, but tools that reload must not spam).
_POLICY_NOTE_EMITTED: set[Path] = set()


def template_dir(root: Path | None = None) -> Path:
    return (root or federation_root()) / "mgf-standard" / "templates"


def _pyproject_policy(root: Path | None = None) -> dict[str, Any] | None:
    """The ``[tool.mgf-fed.policy]`` table from mgf-standard's pyproject, or None."""
    path = (root or federation_root()) / "mgf-standard" / "pyproject.toml"
    if not path.is_file():
        return None
    try:
        data = tomllib.loads(path.read_text(encoding="utf-8"))
    except (OSError, tomllib.TOMLDecodeError):
        return None
    tool = data.get("tool")
    if not isinstance(tool, dict):
        return None
    fed = tool.get("mgf-fed")
    if not isinstance(fed, dict):
        return None
    policy = fed.get("policy")
    return policy if isinstance(policy, dict) else None


def policy_source(root: Path | None = None) -> Path | None:
    """The file that provides the fleet policy under the documented precedence:
    mgf-standard's ``pyproject.toml`` (``[tool.mgf-fed.policy]`` present) →
    ``templates/policy.toml`` → None when neither resolves."""
    if _pyproject_policy(root) is not None:
        return (root or federation_root()) / "mgf-standard" / "pyproject.toml"
    fallback = template_dir(root) / "policy.toml"
    return fallback if fallback.is_file() else None


def load_policy(root: Path | None = None) -> dict[str, Any]:
    """Load the fleet scaffolding policy (see the module docstring for precedence).

    1. ``[tool.mgf-fed.policy]`` in ``mgf-standard/pyproject.toml`` — canonical.
    2. ``templates/policy.toml`` — deprecated fallback, read iff (1) is absent;
       prints a deprecation note on load (once per path per process).

    Raises ``FileNotFoundError`` when neither source exists (same contract as
    before the §3.3 unification).
    """
    canonical = _pyproject_policy(root)
    if canonical is not None:
        return canonical
    path = template_dir(root) / "policy.toml"
    if not path.is_file():
        raise FileNotFoundError(
            f"fleet policy not found: no [tool.mgf-fed.policy] in mgf-standard/pyproject.toml "
            f"and no {path}"
        )
    if path not in _POLICY_NOTE_EMITTED:
        _POLICY_NOTE_EMITTED.add(path)
        print(
            f"note: templates/policy.toml is DEPRECATED (M2 §3.3) — move the policy to "
            f"[tool.mgf-fed.policy] in mgf-standard/pyproject.toml (loaded fallback: {path})",
            file=sys.stderr,
        )
    data: dict[str, Any] = tomllib.loads(path.read_text(encoding="utf-8"))
    return data


def canonical_text(name: str, root: Path | None = None) -> str:
    return (template_dir(root) / name).read_text(encoding="utf-8")
