"""PoC (работа A, этап A0): пост-обработка сгенерированного WAV под формат игры.

Вход:  review/<id>_<seed>.wav — AudioLDM2, 16000 Гц, моно, 16-бит.
Выход: review/<id>_<seed>_loop.wav — 22050 Гц, моно 16-бит, нормализованный,
       бесшовный луп (хвост кроссфейдом вплетается в начало — стык без щелчка).

Запуск: ~/.cache/rpg-ai/venv-aiaudio/bin/python tools/aiaudio/post.py review/amb_ponds_1.wav
Флаги: --check — сверить стык с фоном записи; --noloop — SFX без лупа
       (ресэмпл + нормализация, хвост не вплетается).
"""
import sys
import wave
from pathlib import Path

import numpy as np
from scipy.signal import resample_poly

HERE = Path(__file__).resolve().parent
TARGET_RATE = 22050  # формат игры: моно, 22050 Гц, 16-бит (см. audio/gen.mjs)
FADE = 1.0  # длительность кроссфейда лупа, с
PEAK = 0.85  # целевой пик нормализации


def read_wav(path: Path) -> tuple[np.ndarray, int]:
    with wave.open(str(path), "rb") as w:
        rate = w.getframerate()
        data = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16)
    return data.astype(np.float64) / 32768.0, rate


def write_wav(path: Path, samples: np.ndarray) -> None:
    pcm = np.clip(samples * 32767.0, -32768, 32767).astype(np.int16)
    with wave.open(str(path), "wb") as w:
        w.setnchannels(1)
        w.setsampwidth(2)
        w.setframerate(TARGET_RATE)
        w.writeframes(pcm.tobytes())


def loopify(s: np.ndarray, rate: int, fade: float) -> np.ndarray:
    """Хвост (последние fade с) кроссфейдом вплетается в начало — луп без шва.

    B[0..fade] = tail·(1-w) + head·w (хвост затухает, голова нарастает),
    дальше — средина записи; шов (конец -> начало) соседние сэмплы A.
    Длина выхода = len - fade с.
    """
    n = int(fade * rate)
    w = np.linspace(0.0, 1.0, n)
    tail, head, body = s[-n:], s[:n], s[n:-n]
    return np.concatenate([tail * (1 - w) + head * w, body])


def main() -> None:
    # Разбор аргументов вручную: --peak <значение> — цель нормализации
    # (манифест может задавать тише для слоёв; по умолчанию PEAK).
    peak_target = PEAK
    args: list[str] = []
    raw = sys.argv[1:]
    i = 0
    while i < len(raw):
        if raw[i] == "--peak":
            peak_target = float(raw[i + 1])
            i += 2
        elif not raw[i].startswith("--"):
            args.append(raw[i])
            i += 1
        else:
            i += 1
    check = "--check" in raw
    noloop = "--noloop" in raw
    if len(args) != 1:
        print("использование: post.py <review/xxx.wav> [--check] [--noloop] [--peak 0.85]")
        sys.exit(1)
    src = Path(args[0]).resolve()
    s, rate = read_wav(src)
    s = resample_poly(s, TARGET_RATE, rate)  # 16000 -> 22050 (целые множители: 441/320)
    s = s if noloop else loopify(s, TARGET_RATE, FADE)
    # Нормализация ПОСЛЕ loopify: пик модели часто в фейдах головы/хвоста,
    # которые луп вплетает/выбрасывает — иначе фактический пик ниже цели.
    peak = np.abs(s).max()
    if peak > 1e-9:
        s = s * (peak_target / peak)
    out = src.with_name(src.stem + ("_fx.wav" if noloop else "_loop.wav"))
    write_wav(out, s)
    dur = s.shape[0] / TARGET_RATE
    print(f"готово: {out.relative_to(HERE)} ({dur:.1f} с, {TARGET_RATE} Гц, пик {peak_target})")

    if check:
        # Стык без щелчка: максимальный межсэмпловый скачок на шве против
        # такого же скачка в середине записи — шов не должен выделяться.
        mid = np.abs(np.diff(s[len(s) // 4 : len(s) // 4 + TARGET_RATE])).max()
        seam = np.abs(s[-1] - s[0])
        print(f"стык: скачок на шве {seam:.4f}, максимум в середине {mid:.4f}")
        assert seam <= max(mid, 0.01), "шов лупа подозрительно громкий"


if __name__ == "__main__":
    main()