from __future__ import annotations

import argparse
from dataclasses import dataclass
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
PACKAGE_ROOT = ROOT / "src" / "pinbridge_sdk"
REFERENCE_ROOT = ROOT / "docs" / "reference"
GENERATED_HEADER = "<!-- Generated by scripts/generate_reference.py. Do not edit by hand. -->\n"


@dataclass(frozen=True)
class ModuleGroup:
    name: str
    package: str
    source_dir: Path
    output_dir: Path
    title_overrides: dict[str, str]
    excluded_files: set[str]


MODULE_GROUPS = [
    ModuleGroup(
        name="resources",
        package="pinbridge_sdk.resources",
        source_dir=PACKAGE_ROOT / "resources",
        output_dir=REFERENCE_ROOT / "resources",
        title_overrides={"api_keys": "API Keys"},
        excluded_files={"__init__.py", "base.py"},
    ),
    ModuleGroup(
        name="models",
        package="pinbridge_sdk.models",
        source_dir=PACKAGE_ROOT / "models",
        output_dir=REFERENCE_ROOT / "models",
        title_overrides={"api_keys": "API Keys"},
        excluded_files={"__init__.py", "base.py"},
    ),
]


def pretty_name(module_name: str, title_overrides: dict[str, str]) -> str:
    if module_name in title_overrides:
        return title_overrides[module_name]
    return module_name.replace("_", " ").title()


def render_page(title: str, dotted_path: str) -> str:
    return (
        GENERATED_HEADER
        + f"# {title}\n\n"
        + f"::: {dotted_path}\n"
        + "    options:\n"
        + "      members_order: source\n"
        + "      show_root_heading: true\n"
        + "      show_root_full_path: false\n"
        + "      show_source: false\n"
    )


def render_index(title: str, items: list[tuple[str, str]]) -> str:
    lines = [GENERATED_HEADER, f"# {title}\n", "\n"]
    for label, rel_path in items:
        lines.append(f"- [{label}]({rel_path})\n")
    return "".join(lines)


def build_expected_files() -> dict[Path, str]:
    expected: dict[Path, str] = {
        REFERENCE_ROOT
        / "index.md": (
            GENERATED_HEADER
            + "# Reference\n\n"
            + "- [Clients](client.md)\n"
            + "- [Errors](errors.md)\n"
            + "- [Resources](resources/index.md)\n"
            + "- [Models](models/index.md)\n"
        ),
        REFERENCE_ROOT
        / "client.md": (
            GENERATED_HEADER
            + "# Clients\n\n"
            + "::: pinbridge_sdk.client.PinbridgeClient\n"
            + "    options:\n"
            + "      show_root_heading: true\n"
            + "      show_root_full_path: false\n"
            + "      show_source: false\n"
            + "      members_order: source\n\n"
            + "::: pinbridge_sdk.async_client.AsyncPinbridgeClient\n"
            + "    options:\n"
            + "      show_root_heading: true\n"
            + "      show_root_full_path: false\n"
            + "      show_source: false\n"
            + "      members_order: source\n"
        ),
        REFERENCE_ROOT
        / "errors.md": (
            GENERATED_HEADER
            + "# Errors\n\n"
            + "::: pinbridge_sdk.errors\n"
            + "    options:\n"
            + "      members_order: source\n"
            + "      show_root_heading: true\n"
            + "      show_root_full_path: false\n"
            + "      show_source: false\n"
        ),
    }

    for group in MODULE_GROUPS:
        entries: list[tuple[str, str]] = []
        for file_path in sorted(group.source_dir.glob("*.py")):
            if file_path.name in group.excluded_files:
                continue

            module_name = file_path.stem
            title = pretty_name(module_name, group.title_overrides)
            dotted_path = f"{group.package}.{module_name}"
            output_path = group.output_dir / f"{module_name}.md"
            expected[output_path] = render_page(title, dotted_path)
            entries.append((title, f"{module_name}.md"))

        expected[group.output_dir / "index.md"] = render_index(group.name.title(), entries)

    return expected


def check_generated_files(expected: dict[Path, str]) -> int:
    mismatches: list[str] = []

    for path, content in expected.items():
        if not path.exists():
            mismatches.append(f"missing: {path.relative_to(ROOT)}")
            continue
        if path.read_text() != content:
            mismatches.append(f"outdated: {path.relative_to(ROOT)}")

    generated_dirs = [REFERENCE_ROOT, *(group.output_dir for group in MODULE_GROUPS)]
    expected_paths = {path.resolve() for path in expected}
    for directory in generated_dirs:
        for path in sorted(directory.glob("*.md")):
            if path.resolve() not in expected_paths:
                mismatches.append(f"unexpected: {path.relative_to(ROOT)}")

    if mismatches:
        print("\n".join(mismatches))
        return 1
    return 0


def write_generated_files(expected: dict[Path, str]) -> None:
    for path, content in expected.items():
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content)


def main() -> int:
    parser = argparse.ArgumentParser(description="Generate mkdocstrings reference pages.")
    parser.add_argument("--check", action="store_true", help="Fail if generated pages are stale.")
    args = parser.parse_args()

    expected = build_expected_files()
    if args.check:
        return check_generated_files(expected)

    write_generated_files(expected)
    return 0


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