"""붙여넣기(JSON) 파서 — LLM이 만든 하이라이트 편집안을 관대하게 읽는다.

입력 스키마(JSON 하나):
    {
      "url": "https://www.youtube.com/watch?v=…",
      "title_top": "…", "title_main": "…", "channel": "…",   # 선택
      "cuts": [
        {"start": "0:01.0", "end": "0:03.5",
         "bottom": "하단 자막\n두 줄", "effect": "광속하강"},
        …
      ]
    }

- 배치 시간은 받지 않는다 → 컷을 순서대로 이어붙인 누적 길이로 자동 계산.
- 자막은 컷에 1:1로 묶여 있어 어긋날 수 없다(SRT 타임코드 불필요).
- 시간은 관대하게 파싱: "분:초.밀리" / "시:분:초.밀리", 콤마·점 밀리초 모두 허용, 초 단독도 허용.
"""
from __future__ import annotations

import json
from typing import Dict, List, Tuple


def parse_time(v) -> float:
    """'M:SS.mmm' / 'H:MM:SS.mmm' / 'SS.mmm' / 숫자 → 초(float). 콤마도 허용."""
    s = str(v).strip().replace(",", ".")
    if not s:
        raise ValueError("빈 시간값")
    if ":" in s:
        parts = [float(x) for x in s.split(":")]
        if len(parts) == 3:
            h, m, sec = parts
        elif len(parts) == 2:
            h, (m, sec) = 0.0, parts
        else:
            raise ValueError(f"시간 형식 오류: {v!r}")
        return h * 3600 + m * 60 + sec
    return float(s)


def split_json_objects(text: str) -> List[str]:
    """텍스트에서 최상위 {…} JSON 블록들을 순서대로 추출.

    오팔 출력처럼 JSON 여러 개 사이에 구분선·타이틀 후보·검산표 등 잡문이 섞여
    있어도 중괄호 균형만 맞으면 전부 찾는다. 문자열 안의 중괄호는 무시.
    """
    out: List[str] = []
    depth, start, in_str, esc = 0, -1, False, False
    for i, ch in enumerate(text or ""):
        if in_str:
            if esc:
                esc = False
            elif ch == "\\":
                esc = True
            elif ch == '"':
                in_str = False
            continue
        if ch == '"':
            if depth > 0:
                in_str = True
            continue
        if ch == "{":
            if depth == 0:
                start = i
            depth += 1
        elif ch == "}" and depth > 0:
            depth -= 1
            if depth == 0 and start != -1:
                out.append(text[start:i + 1])
                start = -1
    return out


def parse_paste(raw) -> Dict:
    """붙여넣기 텍스트(JSON) → {url, cuts:[(s,e,bottom,effect)], title_top, title_main, channel}.

    파싱/검증 실패 시 ValueError(사용자에게 그대로 보여줄 한국어 메시지).
    """
    if isinstance(raw, (dict, list)):
        data = raw
    else:
        text = (raw or "").strip()
        # 흔한 실수: 코드펜스로 감싸서 붙여넣음 → 벗겨줌
        if text.startswith("```"):
            text = text.strip("`")
            nl = text.find("\n")
            if nl != -1:
                text = text[nl + 1:]
        try:
            # strict=False: 자막 안 실제 줄바꿈(제어문자) 허용 → LLM 출력 관대 수용
            data = json.loads(text, strict=False)
        except json.JSONDecodeError as e:
            raise ValueError(f"JSON 형식 오류: {e.msg} (줄 {e.lineno})")

    if not isinstance(data, dict):
        raise ValueError("최상위는 JSON 객체({…})여야 합니다.")

    url = str(data.get("url", "")).strip()
    if not (url.startswith("http://") or url.startswith("https://")):
        raise ValueError("url 필드에 올바른 유튜브 주소가 필요합니다.")

    raw_cuts = data.get("cuts")
    if not isinstance(raw_cuts, list) or not raw_cuts:
        raise ValueError("cuts 배열에 컷을 하나 이상 넣어주세요.")

    cuts: List[Tuple[float, float, str, str]] = []
    for i, c in enumerate(raw_cuts, 1):
        if not isinstance(c, dict):
            raise ValueError(f"{i}번 컷이 객체가 아닙니다.")
        try:
            s = parse_time(c["start"])
            e = parse_time(c["end"])
        except KeyError as k:
            raise ValueError(f"{i}번 컷에 {k} 필드가 없습니다.")
        if e <= s:
            raise ValueError(f"{i}번 컷: 끝({c.get('end')})이 시작({c.get('start')})보다 커야 합니다.")
        bottom = str(c.get("bottom", "") or "").strip()
        effect = str(c.get("effect", "") or "").strip()
        cuts.append((s, e, bottom, effect))

    return {
        "url": url,
        "cuts": cuts,
        "title_top": str(data.get("title_top", "") or "").strip(),
        "title_main": str(data.get("title_main", "") or "").strip(),
        "channel": str(data.get("channel", "") or "").strip(),
    }
