"""Heuristic retention scoring for short-form ideas.

The goal is not to predict virality perfectly. The goal is to reject obvious
scroll-past drafts before rendering: slow intros, generic advice, static visuals,
and weak payoffs.
"""

from __future__ import annotations

import re
from typing import Any

DIRECT_ADDRESS = ("you", "your", "du", "dein", "deine", "dich")
OPEN_LOOP = ("do this", "mach", "before", "bevor", "why", "warum", "not", "nicht", "instead")
GENERIC_TERMS = ("tips", "tipps", "habits", "routines", "productivity", "motivation", "mindset")
SPECIFIC_MARKERS = re.compile(r"\b(\d+|one|two|three|first|last|minute|minutes|sekunden|minute|tabs|tiktok|ki|ai)\b", re.I)
SLOW_INTRO_MARKERS = (
    "in this video",
    "in diesem video",
    "today i will",
    "heute werde ich",
    "i will talk",
    "we will discuss",
)
VISUAL_ENERGY_MARKERS = (
    "pattern interrupt",
    "before/after",
    "messy",
    "timer",
    "captions",
    "screen recording",
    "cards",
    "close-up",
    "schnelle",
    "cut",
)
COMMENT_CTA_MARKERS = ("comment", "kommentar", "write", "schreib", "reply", "antwort", "start", "reset")
SCREEN_MARKERS = ("screen", "text", "sms", "popup", "login", "invoice", "payment", "browser", "card", "chat", "shop", "captcha")
DANGER_MARKERS = ("red flag", "trap", "risk", "danger", "fake", "scam", "steal", "pressure", "urgent")
SAFER_MOVE_MARKERS = ("close", "block", "open the official", "verify", "contact", "change", "remove", "do not", "don't", "stop")
WEBSITE_COMPANION_QUESTIONS = (
    "what do i do if i already clicked",
    "what do i check before acting",
    "what should i avoid",
)


def _contains_any(text: str, markers: tuple[str, ...]) -> bool:
    lower = text.lower()
    return any(marker in lower for marker in markers)


def score_hook(hook: str) -> dict[str, Any]:
    """Score a hook for first-second retention signals."""
    signals: list[str] = []
    risks: list[str] = []
    score = 3
    lower = hook.lower()

    if _contains_any(lower, DIRECT_ADDRESS):
        score += 1
        signals.append("direct_address")
    if _contains_any(lower, OPEN_LOOP):
        score += 2
        signals.append("open_loop")
    if SPECIFIC_MARKERS.search(hook):
        score += 2
        signals.append("specificity")
    if any(token in lower for token in ("not", "nicht", "without", "ohne", "stop", "stopp")):
        score += 1
        signals.append("tension")
    if len(hook.split()) <= 14:
        score += 1
        signals.append("short_hook")
    if _contains_any(lower, SLOW_INTRO_MARKERS):
        score -= 4
        risks.append("slow_intro")
    if hook.endswith(".") and not signals:
        score -= 1
        risks.append("flat_statement")

    return {"score": max(0, min(score, 10)), "signals": signals, "risks": risks}


def score_idea_retention(idea: dict[str, Any]) -> dict[str, Any]:
    """Score an idea using hook, pacing, visual energy, value, and CTA."""
    hook_result = score_hook(str(idea.get("hook", "")))
    score = hook_result["score"]
    risks = list(hook_result["risks"])
    signals = list(hook_result["signals"])

    duration = int(idea.get("duration_seconds", 0))
    visual = str(idea.get("visual_concept", ""))
    outline = str(idea.get("script_outline", ""))
    cta = str(idea.get("cta", ""))
    combined_value = f"{outline} {visual}".lower()

    if 20 <= duration <= 45:
        score += 1
        signals.append("growth_duration")
    elif 60 <= duration <= 75:
        signals.append("monetization_duration")
    elif duration > 90:
        score -= 2
        risks.append("too_long_for_unproven_format")

    if _contains_any(visual, VISUAL_ENERGY_MARKERS):
        score += 1
        signals.append("visual_energy")
    elif "static" in visual.lower() or "slideshow" in visual.lower():
        score -= 2
        risks.append("static_visuals")

    generic_count = sum(1 for term in GENERIC_TERMS if term in combined_value)
    concrete_markers = sum(
        1
        for term in ("example", "template", "timer", "calendar", "prompt", "before", "after", "two-minute", "2-minute")
        if term in combined_value
    )
    if generic_count >= 2 and concrete_markers == 0:
        score -= 2
        risks.append("generic_value")
    elif concrete_markers:
        score += 1
        signals.append("concrete_payoff")

    if _contains_any(cta, COMMENT_CTA_MARKERS):
        score += 1
        signals.append("comment_trigger")

    return {
        "score": max(0, min(score, 10)),
        "signals": sorted(set(signals)),
        "risks": sorted(set(risks)),
    }


def evaluate_red_flag_video_gates(candidate: dict[str, Any]) -> dict[str, Any]:
    """Hard retention gates for TrueTraceShorts scam red-flag videos.

    Expected candidate keys are intentionally simple so render scripts, review packages,
    and future V2 candidates can all call this without side effects.
    """

    first_frame = str(candidate.get("first_frame_description") or candidate.get("visibleScreen") or candidate.get("visual_concept") or "")
    hook = str(candidate.get("hook") or "")
    red_flags = candidate.get("red_flags") or candidate.get("redFlag") or []
    safer_moves = candidate.get("safer_moves") or candidate.get("saferMove") or []
    companion_questions = candidate.get("website_companion_questions") or []
    if isinstance(red_flags, str):
        red_flags = [red_flags]
    if isinstance(safer_moves, str):
        safer_moves = [safer_moves]
    if isinstance(companion_questions, str):
        companion_questions = [companion_questions]

    failures: list[str] = []
    first_frame_lower = first_frame.lower()
    hook_lower = hook.lower()

    one_second_recognition = (
        _contains_any(first_frame_lower, SCREEN_MARKERS)
        and _contains_any(first_frame_lower, DANGER_MARKERS)
        and any(term in first_frame_lower for term in ("money", "password", "card", "account", "login", "payment", "permission", "codes"))
    )
    if not one_second_recognition:
        failures.append("one_second_recognition_gate")

    open_loop = "?" in hook or _contains_any(hook_lower, ("what", "why", "before", "already", "mistake", "trap"))
    if not open_loop:
        failures.append("open_loop_gate")

    if len([item for item in red_flags if str(item).strip()]) != 1:
        failures.append("one_red_flag_gate")

    if len([item for item in safer_moves if str(item).strip()]) != 1 or not _contains_any(" ".join(safer_moves).lower(), SAFER_MOVE_MARKERS):
        failures.append("one_safer_move_gate")

    companion_text = " ".join(str(item).lower() for item in companion_questions)
    companion_value = any(question in companion_text for question in WEBSITE_COMPANION_QUESTIONS)
    if not companion_value:
        failures.append("website_companion_value_gate")

    return {
        "allowed": not failures,
        "failures": failures,
        "gates": {
            "one_second_recognition": one_second_recognition,
            "open_loop": open_loop,
            "one_red_flag": "one_red_flag_gate" not in failures,
            "one_safer_move": "one_safer_move_gate" not in failures,
            "website_companion_value": companion_value,
        },
        "required_structure": {
            "scam_red_flag": ["0-2s visible screen + hook", "2-8s why it feels normal", "8-16s one red flag", "16-30s safer move", "30-45s memorable rule + website/help CTA"],
            "recovery_topic": "45-75s allowed when the viewer needs recovery steps",
        },
    }
