"""Write deterministic local preview PNG scene cards."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from textwrap import wrap
from typing import Any

from PIL import Image, ImageDraw, ImageFont

from autoshorts.rendering.local_preview import LocalPreviewPlan, validate_local_preview_plan


@dataclass(frozen=True)
class LocalFrameWriteResult:
    script_id: str
    output_dir: str
    frame_paths: tuple[str, ...]
    side_effects: tuple[str, ...] = ("write_png",)
    external_calls: tuple[str, ...] = ()


@dataclass(frozen=True)
class LocalizedFrameCopy:
    scene_label: str
    caption_text: str
    asset_label: str
    motion_text: str


_GERMAN_SCENE_LABELS = {
    "first_frame_hook": "AUFMERKSAMKEIT",
    "pattern_interrupt": "MUSTERBRUCH",
    "core_explanation": "KERNIDEE",
    "screen_or_card_visual": "BEISPIEL",
    "cta": "FREIGABE",
}

_GERMAN_CAPTIONS = {
    "first_frame_hook": "Wenn KI deine Woche steuert, hast du die Kontrolle bereits abgegeben.",
    "pattern_interrupt": "Das Problem ist nicht zu wenig KI, sondern zu wenig Führung.",
    "core_explanation": "Nutze KI als Assistent: Ziele prüfen, Aufgaben ordnen, nächste Schritte festlegen.",
    "screen_or_card_visual": "Ein 10-Minuten-Review zeigt: Was bleibt, was fällt weg, was kommt zuerst?",
    "cta": "Freigabe prüfen: Versteht man die Idee sofort und bleibt die Kontrolle beim Menschen?",
}

_GERMAN_ASSET_LABELS = {
    "kinetic_text_overlay": "Text-Overlay",
    "mechanism_animation": "Mechanik-Animation",
    "screen_recording_simulation": "Screen/Card-Simulation",
    "cinematic_ai_video": "Cinematic AI-Visual",
}

_GERMAN_MOTION = {
    "first_frame_hook": "Schneller Einstieg mit grossem Hook-Text; kein Text im Bildmaterial eingebrannt.",
    "pattern_interrupt": "Kurzer visueller Bruch, dann klare Problem-Zuspitzung.",
    "core_explanation": "Schrittweise Bewegung: Ziele, Aufgaben, nächste Aktion erscheinen nacheinander.",
    "screen_or_card_visual": "Langsamer Zoom auf eine Review-Karte mit klaren Abschnitten.",
    "cta": "Ruhiger Abschluss mit klarer Freigabe-Frage und kurzem Call-to-Action.",
}


def _validate_output_dir(output_dir: str | Path) -> Path:
    path = Path(output_dir)
    if not str(path).strip() or ".." in path.parts:
        raise ValueError("output_dir must be explicit and normalized")
    return path


def _font(size: int):
    try:
        return ImageFont.truetype("DejaVuSans.ttf", size=size)
    except OSError:
        return ImageFont.load_default()


def _text_width(text: str, font: Any) -> int:
    scratch = Image.new("RGB", (1, 1))
    draw = ImageDraw.Draw(scratch)
    bbox = draw.textbbox((0, 0), text, font=font)
    return int(bbox[2] - bbox[0])


def wrap_text_to_pixel_width(text: str, *, max_width_px: int, font_size: int) -> tuple[str, ...]:
    """Wrap text using measured pixel width instead of a fixed character count."""

    font = _font(font_size)
    lines: list[str] = []
    for paragraph in text.splitlines() or [text]:
        words = paragraph.split()
        if not words:
            lines.append("")
            continue
        current = words[0]
        for word in words[1:]:
            candidate = f"{current} {word}"
            if _text_width(candidate, font) <= max_width_px:
                current = candidate
            else:
                lines.append(current)
                current = word
        lines.append(current)
    return tuple(lines)


def _wrapped(text: str, width: int) -> str:
    lines: list[str] = []
    for paragraph in text.splitlines() or [text]:
        lines.extend(wrap(paragraph, width=width) or [""])
    return "\n".join(lines)


def _palette(asset_mode: str) -> tuple[tuple[int, int, int], tuple[int, int, int]]:
    palettes = {
        "kinetic_text_overlay": ((15, 23, 42), (96, 165, 250)),
        "mechanism_animation": ((17, 24, 39), (52, 211, 153)),
        "screen_recording_simulation": ((24, 24, 27), (250, 204, 21)),
        "cinematic_ai_video": ((28, 25, 23), (251, 146, 60)),
    }
    return palettes.get(asset_mode, ((17, 24, 39), (148, 163, 184)))


def localize_frame_copy(frame, *, language: str = "de") -> LocalizedFrameCopy:
    """Return approval-preview copy, German by default; final videos can stay English later."""

    if language != "de":
        return LocalizedFrameCopy(
            scene_label=str(frame.scene_purpose.replace("_", " ").upper()),
            caption_text=str(frame.caption_text),
            asset_label=str(frame.asset_mode),
            motion_text=str(frame.motion_instruction),
        )
    return LocalizedFrameCopy(
        scene_label=str(_GERMAN_SCENE_LABELS.get(frame.scene_purpose, frame.scene_purpose.replace("_", " ").upper())),
        caption_text=str(_GERMAN_CAPTIONS.get(frame.scene_purpose, frame.caption_text)),
        asset_label=str(_GERMAN_ASSET_LABELS.get(frame.asset_mode, frame.asset_mode)),
        motion_text=str(_GERMAN_MOTION.get(frame.scene_purpose, frame.motion_instruction)),
    )


def _draw_lines(draw: ImageDraw.ImageDraw, xy: tuple[int, int], lines: tuple[str, ...], *, fill, font, spacing: int) -> None:
    x, y = xy
    line_height = font.size + spacing if hasattr(font, "size") else 22 + spacing
    for line in lines:
        draw.text((x, y), line, fill=fill, font=font)
        y += line_height


def _draw_card(plan: LocalPreviewPlan, frame, *, approval_language: str = "de") -> Image.Image:
    background, accent = _palette(frame.asset_mode)
    image = Image.new("RGB", (plan.width, plan.height), background)
    draw = ImageDraw.Draw(image)
    copy = localize_frame_copy(frame, language=approval_language)

    margin = max(28, plan.width // 16)
    content_width = plan.width - (2 * margin)
    title_font_size = max(28, plan.width // 16)
    body_font_size = max(16, plan.width // 30)
    small_font_size = max(14, plan.width // 34)
    title_font = _font(title_font_size)
    body_font = _font(body_font_size)
    small_font = _font(small_font_size)

    draw.rectangle((0, 0, plan.width, 18), fill=accent)
    draw.text((margin, margin), copy.scene_label, fill=accent, font=small_font)

    caption_lines = wrap_text_to_pixel_width(copy.caption_text, max_width_px=content_width, font_size=title_font_size)
    _draw_lines(draw, (margin, margin + 46), caption_lines, fill=(248, 250, 252), font=title_font, spacing=10)

    info_top = plan.height // 2
    draw.rounded_rectangle(
        (margin, info_top, plan.width - margin, plan.height - margin),
        radius=18,
        outline=accent,
        width=2,
        fill=(background[0] + 8, background[1] + 8, background[2] + 8),
    )
    body_labels = {
        "time": "Zeit" if approval_language == "de" else "Time",
        "asset": "Asset",
        "label": "Label",
        "motion": "Bewegung" if approval_language == "de" else "Motion",
    }
    body = (
        f"{body_labels['time']}: {frame.start_ms / 1000:.0f}s + {frame.duration_ms / 1000:.0f}s\n"
        f"{body_labels['asset']}: {copy.asset_label}\n"
        f"{body_labels['label']}: {copy.scene_label.title()}\n\n"
        f"{body_labels['motion']}: {copy.motion_text}"
    )
    body_lines = tuple(
        line
        for paragraph in body.splitlines()
        for line in (wrap_text_to_pixel_width(paragraph, max_width_px=content_width - 36, font_size=body_font_size) or ("",))
    )
    _draw_lines(draw, (margin + 18, info_top + 18), body_lines, fill=(226, 232, 240), font=body_font, spacing=6)
    return image


def write_local_preview_frames(
    plan: LocalPreviewPlan,
    *,
    output_dir: str | Path | None = None,
    approval_language: str = "de",
) -> LocalFrameWriteResult:
    """Write deterministic PNG scene cards for a validated local preview plan."""

    validation = validate_local_preview_plan(plan)
    if not validation.is_valid:
        raise ValueError("; ".join(validation.errors))

    target_dir = _validate_output_dir(output_dir or plan.output_dir)
    target_dir.mkdir(parents=True, exist_ok=True)

    frame_paths: list[str] = []
    for frame in plan.frames:
        path = target_dir / f"{frame.frame_id}.png"
        image = _draw_card(plan, frame, approval_language=approval_language)
        image.save(path, format="PNG")
        frame_paths.append(str(path))

    return LocalFrameWriteResult(
        script_id=plan.script_id,
        output_dir=str(target_dir),
        frame_paths=tuple(frame_paths),
        side_effects=("write_png",),
        external_calls=(),
    )
