"""Subtitle format converters."""

from dataclasses import dataclass
from enum import Enum

from .models import Token


class SubtitleFormat(str, Enum):
    SRT = "srt"
    VTT = "vtt"
    TXT = "txt"


@dataclass
class SubtitleEntry:
    """A single subtitle entry."""

    index: int
    start_ms: int
    end_ms: int
    text: str
    speaker: str | None = None


SENTENCE_ENDINGS = frozenset(".!?。！？")

# CJK Unicode ranges: (start, end) tuples
CJK_RANGES = (
    (0x4E00, 0x9FFF),    # CJK Unified Ideographs
    (0x3000, 0x303F),    # CJK Punctuation
    (0xFF00, 0xFFEF),    # Fullwidth Forms
    (0x3040, 0x30FF),    # Hiragana + Katakana
    (0xAC00, 0xD7AF),    # Korean Hangul
)


def is_cjk_char(char: str) -> bool:
    """Check if character is CJK (Chinese/Japanese/Korean)."""
    code = ord(char)
    return any(start <= code <= end for start, end in CJK_RANGES)


def is_wide_char(char: str) -> bool:
    """Check if character displays as double-width."""
    code = ord(char)
    return any(start <= code <= end for start, end in CJK_RANGES[:3])


def display_width(text: str) -> int:
    """Calculate display width (CJK chars count as 2)."""
    return sum(2 if is_wide_char(c) else 1 for c in text)


def is_word_boundary(token_text: str) -> bool:
    """Check if token starts at a word boundary (safe to break before)."""
    if not token_text:
        return False
    first = token_text[0]
    return first.isspace() or is_cjk_char(first) or (not first.isalnum() and first != "'")


def ends_with_sentence(text: str) -> bool:
    """Check if text ends with sentence-ending punctuation."""
    stripped = text.rstrip()
    return bool(stripped) and stripped[-1] in SENTENCE_ENDINGS


def format_time(ms: int, sep: str = ",") -> str:
    """Format milliseconds to timestamp (HH:MM:SS{sep}mmm)."""
    h, rem = divmod(ms, 3600000)
    m, rem = divmod(rem, 60000)
    s, millis = divmod(rem, 1000)
    return f"{h:02d}:{m:02d}:{s:02d}{sep}{millis:03d}"


def should_flush(
    current_tokens: list[Token],
    current_text: str,
    current_width: int,
    token: Token,
    token_width: int,
    max_width: int,
    max_duration_ms: int,
    min_gap_ms: int,
) -> bool:
    """Determine if current entry should be flushed before adding token."""
    if not current_tokens:
        return False

    # Duration limit
    if token.end_ms - current_tokens[0].start_ms > max_duration_ms:
        return True

    # Sentence break with pause
    has_gap = token.start_ms - current_tokens[-1].end_ms > min_gap_ms
    if ends_with_sentence(current_text) and has_gap:
        return True

    # Width limit at word boundary
    if current_width + token_width > max_width and is_word_boundary(token.text):
        return True

    return False


def tokens_to_entries(
    tokens: list[Token],
    *,
    max_width: int = 42,
    max_duration_ms: int = 7000,
    min_gap_ms: int = 100,
) -> list[SubtitleEntry]:
    """Convert tokens to subtitle entries."""
    if not tokens:
        return []

    entries: list[SubtitleEntry] = []
    current_tokens: list[Token] = []
    current_text = ""
    current_width = 0
    current_speaker: str | None = None

    def flush():
        nonlocal current_tokens, current_text, current_width
        if current_tokens:
            entries.append(SubtitleEntry(
                index=len(entries) + 1,
                start_ms=current_tokens[0].start_ms,
                end_ms=current_tokens[-1].end_ms,
                text=current_text.strip(),
                speaker=current_speaker,
            ))
        current_tokens, current_text, current_width = [], "", 0

    for token in tokens:
        token_width = display_width(token.text)

        # Speaker change
        if token.speaker and token.speaker != current_speaker:
            flush()
            current_speaker = token.speaker

        # Check flush conditions
        if should_flush(
            current_tokens, current_text, current_width, token, token_width,
            max_width, max_duration_ms, min_gap_ms
        ):
            flush()

        current_tokens.append(token)
        current_text += token.text
        current_width += token_width

    flush()
    return entries


def entries_to_srt(entries: list[SubtitleEntry], include_speaker: bool = False) -> str:
    """Convert subtitle entries to SRT format."""
    lines: list[str] = []
    for entry in entries:
        lines.append(str(entry.index))
        lines.append(f"{format_time(entry.start_ms, ',')} --> {format_time(entry.end_ms, ',')}")
        if include_speaker and entry.speaker:
            text = f"[Speaker {entry.speaker}] {entry.text}"
        else:
            text = entry.text
        lines.extend([text, ""])
    return "\n".join(lines)


def entries_to_vtt(entries: list[SubtitleEntry], include_speaker: bool = False) -> str:
    """Convert subtitle entries to VTT format."""
    lines: list[str] = ["WEBVTT", ""]
    for entry in entries:
        lines.append(f"{format_time(entry.start_ms, '.')} --> {format_time(entry.end_ms, '.')}")
        if include_speaker and entry.speaker:
            text = f"<v Speaker {entry.speaker}>{entry.text}"
        else:
            text = entry.text
        lines.extend([text, ""])
    return "\n".join(lines)


def tokens_to_txt(tokens: list[Token], *, include_speaker: bool = False) -> str:
    """Convert tokens to plain text, optionally grouped by speaker."""
    if not tokens:
        return ""

    lines: list[str] = []
    current_speaker: str | None = None
    current_text = ""

    for token in tokens:
        if include_speaker and token.speaker and token.speaker != current_speaker:
            if current_text:
                lines.append(current_text.strip())
            current_speaker = token.speaker
            lines.append(f"Speaker {current_speaker}")
            current_text = ""

        current_text += token.text
        if ends_with_sentence(current_text):
            lines.append(current_text.strip())
            current_text = ""

    if current_text:
        lines.append(current_text.strip())
    return "\n".join(lines)


def tokens_to_subtitle(
    tokens: list[Token],
    format: SubtitleFormat = SubtitleFormat.SRT,
    *,
    max_chars_per_line: int = 42,
    max_duration_ms: int = 7000,
    include_speaker: bool = False,
) -> str:
    """Convert tokens to subtitle format."""
    if format == SubtitleFormat.TXT:
        return tokens_to_txt(tokens, include_speaker=include_speaker)

    entries = tokens_to_entries(
        tokens, max_width=max_chars_per_line, max_duration_ms=max_duration_ms
    )
    if format == SubtitleFormat.VTT:
        return entries_to_vtt(entries, include_speaker)
    return entries_to_srt(entries, include_speaker)
