#!/usr/bin/env python3
"""Собрать готовый ролик и всё, что к нему прилагается, в одну папку.

Рабочая папка проекта к концу монтажа содержит полтора десятка промежуточных
MP4 по сто с лишним мегабайт каждый: `base`, `with_open`, `punched`, `graded`,
`with_fx`, `captioned`, `with_gfx`, `with_broll`, `with_music`. Все они нужны,
пока идёт работа, — по ним возобновляется прогон и ищется, на каком шаге что-то
испортилось. Но искать среди них готовый файл невозможно, а через неделю уже и
не вспомнить, чем `with_gfx2` отличался от `with_gfx`.

Поэтому выдача — отдельный шаг и отдельная папка:

    deliveries/<проект>/
        labalaba_alexnet_v6_20260731.mp4     ← сам ролик
        preview.jpg                          ← контактный лист, видно без плеера
        README.md                            ← что это, из чего собрано, чем проверено
        captions.ass / captions.srt          ← субтитры отдельным файлом
        timeline.json                        ← шкала
        reports/                             ← ворота, провенанс, отчёты шагов

Имя версии не выдумывается: берётся номер, следующий за уже лежащими в папке.
Ролик, не прошедший ворота, не выдаётся вообще — на это есть `--force`, и он
пишет в README, что файл выдан с проваленной проверкой.
"""
from __future__ import annotations

import argparse
import json
import re
import shutil
import subprocess
import sys
from datetime import date
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from media_probe import loudness, probe  # noqa: E402
from skill_config import emit, require_binary, utf8_stdout  # noqa: E402

# Что забирается из проекта, если найдено. Порядок — порядок в README.
SIDECARS: list[tuple[str, str]] = [
    ("captions.ass", "субтитры"),
    ("caption_map.json", "карта блоков субтитров"),
    ("timeline_full.json", "итоговая шкала"),
    ("timeline_recut.json", "шкала до холодного старта"),
    ("recut.json", "план нарезки речи"),
    ("cold_open.json", "выбор крючка"),
    ("retention_plan.json", "план удержания"),
    ("broll_plan.json", "план перебивок"),
    ("gfx_plan.json", "план графики"),
    ("pipeline_report.json", "отчёт прогона"),
    ("voice_restore.json", "диагноз и починка речи"),
    ("cut_sfx.json", "звуки на стыках"),
]


def next_version(folder: Path, stem: str) -> int:
    """Следующий свободный номер версии в папке выдачи."""
    pattern = re.compile(rf"^{re.escape(stem)}_v(\d+)_", re.I)
    used = [int(m.group(1)) for name in folder.glob(f"{stem}_v*")
            if (m := pattern.match(name.name))]
    return max(used, default=0) + 1


def main() -> None:
    utf8_stdout()
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--project", required=True)
    parser.add_argument("--master", help="Файл мастера (по умолчанию master.partial.mp4)")
    parser.add_argument("--name", required=True, help="Имя ролика без версии")
    parser.add_argument("--deliveries", help="Корень папки выдачи "
                                             "(по умолчанию <родитель проекта>/deliveries)")
    parser.add_argument("--source", help="Исходник, чтобы положить рядом ссылку на него")
    parser.add_argument("--note", help="Одна строка: что в этой версии изменилось")
    parser.add_argument("--force", action="store_true",
                        help="Выдать даже с проваленными воротами")
    args = parser.parse_args()

    require_binary("ffmpeg")
    project = Path(args.project).resolve()
    if not project.is_dir():
        raise SystemExit(f"Нет папки проекта: {project}")
    master = Path(args.master).resolve() if args.master else project / "master.partial.mp4"
    if not master.is_file():
        candidates = sorted(project.glob("master*.mp4"))
        if not candidates:
            raise SystemExit(f"Мастер не найден: {master}")
        master = candidates[-1]

    gates_path = project / "gates" / "summary.json"
    gates = json.loads(gates_path.read_text(encoding="utf-8")) if gates_path.is_file() else None
    gates_ok = bool(gates and gates.get("status") == "ok")
    if not gates_ok and not args.force:
        raise SystemExit(
            "Ворота не пройдены (или отчёта нет) — выдача остановлена. "
            "Это защита от «вроде готово»: почини причину или запусти с --force, "
            "и тогда README честно скажет, что файл выдан с проваленной проверкой.")

    root = Path(args.deliveries) if args.deliveries else project.parent / "deliveries"
    folder = root / args.name
    folder.mkdir(parents=True, exist_ok=True)
    version = next_version(folder, args.name)
    stamp = date.today().strftime("%Y%m%d")
    final_name = f"{args.name}_v{version}_{stamp}.mp4"
    final = folder / final_name
    shutil.copy2(master, final)

    info = probe(final)
    video = info.get("video") or {}
    loud = loudness(final)

    # Контактный лист: восемь кадров по всей длине. Смысл в том, чтобы через
    # неделю понять, что это за версия, не открывая плеер.
    preview = folder / f"{final.stem}.preview.jpg"
    duration = float(video.get("duration") or info.get("duration") or 0.0)
    if duration > 1:
        times = [duration * k / 8.0 + duration / 16.0 for k in range(8)]
        select = "+".join(f"lt(abs(t-{t:.3f})\\,0.02)" for t in times)
        subprocess.run(
            ["ffmpeg", "-y", "-v", "error", "-i", str(final),
             "-vf", f"select='{select}',tile=4x2,scale=1200:-1", "-frames:v", "1",
             str(preview)], capture_output=True)

    copied: list[dict] = []
    for name, what in SIDECARS:
        origin = project / name
        if origin.is_file():
            shutil.copy2(origin, folder / f"{final.stem}.{name}")
            copied.append({"file": f"{final.stem}.{name}", "what": what})

    reports = folder / f"{final.stem}.reports"
    if (project / "gates").is_dir():
        shutil.copytree(project / "gates", reports / "gates", dirs_exist_ok=True)
    for manifest in project.rglob("manifest.json"):
        target = reports / "provenance" / f"{manifest.parent.parent.name}_{manifest.parent.name}.json"
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(manifest, target)

    # SRT рядом с ASS: половина площадок принимает только его.
    ass = folder / f"{final.stem}.captions.ass"
    if ass.is_file():
        subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", str(ass),
                        str(folder / f"{final.stem}.captions.srt")], capture_output=True)

    readme = folder / "README.md"
    lines = [f"# {args.name}", ""]
    if readme.is_file():
        # Дописываем, а не перезаписываем: папка — это история версий.
        lines = readme.read_text(encoding="utf-8").rstrip().splitlines()
        lines.append("")
    lines += [
        f"## v{version} — {stamp}",
        "",
        f"- **Файл:** `{final_name}`",
        f"- **Длительность:** {duration:.2f} с",
        f"- **Геометрия:** {video.get('display_width')}×{video.get('display_height')} "
        f"@ {video.get('avg_frame_rate')} fps",
        f"- **Громкость:** {loud.get('integrated_lufs')} LUFS, "
        f"истинный пик {loud.get('true_peak_dbtp')} dBTP",
        f"- **Размер:** {final.stat().st_size / 1e6:.1f} МБ",
        f"- **Ворота:** {'пройдены' if gates_ok else 'НЕ ПРОЙДЕНЫ — выдано с --force'}",
    ]
    if args.source:
        lines.append(f"- **Исходник:** `{args.source}`")
    if args.note:
        lines.append(f"- **Что изменилось:** {args.note}")
    if copied:
        lines += ["", "Рядом лежат:", ""]
        lines += [f"- `{item['file']}` — {item['what']}" for item in copied]
    lines += ["", f"Рабочая папка проекта: `{project}`", ""]
    readme.write_text("\n".join(lines) + "\n", encoding="utf-8")

    emit({
        "status": "ok",
        "delivered": str(final),
        "folder": str(folder),
        "version": version,
        "gates_ok": gates_ok,
        "preview": str(preview) if preview.is_file() else None,
        "sidecars": copied,
        "readme": str(readme),
        "duration": round(duration, 2),
        "loudness": loud,
        "note": ("Промежуточные файлы остались в рабочей папке — по ним возобновляется "
                 "прогон. Здесь лежит только то, что отдают."),
    })


if __name__ == "__main__":
    main()
