"""
src/export/fcpxml_writer.py — EditTimeline → Final Cut Pro XML (FCPXML 1.10)

Generates a standards-compliant FCPXML file that can be imported directly
into Final Cut Pro X, DaVinci Resolve, or Premiere Pro (via FCPXML plugin).

Spec reference: https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference
"""

from __future__ import annotations

import logging
from pathlib import Path
from urllib.parse import quote
from xml.etree import ElementTree as ET
from xml.etree.ElementTree import Element, SubElement

from src.core.config import AppConfig
from src.core.models import EditClip, EditTimeline
from src.export.timecode import (
    fcpxml_format_name,
    fcpxml_frame_duration,
    seconds_to_fcpxml,
)

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Asset registry — one <asset> per unique source file
# ---------------------------------------------------------------------------

class _AssetRegistry:
    def __init__(self) -> None:
        self._assets: dict[Path, str] = {}   # path → asset id
        self._counter = 2                    # r1 reserved for format

    def get_or_create(self, path: Path) -> str:
        if path not in self._assets:
            rid = f"r{self._counter}"
            self._assets[path] = rid
            self._counter += 1
        return self._assets[path]

    @property
    def items(self) -> dict[Path, str]:
        return dict(self._assets)


# ---------------------------------------------------------------------------
# Builder
# ---------------------------------------------------------------------------

def _path_to_url(path: Path) -> str:
    """Convert an absolute Path to a file:// URL as required by FCPXML."""
    posix = path.as_posix()
    if not posix.startswith("/"):
        # Windows drive letter: C:/foo → /C:/foo
        posix = "/" + posix
    return "file://" + quote(posix, safe="/:@")


def build_fcpxml(
    timeline: EditTimeline,
    cfg: AppConfig,
    source_duration_s: float = 7200.0,  # 2-hour fallback if not probed
) -> ET.ElementTree:
    """
    Build a complete FCPXML ElementTree from an EditTimeline.

    Args:
        timeline:          Ordered sequence of EditClips.
        cfg:               Application configuration.
        source_duration_s: Duration of the source movie asset (used for
                           <asset> duration attribute). Will be probed
                           automatically when possible.

    Returns:
        xml.etree.ElementTree.ElementTree — call .write() to serialise.
    """
    fps = timeline.frame_rate

    # ---- root ---------------------------------------------------------------
    root = Element("fcpxml", version=cfg.export.fcpxml_version)
    root.set("xmlns", "http://www.apple.com/dt/FCPXML/1_10")

    # ---- resources ----------------------------------------------------------
    resources = SubElement(root, "resources")

    format_id   = "r1"
    format_name = fcpxml_format_name(fps)
    fmt = SubElement(resources, "format",
        id=format_id,
        name=format_name,
        frameDuration=fcpxml_frame_duration(fps),
        width="1920",
        height="1080",
        colorSpace="1-1-1 (Rec. 709)",
    )

    registry = _AssetRegistry()

    # Pre-register all unique source paths so <asset> elements come before
    # the <library> block (required by FCPXML spec).
    for clip in timeline.clips:
        registry.get_or_create(clip.match.source_path)

    # Probe actual source duration when possible
    _durations: dict[Path, float] = {}
    for path in registry.items:
        try:
            from src.cv.frame_extractor import get_video_info
            info = get_video_info(path)
            _durations[path] = float(info["duration_s"])
        except Exception:
            _durations[path] = source_duration_s

    for path, rid in registry.items.items():
        dur_s = _durations.get(path, source_duration_s)
        SubElement(resources, "asset",
            id=rid,
            name=path.stem,
            src=_path_to_url(path),
            start="0s",
            duration=seconds_to_fcpxml(dur_s, fps),
            hasVideo="1",
            hasAudio="1",
            format=format_id,
        )

    # ---- library / event / project ------------------------------------------
    library = SubElement(root, "library")
    event   = SubElement(library, "event", name=timeline.title)
    project = SubElement(event, "project", name=timeline.title)
    sequence = SubElement(project, "sequence",
        duration=seconds_to_fcpxml(timeline.total_duration_s, fps),
        format=format_id,
        tcStart="0s",
        tcFormat="NDF",
        audioLayout="stereo",
        audioRate="48k",
    )
    spine = SubElement(sequence, "spine")

    # ---- clips --------------------------------------------------------------
    for clip in sorted(timeline.clips, key=lambda c: c.clip_index):
        asset_id = registry.get_or_create(clip.match.source_path)

        source_duration_s = clip.source_timeline_duration_s
        clip_elem = SubElement(spine, "clip",
            name=f"Beat_{clip.beat.beat_id:03d}_{clip.beat.beat_type.name}",
            ref=asset_id,
            # offset = position on the timeline
            offset=seconds_to_fcpxml(clip.timeline_start_s, fps),
            # duration = matched source part only; trailer-only tails become gaps.
            duration=seconds_to_fcpxml(source_duration_s, fps),
            # start = in-point inside the source asset
            start=seconds_to_fcpxml(clip.match.in_point_s, fps),
        )

        # Inline audio role
        SubElement(clip_elem, "audio",
            role="dialogue",
            srcCh="1, 2",
            outCh="L, R",
        )

        if clip.trailer_tail_s > 0:
            gap = SubElement(spine, "gap",
                name=f"Beat_{clip.beat.beat_id:03d}_TRAILER_TAIL_BLACK_FADE",
                offset=seconds_to_fcpxml(clip.timeline_start_s + source_duration_s, fps),
                duration=seconds_to_fcpxml(clip.trailer_tail_s, fps),
                start="0s",
            )
            SubElement(gap, "marker",
                start="0s",
                value="Trailer-only tail: add fade/dissolve to black here",
                completed="0",
            )

    return ET.ElementTree(root)


# ---------------------------------------------------------------------------
# Writer
# ---------------------------------------------------------------------------

def write_fcpxml(
    timeline: EditTimeline,
    cfg: AppConfig,
    output_path: Path | None = None,
) -> Path:
    """
    Serialise the EditTimeline to a .fcpxml file.

    Args:
        timeline:    EditTimeline from build_timeline().
        cfg:         Application configuration.
        output_path: Override destination. Defaults to
                     <output_dir>/<project_name>.fcpxml.

    Returns:
        Path to the written .fcpxml file.
    """
    if output_path is None:
        output_path = cfg.paths.output_dir / f"{timeline.title}.fcpxml"

    output_path.parent.mkdir(parents=True, exist_ok=True)

    tree = build_fcpxml(timeline, cfg)

    # Add XML declaration + DOCTYPE manually (ElementTree doesn't support DOCTYPE)
    xml_bytes = ET.tostring(tree.getroot(), encoding="unicode", xml_declaration=False)
    header = (
        '<?xml version="1.0" encoding="UTF-8"?>\n'
        '<!DOCTYPE fcpxml>\n'
    )

    output_path.write_text(header + xml_bytes, encoding="utf-8")

    logger.info("FCPXML written → %s (%d clips)", output_path, timeline.clip_count)
    return output_path
