"""
src/export/timecode.py — Timecode / rational-time conversion helpers

FCPXML uses rational fractions ("1001/24000s") for all time values.
EDL uses SMPTE timecode strings ("HH:MM:SS:FF").

All conversion functions are pure — no I/O, no state.
"""

from __future__ import annotations

import math
from fractions import Fraction


# ---------------------------------------------------------------------------
# Common frame-rate denominators
# ---------------------------------------------------------------------------

_FPS_RATIONAL: dict[float, tuple[int, int]] = {
    23.976: (24000, 1001),
    24.0:   (24,    1),
    25.0:   (25,    1),
    29.97:  (30000, 1001),
    30.0:   (30,    1),
    50.0:   (50,    1),
    59.94:  (60000, 1001),
    60.0:   (60,    1),
}

_TOLERANCE = 0.01  # fps match tolerance


def _fps_to_rational(fps: float) -> tuple[int, int]:
    """Return (numerator, denominator) for common fps values."""
    for ref_fps, rational in _FPS_RATIONAL.items():
        if abs(fps - ref_fps) < _TOLERANCE:
            return rational
    # Fallback: convert float to exact fraction
    f = Fraction(fps).limit_denominator(1001)
    return f.numerator, f.denominator


# ---------------------------------------------------------------------------
# Seconds → FCPXML rational string
# ---------------------------------------------------------------------------

def seconds_to_fcpxml(seconds: float, fps: float) -> str:
    """
    Convert *seconds* to FCPXML rational time string.

    FCPXML requires exact rational arithmetic to avoid drift.
    Example: 10.0s @23.976fps → "240240/24000s"

    Args:
        seconds: Time in seconds (float).
        fps:     Project frame rate.

    Returns:
        FCPXML time string, e.g. "240240/24000s".
    """
    if seconds == 0.0:
        return "0s"

    num, den = _fps_to_rational(fps)          # frames per second = num/den
    # seconds × (num/den) = frames (float); round to nearest frame
    frames = round(seconds * num / den)
    # frames ÷ (num/den) = frames × den/num  → rational seconds
    total_num = frames * den
    total_den = num
    # Reduce fraction
    g = math.gcd(total_num, total_den)
    return f"{total_num // g}/{total_den // g}s"


def seconds_to_frame_count(seconds: float, fps: float) -> int:
    """Convert seconds to integer frame count."""
    return round(seconds * fps)


# ---------------------------------------------------------------------------
# Seconds → SMPTE timecode (for EDL)
# ---------------------------------------------------------------------------

def seconds_to_smpte(seconds: float, fps: float, drop_frame: bool = False) -> str:
    """
    Convert *seconds* to SMPTE timecode string "HH:MM:SS:FF".

    Drop-frame timecode (;) is not implemented — always returns NDF (:).

    Args:
        seconds:    Time in float seconds.
        fps:        Frame rate (23.976, 24, 25, etc.).
        drop_frame: Ignored; placeholder for future DF support.

    Returns:
        "HH:MM:SS:FF" string.
    """
    total_frames = seconds_to_frame_count(seconds, fps)
    nominal_fps  = round(fps)  # e.g. 23.976 → 24

    ff = total_frames % nominal_fps
    total_s = total_frames // nominal_fps
    ss = total_s % 60
    total_m = total_s // 60
    mm = total_m % 60
    hh = total_m // 60

    return f"{hh:02d}:{mm:02d}:{ss:02d}:{ff:02d}"


# ---------------------------------------------------------------------------
# FCPXML format ID helpers
# ---------------------------------------------------------------------------

def fcpxml_format_name(fps: float, width: int = 1920, height: int = 1080) -> str:
    """
    Return an FCPXML format name string for a given frame rate and resolution.

    Example: fps=23.976, 1080p → "FFVideoFormat1080p2398"
    """
    res = f"{height}p"
    fps_tag = {
        23.976: "2398",
        24.0:   "24",
        25.0:   "25",
        29.97:  "2997",
        30.0:   "30",
        50.0:   "50",
        59.94:  "5994",
        60.0:   "60",
    }.get(fps, str(int(fps * 100)))
    return f"FFVideoFormat{res}{fps_tag}"


def fcpxml_frame_duration(fps: float) -> str:
    """
    Return FCPXML frameDuration attribute for a given fps.

    frame duration = 1 frame = 1/fps seconds = den/num seconds
    Example: 23.976fps → num=24000, den=1001 → frame duration = 1001/24000s
    """
    num, den = _fps_to_rational(fps)   # fps = num/den  (e.g. 24000/1001)
    # frame duration = den/num seconds
    g = math.gcd(den, num)
    return f"{den // g}/{num // g}s"
