#!/usr/bin/env python3
"""Build candidate Radium object labels from Godot catalog order anchors.

This is intentionally an audit artifact, not a best-name source. Ghidra confirms
two native registration blocks whose local object IDs are consecutive but use
different offsets from the Godot sound catalog order. Neighboring rows are useful
lead candidates, but they are not native-confirmed.
"""

from __future__ import annotations

import argparse
import csv
import json
from pathlib import Path


FIELDS = [
    "candidate_status",
    "block_name",
    "range_kind",
    "offset",
    "catalog_index",
    "catalog_constant",
    "candidate_object_id",
    "section8_index",
    "source_wav_path",
    "duration_seconds",
    "channels",
    "current_best_source",
    "current_best_name",
    "native_register_call",
    "native_string_ghidra",
    "sound_test_label",
    "notes",
]


BLOCKS = [
    {
        "block_name": "eob_pokedex_offset_69",
        "offset": 69,
        "start": 30,
        "end": 117,
        "anchor_start": 40,
        "anchor_end": 46,
        "notes": (
            "Native anchors catalog 40-46 to objects 109-115. Neighboring "
            "catalog rows are only offset candidates."
        ),
    },
    {
        "block_name": "attack_and_following_fx_offset_78",
        "offset": 78,
        "start": 118,
        "end": 254,
        "anchor_start": 118,
        "anchor_end": 135,
        "notes": (
            "Native anchors catalog 118-135 to objects 196-213. Following "
            "FX rows are only offset candidates."
        ),
    },
]


def read_tsv(path: Path) -> list[dict[str, str]]:
    if not path.exists():
        return []
    with path.open(newline="", encoding="utf-8") as f:
        return list(csv.DictReader(f, delimiter="\t"))


def write_tsv(path: Path, rows: list[dict[str, object]]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS, delimiter="\t", extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)


def load_catalog(path: Path) -> list[str]:
    with path.open(encoding="utf-8") as f:
        data = json.load(f)
    if isinstance(data, dict):
        return list(data.keys())
    if isinstance(data, list):
        return [str(item) for item in data]
    raise TypeError(f"unsupported catalog format in {path}")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--catalog",
        type=Path,
        default=Path("analysis/godot-introspection/spike_sound_constant_map.json"),
    )
    parser.add_argument(
        "--sound-manifest",
        type=Path,
        default=Path("analysis/radium-image/sound_export_manifest.tsv"),
    )
    parser.add_argument(
        "--best-manifest",
        type=Path,
        default=Path("analysis/all-sound-best-name-manifest.tsv"),
    )
    parser.add_argument(
        "--native-map",
        type=Path,
        default=Path("analysis/radium-image/native_sound_id_map.tsv"),
    )
    parser.add_argument(
        "--sound-test",
        type=Path,
        default=Path("analysis/radium-image/native_sound_test_label_manifest.tsv"),
    )
    parser.add_argument(
        "--out",
        type=Path,
        default=Path("analysis/radium-image/catalog_offset_candidate_labels.tsv"),
    )
    args = parser.parse_args()

    catalog = load_catalog(args.catalog)
    sounds_by_object = {
        int(row["primary_object_local_id"]): row
        for row in read_tsv(args.sound_manifest)
        if row.get("primary_object_local_id", "").isdigit()
    }
    best_by_object = {
        int(row["object_id"]): row
        for row in read_tsv(args.best_manifest)
        if row.get("bank") == "main" and row.get("object_id", "").isdigit()
    }
    native_by_object = {
        int(row["native_sound_id"]): row
        for row in read_tsv(args.native_map)
        if row.get("native_sound_id", "").isdigit()
    }
    sound_test_by_object = {
        int(row["sound_id"]): row
        for row in read_tsv(args.sound_test)
        if row.get("sound_id", "").isdigit()
    }

    rows: list[dict[str, object]] = []
    for block in BLOCKS:
        offset = int(block["offset"])
        for catalog_index in range(int(block["start"]), int(block["end"]) + 1):
            if catalog_index >= len(catalog):
                continue
            constant = catalog[catalog_index]
            object_id = catalog_index + offset
            sound = sounds_by_object.get(object_id, {})
            best = best_by_object.get(object_id, {})
            native = native_by_object.get(object_id)
            sound_test = sound_test_by_object.get(object_id)

            anchor_start = int(block["anchor_start"])
            anchor_end = int(block["anchor_end"])
            if anchor_start <= catalog_index <= anchor_end:
                range_kind = "native_anchor"
            elif catalog_index < anchor_start:
                range_kind = "pre_anchor_extension"
            else:
                range_kind = "post_anchor_extension"

            notes = str(block["notes"])
            if native and native["name"] == constant:
                status = "confirmed_anchor"
            elif native:
                status = "conflicts_with_confirmed_native"
                notes += f" Native object already maps to {native['name']}."
            elif sound_test:
                status = "conflicts_with_sound_test_candidate"
                notes += " Native sound-test-adjacent row disagrees; that table is audit-only."
            else:
                status = "candidate_unpromoted"

            rows.append(
                {
                    "candidate_status": status,
                    "block_name": block["block_name"],
                    "range_kind": range_kind,
                    "offset": offset,
                    "catalog_index": catalog_index,
                    "catalog_constant": constant,
                    "candidate_object_id": object_id,
                    "section8_index": sound.get("section8_index", ""),
                    "source_wav_path": sound.get("wav_path", ""),
                    "duration_seconds": sound.get("duration_seconds", ""),
                    "channels": sound.get("channels", ""),
                    "current_best_source": best.get("name_source", ""),
                    "current_best_name": best.get("canonical_name", ""),
                    "native_register_call": native.get("register_call_ghidra", "") if native else "",
                    "native_string_ghidra": native.get("string_ghidra", "") if native else "",
                    "sound_test_label": sound_test.get("label", "") if sound_test else "",
                    "notes": notes,
                }
            )

    write_tsv(args.out, rows)
    counts: dict[str, int] = {}
    for row in rows:
        counts[str(row["candidate_status"])] = counts.get(str(row["candidate_status"]), 0) + 1
    print(f"wrote {len(rows)} rows to {args.out}")
    for key in sorted(counts):
        print(f"{key}: {counts[key]}")


if __name__ == "__main__":
    main()
