"""
Generate Lib/_opcode_metadata.py for RustPython bytecode.

This file generates opcode metadata that is compatible with CPython 3.14.
"""

import functools
import io
import itertools
import operator
import pathlib
import typing

from opcodes import OpcodeInfo
from utils import DEFAULT_INPUT, ROOT, get_conf, to_pascal_case, to_upper_snake_case

OUT_FILE = ROOT / "Lib/_opcode_metadata.py"


# Opcodes that needs to be first, regardless of their opcode ID.
PRIORITY_OPMAP = {
    "CACHE",
    "RESERVED",
    "RESUME",
    "INSTRUMENTED_LINE",
    "ENTER_EXECUTOR",
}

INDENT = " " * 4
INDENT2 = INDENT * 2


def main():
    override_conf = get_conf()
    inp = DEFAULT_INPUT.read_text()

    infos = tuple(OpcodeInfo.iter_infos(inp, override_conf))
    opcodes = tuple(itertools.chain.from_iterable(info.opcodes for info in infos))

    script_path = pathlib.Path(__file__).resolve().relative_to(ROOT).as_posix()
    out = io.StringIO()

    out.write(
        f"""
# This file is generated by {script_path}
# for RustPython bytecode format (CPython 3.14 compatible opcode numbers).
# Do not edit!
""".lstrip()
    )

    # _specializations
    out.write("\n")
    out.write("_specializations = {\n")

    deopts = functools.reduce(operator.ior, map(operator.attrgetter("deopts"), infos))

    for key, lst in deopts.items():
        key = to_upper_snake_case(key)
        out.write(f'{INDENT}"{key}": [\n')

        for item in map(to_upper_snake_case, lst):
            out.write(f'{INDENT2}"{item}",\n')

        out.write(f"{INDENT}],\n")

    out.write("}\n")

    # _specialized_opmap
    out.write("\n")
    out.write("_specialized_opmap = {\n")

    specialized = set(itertools.chain.from_iterable(deopts.values()))
    for opcode in sorted(opcodes, key=lambda op: op.cpython_name):
        if opcode.rust_name not in specialized:
            continue
        out.write(f"{INDENT}'{opcode.cpython_name}': {opcode.id},\n")

    out.write("}\n")

    # opmap
    out.write("\n")
    out.write("opmap = {\n")

    key = lambda op: (op.cpython_name not in PRIORITY_OPMAP, op.id)
    for opcode in sorted(opcodes, key=key):
        if opcode.rust_name in specialized:
            continue

        out.write(f"{INDENT}'{opcode.cpython_name}': {opcode.id},\n")

    out.write("}\n")

    # min
    out.write("\n")
    have_argument = min(opcode.id for opcode in opcodes if opcode.have_argument) - 1
    out.write(f"HAVE_ARGUMENT = {have_argument}\n")

    min_instrumented = min(opcode.id for opcode in opcodes if opcode.is_instrumented)
    out.write(f"MIN_INSTRUMENTED_OPCODE = {min_instrumented}\n")

    # write output
    generated = out.getvalue()
    OUT_FILE.write_text(generated)


if __name__ == "__main__":
    main()
