"""Import des sons retenus depuis la banque locale vers le projet.

Ce script ne choisit rien: il execute une selection ecrite ailleurs
(docs/audio/selection-proposee.csv et docs/audio/selection-retenue.csv) et
garde la provenance de chaque fichier dans assets/audio/import-manifest.json.

Trois proprietes comptent:

* **Idempotence.** Un fichier deja importe avec le meme contenu n'est pas
  recopie, et un reglage change a la main n'est pas ecrase.
* **Dedoublonnage par contenu**, pas par nom: la banque contient le meme bruit
  sous plusieurs titres, et deux identifiants sur un meme octet gaspillent le
  catalogue.
* **Aucune dependance a Downloads a l'execution.** Le jeu ne lit que
  res://assets/audio; la banque n'est touchee qu'ici.

Usage: python tools/audio_import.py [--plan]
"""

from __future__ import annotations

import argparse
import csv
import hashlib
import json
import re
import shutil
import struct
import sys
from pathlib import Path

PROJECT = Path(__file__).resolve().parents[1]
BANK = Path(r"C:\Users\ELR17\Downloads\RAREVGSFX 1+2")
MUSIC_SOURCE = BANK / "musique pour big egg"
AUDIO_ROOT = PROJECT / "assets" / "audio"
MANIFEST = AUDIO_ROOT / "import-manifest.json"

# Ou ranger un clip selon la nature de l'evenement vise.
AMBIENCE_HINTS = ("ambience", "_loop")
MUSIC_SUFFIXES = (".mp3", ".ogg")


def wav_duration(path: Path) -> float:
    """Duree d'un WAV par ses en-tetes: octets de `data` / debit de `fmt`."""
    try:
        with path.open("rb") as handle:
            if handle.read(4) != b"RIFF":
                return 0.0
            handle.seek(8)
            if handle.read(4) != b"WAVE":
                return 0.0
            byte_rate = 0
            data_bytes = 0
            size = path.stat().st_size
            while handle.tell() + 8 <= size:
                header = handle.read(8)
                if len(header) < 8:
                    break
                chunk, length = header[:4], struct.unpack("<I", header[4:])[0]
                start = handle.tell()
                if chunk == b"fmt " and length >= 16:
                    fmt = handle.read(16)
                    if len(fmt) == 16:
                        byte_rate = struct.unpack("<I", fmt[8:12])[0]
                elif chunk == b"data":
                    data_bytes = length
                handle.seek(start + length + (length % 2))
            return round(data_bytes / byte_rate, 3) if byte_rate else 0.0
    except OSError:
        return 0.0


def digest(path: Path) -> str:
    sha = hashlib.sha256()
    with path.open("rb") as handle:
        for block in iter(lambda: handle.read(1 << 20), b""):
            sha.update(block)
    return sha.hexdigest()


def slug(text: str) -> str:
    cleaned = re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_")
    return re.sub(r"_+", "_", cleaned) or "clip"


def kind_for(event: str, suffix: str) -> str:
    if suffix.lower() in MUSIC_SUFFIXES:
        return "music"
    if any(hint in event for hint in AMBIENCE_HINTS):
        return "ambience"
    return "sfx"


def load_selection() -> list[dict]:
    """La proposition d'analyse, plus les ajouts eventuels du meme format."""
    rows: list[dict] = []
    for name in ("selection-proposee.csv", "selection-retenue.csv"):
        path = PROJECT / "docs" / "audio" / name
        if not path.exists():
            continue
        with path.open(encoding="utf-8-sig", newline="") as handle:
            for row in csv.DictReader(handle):
                if row.get("review_status", "").strip().lower() == "rejete":
                    continue
                row["_origin"] = name
                rows.append(row)
    return rows


def music_rows() -> list[dict]:
    """Les morceaux fournis par l'auteur. Leurs noms disent deja leur usage."""
    if not MUSIC_SOURCE.is_dir():
        return []
    rows = []
    for path in sorted(MUSIC_SOURCE.iterdir()):
        if path.suffix.lower() not in MUSIC_SUFFIXES:
            continue
        rows.append({
            "target_event": "music_" + slug(path.stem),
            "source_relative": str(path.relative_to(BANK)).replace("\\", "/"),
            "duration_seconds": "0",
            "initial_volume_db": "-14",
            "semantic_confidence": "auteur",
            "selection_reason": "Morceau fourni par l auteur, nomme pour son usage.",
            "review_status": "",
            "_origin": "musique pour big egg",
        })
    return rows


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--plan", action="store_true", help="n ecrit rien")
    options = parser.parse_args()

    previous = {}
    if MANIFEST.exists():
        previous = {
            entry["clip_id"]: entry
            for entry in json.loads(MANIFEST.read_text(encoding="utf-8")).get("clips", [])
        }

    rows = load_selection() + music_rows()
    by_hash: dict[str, str] = {}
    entries: list[dict] = []
    used_ids: set[str] = set()
    copied = skipped = duplicates = missing = 0

    for row in rows:
        source = BANK / row["source_relative"].replace("/", "\\")
        if not source.is_file():
            print(f"ABSENT  {row['source_relative']}")
            missing += 1
            continue
        file_hash = digest(source)
        if file_hash in by_hash:
            # Meme octet sous un autre nom: on garde le premier identifiant et
            # on note l usage supplementaire plutot que de copier deux fois.
            duplicates += 1
            for entry in entries:
                if entry["clip_id"] == by_hash[file_hash]:
                    if row["target_event"] not in entry["suggested_events"]:
                        entry["suggested_events"].append(row["target_event"])
            continue

        event = row["target_event"]
        kind = kind_for(event, source.suffix)
        base = slug(event)
        clip_id = base
        counter = 2
        while clip_id in used_ids:
            clip_id = f"{base}_{counter}"
            counter += 1
        used_ids.add(clip_id)
        by_hash[file_hash] = clip_id

        folder = {"music": "music", "ambience": "ambience"}.get(kind, "sfx")
        destination = AUDIO_ROOT / folder / f"{clip_id}{source.suffix.lower()}"
        relative = f"res://assets/audio/{folder}/{destination.name}"

        known = previous.get(clip_id)
        unchanged = (
            known is not None
            and known.get("sha256") == file_hash
            and destination.exists()
        )
        if unchanged:
            skipped += 1
        elif not options.plan:
            destination.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(source, destination)
            copied += 1
        else:
            copied += 1

        duration = wav_duration(destination if destination.exists() else source)
        if duration == 0.0:
            try:
                duration = float(row.get("duration_seconds") or 0.0)
            except ValueError:
                duration = 0.0
        entries.append({
            "clip_id": clip_id,
            "path": relative,
            "kind": kind,
            "source_relative": row["source_relative"],
            "source_name": source.name,
            "sha256": file_hash,
            "bytes": source.stat().st_size,
            "duration_seconds": round(duration, 3),
            "suggested_events": [event],
            "volume_db": float(row.get("initial_volume_db") or -6),
            "confidence": row.get("semantic_confidence", ""),
            "reason": row.get("selection_reason", ""),
            "origin": row["_origin"],
            # Rien n a ete ecoute: l analyse ne certifie pas le rendu sonore.
            "listened": bool(known.get("listened", False)) if known else False,
        })

    entries.sort(key=lambda entry: entry["clip_id"])
    payload = {
        "_lisez_moi": (
            "Provenance des sons importes. Ce fichier n est PAS charge par le jeu: "
            "le catalogue d auteur vit dans data/game_data_master.json sous audio_clips."
        ),
        "bank": str(BANK),
        "clips": entries,
    }
    if not options.plan:
        MANIFEST.parent.mkdir(parents=True, exist_ok=True)
        MANIFEST.write_text(
            json.dumps(payload, indent=4, ensure_ascii=False) + "\n", encoding="utf-8")

    verb = "a copier" if options.plan else "copies"
    print(f"{len(entries)} clips retenus | {copied} {verb} | {skipped} deja a jour "
          f"| {duplicates} doublons de contenu | {missing} absents")
    return 0


if __name__ == "__main__":
    sys.exit(main())
