"""Align a retention curve with the script's segments and report drop per minute by segment kind.

usage: python3 align_retention.py <research_dir> <video_id> <segments.json> [--skip 60] [--max-diff 5]
                                  [--uncalibrated] [--out analysis/retention-<id>-segments.json]

segments.json: [{"start": 0, "end": 42.5, "kind": "narration", "label": "hook", "source": "script-v7.txt:L3-L9"}, ...]
  Times are the PUBLISHED video's seconds. Take boundaries from your own script or edit decision list when you have
  one (say which file and line in "source"); auto-subtitle times are the fallback and are labelled 자동자막 기준.
Calibration first: the curve's value at 0:30 must agree with Studio's own sentence ("N%의 시청자가 약 0:30 지점에서도…")
within --max-diff %p. Write that value into analysis/studio-metrics.json as
  [{"id": "<video_id>", "ret_point": {"at": "0:30", "pct": 61.2}}, ...]
In the first run, curves of videos of 34 minutes or more drifted 8.7–21.3 %p at 0:30. An uncalibrated curve is refused
unless --uncalibrated, and the output then says so.
The first --skip seconds are excluded (every video drops fastest at the start, which would bias whichever kind opens
it). The result is correlation: a segment kind that loses viewers faster is not shown to cause the loss."""
import argparse, json, sys


def value_at(curve, t):
    if t <= curve[0][0]: return curve[0][1]
    for (t0, v0), (t1, v1) in zip(curve, curve[1:]):
        if t1 >= t:
            return v0 + (v1 - v0) * (t - t0) / max(t1 - t0, 1e-9)
    return curve[-1][1]


def align(curve, duration, segments, skip=60.0):
    per, kinds = [], {}
    for s in segments:
        a, b = max(float(s['start']), skip), min(float(s['end']), duration)
        if b - a < 1: continue
        drop = value_at(curve, a) - value_at(curve, b)
        mins = (b - a) / 60
        per.append({**s, 'from_s': round(a, 1), 'to_s': round(b, 1), 'drop_pp': round(drop, 2),
                    'pp_per_min': round(drop / mins, 2)})
        k = kinds.setdefault(s['kind'], {'minutes': 0.0, 'drop_pp': 0.0, 'segments': 0})
        k['minutes'] += mins; k['drop_pp'] += drop; k['segments'] += 1
    for k in kinds.values():
        k['pp_per_min'] = round(k['drop_pp'] / k['minutes'], 2); k['minutes'] = round(k['minutes'], 2)
        k['drop_pp'] = round(k['drop_pp'], 2)
    return per, kinds


def main(argv=None):
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument('research_dir'); ap.add_argument('video_id'); ap.add_argument('segments')
    ap.add_argument('--skip', type=float, default=60.0); ap.add_argument('--max-diff', type=float, default=5.0)
    ap.add_argument('--uncalibrated', action='store_true'); ap.add_argument('--out')
    a = ap.parse_args(argv); B = a.research_dir
    ret = json.load(open(f'{B}/analysis/retention-curves.json')).get(a.video_id)
    if not ret: sys.exit(f'no retention curve for {a.video_id} (run parse_retention.py first)')
    try:
        metrics = {m['id']: m for m in json.load(open(f'{B}/analysis/studio-metrics.json'))}
    except FileNotFoundError:
        metrics = {}
    rp = (metrics.get(a.video_id) or {}).get('ret_point') or {}
    cal = {'studio_at_30s': rp.get('pct') if rp.get('at') == '0:30' else None,
           'curve_at_30s': round(value_at(ret['curve'], 30), 1)}
    cal['diff_pp'] = None if cal['studio_at_30s'] is None else round(cal['curve_at_30s'] - cal['studio_at_30s'], 1)
    cal['ok'] = cal['diff_pp'] is not None and abs(cal['diff_pp']) <= a.max_diff
    if not cal['ok'] and not a.uncalibrated:
        print(json.dumps(cal, ensure_ascii=False), file=sys.stderr)
        sys.exit(f"curve not calibrated against Studio's 0:30 sentence (diff {cal['diff_pp']} %p, max {a.max_diff}); "
                 'quote Studio values instead, or pass --uncalibrated and label the result')
    per, kinds = align(ret['curve'], ret['duration'], json.load(open(a.segments)), a.skip)
    out = {'video': a.video_id, 'calibration': cal, 'calibrated': cal['ok'], 'skip_s': a.skip,
           'captured_from': ret.get('captured_from'), 'segments': per, 'by_kind': kinds,
           'note': '상관관계: 구간 종류별 이탈 속도 차이는 원인을 증명하지 않습니다.'}
    path = a.out or f'{B}/analysis/retention-{a.video_id}-segments.json'
    json.dump(out, open(path, 'w'), ensure_ascii=False, indent=1)
    for k, v in kinds.items():
        print(f"{k}: {v['pp_per_min']} %p/min over {v['minutes']} min ({v['segments']} segments)")
    print(('calibrated' if cal['ok'] else 'UNCALIBRATED'), cal, '→', path)
    return 0


if __name__ == '__main__':
    sys.exit(main())
