"""Retention-led shot planning before any video rendering."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

from autoshorts.content.script_draft import ScriptDraft, validate_script_draft
from autoshorts.ideas.candidate import ValidationResult

ShotPurpose = Literal[
    "first_frame_hook",
    "pattern_interrupt",
    "core_explanation",
    "screen_or_card_visual",
    "cta",
]
AssetMode = Literal[
    "kinetic_text_overlay",
    "mechanism_animation",
    "screen_recording_simulation",
    "cinematic_ai_video",
    "static_slideshow",
]

_ALLOWED_ASSET_MODES = {
    "kinetic_text_overlay",
    "mechanism_animation",
    "screen_recording_simulation",
    "cinematic_ai_video",
}
_BAKED_TEXT_MARKERS = ("baked-in", "fake ui text", "generated text", "unreadable ui")


@dataclass(frozen=True)
class ShotScene:
    purpose: ShotPurpose | str
    start_seconds: int
    end_seconds: int
    voiceover_beat: str
    visual_action: str
    asset_mode: AssetMode | str
    caption_beat: str
    retention_goal: str
    labels_renderer_owned: bool = True


@dataclass(frozen=True)
class ShotPlan:
    script_id: str
    title: str
    estimated_duration_seconds: int
    scenes: tuple[ShotScene, ...]


def _is_blank(value: str) -> bool:
    return not (value or "").strip()


def _scene_windows(duration_seconds: int) -> tuple[tuple[int, int], ...]:
    first_end = min(3, duration_seconds)
    second_end = min(8, duration_seconds)
    third_end = min(17, duration_seconds)
    fourth_end = min(max(duration_seconds - 5, third_end + 1), duration_seconds)
    return (
        (0, first_end),
        (first_end, second_end),
        (second_end, third_end),
        (third_end, fourth_end),
        (fourth_end, duration_seconds),
    )


def create_shot_plan(script: ScriptDraft) -> ShotPlan:
    """Create a deterministic five-scene retention shot plan from a script draft."""

    validation = validate_script_draft(script)
    if not validation.is_valid:
        raise ValueError("; ".join(validation.errors))

    windows = _scene_windows(script.estimated_duration_seconds)
    beats = script.retention_beats
    scenes = (
        ShotScene(
            purpose="first_frame_hook",
            start_seconds=windows[0][0],
            end_seconds=windows[0][1],
            voiceover_beat=script.hook,
            visual_action="Immediate motion punch-in on clean kinetic hook text; no generated UI lettering.",
            asset_mode="kinetic_text_overlay",
            caption_beat=script.hook,
            retention_goal="Stop the scroll with a first-frame hook and pattern interrupt.",
        ),
        ShotScene(
            purpose="pattern_interrupt",
            start_seconds=windows[1][0],
            end_seconds=windows[1][1],
            voiceover_beat=beats[0],
            visual_action="Rapid desk-to-phone change showing messy priorities becoming a controlled workflow.",
            asset_mode="mechanism_animation",
            caption_beat=beats[0],
            retention_goal="Create visible change before the viewer can classify it as a slideshow.",
        ),
        ShotScene(
            purpose="core_explanation",
            start_seconds=windows[2][0],
            end_seconds=windows[2][1],
            voiceover_beat=beats[1] if len(beats) > 1 else script.script_outline,
            visual_action="Simulated screen flow with renderer-owned boxes, cursor motion, and approval checkpoint.",
            asset_mode="screen_recording_simulation",
            caption_beat=beats[1] if len(beats) > 1 else script.script_outline,
            retention_goal="Make the mechanism concrete without trusting generated text inside assets.",
        ),
        ShotScene(
            purpose="screen_or_card_visual",
            start_seconds=windows[3][0],
            end_seconds=windows[3][1],
            voiceover_beat=beats[2] if len(beats) > 2 else script.script_outline,
            visual_action="Animated checklist/card sequence showing the weekly review routine step by step.",
            asset_mode="mechanism_animation",
            caption_beat=beats[2] if len(beats) > 2 else script.script_outline,
            retention_goal="Deliver the payoff visually before the CTA.",
        ),
        ShotScene(
            purpose="cta",
            start_seconds=windows[4][0],
            end_seconds=windows[4][1],
            voiceover_beat=script.cta,
            visual_action="Clean kinetic CTA overlay with renderer-owned caption and final checklist card motion.",
            asset_mode="kinetic_text_overlay",
            caption_beat=script.cta,
            retention_goal="Give a concrete comment action tied to the promised checklist.",
        ),
    )
    return ShotPlan(
        script_id=script.brief_id,
        title=script.title,
        estimated_duration_seconds=script.estimated_duration_seconds,
        scenes=scenes,
    )


def validate_shot_plan(plan: ShotPlan) -> ValidationResult:
    errors: list[str] = []
    if _is_blank(plan.script_id):
        errors.append("script_id is required")
    if _is_blank(plan.title):
        errors.append("title is required")
    if not 10 <= plan.estimated_duration_seconds <= 180:
        errors.append("duration_seconds must be between 10 and 180")
    if not plan.scenes:
        errors.append("at least one scene is required")
    elif plan.scenes[0].purpose != "first_frame_hook":
        errors.append("first scene must be first_frame_hook")

    previous_end: int | None = None
    for index, scene in enumerate(plan.scenes, start=1):
        if scene.end_seconds <= scene.start_seconds:
            errors.append(f"scene {index} end_seconds must be greater than start_seconds")
        if previous_end is not None and scene.start_seconds < previous_end:
            errors.append(f"scene {index} start_seconds overlaps previous scene")
        previous_end = scene.end_seconds

        for field_name, value in {
            "purpose": scene.purpose,
            "voiceover_beat": scene.voiceover_beat,
            "visual_action": scene.visual_action,
            "asset_mode": scene.asset_mode,
            "caption_beat": scene.caption_beat,
            "retention_goal": scene.retention_goal,
        }.items():
            if _is_blank(value):
                errors.append(f"scene {index} {field_name} is required")

        if scene.asset_mode == "static_slideshow":
            errors.append("static slideshow asset mode is not allowed")
        elif scene.asset_mode not in _ALLOWED_ASSET_MODES:
            errors.append(f"unsupported asset mode: {scene.asset_mode}")
        if not scene.labels_renderer_owned:
            errors.append("labels and captions must be renderer-owned")
        visual = scene.visual_action.casefold()
        if any(marker in visual for marker in _BAKED_TEXT_MARKERS):
            errors.append("baked-in fake UI text is not allowed")

    return ValidationResult(is_valid=not errors, errors=tuple(errors))
