"""TrueTraceShorts format-family spine.

Side-effect-free planning metadata for expanding beyond one repeated red-flag
short pattern. This module deliberately does not render, upload, write files, or
call external APIs.
"""

from __future__ import annotations

from dataclasses import asdict, dataclass
from enum import StrEnum
import json
from typing import Mapping, Sequence

from autoshorts.ideas.candidate import ValidationResult


class FormatType(StrEnum):
    RED_FLAG_SHORT = "red_flag_short"
    ALREADY_CLICKED = "already_clicked"
    SPOT_THE_TRAP_QUIZ = "spot_the_trap_quiz"
    TRUE_SCAM_STORY = "true_scam_story"
    SCAM_BREAKDOWN = "scam_breakdown"


class HookType(StrEnum):
    DAMAGE_FIRST = "damage_first"
    IDENTIFICATION = "identification"
    QUIZ = "quiz"
    STORY = "story"
    COUNTERINTUITIVE = "counterintuitive"
    RECOVERY = "recovery"


class ViewerState(StrEnum):
    NOT_CLICKED = "not_clicked"
    ALREADY_CLICKED = "already_clicked"
    HELPING_FAMILY = "helping_family"
    LEARNING_PATTERN = "learning_pattern"


class CTAType(StrEnum):
    FOLLOW = "follow"
    SAVE = "save"
    COMMENT = "comment"
    CHECKLIST = "checklist"
    SHARE_FAMILY = "share_family"


class DurationClass(StrEnum):
    SHORT_35_45 = "35-45"
    MEDIUM_45_60 = "45-60"
    RECOVERY_50_75 = "50-75"


class EvidenceLevel(StrEnum):
    GENERIC_PATTERN = "generic_pattern"
    SOURCED_CASE = "sourced_case"
    OFFICIAL_GUIDANCE = "official_guidance"


class ClaimMode(StrEnum):
    EDUCATIONAL_PATTERN = "educational_pattern"
    SOURCED_CASE_SUMMARY = "sourced_case_summary"
    OFFICIAL_GUIDANCE_SUMMARY = "official_guidance_summary"


@dataclass(frozen=True)
class FormatTemplate:
    format_type: FormatType
    share_of_next_20: int
    goal: str
    default_hook_type: HookType
    default_viewer_state: ViewerState
    default_cta_type: CTAType
    duration_class: DurationClass
    structure: tuple[str, ...]
    website_target_pattern: str
    notes: tuple[str, ...] = ()

    def to_dict(self) -> dict[str, object]:
        data = asdict(self)
        for key, value in tuple(data.items()):
            if isinstance(value, StrEnum):
                data[key] = value.value
        data["structure"] = list(self.structure)
        data["notes"] = list(self.notes)
        return data


@dataclass(frozen=True)
class FormatMetadata:
    format_type: FormatType
    hook_type: HookType
    viewer_state: ViewerState
    website_target: str
    cta_type: CTAType
    duration_class: DurationClass
    first_frame_scam_label: str
    evidence_level: EvidenceLevel
    claim_mode: ClaimMode

    def to_dict(self) -> dict[str, str]:
        return {
            "format_type": self.format_type.value,
            "hook_type": self.hook_type.value,
            "viewer_state": self.viewer_state.value,
            "website_target": self.website_target,
            "cta_type": self.cta_type.value,
            "duration_class": self.duration_class.value,
            "first_frame_scam_label": self.first_frame_scam_label,
            "evidence_level": self.evidence_level.value,
            "claim_mode": self.claim_mode.value,
        }


@dataclass(frozen=True)
class FormatCandidatePlan:
    candidate_id: str
    title: str
    hook: str
    topic_slug: str
    format_metadata: FormatMetadata
    script_beats: tuple[str, ...]
    first_frame_brief: str
    cta: str

    def to_dict(self) -> dict[str, object]:
        return {
            "candidate_id": self.candidate_id,
            "title": self.title,
            "hook": self.hook,
            "topic_slug": self.topic_slug,
            "format_metadata": self.format_metadata.to_dict(),
            "script_beats": list(self.script_beats),
            "first_frame_brief": self.first_frame_brief,
            "cta": self.cta,
        }

    def to_json(self) -> str:
        return json.dumps(self.to_dict(), indent=2, sort_keys=True, ensure_ascii=False) + "\n"


FORMAT_MIX_NEXT_20: Mapping[FormatType, int] = {
    FormatType.RED_FLAG_SHORT: 10,
    FormatType.ALREADY_CLICKED: 4,
    FormatType.TRUE_SCAM_STORY: 3,
    FormatType.SPOT_THE_TRAP_QUIZ: 2,
    FormatType.SCAM_BREAKDOWN: 1,
}


FORMAT_TEMPLATES: Mapping[FormatType, FormatTemplate] = {
    FormatType.RED_FLAG_SHORT: FormatTemplate(
        format_type=FormatType.RED_FLAG_SHORT,
        share_of_next_20=10,
        goal="Fast warning, saves, shares, and series recognition.",
        default_hook_type=HookType.DAMAGE_FIRST,
        default_viewer_state=ViewerState.NOT_CLICKED,
        default_cta_type=CTAType.FOLLOW,
        duration_class=DurationClass.SHORT_35_45,
        structure=(
            "0-2s: scam visible plus first-frame red scam label",
            "2-8s: why it feels real",
            "8-18s: the red flag",
            "18-32s: safer move",
            "32-45s: memorable rule plus follow/site CTA",
        ),
        website_target_pattern="/redflags/{slug}/",
    ),
    FormatType.ALREADY_CLICKED: FormatTemplate(
        format_type=FormatType.ALREADY_CLICKED,
        share_of_next_20=4,
        goal="Recovery help, trust, and search value for viewers in stress.",
        default_hook_type=HookType.RECOVERY,
        default_viewer_state=ViewerState.ALREADY_CLICKED,
        default_cta_type=CTAType.CHECKLIST,
        duration_class=DurationClass.RECOVERY_50_75,
        structure=(
            "0-2s: Already clicked / entered / paid?",
            "2-8s: first, stop doing more",
            "8-30s: three immediate steps",
            "30-50s: what not to do",
            "50-70s: when to contact bank/provider/authority",
        ),
        website_target_pattern="/already-clicked/",
        notes=("Use calm no-shame tone: You're not stupid; this is designed to make you hurry.",),
    ),
    FormatType.SPOT_THE_TRAP_QUIZ: FormatTemplate(
        format_type=FormatType.SPOT_THE_TRAP_QUIZ,
        share_of_next_20=2,
        goal="Comments, rewatch, and active scam-spotting practice.",
        default_hook_type=HookType.QUIZ,
        default_viewer_state=ViewerState.LEARNING_PATTERN,
        default_cta_type=CTAType.COMMENT,
        duration_class=DurationClass.SHORT_35_45,
        structure=(
            "0-3s: Can you spot the scam?",
            "3-8s: hold screen / pause prompt",
            "8-15s: reveal",
            "15-30s: why it is dangerous",
            "30-40s: safer move",
        ),
        website_target_pattern="/redflags/{slug}/",
        notes=("Reveal with cut/zoom/crop/image change, not arrows/circles/explainer overlays.",),
    ),
    FormatType.TRUE_SCAM_STORY: FormatTemplate(
        format_type=FormatType.TRUE_SCAM_STORY,
        share_of_next_20=3,
        goal="Retention through story and emotional consequence.",
        default_hook_type=HookType.STORY,
        default_viewer_state=ViewerState.HELPING_FAMILY,
        default_cta_type=CTAType.SHARE_FAMILY,
        duration_class=DurationClass.RECOVERY_50_75,
        structure=(
            "0-3s: result / emotional hook",
            "3-12s: how it started",
            "12-25s: escalation",
            "25-40s: the moment that flipped it",
            "40-60s: red flag plus safer move",
            "60-75s: lesson / no-shame / website CTA",
        ),
        website_target_pattern="/redflags/{slug}/ or later /stories/{slug}/",
        notes=("Use 'based on common scam reports' unless a source is explicitly cited.",),
    ),
    FormatType.SCAM_BREAKDOWN: FormatTemplate(
        format_type=FormatType.SCAM_BREAKDOWN,
        share_of_next_20=1,
        goal="Authority and pattern learning without cybersecurity jargon.",
        default_hook_type=HookType.COUNTERINTUITIVE,
        default_viewer_state=ViewerState.LEARNING_PATTERN,
        default_cta_type=CTAType.SAVE,
        duration_class=DurationClass.MEDIUM_45_60,
        structure=(
            "0-2s: counterintuitive statement",
            "2-15s: three simple parts",
            "15-35s: example screen",
            "35-50s: safer move",
        ),
        website_target_pattern="/redflags/{slug}/",
    ),
}


FIRST_TEST_WAVE: tuple[FormatCandidatePlan, ...] = (
    FormatCandidatePlan(
        candidate_id="tt-recovery-fake-delivery-link",
        title="Already clicked a fake delivery link? Do this now.",
        hook="Already clicked a fake delivery link? Do this now.",
        topic_slug="delivery-sms-trap",
        format_metadata=FormatMetadata(
            format_type=FormatType.ALREADY_CLICKED,
            hook_type=HookType.RECOVERY,
            viewer_state=ViewerState.ALREADY_CLICKED,
            website_target="/already-clicked/",
            cta_type=CTAType.CHECKLIST,
            duration_class=DurationClass.RECOVERY_50_75,
            first_frame_scam_label="FAKE DELIVERY LINK",
            evidence_level=EvidenceLevel.GENERIC_PATTERN,
            claim_mode=ClaimMode.EDUCATIONAL_PATTERN,
        ),
        script_beats=("stop entering anything", "freeze card if details were entered", "open delivery app/site yourself", "watch for bank/login alerts"),
        first_frame_brief="Premium AI phone/laptop screen; first visible screen says FAKE DELIVERY LINK in red only on first keyframe.",
        cta="Full recovery checklist on TrueTraceShorts.",
    ),
    FormatCandidatePlan(
        candidate_id="tt-quiz-delivery-text",
        title="Can you spot the scam in this delivery text?",
        hook="Can you spot what’s wrong before I tell you?",
        topic_slug="delivery-sms-trap",
        format_metadata=FormatMetadata(
            format_type=FormatType.SPOT_THE_TRAP_QUIZ,
            hook_type=HookType.QUIZ,
            viewer_state=ViewerState.LEARNING_PATTERN,
            website_target="/redflags/delivery-sms-trap/",
            cta_type=CTAType.COMMENT,
            duration_class=DurationClass.SHORT_35_45,
            first_frame_scam_label="DELIVERY TEXT SCAM",
            evidence_level=EvidenceLevel.GENERIC_PATTERN,
            claim_mode=ClaimMode.EDUCATIONAL_PATTERN,
        ),
        script_beats=("show suspicious text", "pause prompt", "reveal urgency/link clue", "safer move"),
        first_frame_brief="Premium AI phone screen; first visible screen says DELIVERY TEXT SCAM in red only on first keyframe.",
        cta="Comment what you noticed first.",
    ),
    FormatCandidatePlan(
        candidate_id="tt-story-fake-support-popup",
        title="It started with a fake support popup.",
        hook="It started with a popup. Then came the phone call.",
        topic_slug="fake-support-popup",
        format_metadata=FormatMetadata(
            format_type=FormatType.TRUE_SCAM_STORY,
            hook_type=HookType.STORY,
            viewer_state=ViewerState.HELPING_FAMILY,
            website_target="/redflags/fake-support-popup/",
            cta_type=CTAType.SHARE_FAMILY,
            duration_class=DurationClass.RECOVERY_50_75,
            first_frame_scam_label="FAKE SUPPORT POPUP",
            evidence_level=EvidenceLevel.GENERIC_PATTERN,
            claim_mode=ClaimMode.EDUCATIONAL_PATTERN,
        ),
        script_beats=("common-report framing", "popup creates fear", "phone call keeps pressure", "safe exit rule"),
        first_frame_brief="Premium AI desktop screen; first visible screen says FAKE SUPPORT POPUP in red only on first keyframe.",
        cta="Send this to someone who freezes at scary popups.",
    ),
    FormatCandidatePlan(
        candidate_id="tt-breakdown-fake-logo-not-trap",
        title="The fake logo is not the trap.",
        hook="The fake logo is not the trap. The hurry is.",
        topic_slug="delivery-sms-trap",
        format_metadata=FormatMetadata(
            format_type=FormatType.SCAM_BREAKDOWN,
            hook_type=HookType.COUNTERINTUITIVE,
            viewer_state=ViewerState.LEARNING_PATTERN,
            website_target="/redflags/delivery-sms-trap/",
            cta_type=CTAType.SAVE,
            duration_class=DurationClass.MEDIUM_45_60,
            first_frame_scam_label="FAKE LOGO TRAP",
            evidence_level=EvidenceLevel.GENERIC_PATTERN,
            claim_mode=ClaimMode.EDUCATIONAL_PATTERN,
        ),
        script_beats=("counterintuitive hook", "logo lowers guard", "urgency creates action", "check route yourself"),
        first_frame_brief="Premium AI screen with generic logo-like shapes; first visible screen says FAKE LOGO TRAP in red only on first keyframe.",
        cta="Save this rule before the next urgent message.",
    ),
    FormatCandidatePlan(
        candidate_id="tt-recovery-fake-login-password",
        title="Entered your password on a fake page? Do this first.",
        hook="Entered your password on a fake page? Do this first.",
        topic_slug="fake-login-page",
        format_metadata=FormatMetadata(
            format_type=FormatType.ALREADY_CLICKED,
            hook_type=HookType.RECOVERY,
            viewer_state=ViewerState.ALREADY_CLICKED,
            website_target="/already-clicked/",
            cta_type=CTAType.CHECKLIST,
            duration_class=DurationClass.RECOVERY_50_75,
            first_frame_scam_label="FAKE LOGIN PAGE",
            evidence_level=EvidenceLevel.GENERIC_PATTERN,
            claim_mode=ClaimMode.EDUCATIONAL_PATTERN,
        ),
        script_beats=("do not try again from the same link", "open the real service yourself", "change password", "secure sessions and alerts"),
        first_frame_brief="Premium AI laptop login screen; first visible screen says FAKE LOGIN PAGE in red only on first keyframe.",
        cta="Use the recovery checklist before doing anything else.",
    ),
)


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


def default_format_template(format_type: FormatType | str) -> FormatTemplate:
    return FORMAT_TEMPLATES[FormatType(format_type)]


def validate_format_metadata(metadata: FormatMetadata) -> ValidationResult:
    errors: list[str] = []
    if _is_blank(metadata.website_target):
        errors.append("website_target is required")
    if not metadata.website_target.startswith("/"):
        errors.append("website_target must be a site path")
    if _is_blank(metadata.first_frame_scam_label):
        errors.append("first_frame_scam_label is required")
    if metadata.format_type is FormatType.ALREADY_CLICKED and metadata.viewer_state is not ViewerState.ALREADY_CLICKED:
        errors.append("already_clicked format requires viewer_state=already_clicked")
    if metadata.format_type is FormatType.SPOT_THE_TRAP_QUIZ and metadata.cta_type is not CTAType.COMMENT:
        errors.append("spot_the_trap_quiz should use cta_type=comment")
    if metadata.format_type is FormatType.TRUE_SCAM_STORY and metadata.evidence_level is EvidenceLevel.GENERIC_PATTERN and metadata.claim_mode is not ClaimMode.EDUCATIONAL_PATTERN:
        errors.append("generic story patterns must use educational_pattern claim_mode")
    return ValidationResult(is_valid=not errors, errors=tuple(errors))


def validate_format_candidate_plan(plan: FormatCandidatePlan) -> ValidationResult:
    errors: list[str] = []
    if _is_blank(plan.candidate_id):
        errors.append("candidate_id is required")
    if _is_blank(plan.title):
        errors.append("title is required")
    if _is_blank(plan.hook):
        errors.append("hook is required")
    if _is_blank(plan.topic_slug):
        errors.append("topic_slug is required")
    if not plan.script_beats:
        errors.append("script_beats are required")
    if _is_blank(plan.first_frame_brief):
        errors.append("first_frame_brief is required")
    if "red" not in plan.first_frame_brief.casefold():
        errors.append("first_frame_brief must mention red first-frame scam label")
    metadata_result = validate_format_metadata(plan.format_metadata)
    errors.extend(metadata_result.errors)
    return ValidationResult(is_valid=not errors, errors=tuple(errors))


def build_first_test_wave() -> tuple[FormatCandidatePlan, ...]:
    return FIRST_TEST_WAVE


def format_mix_summary() -> dict[str, int]:
    return {key.value: value for key, value in FORMAT_MIX_NEXT_20.items()}


def render_test_wave_json(plans: Sequence[FormatCandidatePlan] | None = None) -> str:
    plans = tuple(plans or FIRST_TEST_WAVE)
    return json.dumps([plan.to_dict() for plan in plans], indent=2, sort_keys=True, ensure_ascii=False) + "\n"
