"""ffmpeg silencedetect 기반 무음 감지 → 발화(speech) 구간 추출.

핵심: 무음을 직접 찾고, 그 여집합 = 발화 구간. 발화 구간 양 끝에 padding 을
주어 단어 시작/끝이 잘리지 않게 한다(점프컷 특유의 '말 잘림' 방지).
"""
from __future__ import annotations

import re
import subprocess
from typing import List, Tuple

Segment = Tuple[float, float]  # (start_sec, end_sec)

_SILENCE_START = re.compile(r"silence_start:\s*(-?[\d.]+)")
_SILENCE_END = re.compile(r"silence_end:\s*(-?[\d.]+)")


def _detect_silences(video_path: str, noise_db: float, min_silence: float) -> List[Segment]:
    """무음 구간 [(start, end), ...] (초) 반환."""
    cmd = [
        "ffmpeg", "-hide_banner", "-nostats", "-i", video_path,
        "-af", f"silencedetect=noise={noise_db}dB:d={min_silence}",
        "-f", "null", "-",
    ]
    # silencedetect 로그는 stderr 로 나온다. 단, Windows에서 부모 프로세스(uvicorn)의
    # stderr 핸들이 리다이렉트된 경우 capture_output 의 stderr PIPE 가 None 으로 잡히는
    # 케이스가 있어, stderr→stdout 병합 후 stdout 을 읽는다(검증된 경로).
    proc = subprocess.run(
        cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
        text=True, encoding="utf-8", errors="replace",
    )
    log = proc.stdout or ""

    silences: List[Segment] = []
    cur_start = None
    for line in log.splitlines():
        m = _SILENCE_START.search(line)
        if m:
            cur_start = float(m.group(1))
            continue
        m = _SILENCE_END.search(line)
        if m and cur_start is not None:
            silences.append((cur_start, float(m.group(1))))
            cur_start = None
    return silences


def detect_speech_segments(
    video_path: str,
    duration: float,
    *,
    noise_db: float = -30.0,
    min_silence: float = 0.4,
    pad: float = 0.08,
    min_speech: float = 0.2,
) -> List[Segment]:
    """무음의 여집합 = 발화 구간. padding/merge/최소길이 필터 적용.

    Args:
        duration: 전체 영상 길이(초). probe 로 미리 구한 값.
        noise_db: 이 dB 이하를 무음으로 간주.
        min_silence: 이 길이(초) 이상 지속돼야 무음으로 컷.
        pad: 발화 구간 양 끝 여유(초). 말 잘림 방지.
        min_speech: 이보다 짧은 발화 조각은 버림(노이즈성 컷 방지).
    """
    silences = _detect_silences(video_path, noise_db, min_silence)

    # 무음의 여집합 = 발화
    speech: List[Segment] = []
    cursor = 0.0
    for s_start, s_end in silences:
        if s_start > cursor:
            speech.append((cursor, s_start))
        cursor = max(cursor, s_end)
    if cursor < duration:
        speech.append((cursor, duration))

    # padding (양 끝 확장) + 클램프
    padded = [(max(0.0, s - pad), min(duration, e + pad)) for s, e in speech]

    # padding 으로 겹친 구간 병합
    merged: List[Segment] = []
    for s, e in padded:
        if merged and s <= merged[-1][1]:
            merged[-1] = (merged[-1][0], max(merged[-1][1], e))
        else:
            merged.append((s, e))

    # 최소 발화 길이 필터
    return [(s, e) for s, e in merged if (e - s) >= min_speech]
