"""Generate ``PUBLIC_API.json`` from the live ``__all__`` of every public module.

v0.4 Phase G6. The markdown ``PUBLIC_API.md`` is the human-readable
contract; ``PUBLIC_API.json`` is the machine-readable companion.
Consumers and downstream CI scripts can ``grep`` / ``jq`` against
the JSON to verify their pinned imports still exist after an
upgrade.

Schema::

    {
      "__version__": "0.4.0",
      "modules": {
        "mgf.common": ["AppContext", "UnconfiguredWarning", ...],
        "mgf.common.exceptions": ["AppError", ...],
        ...
      },
      "__generated_at_module_count": 10,
      "__generated_total_names": 121
    }

Top-level keys with leading double-underscore are metadata —
consumer parsers iterate ``modules`` for the contract list.

Usage (one-off regeneration)::

    uv run python tools/generate_public_api_json.py

Pinned by ``tests/unit/test_public_api_json_pin.py`` — running
the generator MUST produce a result identical to the file on
disk; CI fails if they drift.
"""

from __future__ import annotations

import importlib
import json
from pathlib import Path
from typing import Any

# Order matches ``tests/unit/test_public_api_doc.py`` so the JSON
# layout is deterministic + matches what the AP-10 pin test
# already iterates.
PUBLIC_MODULES = (
    "mgf.common",
    "mgf.common.exceptions",
    "mgf.common.config",
    "mgf.common.observability",
    "mgf.common.settings",
    "mgf.common.typing",
    "mgf.common.progress",
    "mgf.common.progress.asyncio",
    "mgf.common.vault",
    "mgf.common.versioned",
    "mgf.common.cli",
    "mgf.common.process",
    "mgf.common.pytest_plugin",
    "mgf.common.registry",
    # mgf.common.fastapi.* was extracted to the mgf-fastapi sibling at
    # v0.28.0; mgf.common.http.* was extracted to the mgf-http sibling
    # at v0.29.0; mgf.common.db.* + mgf.common.alembic.* were extracted
    # to the mgf-sqlalchemy + mgf-alembic siblings at v0.30.0;
    # mgf.common.django.* was extracted to the mgf-django sibling at
    # v0.31.0 — all per ASPIRE-06 (federation split plan, see
    # docs/inprogress/federation_roadmap.md). With the django extraction
    # the mgf-common federation leaf ships pure-Python infrastructure
    # only; framework-coupled adapters all live in their respective
    # siblings.
    "mgf.common.providers",
    # 0.35.0 — federation primitives (experimental tier per AP-11):
    "mgf.common.flags",
    "mgf.common.schedule",
    # 0.36.0 — Wave 1 continuation:
    "mgf.common.plugins",
    "mgf.common.doctor",
    # 0.37.0 — Wave 3 second-tier primitives:
    "mgf.common.rate_limit",
    "mgf.common.idempotency",
    "mgf.common.health",
    "mgf.common.cache",
    # 0.48.0 — EH-11 resilience primitive (retry w/ exponential backoff + jitter):
    "mgf.common.retry",
    # 0.48.0 — public atomic-write (AP-14: promoted from _io so siblings stop
    # reaching into the closed-box surface):
    "mgf.common.io",
    # 0.49.0 — framework-neutral Svix webhook HMAC primitive (PAPER-26:
    # consolidates the dance hand-rolled in mgf-auth + mgf-fastapi):
    "mgf.common.webhooks",
    # The mgf-test-supervisor schema contract. Public + documented as
    # stable in PUBLIC_API.md; previously (pre-0.42.1) omitted here, so
    # the .json under-reported the surface vs the .md (F-013). Included
    # so the machine-readable pin file matches the human contract.
    "mgf.common.testing",
)


def build_payload() -> dict[str, Any]:
    """Build the full PUBLIC_API.json payload."""
    modules: dict[str, list[str]] = {}
    total = 0
    for mod_name in PUBLIC_MODULES:
        mod = importlib.import_module(mod_name)
        names = sorted(getattr(mod, "__all__", []))
        modules[mod_name] = names
        total += len(names)

    # ``__version__`` lives on the top-level package only.
    version = importlib.import_module("mgf.common").__version__
    # The ``__version__`` name itself is also part of mgf.common's
    # public surface (per AP-10 pin), even though it's NOT in
    # ``__all__`` — add it explicitly.
    if "__version__" not in modules["mgf.common"]:
        modules["mgf.common"].append("__version__")
        modules["mgf.common"].sort()
        total += 1

    return {
        "__version__": version,
        "modules": modules,
        "__generated_at_module_count": len(PUBLIC_MODULES),
        "__generated_total_names": total,
    }


def write_json(path: Path) -> None:
    """Write the payload to ``path`` with stable formatting.

    Stable formatting (sorted keys, 2-space indent, trailing
    newline) makes diffs minimal — adding a single public name
    produces a single-line diff.
    """
    payload = build_payload()
    text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
    path.write_text(text, encoding="utf-8")


def main() -> int:
    """CLI entry — regenerate PUBLIC_API.json at the repo root."""
    repo_root = Path(__file__).resolve().parent.parent
    target = repo_root / "PUBLIC_API.json"
    write_json(target)
    print(f"wrote {target}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
