"""Build generated references for the datamodel-code-generator agent skill."""

from __future__ import annotations

import argparse
import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING

from datamodel_code_generator.arguments import arg_parser as datamodel_arg_parser
from datamodel_code_generator.cli_options import OptionCategory, get_option_meta

if TYPE_CHECKING:
    from collections.abc import Iterable, Sequence

REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUTPUT = REPO_ROOT / "skills" / "datamodel-code-generator" / "references" / "cli-options.md"

CATEGORY_DESCRIPTIONS = {
    OptionCategory.BASE: "Input sources, output paths, and schema version handling.",
    OptionCategory.TYPING: "Type annotation, import, and primitive type behavior.",
    OptionCategory.FIELD: "Field naming, aliases, defaults, and constraints.",
    OptionCategory.MODEL: "Generated model class and package behavior.",
    OptionCategory.TEMPLATE: "Formatting, custom templates, and generated file headers.",
    OptionCategory.OPENAPI: "OpenAPI-specific generation behavior.",
    OptionCategory.GRAPHQL: "GraphQL-specific generation behavior.",
    OptionCategory.GENERAL: "General utility, HTTP, checking, and project integration options.",
}
OPTION_CATEGORIES = (
    OptionCategory.BASE,
    OptionCategory.TYPING,
    OptionCategory.FIELD,
    OptionCategory.MODEL,
    OptionCategory.TEMPLATE,
    OptionCategory.OPENAPI,
    OptionCategory.GRAPHQL,
    OptionCategory.GENERAL,
)


def _clean_text(text: object) -> str:
    return re.sub(r"\s+", " ", str(text)).strip()


def _choice_label(choice: object) -> str:
    value = getattr(choice, "value", choice)
    return str(value)


def _format_help(action: argparse.Action) -> str:
    if action.help in {None, argparse.SUPPRESS}:
        return ""

    params = vars(action).copy()
    params["prog"] = "datamodel-codegen"
    try:
        help_text = action.help % params
    except (KeyError, TypeError, ValueError):
        help_text = action.help

    help_text = _clean_text(help_text)
    if action.choices:
        choices = ", ".join(f"`{_choice_label(choice)}`" for choice in action.choices)
        help_text = f"{help_text} Choices: {choices}."
    return help_text


def _iter_long_options() -> Iterable[tuple[str, argparse.Action]]:
    seen: set[str] = set()
    for action in datamodel_arg_parser._actions:  # noqa: SLF001
        if action.help is argparse.SUPPRESS:
            continue
        for option in action.option_strings:
            if not option.startswith("--") or option in seen:
                continue
            seen.add(option)
            yield option, action


def _category_for(option: str) -> OptionCategory:
    meta = get_option_meta(option)
    return meta.category if meta is not None else OptionCategory.GENERAL


def _line_for(option: str, action: argparse.Action) -> str:
    meta = get_option_meta(option)
    label = f"`{option}`"
    if meta is not None and meta.deprecated:
        label = f"{label} (deprecated)"

    aliases = [
        alias
        for alias in action.option_strings
        if alias != option and alias.startswith("-") and not alias.startswith("--")
    ]
    if aliases:
        label = f"{label} (alias: {', '.join(f'`{alias}`' for alias in aliases)})"

    help_text = _format_help(action)
    if meta is not None and meta.deprecated_message and meta.deprecated_message not in help_text:
        help_text = f"{help_text} Deprecated: {meta.deprecated_message}"
    return f"- {label}: {help_text}"


def build_cli_options_reference() -> str:
    """Build the skill CLI options reference from the current argparse definitions."""
    options_by_category: dict[OptionCategory, list[tuple[str, argparse.Action]]] = defaultdict(list)
    for option, action in _iter_long_options():
        options_by_category[_category_for(option)].append((option, action))

    lines = [
        "# CLI options reference",
        "",
        "<!-- generated by scripts/build_datamodel_codegen_skill_docs.py; do not edit by hand -->",
        "",
        "This file is generated from datamodel-codegen's argparse definitions and shared CLI option metadata.",
        "Regenerate it after CLI option changes:",
        "",
        "```bash",
        "uv run python scripts/build_datamodel_codegen_skill_docs.py",
        "```",
        "",
    ]

    for category in OPTION_CATEGORIES:
        options = options_by_category.get(category)
        if not options:
            continue
        lines.extend([
            f"## {category.value}",
            "",
            CATEGORY_DESCRIPTIONS[category],
            "",
        ])
        for option, action in options:
            lines.append(_line_for(option, action))
        lines.append("")

    return "\n".join(lines).rstrip() + "\n"


def main(argv: Sequence[str] | None = None) -> int:
    """Write or check the generated skill CLI options reference."""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--check", action="store_true", help="Fail when the generated reference is out of date.")
    parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT, help="Path to write or check.")
    args = parser.parse_args(argv)

    content = build_cli_options_reference()
    output = args.output
    if args.check:
        try:
            current = output.read_text(encoding="utf-8")
        except FileNotFoundError:
            print(f"{output} does not exist", file=sys.stderr)
            return 1
        if current != content:
            print(f"{output} is out of date; run scripts/build_datamodel_codegen_skill_docs.py", file=sys.stderr)
            return 1
        return 0

    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(content, encoding="utf-8")
    return 0


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