"""``ALL_MANAGED`` resolution — the fleet of repos a fleet-wide surface covers.

The set of managed repos is the union of the two library registries
(``MGF_LIBRARIES.json`` ``libraries[].repo`` + ``SOOTH_LIBRARIES.json``
``repos[].name``) and the Woodpecker activated-repos list (SPEC §3). Reading the
committed registries is what lets C5 know a *newly activated* repo must carry the
fleet deploy key even before a human updates a doc.
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from .model import ALL_MANAGED

if TYPE_CHECKING:
    from collections.abc import Iterable
    from pathlib import Path


def normalize_repos(repos_decl: Any, *, managed: Iterable[str]) -> set[str]:
    """Resolve a ``forge-deploy-key`` ``repos`` declaration to a SET of repo names.

    The single parser shared by the doctor coverage check (C5) and the sync
    coverage sweep, so the two never diverge on shape. Accepts, in every form the
    live registry uses:

    * the ``"ALL_MANAGED"`` / ``"*"`` sentinel → the resolved managed fleet;
    * a **comma-separated string** (the live steward-key shape:
      ``"first-light,mgf-standard,…"``) → split + stripped into individual names;
    * a proper list (each item may itself be a sentinel or comma-string).

    Without this, a comma-separated string is mistaken for ONE literal repo named
    ``"a,b,c"`` — the false-``register`` bug (never a real repo, so it reads
    "missing" and plans a bogus registration).
    """
    managed_set = {str(m) for m in managed}
    items = repos_decl if isinstance(repos_decl, (list, tuple)) else [repos_decl]
    out: set[str] = set()
    for item in items:
        s = str(item).strip()
        if s in (ALL_MANAGED, "*"):
            out |= managed_set
        else:
            out |= {r.strip() for r in s.split(",") if r.strip()}
    return out


@dataclass(frozen=True)
class ManagedResolution:
    """The resolved ``ALL_MANAGED`` fleet + its provenance (SPEC section 4: computed
    once, logged with count + source, stable across runs on the same inputs).
    """

    repos: frozenset[str]
    sources: dict[str, int]  # source label -> repo count contributed (pre-union)

    @property
    def count(self) -> int:
        return len(self.repos)

    def to_json(self) -> dict[str, object]:
        return {
            "count": self.count,
            "sources": dict(self.sources),
            "repos": sorted(self.repos),
        }


def _names_from_libraries(path: Path) -> set[str]:
    if not path.is_file():
        return set()
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return set()
    out: set[str] = set()
    # MGF shape: {"libraries": [{"repo": ...}]}
    for lib in data.get("libraries", []) or []:
        name = lib.get("repo") or lib.get("name") or lib.get("slug")
        if name:
            out.add(str(name))
    # Sooth shape: {"repos": [{"name": ..., "slug": ...}]}
    for repo in data.get("repos", []) or []:
        if isinstance(repo, str):
            out.add(repo)
        else:
            name = repo.get("name") or repo.get("slug") or repo.get("repo")
            if name:
                out.add(str(name))
    return out


def resolve_all_managed(
    *,
    fed_root: Path,
    activated_repos: Iterable[str] = (),
) -> set[str]:
    """Every managed repo name: both library registries plus Woodpecker-activated."""
    return set(resolve_all_managed_detail(fed_root=fed_root, activated_repos=activated_repos).repos)


def resolve_all_managed_detail(
    *,
    fed_root: Path,
    activated_repos: Iterable[str] = (),
) -> ManagedResolution:
    """The defined union of MGF_LIBRARIES, SOOTH_LIBRARIES and Woodpecker-activated
    repos, with per-source counts for logging. Deterministic: the same inputs always
    resolve to the identical set + provenance (the coverage sweep iterates this set)."""
    mgf = _names_from_libraries(fed_root / "mgf-standard" / "MGF_LIBRARIES.json")
    sooth = _names_from_libraries(fed_root / "sooth-standard" / "SOOTH_LIBRARIES.json")
    activated = {str(r) for r in activated_repos}
    sources = {
        "MGF_LIBRARIES": len(mgf),
        "SOOTH_LIBRARIES": len(sooth),
        "woodpecker_activated": len(activated),
    }
    return ManagedResolution(repos=frozenset(mgf | sooth | activated), sources=sources)
