"""Script to generate `crates/ruff_python_stdlib/src/sys/builtin_modules.rs`.

This script requires `uvx` to be available on PATH.
"""

from __future__ import annotations

import builtins
import subprocess
import textwrap
from functools import partial
from pathlib import Path
from typing import Final

MODULE_CRATE = "ruff_python_stdlib"
MODULE_PATH = Path("crates") / MODULE_CRATE / "src" / "sys" / "builtin_modules.rs"
PYTHON_VERSIONS: Final = range(8, 16)  # Python 3.8 through 3.15


def run(command: list[str]) -> str:
    try:
        proc = subprocess.run(
            command,
            check=True,
            text=True,
            capture_output=True,
        )
    except subprocess.CalledProcessError as e:
        print(e.stdout)
        print(e.stderr)
        raise
    return proc.stdout


def builtin_modules_on_version(minor_version: int) -> set[str]:
    command_1 = [
        "uv",
        "--managed-python",
        "python",
        "install",
        f"python3.{minor_version}",
        "--upgrade",
    ]
    run(command_1)
    command_2 = [
        "uvx",
        "--managed-python",
        f"python3.{minor_version}",
        "-c",
        "import sys; print(sys.builtin_module_names)",
    ]
    return set(eval(run(command_2)))


def generate_module() -> None:
    with MODULE_PATH.open("w") as f:
        print = partial(builtins.print, file=f)

        print(
            textwrap.dedent(
                """\
                //! This file is generated by `scripts/generate_builtin_modules.py`

                /// Return `true` if `module` is a [builtin module] on the given
                /// Python 3 version.
                ///
                /// "Builtin modules" are modules that are compiled directly into the
                /// Python interpreter. These can never be shadowed by first-party
                /// modules; the normal rules of module resolution do not apply to these
                /// modules.
                ///
                /// [builtin module]: https://docs.python.org/3/library/sys.html#sys.builtin_module_names
                #[allow(clippy::unnested_or_patterns)]
                pub fn is_builtin_module(minor_version: u8, module: &str) -> bool {
                    matches!((minor_version, module),
                """,
            )
        )

        modules_by_version = {
            minor_version: builtin_modules_on_version(minor_version)
            for minor_version in PYTHON_VERSIONS
        }

        # First, add a case for the modules that are in all versions.
        ubiquitous_modules = set.intersection(*modules_by_version.values())

        print("(_, ")
        for i, module in enumerate(sorted(ubiquitous_modules)):
            if i > 0:
                print(" | ", end="")
            print(f'"{module}"')
        print(")")

        # Next, add any version-specific modules.
        for minor_version in PYTHON_VERSIONS:
            version_modules = set.difference(
                modules_by_version[minor_version],
                ubiquitous_modules,
            )

            print(" | ")
            print(f"({minor_version}, ")
            for i, module in enumerate(sorted(version_modules)):
                if i > 0:
                    print(" | ", end="")
                print(f'"{module}"')
            print(")")

        print(")}")

    subprocess.run(["cargo", "fmt", "--package", MODULE_CRATE], check=True)


if __name__ == "__main__":
    generate_module()
