#!/usr/bin/env python3

from __future__ import annotations

import sys
from pathlib import Path
import re

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from shared.cdefs import expand_def_file


ROOT = Path(__file__).resolve().parents[2]
TR4_OBJECTS_H = ROOT / "src" / "tomb4" / "TOMB4" / "game" / "objects.h"
TRX_OBJECTS_DEF = ROOT / "src" / "trx" / "game" / "catalog" / "objects.def"
OUTPUT_PATH = ROOT / "data" / "trx" / "ship" / "games" / "tr4" / "catalog_objects.csv"

FORCED_ROWS: dict[int, str] = {
    465: "O_LARA_SKIN_SWAP_1",
    466: "O_LARA_SKIN_SWAP_2",
    467: "O_LARA_SKIN_SWAP_3",
    468: "O_LARA_SKIN_SWAP_4",
    469: "O_LARA_SKIN_SWAP_5",
    470: "O_LARA_SKIN_SWAP_6",
    471: "O_LARA_SKIN_SWAP_7",
    472: "O_LARA_SKIN_SWAP_8",
    473: "O_LARA_SKIN_SWAP_9",
    474: "O_LARA_SKIN_SWAP_10",
    475: "O_LARA_SKIN_SWAP_11",
    476: "O_LARA_SKIN_SWAP_12",
    477: "O_LARA_SKIN_SWAP_13",
    478: "O_LARA_SKIN_SWAP_14",
    479: "O_LARA_SKIN_SWAP_15",
    480: "O_LARA_SKIN_SWAP_16",
    481: "O_LARA_SKIN_SWAP_17",
    482: "O_LARA_SKIN_SWAP_18",
    483: "O_LARA_SKIN_SWAP_19",
    484: "O_LARA_SKIN_SWAP_20",
    485: "O_LARA_SKIN_SWAP_21",
    486: "O_LARA_SKIN_SWAP_22",
    487: "O_LARA_SKIN_SWAP_23",
    488: "O_LARA_SKIN_SWAP_24",
    489: "O_LARA_SKIN_SWAP_25",
    490: "O_LARA_SKIN_SWAP_26",
    491: "O_LARA_SKIN_SWAP_27",
    492: "O_LARA_SKIN_SWAP_28",
    493: "O_LARA_SKIN_SWAP_29",
    494: "O_LARA_SKIN_SWAP_30",
    495: "O_LARA_SKIN_SWAP_31",
    496: "O_LARA_SKIN_SWAP_32",
    497: "O_LARA_SKIN_SWAP_EXTRA",
    498: "O_LARA_SKIN_SWAP_GUNS",
    499: "O_LARA_SKIN_SWAP_LEGS",
    500: "O_ALPHABET",
    501: "O_ALPHABET_SMALL",
    502: "O_LARA_SKIN_JOINTS_1",
    503: "O_LARA_SKIN_JOINTS_2",
    504: "O_LARA_SKIN_JOINTS_3",
    505: "O_LARA_SKIN_JOINTS_4",
    506: "O_LARA_SKIN_JOINTS_5",
    507: "O_LARA_SKIN_JOINTS_6",
    508: "O_LARA_SKIN_JOINTS_7",
    509: "O_LARA_SKIN_JOINTS_8",
    510: "O_LARA_SKIN_JOINTS_9",
    511: "O_LARA_SKIN_JOINTS_10",
    512: "O_LARA_SKIN_JOINTS_11",
    513: "O_LARA_SKIN_JOINTS_12",
    514: "O_LARA_SKIN_JOINTS_13",
    515: "O_LARA_SKIN_JOINTS_14",
    516: "O_LARA_SKIN_JOINTS_15",
    517: "O_LARA_SKIN_JOINTS_16",
    518: "O_LARA_SKIN_JOINTS_17",
    519: "O_LARA_SKIN_JOINTS_18",
    520: "O_LARA_SKIN_JOINTS_19",
    521: "O_LARA_SKIN_JOINTS_20",
    522: "O_LARA_SKIN_JOINTS_21",
    523: "O_LARA_SKIN_JOINTS_22",
    524: "O_LARA_SKIN_JOINTS_23",
    525: "O_LARA_SKIN_JOINTS_24",
    526: "O_LARA_SKIN_JOINTS_25",
    527: "O_LARA_SKIN_JOINTS_26",
    528: "O_LARA_SKIN_JOINTS_27",
    529: "O_LARA_SKIN_JOINTS_28",
    530: "O_LARA_SKIN_JOINTS_29",
    531: "O_LARA_SKIN_JOINTS_30",
    532: "O_LARA_SKIN_JOINTS_31",
    533: "O_LARA_SKIN_JOINTS_32",
}

ALIAS_MAP: dict[str, str] = {
    "PISTOLS_ANIM": "O_LARA_PISTOLS",
    "UZI_ANIM": "O_LARA_UZIS",
    "SHOTGUN_ANIM": "O_LARA_SHOTGUN",
    "GRENADE_GUN_ANIM": "O_LARA_GRENADE_GUN",
    "FLARE_ANIM": "O_LARA_FLARE",
    "HAIR": "O_LARA_HAIR",
    "VEHICLE_EXTRA": "O_LARA_VEHICLE_ANIM",
    "BINOCULAR_GRAPHICS": "O_BINOCULAR_GFX",
    "DARTS": "O_DART",
    "FALLING_CEILING": "O_FALLING_CEILING_1",
    "FALLING_BLOCK": "O_FALLING_BLOCK_1",
    "FALLING_BLOCK2": "O_FALLING_BLOCK_2",
    "TRAPDOOR1": "O_TRAPDOOR_TYPE_1",
    "TRAPDOOR2": "O_TRAPDOOR_TYPE_2",
    "TRAPDOOR3": "O_TRAPDOOR_TYPE_3",
    "ROLLINGBALL": "O_ROLLING_BALL_1",
    "TEETH_SPIKES": "O_SPIKES",
    "SLICER_DICER": "O_SPINNING_BLADE",
    "JOBY_SPIKES": "O_CEILING_SPIKES",
    "COG": "O_COG_1",
    "PUSHABLE_OBJECT1": "O_MOVABLE_BLOCK_1",
    "PUSHABLE_OBJECT2": "O_MOVABLE_BLOCK_2",
    "PUSHABLE_OBJECT3": "O_MOVABLE_BLOCK_3",
    "PUSHABLE_OBJECT4": "O_MOVABLE_BLOCK_4",
    "FUCKED_UP_DOG": "O_DOG",
    "HAMMERHEAD": "O_SHARK",
    "BRIDGE_TILT1": "O_BRIDGE_TILT_1",
    "BRIDGE_TILT2": "O_BRIDGE_TILT_2",
    "PUZZLE_ITEM1": "O_PUZZLE_ITEM_1",
    "PUZZLE_ITEM2": "O_PUZZLE_ITEM_2",
    "PUZZLE_ITEM3": "O_PUZZLE_ITEM_3",
    "PUZZLE_ITEM4": "O_PUZZLE_ITEM_4",
    "KEY_ITEM1": "O_KEY_ITEM_1",
    "KEY_ITEM2": "O_KEY_ITEM_2",
    "KEY_ITEM3": "O_KEY_ITEM_3",
    "KEY_ITEM4": "O_KEY_ITEM_4",
    "PICKUP_ITEM1": "O_PICKUP_ITEM_1",
    "PICKUP_ITEM2": "O_PICKUP_ITEM_2",
    "QUEST_ITEM1": "O_QUEST_ITEM_1",
    "QUEST_ITEM2": "O_QUEST_ITEM_2",
    "QUEST_ITEM3": "O_QUEST_ITEM_3",
    "QUEST_ITEM4": "O_QUEST_ITEM_4",
    "PUZZLE_HOLE1": "O_PUZZLE_HOLE_1",
    "PUZZLE_HOLE2": "O_PUZZLE_HOLE_2",
    "PUZZLE_HOLE3": "O_PUZZLE_HOLE_3",
    "PUZZLE_HOLE4": "O_PUZZLE_HOLE_4",
    "PUZZLE_DONE1": "O_PUZZLE_DONE_1",
    "PUZZLE_DONE2": "O_PUZZLE_DONE_2",
    "PUZZLE_DONE3": "O_PUZZLE_DONE_3",
    "PUZZLE_DONE4": "O_PUZZLE_DONE_4",
    "KEY_HOLE1": "O_KEY_HOLE_1",
    "KEY_HOLE2": "O_KEY_HOLE_2",
    "KEY_HOLE3": "O_KEY_HOLE_3",
    "KEY_HOLE4": "O_KEY_HOLE_4",
    "SWITCH_TYPE1": "O_SWITCH_TYPE_GENERIC_1",
    "SWITCH_TYPE2": "O_SWITCH_TYPE_GENERIC_2",
    "SWITCH_TYPE3": "O_SWITCH_TYPE_GENERIC_3",
    "SWITCH_TYPE4": "O_SWITCH_TYPE_GENERIC_4",
    "SWITCH_TYPE5": "O_SWITCH_TYPE_GENERIC_5",
    "SWITCH_TYPE6": "O_SWITCH_TYPE_GENERIC_6",
    "UNDERWATER_SWITCH1": "O_SWITCH_TYPE_UW",
    "DOOR_TYPE1": "O_DOOR_TYPE_1",
    "DOOR_TYPE2": "O_DOOR_TYPE_2",
    "DOOR_TYPE3": "O_DOOR_TYPE_3",
    "DOOR_TYPE4": "O_DOOR_TYPE_4",
    "DOOR_TYPE5": "O_DOOR_TYPE_5",
    "DOOR_TYPE6": "O_DOOR_TYPE_6",
    "DOOR_TYPE7": "O_DOOR_TYPE_7",
    "DOOR_TYPE8": "O_DOOR_TYPE_8",
    "PISTOLS_ITEM": "O_PISTOL_ITEM",
    "PISTOLS_AMMO_ITEM": "O_PISTOL_AMMO_ITEM",
    "SHOTGUN_AMMO1_ITEM": "O_SHOTGUN_AMMO_ITEM",
    "BIGMEDI_ITEM": "O_LARGE_MEDIPACK_ITEM",
    "SMALLMEDI_ITEM": "O_SMALL_MEDIPACK_ITEM",
    "FLAME_EMITTER": "O_FLAME_EMITTER_TR4_GROUND",
    "FLAME_EMITTER2": "O_FLAME_EMITTER_TR4_WALL",
    "BUBBLES": "O_BUBBLE_EMITTER",
    "GUNSHELL": "O_GUN_SHELL",
    "SHOTGUNSHELL": "O_SHOTGUN_SHELL",
    "AI_PATROL1": "O_AI_PATROL_1",
    "AI_PATROL2": "O_AI_PATROL_2",
    "KILL_ALL_TRIGGERS": "O_KILL_ALL_TRIGGERED",
    "TRIGGER_TRIGGERER": "O_TRIGGER_GATE",
    "SMASH_OBJECT1": "O_SMASH_OBJECT_1",
    "SMASH_OBJECT2": "O_SMASH_OBJECT_2",
    "SMASH_OBJECT3": "O_SMASH_OBJECT_3",
    "SMASH_OBJECT4": "O_SMASH_OBJECT_4",
    "MESHSWAP1": "O_MESH_SWAP_1",
    "MESHSWAP2": "O_MESH_SWAP_2",
    "MESHSWAP3": "O_MESH_SWAP_3",
    "DEATH_SLIDE": "O_ZIPLINE_HANDLE",
    "ROPE": "O_ROPE",
    "POLEROPE": "O_CLIMBING_POLE",
    "PUSHPULL_DOOR1": "O_PUSHPULL_DOOR_1",
    "PUSHPULL_DOOR2": "O_PUSHPULL_DOOR_2",
    "KICK_DOOR1": "O_KICK_DOOR_1",
    "KICK_DOOR2": "O_KICK_DOOR_2",
    "FLOOR_TRAPDOOR1": "O_FLOOR_TRAPDOOR_1",
    "FLOOR_TRAPDOOR2": "O_FLOOR_TRAPDOOR_2",
    "CEILING_TRAPDOOR1": "O_CEILING_TRAPDOOR_1",
    "CEILING_TRAPDOOR2": "O_CEILING_TRAPDOOR_2",
    "LEVER_SWITCH": "O_SWITCH_TYPE_FLOOR",
    "JUMP_SWITCH": "O_SWITCH_TYPE_JUMP",
    "CROWBAR_SWITCH": "O_SWITCH_TYPE_CROWBAR",
    "PULLEY": "O_SWITCH_TYPE_PULLEY",
    "UNDERWATER_SWITCH2": "O_SWITCH_TYPE_UW_CEILING",
    "WATERFALL1": "O_WATERFALL",
    "ANIMATING1": "O_ANIMATING_1",
    "ANIMATING2": "O_ANIMATING_2",
    "ANIMATING3": "O_ANIMATING_3",
    "ANIMATING4": "O_ANIMATING_4",
    "ANIMATING5": "O_ANIMATING_5",
    "ANIMATING6": "O_ANIMATING_6",
    "ANIMATING7": "O_ANIMATING_7",
    "ANIMATING8": "O_ANIMATING_8",
    "ANIMATING9": "O_ANIMATING_9",
    "ANIMATING10": "O_ANIMATING_10",
    "ANIMATING11": "O_ANIMATING_11",
    "ANIMATING12": "O_ANIMATING_12",
    "ANIMATING13": "O_ANIMATING_13",
    "ANIMATING14": "O_ANIMATING_14",
    "ANIMATING15": "O_ANIMATING_15",
    "ANIMATING16": "O_ANIMATING_16",
    "HORIZON": "O_SKYBOX",
    "DEFAULT_SPRITES": "O_SPARKS_GFX",
    "CROSSBOW_ANIM": "O_LARA_CROSSBOW",
    "SIXSHOOTER_ANIM": "O_LARA_REVOLVER",
    "CROWBAR_ANIM": "O_CROWBAR_ANIM",
    "PUZZLE_ITEM5": "O_PUZZLE_ITEM_5",
    "KEY_ITEM5": "O_KEY_ITEM_5",
    "PUZZLE_HOLE5": "O_PUZZLE_HOLE_5",
    "PUZZLE_DONE5": "O_PUZZLE_DONE_5",
    "KEY_HOLE5": "O_KEY_HOLE_5",
    "PUZZLE_ITEM6": "O_PUZZLE_ITEM_6",
    "KEY_ITEM6": "O_KEY_ITEM_6",
    "PUZZLE_HOLE6": "O_PUZZLE_HOLE_6",
    "PUZZLE_DONE6": "O_PUZZLE_DONE_6",
    "KEY_HOLE6": "O_KEY_HOLE_6",
    "PUZZLE_ITEM7": "O_PUZZLE_ITEM_7",
    "KEY_ITEM7": "O_KEY_ITEM_7",
    "PUZZLE_HOLE7": "O_PUZZLE_HOLE_7",
    "PUZZLE_DONE7": "O_PUZZLE_DONE_7",
    "KEY_HOLE7": "O_KEY_HOLE_7",
    "PUZZLE_ITEM8": "O_PUZZLE_ITEM_8",
    "KEY_ITEM8": "O_KEY_ITEM_8",
    "PUZZLE_HOLE8": "O_PUZZLE_HOLE_8",
    "PUZZLE_DONE8": "O_PUZZLE_DONE_8",
    "KEY_HOLE8": "O_KEY_HOLE_8",
    "PUZZLE_ITEM9": "O_PUZZLE_ITEM_9",
    "KEY_ITEM9": "O_KEY_ITEM_9",
    "PUZZLE_HOLE9": "O_PUZZLE_HOLE_9",
    "PUZZLE_DONE9": "O_PUZZLE_DONE_9",
    "KEY_HOLE9": "O_KEY_HOLE_9",
    "PUZZLE_ITEM10": "O_PUZZLE_ITEM_10",
    "KEY_ITEM10": "O_KEY_ITEM_10",
    "PUZZLE_HOLE10": "O_PUZZLE_HOLE_10",
    "PUZZLE_DONE10": "O_PUZZLE_DONE_10",
    "KEY_HOLE10": "O_KEY_HOLE_10",
    "PUZZLE_ITEM11": "O_PUZZLE_ITEM_11",
    "KEY_ITEM11": "O_KEY_ITEM_11",
    "PUZZLE_HOLE11": "O_PUZZLE_HOLE_11",
    "PUZZLE_DONE11": "O_PUZZLE_DONE_11",
    "KEY_HOLE11": "O_KEY_HOLE_11",
    "PUZZLE_ITEM12": "O_PUZZLE_ITEM_12",
    "KEY_ITEM12": "O_KEY_ITEM_12",
    "PUZZLE_HOLE12": "O_PUZZLE_HOLE_12",
    "PUZZLE_DONE12": "O_PUZZLE_DONE_12",
    "KEY_HOLE12": "O_KEY_HOLE_12",
    "PUZZLE_ITEM1_COMBO1": "O_PUZZLE_ITEM_1_COMBO_1",
    "KEY_ITEM1_COMBO1": "O_KEY_ITEM_1_COMBO_1",
    "PUZZLE_ITEM1_COMBO2": "O_PUZZLE_ITEM_1_COMBO_2",
    "KEY_ITEM1_COMBO2": "O_KEY_ITEM_1_COMBO_2",
    "PUZZLE_ITEM2_COMBO1": "O_PUZZLE_ITEM_2_COMBO_1",
    "KEY_ITEM2_COMBO1": "O_KEY_ITEM_2_COMBO_1",
    "PUZZLE_ITEM2_COMBO2": "O_PUZZLE_ITEM_2_COMBO_2",
    "KEY_ITEM2_COMBO2": "O_KEY_ITEM_2_COMBO_2",
    "PUZZLE_ITEM3_COMBO1": "O_PUZZLE_ITEM_3_COMBO_1",
    "KEY_ITEM3_COMBO1": "O_KEY_ITEM_3_COMBO_1",
    "PUZZLE_ITEM3_COMBO2": "O_PUZZLE_ITEM_3_COMBO_2",
    "KEY_ITEM3_COMBO2": "O_KEY_ITEM_3_COMBO_2",
    "PUZZLE_ITEM4_COMBO1": "O_PUZZLE_ITEM_4_COMBO_1",
    "KEY_ITEM4_COMBO1": "O_KEY_ITEM_4_COMBO_1",
    "PUZZLE_ITEM4_COMBO2": "O_PUZZLE_ITEM_4_COMBO_2",
    "KEY_ITEM4_COMBO2": "O_KEY_ITEM_4_COMBO_2",
    "PUZZLE_ITEM5_COMBO1": "O_PUZZLE_ITEM_5_COMBO_1",
    "KEY_ITEM5_COMBO1": "O_KEY_ITEM_5_COMBO_1",
    "PUZZLE_ITEM5_COMBO2": "O_PUZZLE_ITEM_5_COMBO_2",
    "KEY_ITEM5_COMBO2": "O_KEY_ITEM_5_COMBO_2",
    "PUZZLE_ITEM6_COMBO1": "O_PUZZLE_ITEM_6_COMBO_1",
    "KEY_ITEM6_COMBO1": "O_KEY_ITEM_6_COMBO_1",
    "PUZZLE_ITEM6_COMBO2": "O_PUZZLE_ITEM_6_COMBO_2",
    "KEY_ITEM6_COMBO2": "O_KEY_ITEM_6_COMBO_2",
    "PUZZLE_ITEM7_COMBO1": "O_PUZZLE_ITEM_7_COMBO_1",
    "KEY_ITEM7_COMBO1": "O_KEY_ITEM_7_COMBO_1",
    "PUZZLE_ITEM7_COMBO2": "O_PUZZLE_ITEM_7_COMBO_2",
    "KEY_ITEM7_COMBO2": "O_KEY_ITEM_7_COMBO_2",
    "PUZZLE_ITEM8_COMBO1": "O_PUZZLE_ITEM_8_COMBO_1",
    "KEY_ITEM8_COMBO1": "O_KEY_ITEM_8_COMBO_1",
    "PUZZLE_ITEM8_COMBO2": "O_PUZZLE_ITEM_8_COMBO_2",
    "KEY_ITEM8_COMBO2": "O_KEY_ITEM_8_COMBO_2",
    "PICKUP_ITEM3": "O_PICKUP_ITEM_3",
    "PICKUP_ITEM4": "O_PICKUP_ITEM_4",
    "PICKUP_ITEM1_COMBO1": "O_PICKUP_ITEM_1_COMBO_1",
    "PICKUP_ITEM1_COMBO2": "O_PICKUP_ITEM_1_COMBO_2",
    "PICKUP_ITEM2_COMBO1": "O_PICKUP_ITEM_2_COMBO_1",
    "PICKUP_ITEM2_COMBO2": "O_PICKUP_ITEM_2_COMBO_2",
    "PICKUP_ITEM3_COMBO1": "O_PICKUP_ITEM_3_COMBO_1",
    "PICKUP_ITEM3_COMBO2": "O_PICKUP_ITEM_3_COMBO_2",
    "PICKUP_ITEM4_COMBO1": "O_PICKUP_ITEM_4_COMBO_1",
    "PICKUP_ITEM4_COMBO2": "O_PICKUP_ITEM_4_COMBO_2",
    "QUEST_ITEM5": "O_QUEST_ITEM_5",
    "QUEST_ITEM6": "O_QUEST_ITEM_6",
    "EXAMINE1": "O_EXAMINE_ITEM_1",
    "EXAMINE2": "O_EXAMINE_ITEM_2",
    "EXAMINE3": "O_EXAMINE_ITEM_3",
    "CROSSBOW_AMMO1_ITEM": "O_CROSSBOW_AMMO_1_ITEM",
    "CROSSBOW_AMMO2_ITEM": "O_CROSSBOW_AMMO_2_ITEM",
    "CROSSBOW_AMMO3_ITEM": "O_CROSSBOW_AMMO_3_ITEM",
    "SIXSHOOTER_ITEM": "O_REVOLVER_ITEM",
    "SIXSHOOTER_AMMO_ITEM": "O_REVOLVER_AMMO_ITEM",
    "SHOTGUN_AMMO2_ITEM": "O_SHOTGUN_AMMO_2_ITEM",
    "GRENADE_GUN_AMMO1_ITEM": "O_GRENADE_AMMO_ITEM",
    "GRENADE_GUN_AMMO2_ITEM": "O_GRENADE_AMMO_2_ITEM",
    "GRENADE_GUN_AMMO3_ITEM": "O_GRENADE_AMMO_3_ITEM",
    "FLARE_ITEM": "O_FLARE_ITEM",
    "FLARE_INV_ITEM": "O_FLAREBOX_ITEM",
    "COMPASS_ITEM": "O_COMPASS_OPTION",
    "WATERSKIN1_EMPTY": "O_WATERSKIN_1_EMPTY",
    "WATERSKIN1_1": "O_WATERSKIN_1_1",
    "WATERSKIN1_2": "O_WATERSKIN_1_2",
    "WATERSKIN1_3": "O_WATERSKIN_1_3",
    "WATERSKIN2_EMPTY": "O_WATERSKIN_2_EMPTY",
    "WATERSKIN2_1": "O_WATERSKIN_2_1",
    "WATERSKIN2_2": "O_WATERSKIN_2_2",
    "WATERSKIN2_3": "O_WATERSKIN_2_3",
    "WATERSKIN2_4": "O_WATERSKIN_2_4",
    "WATERSKIN2_5": "O_WATERSKIN_2_5",
}

KNOWN_UNUSED = {
    "FLAME_EMITTER3",
    "SPIKEY_FLOOR",
}


def parse_trx_symbols() -> set[str]:
    # objects.def includes pickups.def, so expand it with the real
    # C preprocessor instead of scraping the file.
    text = expand_def_file(TRX_OBJECTS_DEF, {"X_CATALOG_ID(x)": "SYMBOL(x)"})
    return set(re.findall(r"SYMBOL\((O_[A-Z0-9_]+)\)", text))


def parse_tr4_object_names() -> list[str]:
    text = TR4_OBJECTS_H.read_text()
    match = re.search(
        r"enum object_types\s*\{(?P<body>.*?)\bNUMBER_OBJECTS\b",
        text,
        re.DOTALL,
    )
    if match is None:
        raise ValueError("Failed to locate enum object_types in TOMB4 objects.h")

    body = re.sub(r"//.*", "", match.group("body"))
    body = re.sub(r"/\*.*?\*/", "", body, flags=re.DOTALL)
    return [name.strip() for name in body.replace("\n", " ").split(",") if name.strip()]


def resolve_symbol(name: str, trx_symbols: set[str]) -> str | None:
    alias_symbol = ALIAS_MAP.get(name)
    if alias_symbol is not None:
        if alias_symbol not in trx_symbols:
            raise ValueError(f"Alias target {alias_symbol} for {name} is not a TRX catalog symbol")
        return alias_symbol

    exact_symbol = f"O_{name}"
    if exact_symbol in trx_symbols:
        return exact_symbol

    return None


def build_catalog_rows() -> tuple[list[tuple[int, str]], list[tuple[int, str]]]:
    trx_symbols = parse_trx_symbols()
    names = parse_tr4_object_names()

    rows: list[tuple[int, str]] = sorted(FORCED_ROWS.items())
    skipped: list[tuple[int, str]] = []
    used_symbols: set[str] = set(FORCED_ROWS.values())
    forced_ids = set(FORCED_ROWS)

    for game_id, name in enumerate(names):
        if game_id in forced_ids:
            skipped.append((game_id, name))
            continue
        symbol = resolve_symbol(name, trx_symbols)
        if symbol is None:
            skipped.append((game_id, name))
            continue
        if symbol in used_symbols:
            raise ValueError(f"Duplicate TRX symbol mapping detected for {name}: {symbol}")
        used_symbols.add(symbol)
        rows.append((game_id, symbol))

    return rows, skipped


def write_catalog() -> None:
    rows, skipped = build_catalog_rows()
    row_map = {game_id: symbol for game_id, symbol in rows}

    rows = sorted(row_map.items())

    visible_skipped = [
        (game_id, symbol)
        for game_id, symbol in skipped
        if symbol not in KNOWN_UNUSED
    ]
    lines = [
        "# Generated by tools/tr4/generate_object_catalog",
        f"# Mapped {len(rows)} of {len(rows) + len(visible_skipped)} TOMB4 object slots.",
        "# Unmapped slots are intentionally omitted until TRX has a stable object equivalent.",
    ]
    lines.extend(f"{game_id}, {symbol}" for game_id, symbol in rows)
    lines.append("")
    lines.append("# Unmapped TOMB4 slots:")
    lines.extend(f"# {game_id}: {name}" for game_id, name in visible_skipped)
    OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_PATH.write_text("\n".join(lines) + "\n")


def main() -> None:
    write_catalog()


if __name__ == "__main__":
    main()
