#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from pathlib import Path

GROUNDED_LOCOMOTIVE_PREFIX = {
    1: "2-D-2",
    2: "E-88",
    3: "Adler  2-2-2",
    4: "USA 103",
    5: "American 4-4-0",
    6: "Atlantic 4-4-2",
    7: "Baldwin 0-6-0",
    8: "Be 5/7",
    9: "Beuth 2-2-2",
    10: "Big Boy 4-8-8-4",
    11: "C55 Deltic",
    12: "Camelback 0-6-0",
    13: "Challenger 4-6-6-4",
    14: "Class 01 4-6-2",
    15: "Class 103",
    16: "Class 132",
    17: "Class 500 4-6-0",
    18: "Class 9100",
    19: "Class EF 66",
    20: "Class 6E",
    21: "Consolidation 2-8-0",
    22: "Crampton 4-2-0",
    23: "DD 080-X",
    24: "DD40AX",
    25: "Duke Class 4-4-0",
    26: "E18",
    27: "E428",
    28: "Brenner E412",
    29: "E60CP",
    30: "Eight Wheeler 4-4-0",
    31: "EP-2 Bipolar",
    32: "ET22",
    33: "F3",
    34: "Fairlie 0-6-6-0",
    35: "Firefly 2-2-2",
    36: "FP45",
    37: "Ge 6/6 Crocodile",
    38: "GG1",
    39: "GP7",
    40: "H10 2-8-2",
    41: "HST 125",
    42: "Kriegslok 2-10-0",
    43: "Mallard 4-6-2",
    44: "Norris 4-2-0",
    45: "Northern 4-8-4",
    46: "Orca NX462",
    47: "Pacific 4-6-2",
    48: "Planet 2-2-0",
    49: "Re 6/6",
    50: "Red Devil 4-8-4",
    51: "S3 4-4-0",
    52: "NA-90D",
    53: "Shay (2-Truck)",
    54: "Shinkansen Series 0",
    55: "Stirling 4-2-2",
    56: "Trans-Euro",
    57: "V200",
    58: "VL80T",
    59: "GP 35",
    60: "U1",
    61: "Zephyr",
}


def normalize_world_scalar_key(label: str) -> str:
    parts: list[str] = []
    current = []
    for ch in label:
        if ch.isalnum():
            current.append(ch.lower())
        elif current:
            parts.append("".join(current))
            current = []
    if current:
        parts.append("".join(current))
    return "world." + "_".join(parts)


def locomotive_availability_label(locomotive_id: int) -> str:
    name = GROUNDED_LOCOMOTIVE_PREFIX.get(locomotive_id)
    if name is not None:
        return f"{name} Availability"
    return f"Lower-Band Locomotive Availability Slot {locomotive_id}"


def locomotive_cost_label(locomotive_id: int) -> str:
    name = GROUNDED_LOCOMOTIVE_PREFIX.get(locomotive_id)
    if name is not None:
        return f"{name} Cost"
    return f"Lower-Band Locomotive Cost Slot {locomotive_id}"


def cargo_price_label(descriptor_id: int, binding: dict[str, object] | None) -> str:
    if binding is not None:
        return f"{binding['cargo_name']} Price"
    return f"Named Cargo Price Slot {descriptor_id - 105}"


def load_cargo_bindings(raw_table_path: Path) -> dict[int, dict[str, object]]:
    bindings_path = raw_table_path.parent / "event-effects-cargo-bindings.json"
    if not bindings_path.exists():
        return {}
    artifact = json.loads(bindings_path.read_text(encoding="utf-8"))
    return {
        int(binding["descriptor_id"]): binding
        for binding in artifact.get("bindings", [])
    }


def load_building_bindings(raw_table_path: Path) -> dict[int, dict[str, object]]:
    bindings_path = raw_table_path.parent / "event-effects-building-bindings.json"
    if not bindings_path.exists():
        return {}
    artifact = json.loads(bindings_path.read_text(encoding="utf-8"))
    return {
        int(binding["descriptor_id"]): binding
        for binding in artifact.get("bindings", [])
    }


def classify(
    row: dict[str, object],
    cargo_bindings: dict[int, dict[str, object]],
    building_bindings: dict[int, dict[str, object]],
) -> dict[str, object]:
    descriptor_id = int(row["descriptor_id"])
    label = str(row["label"])
    signature_byte_0x63 = int(row["signature_byte_0x63"])
    signature_byte_0x64 = int(row["signature_byte_0x64"])

    parameter_family = "recovered_effect_table_descriptor"
    runtime_key = None
    runtime_status = "evidence_blocked"
    executable_in_runtime = False

    if 4 <= descriptor_id <= 7:
        parameter_family = "scenario_outcome_shell_action"
        runtime_status = "shell_owned"
    elif descriptor_id in {10, 11, 12, 23}:
        parameter_family = "company_confiscation_variant"
        runtime_status = "variant_or_scope_blocked"
    elif 17 <= descriptor_id <= 21:
        parameter_family = "company_or_territory_destruction_variant"
        runtime_status = "shell_owned"
    elif descriptor_id == 24:
        parameter_family = "control_transfer_shell_action"
        runtime_status = "shell_owned"
    elif descriptor_id in {55, 58}:
        parameter_family = "company_finance_shell_scalar"
        runtime_status = "shell_owned"
    elif descriptor_id in {56, 57}:
        parameter_family = "company_governance_scalar"
        runtime_status = "executable"
        executable_in_runtime = True
    elif 39 <= descriptor_id <= 54:
        parameter_family = "runtime_variable_scalar"
        runtime_status = "executable"
        executable_in_runtime = True
    elif 59 <= descriptor_id <= 104:
        parameter_family = "world_scalar_override"
        runtime_key = normalize_world_scalar_key(label)
        runtime_status = "executable"
        executable_in_runtime = True
    elif descriptor_id == 105:
        parameter_family = "cargo_price_scalar"
        runtime_status = "executable"
        executable_in_runtime = True
    elif 106 <= descriptor_id <= 176:
        parameter_family = "cargo_price_scalar"
        binding = cargo_bindings.get(descriptor_id)
        label = cargo_price_label(descriptor_id, binding)
        if binding is not None:
            runtime_status = "executable"
            executable_in_runtime = True
    elif 177 <= descriptor_id <= 179:
        parameter_family = "cargo_production_scalar"
        runtime_status = "executable"
        executable_in_runtime = True
    elif 180 <= descriptor_id <= 229:
        parameter_family = "cargo_production_scalar"
        binding = cargo_bindings.get(descriptor_id)
        if binding is not None:
            label = f"{binding['cargo_name']} Production"
            runtime_status = "executable"
            executable_in_runtime = True
    elif 230 <= descriptor_id <= 240:
        parameter_family = "cargo_production_scalar"
        runtime_status = "executable"
        executable_in_runtime = True
    elif 241 <= descriptor_id <= 351:
        parameter_family = "locomotive_availability_scalar"
        locomotive_id = descriptor_id - 240
        label = locomotive_availability_label(locomotive_id)
        if locomotive_id <= len(GROUNDED_LOCOMOTIVE_PREFIX):
            runtime_status = "executable"
            executable_in_runtime = True
    elif 457 <= descriptor_id <= 474:
        parameter_family = "locomotive_availability_scalar"
        label = f"Upper-Band Locomotive Availability Slot {descriptor_id - 456}"
    elif 352 <= descriptor_id <= 451:
        parameter_family = "locomotive_cost_scalar"
        locomotive_id = descriptor_id - 351
        label = locomotive_cost_label(locomotive_id)
        if locomotive_id <= len(GROUNDED_LOCOMOTIVE_PREFIX):
            runtime_status = "executable"
            executable_in_runtime = True
    elif 475 <= descriptor_id <= 502:
        parameter_family = "locomotive_cost_scalar"
        label = f"Upper-Band Locomotive Cost Slot {descriptor_id - 474}"
    elif descriptor_id == 453:
        parameter_family = "territory_access_cost_scalar"
        runtime_status = "executable"
        executable_in_runtime = True
    elif descriptor_id == 454:
        parameter_family = "world_flag_toggle"
        runtime_key = "world.all_steam_locos_available"
        runtime_status = "executable"
        executable_in_runtime = True
    elif descriptor_id == 455:
        parameter_family = "world_flag_toggle"
        runtime_key = "world.all_diesel_locos_available"
        runtime_status = "executable"
        executable_in_runtime = True
    elif descriptor_id == 456:
        parameter_family = "world_flag_toggle"
        runtime_key = "world.all_electric_locos_available"
        runtime_status = "executable"
        executable_in_runtime = True
    elif 503 <= descriptor_id <= 613:
        parameter_family = "world_building_spawn"
        binding = building_bindings.get(descriptor_id)
        if binding is not None:
            candidate_name = binding.get("candidate_name")
            if candidate_name:
                label = f"Add Building {candidate_name}"
            else:
                label = f"Add Building Candidate Slot {int(binding['candidate_id']) + 1}"
        else:
            label = f"Add Building Candidate Slot {descriptor_id - 502}"
        runtime_status = "shell_owned"
    elif "Earthquake" in label or "Storm" in label:
        parameter_family = "world_disaster_scalar"

    return {
        "descriptor_id": descriptor_id,
        "label": label,
        "target_mask_bits": int(row["target_mask_bits"]),
        "parameter_family": parameter_family,
        "runtime_key": runtime_key,
        "runtime_status": runtime_status,
        "executable_in_runtime": executable_in_runtime,
    }


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Build a checked-in semantic catalog over the raw RT3 EventEffects table."
    )
    parser.add_argument("raw_table", type=Path)
    parser.add_argument("out", type=Path)
    args = parser.parse_args()

    raw_artifact = json.loads(args.raw_table.read_text(encoding="utf-8"))
    cargo_bindings = load_cargo_bindings(args.raw_table)
    building_bindings = load_building_bindings(args.raw_table)
    descriptors = [
        classify(row, cargo_bindings, building_bindings)
        for row in raw_artifact["descriptors"]
    ]
    artifact = {
        "descriptor_count": len(descriptors),
        "raw_table_binary_sha256": raw_artifact.get("binary_sha256"),
        "semantic_catalog_version": 1,
        "descriptors": descriptors,
    }
    args.out.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()
