"""여성 2인이 갈리는지 — **판정에 쓰는 숫자를 만드는 정본 스크립트**.

`analyze_voices.py` 는 탐색용(전체 궤적)이고, 문서에 싣는 표는 여기서만 나온다.
(1차 판정은 인라인 코드로 냈다가 재현이 안 돼 verifier 에 걸렸다 — 그래서 스크립트로 내렸다.)

설계
  ⓐ **구간은 STT word timing 에서 온다.** 입 모션 박스는 대리지표라 경계가 ±1s 씩 밀렸다
     (실측: 유민 턴을 2.5~5.5s 로 잡으면 비발화 1.4s 가 섞인다). 아래 LINES 는
     whisper-cuda-spark 의 word timestamp 를 그대로 적은 것이다.
  ⓑ **비교는 클립 자기 채널 안에서만** 한다. ref 도메인 CMVN 을 클립 오디오에 씌우면
     정규화가 어긋난다 — 그렇게 낸 "어느 ref 에 가깝나" 는 **무오디오 대조군에서도 같은 값이 나와**
     정보가 없다는 것이 확인됐다. 그래서 ref 대조 열은 이 스크립트에 없다.
  ⓒ **양성 대조 없이 음수 코사인을 해석하지 않는다.** 각 줄을 시간으로 반 갈라 자기 자신과 비교한
     값이 곧 그 클립의 **동일 화자 바닥값**이고, 남↔여가 그 아래로 얼마나 내려가는지가
     이 지표의 **검정력**이다. 검정력이 안 보이면 여↔여 값도 해석하지 않는다.
  ⓓ **경계 민감도를 같이 낸다** — 창을 ±0.15s 씩 조여·늘려 3판을 돌린 범위. 단일 값만 실으면
     재현자가 다른 숫자를 보고 "안 맞는다" 로 읽는다(feasibility ⑤ 의 박스 스윕과 같은 규율).
"""
import json

import numpy as np

import analyze_voices as A

# whisper-cuda-spark (model=whisper-speech · language=ko · word timestamps) 실측.
# 대사는 프롬프트가 지정한 3줄이고 화자 배정도 프롬프트가 정한 것이다.
LINES = {
    "out_stage2_j26680.mp4": {
        "L1_hanna": (0.00, 2.04),   # 조명이 조금 더 필요해요
        "L2_yumin": (2.30, 4.10),   # 제가 지금 바로 옮길게요
        "L3_jiho": (5.74, 7.82),    # 좋아요, 그럼 다시 갑시다
    },
    "out_stage1ctl_j26681.mp4": {
        "L1_hanna": (0.00, 2.32),
        "L2_yumin": (2.86, 5.04),
        "L3_jiho": (5.66, 7.62),
    },
}
PADS = [0.0, -0.15, 0.15]          # 경계 민감도 — 창을 조이거나 늘려본다


def analyse(path, lines):
    wav = A.load(path)
    dom = A.Domain([wav])

    def vec(a, b):
        a, b = max(0.0, a), min(len(wav) / A.SR, b)
        return dom.vec(wav[int(a * A.SR):int(b * A.SR)])

    def run(pad):
        span = {k: (a - pad, b + pad) for k, (a, b) in lines.items()}
        floors, vecs = {}, {}
        for k, (a, b) in span.items():
            m = (a + b) / 2
            va, vb = vec(a, m), vec(m, b)
            floors[k] = None if va is None or vb is None else round(A.cos(va, vb), 3)
            vecs[k] = vec(a, b)
        cross = {}
        ks = list(span)
        for i in range(len(ks)):
            for j in range(i + 1, len(ks)):
                cross[f"{ks[i]}~{ks[j]}"] = round(A.cos(vecs[ks[i]], vecs[ks[j]]), 3)
        return floors, cross

    runs = [run(p) for p in PADS]
    keys_f, keys_c = list(runs[0][0]), list(runs[0][1])
    out = {
        "same_speaker_floor": {k: [r[0][k] for r in runs] for k in keys_f},
        "cross_line": {k: [r[1][k] for r in runs] for k in keys_c},
        "profile": {},
    }
    for k, (a, b) in lines.items():
        seg = wav[int(a * A.SR):int(b * A.SR)]
        out["profile"][k] = {"f0_med_hz": round(A.f0_median(seg), 1), "tilt": round(A.tilt(seg), 3)}
    return out


def main():
    res = {p: analyse(p, l) for p, l in LINES.items()}
    print(json.dumps(res, ensure_ascii=False, indent=1))
    with open("analysis_turns.json", "w") as f:
        json.dump({"_source": "turn_compare.py", "_pads_s": PADS, "clips": res}, f,
                  ensure_ascii=False, indent=1)

    print("\n요약 (값 = 창 pad 0 / -0.15 / +0.15 s)")
    for p, r in res.items():
        print(f"\n[{p}]")
        print("  동일화자 바닥값(양성 대조):", r["same_speaker_floor"])
        print("  줄끼리(음성 대조 = 남↔여 포함):", r["cross_line"])
        print("  f0/tilt:", {k: v for k, v in r["profile"].items()})


if __name__ == "__main__":
    main()
