"""AI auto-edit — turn raw clips into a tight, dynamic cut automatically.

Speech-aware: each clip is transcribed (word timing), the pauses between words are
removed, the spoken spans are kept (padded so words never clip), and the result is laid
out as trimmed timeline clips end-to-end. A subtle alternating punch-in zoom adds energy
(the talking-head / TikTok jump-cut feel). Clips with no detected speech are kept whole.

Returns plain timeline-clip dicts ({path,name,kind,start,end,zoom}) so the studio can drop
them straight onto V1.
"""
from __future__ import annotations
import os
from typing import Callable, Optional

import wivexx_media as mm


def keep_spans_from_words(words: list, gap: float = 0.35, padding: float = 0.12,
                          min_seg: float = 0.4, clip_dur: Optional[float] = None) -> list:
    """From word timings build the [start,end] spans to KEEP: merge consecutive words,
    break where a pause exceeds `gap`, pad each span by `padding`, merge spans that touch
    after padding, and drop spans shorter than `min_seg`."""
    ws = [w for w in (words or []) if w.get("start") is not None and w.get("end") is not None]
    if not ws:
        return []
    spans = [[float(ws[0]["start"]), float(ws[0]["end"])]]
    for w in ws[1:]:
        s, e = float(w["start"]), float(w["end"])
        if s - spans[-1][1] > gap:
            spans.append([s, e])
        else:
            spans[-1][1] = e
    out = []
    for s, e in spans:
        s = max(0.0, s - padding)
        e = e + padding
        if clip_dur:
            e = min(e, float(clip_dur))
        if e - s < min_seg:
            continue
        if out and s <= out[-1][1] + 0.05:        # touches the previous span → merge
            out[-1][1] = max(out[-1][1], e)
        else:
            out.append([s, e])
    return out


def auto_edit(paths: list, settings: Optional[dict] = None,
              progress: Optional[Callable[[float], None]] = None,
              cancel: Optional[Callable[[], bool]] = None) -> dict:
    """Build a tightened, dynamic clip list from `paths` (used IN ORDER).

    settings: {gap, padding, min_seg, punch(0..0.3), dynamic(bool), keep_silent(bool)}.
    Returns {ok, clips:[...], count, kept, removed_s} (removed_s = seconds of silence cut)."""
    import wivexx_captions as cap
    s = settings or {}
    gap = max(0.05, float(s.get("gap", 0.35)))
    padding = max(0.0, float(s.get("padding", 0.12)))
    min_seg = max(0.1, float(s.get("min_seg", 0.4)))
    punch = max(0.0, min(0.3, float(s.get("punch", 0.08))))
    dynamic = s.get("dynamic", True)

    clips, idx, src_total, kept_total = [], 0, 0.0, 0.0
    n = max(1, len(paths))
    for pi, p in enumerate(paths):
        if cancel and cancel():
            return {"ok": False, "cancelled": True}
        if not p or not os.path.isfile(p):
            continue
        info = mm.media_info(p)
        dur = float(info.get("duration") or 0)
        src_total += dur
        name = os.path.basename(p)
        is_log = False                                  # auto-correct flat/log footage too
        try:
            import wivexx_timeline as tl
            is_log = bool(tl.detect_log(p).get("is_log"))
        except Exception:
            is_log = False
        words = []
        try:
            tr = cap.transcribe_segments(p)            # auto-detect language, word timing
            if tr.get("ok"):
                for seg in tr.get("segments", []):
                    words += (seg.get("words") or [])
        except Exception:
            words = []
        spans = keep_spans_from_words(words, gap, padding, min_seg, dur) if words else []
        if not spans:                                  # no speech → keep the whole clip
            spans = [[0.0, dur or 0.0]]
        for st, en in spans:
            if en - st < 0.05:
                continue
            c = {"path": p, "name": name, "kind": "video",
                 "start": round(st, 2), "end": round(en, 2)}
            if is_log:
                c["log"] = True
                c["logAuto"] = True
            if dynamic and punch > 0 and (idx % 2 == 1):
                c["zoom"] = round(1.0 + punch, 3)      # every other kept span punches in
            clips.append(c)
            kept_total += (en - st)
            idx += 1
        if progress:
            try:
                progress((pi + 1) / n)
            except Exception:
                pass
    return {"ok": True, "clips": clips, "count": len(clips),
            "kept": round(kept_total, 1), "removed_s": round(max(0.0, src_total - kept_total), 1)}
