#!/usr/bin/env python3
"""Extract native sound-test-adjacent label candidates and build preview aliases.

This is static analysis of /games/pokemon_pro/game. The table at
0x325d940 contains localized label-list pointers paired with a numeric field.
Follow-up WAV/ASR checks showed that numeric field is not a reliable direct
Radium local sound ID for every row, so these labels are retained for audit
but are not promoted into the best-name export.
"""

from __future__ import annotations

import argparse
import csv
import os
import re
import struct
from pathlib import Path


SOUND_TEST_LABEL_TABLE_OFFSET = 0x325D940
SOUND_TEST_LABEL_COUNT = 4
LOCALIZED_STRING_COUNT = 5


def u64(data: bytes, offset: int) -> int:
    return struct.unpack_from("<Q", data, offset)[0]


def cstring(data: bytes, offset: int) -> str:
    if offset <= 0 or offset >= len(data):
        return ""
    end = data.find(b"\x00", offset)
    if end < 0:
        end = len(data)
    return data[offset:end].decode("utf-8", errors="replace")


def slug(value: str) -> str:
    return re.sub(r"[^A-Za-z0-9_]+", "_", value).strip("_") or "sound"


def localized_labels(data: bytes, list_offset: int) -> list[tuple[int, str]]:
    labels: list[tuple[int, str]] = []
    for index in range(LOCALIZED_STRING_COUNT):
        ptr_offset = list_offset + index * 8
        if ptr_offset + 8 > len(data):
            break
        string_offset = u64(data, ptr_offset)
        if string_offset == 0:
            break
        labels.append((string_offset, cstring(data, string_offset)))
    return labels


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--game-binary", type=Path, default=Path("games/pokemon_pro/game"))
    parser.add_argument("--sound-manifest", type=Path, default=Path("analysis/radium-image/sound_export_manifest.tsv"))
    parser.add_argument("--alias-dir", type=Path, default=Path("analysis/radium-image/sounds-native-sound-test"))
    parser.add_argument("--out", type=Path, default=Path("analysis/radium-image/native_sound_test_label_manifest.tsv"))
    args = parser.parse_args()

    data = args.game_binary.read_bytes()
    manifest = list(csv.DictReader(args.sound_manifest.open(newline=""), delimiter="\t"))
    by_object = {int(row["primary_object_local_id"]): row for row in manifest if row["primary_object_local_id"]}
    args.alias_dir.mkdir(parents=True, exist_ok=True)

    rows: list[dict[str, object]] = []
    for index in range(SOUND_TEST_LABEL_COUNT):
        entry_offset = SOUND_TEST_LABEL_TABLE_OFFSET + index * 16
        label_list_offset = u64(data, entry_offset)
        sound_id = u64(data, entry_offset + 8)
        labels = localized_labels(data, label_list_offset)
        if not labels:
            continue
        label_string_offset, label = labels[0]
        source = by_object.get(sound_id)
        if source is None:
            continue
        source_path = Path(source["wav_path"])
        alias_name = (
            f"{slug(label)}"
            f"__obj{sound_id:04d}"
            f"_s{int(source['section8_index']):04d}"
            f"_{source['channels']}ch_{source['sample_rate']}hz.wav"
        )
        alias_path = args.alias_dir / alias_name
        if not alias_path.exists():
            try:
                os.link(source_path, alias_path)
                link_type = "hardlink"
            except OSError:
                alias_path.symlink_to(Path("..") / "sounds" / source_path.name)
                link_type = "symlink"
        else:
            link_type = "existing"
        rows.append(
            {
                "label": label,
                "sound_id": sound_id,
                "alias_wav_path": alias_path.as_posix(),
                "source_wav_path": source_path.as_posix(),
                "section8_index": source["section8_index"],
                "duration_seconds": source["duration_seconds"],
                "channels": source["channels"],
                "sample_rate": source["sample_rate"],
                "command_ref_count": source["command_ref_count"],
                "table_entry_offset": f"0x{entry_offset:08x}",
                "label_list_offset": f"0x{label_list_offset:08x}",
                "label_string_offset": f"0x{label_string_offset:08x}",
                "localized_labels": " | ".join(text for _, text in labels),
                "link_type": link_type,
            }
        )

    fields = [
        "label",
        "sound_id",
        "alias_wav_path",
        "source_wav_path",
        "section8_index",
        "duration_seconds",
        "channels",
        "sample_rate",
        "command_ref_count",
        "table_entry_offset",
        "label_list_offset",
        "label_string_offset",
        "localized_labels",
        "link_type",
    ]
    with args.out.open("w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fields, delimiter="\t")
        writer.writeheader()
        writer.writerows(rows)
    print(f"wrote {args.out} rows={len(rows)} alias_dir={args.alias_dir}")
    return 0 if len(rows) == SOUND_TEST_LABEL_COUNT else 1


if __name__ == "__main__":
    raise SystemExit(main())
