#!/usr/bin/env python3
"""Remap a word transcript after pause-safe speech recuts.

The recut spec contains non-overlapping source intervals and their exact global
positions. Every transcript word must land in exactly one interval; otherwise
the command fails instead of silently dropping or duplicating speech.
"""
from __future__ import annotations

import argparse
import json
from pathlib import Path


EPSILON = 0.015


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("words_json")
    parser.add_argument("recut_spec")
    parser.add_argument("output")
    args = parser.parse_args()

    transcript_path = Path(args.words_json).resolve()
    spec_path = Path(args.recut_spec).resolve()
    transcript = json.loads(transcript_path.read_text(encoding="utf-8"))
    spec = json.loads(spec_path.read_text(encoding="utf-8"))
    segments = spec["speech_segments"]
    words = transcript["words"]

    mapped: list[dict] = []
    coverage: dict[int, int] = {int(word["id"]): 0 for word in words}
    for segment_index, segment in enumerate(segments):
        source_in = float(segment["source_in"])
        source_out = float(segment["source_out"])
        global_start = float(segment["global_start"])
        if source_out <= source_in:
            raise SystemExit(f"Invalid source interval in segment {segment_index}")
        for word in words:
            local_start = float(word["start"])
            local_end = float(word["end"])
            if local_start >= source_in - EPSILON and local_end <= source_out + EPSILON:
                source_id = int(word["id"])
                coverage[source_id] += 1
                item = dict(word)
                item["source_word_id"] = source_id
                item["source_index"] = int(spec.get("speech_source_index", 0))
                item["local_start"] = local_start
                item["local_end"] = local_end
                item["start"] = round(global_start + local_start - source_in, 6)
                item["end"] = round(global_start + local_end - source_in, 6)
                item["recut_segment"] = segment_index
                mapped.append(item)

    missing = [word_id for word_id, count in coverage.items() if count == 0]
    duplicated = [word_id for word_id, count in coverage.items() if count > 1]
    if missing or duplicated:
        raise SystemExit(
            f"Unsafe recut: missing source words={missing}, duplicated source words={duplicated}"
        )

    mapped.sort(key=lambda item: (item["start"], item["source_word_id"]))
    for new_id, item in enumerate(mapped):
        item["id"] = new_id

    expected_duration = float(spec["expected_duration"])
    first_word_start = min(float(word["start"]) for word in mapped)
    last_word_end = max(float(word["end"]) for word in mapped)
    if last_word_end > expected_duration + EPSILON:
        raise SystemExit("Last word extends past expected duration")

    timeline = {
        "version": "3.1-director-recut",
        "source_transcript": str(transcript_path),
        "recut_spec": str(spec_path),
        "clips": segments,
        "visual_segments": spec.get("visual_segments", []),
        "words": mapped,
        "expected_duration": expected_duration,
        "first_word_start": round(first_word_start, 6),
        "last_word_end": round(last_word_end, 6),
        "tail_after_speech": round(expected_duration - last_word_end, 6),
        "word_integrity": {
            "source_words": len(words),
            "mapped_words": len(mapped),
            "missing": missing,
            "duplicated": duplicated,
        },
    }
    output = Path(args.output)
    output.write_text(
        json.dumps(timeline, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    print(
        json.dumps(
            {
                "output": str(output.resolve()),
                "words": len(mapped),
                "expected_duration": expected_duration,
                "last_word_end": last_word_end,
                "status": "ok",
            },
            ensure_ascii=False,
        )
    )


if __name__ == "__main__":
    main()
