"""Regenerate per-module mypy disable_error_code overrides in pyproject.toml.

Usage:
    python -m tools.regenerate_mypy_ignores
"""

from collections import defaultdict
from pathlib import Path
import re

from mypy import api as mypy_api

START_MARKER = "# Autogenerated mypy overrides: start"
END_MARKER = "# Autogenerated mypy overrides: end"


def run_mypy() -> list[str]:
    """Run mypy with no error codes disabled and capture output."""
    stdout, stderr, _ = mypy_api.run(
        [
            "zigpy",
            "--no-error-summary",
            "--config-file=/dev/null",
            "--ignore-missing-imports",
        ]
    )
    return stdout.splitlines() + stderr.splitlines()


def parse_errors(lines: list[str]) -> dict[str, dict[str, int]]:
    """Parse mypy output into {module: {error_code: count}}."""
    errors_by_module: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))

    for line in lines:
        match = re.match(r"^([^:]+):.* \[([a-z-]+)\]$", line.strip())
        if match:
            filepath, error_code = match.groups()
            module = filepath.replace("/", ".").replace(".py", "")
            module = module.removesuffix(".__init__")
            errors_by_module[module][error_code] += 1

    return dict(errors_by_module)


def format_override(module: str, codes: dict[str, int]) -> str:
    """Format a single [[tool.mypy.overrides]] section."""
    lines = [
        "[[tool.mypy.overrides]]",
        f'module = "{module}"',
        "disable_error_code = [",
    ]
    for code in sorted(codes.keys()):
        lines.append(f'    "{code}",  # {codes[code]}')  # noqa: PERF401
    lines.append("]")
    return "\n".join(lines)


def generate_overrides(errors_by_module: dict[str, dict[str, int]]) -> str:
    """Generate all override sections."""
    # Aggregate zigpy.zcl.clusters.* into one entry
    clusters_errors: dict[str, int] = defaultdict(int)
    non_clusters: dict[str, dict[str, int]] = {}

    for module, codes in errors_by_module.items():
        if module.startswith("zigpy.zcl.clusters.") or module == "zigpy.zcl.clusters":
            for code, count in codes.items():
                clusters_errors[code] += count
        else:
            non_clusters[module] = codes

    # Sort by (total error count, module name) so easiest fixes are at the top
    sorted_modules = sorted(
        non_clusters.items(), key=lambda x: (sum(x[1].values()), x[0])
    )

    sections = [format_override(module, codes) for module, codes in sorted_modules]

    # Add aggregated clusters entry at the end
    if clusters_errors:
        sections.append(format_override("zigpy.zcl.clusters.*", dict(clusters_errors)))

    return "\n\n".join(sections)


def main():
    pyproject_path = Path("pyproject.toml")
    content = pyproject_path.read_text()

    lines = run_mypy()
    errors_by_module = parse_errors(lines)
    overrides_content = generate_overrides(errors_by_module)

    # Replace content between markers
    pattern = re.compile(
        rf"({re.escape(START_MARKER)})\n.*?\n({re.escape(END_MARKER)})",
        re.DOTALL,
    )
    new_content = pattern.sub(rf"\1\n{overrides_content}\n\2", content)

    if new_content != content:
        pyproject_path.write_text(new_content)


if __name__ == "__main__":
    main()
