#!/usr/bin/env python3
from __future__ import annotations

import asyncio
import contextlib
import contextvars
import copy
import hashlib
import importlib.util
import json
import logging
import mimetypes
import os
import re
import time
import threading
import uuid
from collections import Counter
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from html import escape, unescape
from html.parser import HTMLParser
from pathlib import Path, PurePosixPath
from typing import Any, Callable
from urllib.parse import urlparse, urlunparse

import boto3
import httpx
import uvicorn
from dotenv import load_dotenv
from mcp.server.fastmcp import Context, FastMCP, Icon
from mcp.server.streamable_http import (
    LAST_EVENT_ID_HEADER,
    MCP_PROTOCOL_VERSION_HEADER,
    MCP_SESSION_ID_HEADER,
)
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import FileResponse, HTMLResponse, JSONResponse, Response
from starlette.routing import Mount, Route

try:
    from playwright.sync_api import Error as PlaywrightError, sync_playwright
except ImportError:  # pragma: no cover - optional in local dev until installed
    PlaywrightError = RuntimeError  # type: ignore[assignment]
    sync_playwright = None

try:
    from .agent_critic import run_quick_checks, run_sdk_review, run_visual_checks, sdk_backend_available
    from .html_optimizer import optimize_artifact_html
    from .model_router import route_anthropic_model
    from .prompt_stack_runtime import (
        PROMPT_STACK_BUNDLE_VERSION,
        audit_plan_against_prompt_stack,
        build_runtime_critic_lines,
        build_runtime_planner_lines,
        resolve_active_prompt_modules,
    )
    from .runtime_paths import resolve_default_vault_path
    from .vault_templates import (
        build_template_plan,
        get_template_definition,
        list_public_templates,
        match_public_templates,
        normalize_template_input as normalize_template_instance_input,
        render_template_instance,
        validate_template_input_payload,
    )
except ImportError:
    from agent_critic import run_quick_checks, run_sdk_review, run_visual_checks, sdk_backend_available
    from html_optimizer import optimize_artifact_html
    from model_router import route_anthropic_model
    from prompt_stack_runtime import (
        PROMPT_STACK_BUNDLE_VERSION,
        audit_plan_against_prompt_stack,
        build_runtime_critic_lines,
        build_runtime_planner_lines,
        resolve_active_prompt_modules,
    )
    from runtime_paths import resolve_default_vault_path
    from vault_templates import (
        build_template_plan,
        get_template_definition,
        list_public_templates,
        match_public_templates,
        normalize_template_input as normalize_template_instance_input,
        render_template_instance,
        validate_template_input_payload,
    )


load_dotenv()
LOGGER = logging.getLogger("iterfact.mcp")


def env_or_default(name: str, default: str) -> str:
    value = os.getenv(name)
    if value is None or not value.strip():
        return default
    return value


@dataclass
class Settings:
    anthropic_api_key: str = env_or_default("ANTHROPIC_API_KEY", "")
    anthropic_admin_api_key: str = env_or_default("ANTHROPIC_ADMIN_API_KEY", "")
    anthropic_api_key_label: str = env_or_default("ANTHROPIC_API_KEY_LABEL", "iterfact-mcp")
    anthropic_model: str = env_or_default("ANTHROPIC_MODEL", "claude-sonnet-4-20250514")
    anthropic_fast_model: str = env_or_default("ANTHROPIC_FAST_MODEL", "claude-haiku-4-5-20251001")
    anthropic_critic_model: str = env_or_default("ANTHROPIC_CRITIC_MODEL", env_or_default("ANTHROPIC_MODEL", "claude-sonnet-4-20250514"))
    anthropic_version: str = env_or_default("ANTHROPIC_VERSION", "2023-06-01")
    anthropic_max_tokens: int = int(env_or_default("ANTHROPIC_MAX_TOKENS", "4096"))
    anthropic_critic_max_tokens: int = int(env_or_default("ANTHROPIC_CRITIC_MAX_TOKENS", "1800"))
    anthropic_request_timeout_seconds: int = int(env_or_default("ANTHROPIC_REQUEST_TIMEOUT_SECONDS", "90"))
    anthropic_critic_timeout_seconds: int = int(env_or_default("ANTHROPIC_CRITIC_TIMEOUT_SECONDS", "45"))
    anthropic_smart_routing_enabled: bool = env_or_default("ANTHROPIC_SMART_ROUTING_ENABLED", "1").strip().lower() not in {"0", "false", "no", "off"}
    agentic_critic_enabled: bool = env_or_default("AGENTIC_CRITIC_ENABLED", "1").strip().lower() not in {"0", "false", "no", "off"}
    agent_sdk_critic_enabled: bool = env_or_default("AGENT_SDK_CRITIC_ENABLED", "1").strip().lower() not in {"0", "false", "no", "off"}
    agentic_critic_visual_enabled: bool = env_or_default("AGENTIC_CRITIC_VISUAL_ENABLED", "1").strip().lower() not in {"0", "false", "no", "off"}
    agentic_critic_model: str = env_or_default("AGENTIC_CRITIC_MODEL", env_or_default("ANTHROPIC_CRITIC_MODEL", env_or_default("ANTHROPIC_MODEL", "claude-sonnet-4-20250514")))
    agentic_critic_timeout_seconds: int = int(env_or_default("AGENTIC_CRITIC_TIMEOUT_SECONDS", "20"))
    anthropic_input_cost_per_million: float = float(env_or_default("ANTHROPIC_INPUT_COST_PER_MILLION", "3.0"))
    anthropic_output_cost_per_million: float = float(env_or_default("ANTHROPIC_OUTPUT_COST_PER_MILLION", "15.0"))
    host: str = env_or_default("HOST", "0.0.0.0")
    port: int = int(env_or_default("PORT", "8000"))
    bearer_token: str = env_or_default("ITERFACT_MCP_BEARER_TOKEN", "")
    admin_bearer_token: str = env_or_default("ITERFACT_MCP_ADMIN_BEARER_TOKEN", "")
    output_dir: Path = Path(env_or_default("ITERFACT_OUTPUT_DIR", "./generated")).expanduser().resolve()
    analytics_dir: Path = Path(env_or_default("ITERFACT_ANALYTICS_DIR", "./analytics")).expanduser().resolve()
    public_mcp_base_url: str = env_or_default("ITERFACT_MCP_PUBLIC_BASE_URL", "https://mcp.iterfact.com").rstrip("/")
    public_artifact_base_url: str = env_or_default("ITERFACT_PUBLIC_ARTIFACT_BASE_URL", "").rstrip("/")
    public_app_base_url: str = env_or_default("NEXT_PUBLIC_SITE_URL", "https://iterfact.com").rstrip("/")
    r2_account_id: str = env_or_default("R2_ACCOUNT_ID", "")
    r2_access_key_id: str = env_or_default("R2_ACCESS_KEY_ID", "")
    r2_secret_access_key: str = env_or_default("R2_SECRET_ACCESS_KEY", "")
    r2_bucket: str = env_or_default("R2_BUCKET", "")
    r2_prefix: str = env_or_default("R2_PREFIX", "generated").strip("/")
    supabase_url: str = env_or_default("NEXT_PUBLIC_SUPABASE_URL", "").rstrip("/")
    supabase_service_key: str = env_or_default("SUPABASE_SECRET_KEY", "") or env_or_default("SUPABASE_SERVICE_ROLE_KEY", "")
    headless_validation_enabled: bool = env_or_default("ITERFACT_HEADLESS_VALIDATION_ENABLED", "1").strip().lower() not in {"0", "false", "no"}
    headless_validation_timeout_ms: int = int(env_or_default("ITERFACT_HEADLESS_VALIDATION_TIMEOUT_MS", "5000"))
    iterative_refinement_enabled: bool = env_or_default("ITERFACT_ITERATIVE_REFINEMENT_ENABLED", "1").strip().lower() not in {"0", "false", "no"}
    iterative_refinement_max_passes: int = int(env_or_default("ITERFACT_ITERATIVE_REFINEMENT_MAX_PASSES", "2"))
    iterative_refinement_min_quality: int = int(env_or_default("ITERFACT_ITERATIVE_REFINEMENT_MIN_QUALITY", "93"))
    iterative_refinement_max_sections_per_pass: int = int(env_or_default("ITERFACT_ITERATIVE_REFINEMENT_MAX_SECTIONS_PER_PASS", "3"))
    minimum_publish_quality_score: int = int(env_or_default("ITERFACT_MINIMUM_PUBLISH_QUALITY_SCORE", "75"))
    library_preview_capture_enabled: bool = env_or_default("ITERFACT_LIBRARY_PREVIEW_CAPTURE_ENABLED", "1").strip().lower() not in {"0", "false", "no"}
    library_preview_capture_timeout_ms: int = int(env_or_default("ITERFACT_LIBRARY_PREVIEW_CAPTURE_TIMEOUT_MS", "4500"))
    vault_path: Path = resolve_default_vault_path(__file__)

    @property
    def has_r2(self) -> bool:
        return all(
            [
                self.r2_account_id,
                self.r2_access_key_id,
                self.r2_secret_access_key,
                self.r2_bucket,
            ]
        )

    @property
    def has_supabase_telemetry(self) -> bool:
        return bool(self.supabase_url and self.supabase_service_key)


SETTINGS = Settings()
SETTINGS.output_dir.mkdir(parents=True, exist_ok=True)
SETTINGS.analytics_dir.mkdir(parents=True, exist_ok=True)

SYSTEM_PROMPT_PATH = SETTINGS.vault_path / "spec" / "system_prompt_raw.txt"
DATA_VISUAL_GUIDANCE_PATH = SETTINGS.vault_path / "spec" / "data_visual_cognition_guidance.txt"
ASSEMBLER_PATH = SETTINGS.vault_path / "assembler.py"
MANIFEST_PATH = SETTINGS.vault_path / "manifest.json"
STATIC_DIR = Path(__file__).resolve().parent / "static"
GENERATION_EVENTS_PATH = SETTINGS.analytics_dir / "generation_events.jsonl"
FAILURE_EVENTS_PATH = SETTINGS.analytics_dir / "generation_failures.jsonl"
TELEMETRY_DIR = SETTINGS.analytics_dir / "telemetry"
VIEW_EVENTS_PATH = SETTINGS.analytics_dir / "view_events.jsonl"
ENGAGEMENT_EVENTS_PATH = SETTINGS.analytics_dir / "engagement_events.jsonl"
ARTIFACT_STATE_FILENAME = "artifact_state.json"
ARTIFACT_STATE_LOCK = threading.RLock()
FINALIZE_JOB_LOCK = threading.Lock()
ACTIVE_FINALIZE_THREADS: dict[str, threading.Thread] = {}
TOKEN_TRACKER_VAR: contextvars.ContextVar["TokenTracker | None"] = contextvars.ContextVar(
    "iterfact_token_tracker",
    default=None,
)

MODEL_PRICING_PER_MILLION: dict[str, tuple[float, float]] = {
    "haiku": (1.0, 5.0),
    "sonnet": (3.0, 15.0),
    "opus": (5.0, 25.0),
}


@dataclass
class TokenUsageRecord:
    model: str
    input_tokens: int
    output_tokens: int
    step_name: str
    latency_ms: int | None = None


@dataclass
class TokenTracker:
    calls: list[TokenUsageRecord] = field(default_factory=list)

    def record(
        self,
        *,
        model: str,
        input_tokens: int,
        output_tokens: int,
        step_name: str,
        latency_ms: int | None = None,
    ) -> None:
        self.calls.append(
            TokenUsageRecord(
                model=model,
                input_tokens=max(0, int(input_tokens or 0)),
                output_tokens=max(0, int(output_tokens or 0)),
                step_name=step_name,
                latency_ms=int(latency_ms) if latency_ms is not None else None,
            )
        )

    @property
    def total_input_tokens(self) -> int:
        return sum(call.input_tokens for call in self.calls)

    @property
    def total_output_tokens(self) -> int:
        return sum(call.output_tokens for call in self.calls)

    @property
    def total_calls(self) -> int:
        return len(self.calls)

    @property
    def models_used(self) -> list[str]:
        return sorted({call.model for call in self.calls if call.model})

    @property
    def dominant_model(self) -> str | None:
        if not self.calls:
            return None
        by_model = Counter(call.model for call in self.calls if call.model)
        if not by_model:
            return None
        return by_model.most_common(1)[0][0]

    @property
    def estimated_cost_usd(self) -> float:
        total = 0.0
        for call in self.calls:
            input_rate, output_rate = model_pricing_for(call.model)
            total += (call.input_tokens / 1_000_000) * input_rate
            total += (call.output_tokens / 1_000_000) * output_rate
        return round(total, 6)

    def to_summary(self) -> dict[str, Any]:
        return {
            "llm_calls_count": self.total_calls,
            "llm_models_used": self.models_used,
            "llm_model_used": self.dominant_model,
            "total_input_tokens": self.total_input_tokens,
            "total_output_tokens": self.total_output_tokens,
            "estimated_cost_usd": self.estimated_cost_usd,
        }


TELEMETRY_DIR.mkdir(parents=True, exist_ok=True)


def model_pricing_for(model: str | None) -> tuple[float, float]:
    normalized = str(model or "").strip().lower()
    for family, pricing in MODEL_PRICING_PER_MILLION.items():
        if family in normalized:
            return pricing
    return (
        SETTINGS.anthropic_input_cost_per_million,
        SETTINGS.anthropic_output_cost_per_million,
    )


@contextlib.contextmanager
def bind_token_tracker(existing: TokenTracker | None = None):
    tracker = existing or TokenTracker()
    token = TOKEN_TRACKER_VAR.set(tracker)
    try:
        yield tracker
    finally:
        TOKEN_TRACKER_VAR.reset(token)


def load_assembler() -> Any:
    spec = importlib.util.spec_from_file_location("iterfact_assembler", ASSEMBLER_PATH)
    if spec is None or spec.loader is None:
        raise RuntimeError(f"Could not load assembler from {ASSEMBLER_PATH}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


ASSEMBLER_MODULE = load_assembler()
assemble = ASSEMBLER_MODULE.assemble

SYSTEM_PROMPT = SYSTEM_PROMPT_PATH.read_text(encoding="utf-8")
DATA_VISUAL_GUIDANCE = (
    DATA_VISUAL_GUIDANCE_PATH.read_text(encoding="utf-8").strip()
    if DATA_VISUAL_GUIDANCE_PATH.exists()
    else ""
)
MANIFEST = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
ENGINE_CATALOG: dict[str, dict[str, Any]] = {}
for engine in MANIFEST.get("engines", []):
    folder = engine.get("folder", "")
    engine_key = folder[:2] if folder[:2].isdigit() else ""
    if engine_key:
        ENGINE_CATALOG[engine_key] = engine

VALID_WRAPPERS = {"01", "02", "04", "12", "18", "21", "24", "25"}
WRAPPER_LABELS = {
    "01": "immersive landing page",
    "02": "slide deck",
    "04": "guided walkthrough",
    "12": "scrollytelling report",
    "18": "chaptered report",
    "21": "guided tour",
    "24": "document flow",
    "25": "guided document",
}
THEME_LABELS = {
    "midnight-mint": "Midnight Mint",
    "warm-editorial": "Warm Editorial",
    "paper-ledger": "Paper Ledger",
    "signal-grid": "Signal Grid",
    "coastal-report": "Coastal Report",
    "obsidian-cyan": "Obsidian Cyan",
}
ENGINE_SUPPORTS_OVERRIDES: dict[str, list[str]] = {
    "03": ["kpi_cards", "line", "bar", "horizontal_bar", "stacked_bar", "area", "scatter", "donut", "radar", "annotations", "tooltips"],
    "05": ["timeline", "milestones", "chronology"],
    "07": ["comparison_matrix", "ranking", "feature_comparison"],
    "08": ["calculator", "scenario_modeling", "inputs"],
    "17": ["scenario_modeling", "financial_modeling", "what_if"],
    "22": ["faq", "details_summary", "search"],
    "26": ["table", "reference_data"],
    "29": ["waterfall", "financial_bridge", "tooltips"],
    "31": ["gantt", "roadmap", "timeline"],
    "32": ["donut", "pie", "composition"],
    "39": ["testimonials", "social_proof", "globe", "global_footprint", "customer_stories"],
    "44": ["ranking", "horizontal_bar", "range_bar", "sorting", "tooltips", "market_sizing", "uncertainty"],
}
ENGINE_BEST_FOR_OVERRIDES: dict[str, str] = {
    "02": "speaker-led decks, board reviews, and investor-style presentations",
    "03": "data-heavy reports, KPI summaries, and chart-forward analytics",
    "12": "single-page scroll stories and proof-first explainers",
    "18": "chaptered narratives, research, and immersive longform reports",
    "21": "guided walkthroughs, product demos, and training flows",
    "22": "FAQ, disclosure, and expandable reference sections",
    "29": "financial bridges, variance walks, and start-to-finish value changes",
    "39": "global social proof, customer stories, and testimonial-heavy landing sections",
    "44": "ranked comparisons, consulting-style market maps, league tables, and editorial data storytelling",
}
PROMPT_STACK_BRIDGE_VERSION = PROMPT_STACK_BUNDLE_VERSION
VISIBLE_PLACEHOLDER_TOKENS = ("undefined", "nan", "null")
EMPTY_TEXT_TAGS = ("h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "li", "summary", "figcaption", "small")
HEADLESS_VISIBLE_FAILURE_PATTERN = re.compile(r"\b(?:nan|undefined|null|\[object object\])\b", flags=re.IGNORECASE)
UNFORMATTED_FLOAT_PATTERN = re.compile(r"(?<![\w-])-?\d+\.\d{6,}(?![\w-])")
HEX_COLOR_PATTERN = re.compile(r"#[0-9a-fA-F]{6}\b")
UPLOAD_SAFE_TEXT_TAGS = {"h1", "h2", "h3", "h4", "h5", "h6", "p", "blockquote", "figcaption", "pre", "code"}
UPLOAD_INTERACTIVE_TAGS = {"form", "input", "button", "select", "textarea", "canvas", "video", "audio", "iframe"}
PLANNER_UPLOADED_CONTENT_INLINE_MAX_CHARS = 18_000
PLANNER_UPLOADED_CONTENT_HEADING_LIMIT = 10
PLANNER_UPLOADED_CONTENT_SECTION_EXCERPT_MAX_CHARS = 700
PLANNER_UPLOADED_CONTENT_OPENING_EXCERPT_MAX_CHARS = 1_200
PLANNER_UPLOADED_CONTENT_MIDDLE_EXCERPT_MAX_CHARS = 900
PLANNER_UPLOADED_CONTENT_CLOSING_EXCERPT_MAX_CHARS = 900
VALID_VISUALIZER_RAMP_HEXES = {
    "#eeedfe", "#cecbf6", "#afa9ec", "#7f77dd", "#534ab7", "#3c3489", "#26215c",
    "#e1f5ee", "#9fe1cb", "#5dcaa5", "#1d9e75", "#0f6e56", "#085041", "#04342c",
    "#faece7", "#f5c4b3", "#f0997b", "#d85a30", "#993c1d", "#712b13", "#4a1b0c",
    "#fbeaf0", "#f4c0d1", "#ed93b1", "#d4537e", "#993556", "#72243e", "#4b1528",
    "#f1efe8", "#d3d1c7", "#b4b2a9", "#888780", "#5f5e5a", "#444441", "#2c2c2a",
    "#e6f1fb", "#b5d4f4", "#85b7eb", "#378add", "#185fa5", "#0c447c", "#042c53",
    "#eaf3de", "#c0dd97", "#97c459", "#639922", "#3b6d11", "#27500a", "#173404",
    "#faeeda", "#fac775", "#ef9f27", "#ba7517", "#854f0b", "#633806", "#412402",
    "#fcebeb", "#f7c1c1", "#f09595", "#e24b4a", "#a32d2d", "#791f1f", "#501313",
}


@dataclass
class UploadFinding:
    severity: str
    code: str
    detail: str
    evidence: str | None = None


@dataclass
class UploadAnalysis:
    decision: str
    category: str
    preservation_level: str
    policy_profile: str
    title: str | None
    findings: list[UploadFinding]
    blocks: list[dict[str, Any]]
    metrics: dict[str, int]
    external_domains: list[str]
    rejection_reason: str | None = None

    def finding_counts(self) -> dict[str, int]:
        counts = {"high": 0, "medium": 0, "low": 0, "info": 0}
        for finding in self.findings:
            counts[finding.severity] = counts.get(finding.severity, 0) + 1
        return counts

    def summary(self) -> dict[str, Any]:
        return {
            "decision": self.decision,
            "category": self.category,
            "preservation_level": self.preservation_level,
            "policy_profile": self.policy_profile,
            "rejection_reason": self.rejection_reason,
            "metrics": self.metrics,
            "external_domains": self.external_domains,
            "finding_counts": self.finding_counts(),
        }


@dataclass
class PlannerUploadContext:
    message_block: str
    strategy: str
    compacted: bool
    original_length: int
    detected_heading_count: int
    preserved_heading_count: int


@dataclass
class SourceChunk:
    index: int
    title: str
    body: str
    heading_level: int | None = None
    source_heading: str | None = None


class UploadRejectedError(RuntimeError):
    def __init__(self, message: str, analysis: UploadAnalysis):
        super().__init__(message)
        self.analysis = analysis


class UploadContentParser(HTMLParser):
    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.findings: list[UploadFinding] = []
        self.blocks: list[dict[str, Any]] = []
        self.metrics: dict[str, int] = {}
        self.external_domains: set[str] = set()
        self.title_chunks: list[str] = []
        self._ignored_depth = 0
        self._current_text_block: dict[str, Any] | None = None
        self._list_stack: list[dict[str, Any]] = []
        self._current_table: dict[str, Any] | None = None
        self._current_row: list[str] | None = None
        self._current_cell: list[str] | None = None

    def _bump(self, key: str) -> None:
        self.metrics[key] = self.metrics.get(key, 0) + 1

    def _add_finding(self, severity: str, code: str, detail: str, evidence: str | None = None) -> None:
        if any(
            existing.severity == severity
            and existing.code == code
            and existing.detail == detail
            and existing.evidence == evidence
            for existing in self.findings
        ):
            return
        self.findings.append(UploadFinding(severity=severity, code=code, detail=detail, evidence=evidence))

    def _inspect_url(self, attr_name: str, raw_value: str | None, *, tag: str) -> None:
        value = (raw_value or "").strip()
        if not value:
            return
        lowered = value.lower()
        if lowered.startswith("javascript:"):
            self._add_finding("high", "javascript_url", f"{tag} uses a javascript: URL.", value[:240])
            return
        if lowered.startswith(("data:", "mailto:", "tel:", "#")):
            return
        parsed = urlparse(value)
        if parsed.scheme in {"http", "https"}:
            domain = parsed.netloc.lower()
            if domain:
                self.external_domains.add(domain)
            if tag == "form" and attr_name in {"action", "formaction"}:
                self._add_finding("high", "external_form_post", "Upload contains a form that can submit to an external destination.", domain or value[:240])
            elif tag == "iframe":
                self._add_finding("medium", "external_iframe", "Upload embeds an external iframe and cannot be rebuilt deterministically.", domain or value[:240])
            elif tag == "script":
                self._add_finding("medium", "external_script", "Upload depends on external JavaScript.", domain or value[:240])
            elif tag == "link":
                self._add_finding("low", "external_stylesheet", "Upload depends on external stylesheet or font assets.", domain or value[:240])
            return
        if parsed.scheme:
            self._add_finding("medium", "unsupported_url_scheme", f"{tag} uses an unsupported URL scheme.", value[:240])

    def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
        lower_tag = tag.lower()
        self._bump(lower_tag)
        attr_map = {name.lower(): (value or "") for name, value in attrs}

        for name, value in attrs:
            attr_name = name.lower()
            if attr_name.startswith("on"):
                self._add_finding("high", "inline_event_handler", f"Upload contains inline event handler {attr_name}.", attr_name)
            if attr_name in {"href", "src", "action", "formaction", "poster"}:
                self._inspect_url(attr_name, value, tag=lower_tag)

        if lower_tag in {"script", "style", "noscript"}:
            self._ignored_depth += 1
            if lower_tag == "script":
                self._add_finding("medium", "script_tag", "Upload contains script execution and will not be hosted directly.")
            return

        if lower_tag == "title":
            self._current_text_block = {"type": "title", "chunks": []}
            return

        if lower_tag == "meta" and attr_map.get("http-equiv", "").lower() == "refresh":
            self._add_finding("high", "meta_refresh", "Upload contains a meta refresh redirect.", attr_map.get("content", "")[:240])
        if lower_tag == "form":
            self._add_finding("medium", "form_present", "Upload contains a form and will require guided reconstruction.")
        if lower_tag == "input":
            input_type = attr_map.get("type", "text").lower()
            self._bump(f"input_{input_type}")
            if input_type == "password":
                self._add_finding("high", "password_field", "Upload contains a password field and cannot be hosted or reconstructed automatically.")
            elif input_type in {"range", "checkbox", "radio", "number"}:
                self._add_finding("info", "interactive_control", "Upload contains form controls that will require guided reconstruction.", input_type)
        if lower_tag in UPLOAD_INTERACTIVE_TAGS:
            self._bump("interactive_nodes")
        if lower_tag == "iframe":
            self._add_finding("medium", "iframe_present", "Upload contains iframe content and will require guided reconstruction.")
        if lower_tag == "canvas":
            self._add_finding("medium", "canvas_present", "Upload contains canvas rendering and will require guided reconstruction.")
        if lower_tag in UPLOAD_SAFE_TEXT_TAGS:
            self._current_text_block = {
                "type": "heading" if lower_tag.startswith("h") else ("quote" if lower_tag == "blockquote" else "paragraph"),
                "level": int(lower_tag[1]) if lower_tag.startswith("h") else None,
                "chunks": [],
            }
            return
        if lower_tag in {"ul", "ol"}:
            self._list_stack.append({"ordered": lower_tag == "ol", "items": [], "current_item": None})
            return
        if lower_tag == "li" and self._list_stack:
            self._list_stack[-1]["current_item"] = []
            return
        if lower_tag == "table":
            self._current_table = {"type": "table", "rows": []}
            return
        if lower_tag == "tr" and self._current_table is not None:
            self._current_row = []
            return
        if lower_tag in {"td", "th"} and self._current_row is not None:
            self._current_cell = []
            return
        if lower_tag == "img":
            src = attr_map.get("src", "").strip()
            alt = compact_whitespace(attr_map.get("alt", ""))
            if src:
                self.blocks.append({"type": "image", "src": src, "alt": alt})

    def handle_endtag(self, tag: str) -> None:
        lower_tag = tag.lower()
        if lower_tag in {"script", "style", "noscript"}:
            self._ignored_depth = max(0, self._ignored_depth - 1)
            return
        if lower_tag == "title" and self._current_text_block and self._current_text_block.get("type") == "title":
            title = compact_whitespace(" ".join(self._current_text_block.get("chunks", [])))
            if title:
                self.title_chunks.append(title)
            self._current_text_block = None
            return
        if lower_tag in UPLOAD_SAFE_TEXT_TAGS and self._current_text_block:
            text = compact_whitespace(" ".join(self._current_text_block.get("chunks", [])))
            if text:
                block = {key: value for key, value in self._current_text_block.items() if key != "chunks"}
                block["text"] = text
                self.blocks.append(block)
            self._current_text_block = None
            return
        if lower_tag == "li" and self._list_stack:
            item = compact_whitespace(" ".join(self._list_stack[-1].get("current_item") or []))
            if item:
                self._list_stack[-1]["items"].append(item)
            self._list_stack[-1]["current_item"] = None
            return
        if lower_tag in {"ul", "ol"} and self._list_stack:
            list_state = self._list_stack.pop()
            if list_state["items"]:
                self.blocks.append(
                    {
                        "type": "list",
                        "ordered": list_state["ordered"],
                        "items": list_state["items"][:20],
                    }
                )
            return
        if lower_tag in {"td", "th"} and self._current_cell is not None and self._current_row is not None:
            cell_text = compact_whitespace(" ".join(self._current_cell))
            self._current_row.append(cell_text)
            self._current_cell = None
            return
        if lower_tag == "tr" and self._current_row is not None and self._current_table is not None:
            if any(cell for cell in self._current_row):
                self._current_table["rows"].append(self._current_row[:8])
            self._current_row = None
            return
        if lower_tag == "table" and self._current_table is not None:
            if self._current_table["rows"]:
                self.blocks.append(self._current_table)
            self._current_table = None

    def handle_data(self, data: str) -> None:
        if self._ignored_depth:
            return
        if self._current_cell is not None:
            self._current_cell.append(data)
            return
        if self._list_stack and self._list_stack[-1].get("current_item") is not None:
            self._list_stack[-1]["current_item"].append(data)
            return
        if self._current_text_block is not None:
            self._current_text_block["chunks"].append(data)


def compact_whitespace(value: str | None) -> str:
    return re.sub(r"\s+", " ", (value or "")).strip()


def add_upload_finding(findings: list[UploadFinding], severity: str, code: str, detail: str, evidence: str | None = None) -> None:
    if any(
        existing.severity == severity
        and existing.code == code
        and existing.detail == detail
        and existing.evidence == evidence
        for existing in findings
    ):
        return
    findings.append(UploadFinding(severity=severity, code=code, detail=detail, evidence=evidence))


def infer_upload_category(blocks: list[dict[str, Any]], metrics: dict[str, int]) -> str:
    if any(block.get("type") == "table" for block in blocks):
        return "data_report"
    if metrics.get("interactive_nodes", 0) or metrics.get("script", 0) or metrics.get("canvas", 0):
        return "interactive_surface"
    if sum(1 for block in blocks if block.get("type") == "image") >= 3:
        return "media_story"
    if blocks:
        return "content_document"
    return "unknown_app"


def scan_runtime_signals(uploaded_content: str, findings: list[UploadFinding]) -> None:
    lowered = uploaded_content.lower()
    signal_map = [
        ("serviceworker.register", "high", "service_worker", "Upload attempts to register a service worker."),
        ("navigator.sendbeacon", "high", "send_beacon", "Upload contains navigator.sendBeacon and may exfiltrate data."),
        ("webassembly", "medium", "webassembly", "Upload references WebAssembly and cannot be hosted directly."),
        (".wasm", "medium", "wasm_asset", "Upload references a WebAssembly asset."),
        ("sharedarraybuffer", "medium", "shared_array_buffer", "Upload references SharedArrayBuffer."),
        ("new websocket", "high", "websocket", "Upload opens a WebSocket connection."),
        ("websocket(", "high", "websocket", "Upload opens a WebSocket connection."),
        ("window.top", "medium", "frame_escape", "Upload references window.top."),
        ("window.parent", "medium", "frame_escape", "Upload references window.parent."),
        ("window.opener", "medium", "frame_escape", "Upload references window.opener."),
        ("document.cookie", "high", "cookie_access", "Upload accesses document.cookie."),
        ("eval(", "high", "dynamic_code", "Upload uses eval()."),
        ("new function(", "high", "dynamic_code", "Upload uses Function() constructor."),
    ]
    for needle, severity, code, detail in signal_map:
        if needle in lowered:
            add_upload_finding(findings, severity, code, detail, needle)


def analyze_uploaded_html(uploaded_content: str, prompt: str) -> UploadAnalysis:
    parser = UploadContentParser()
    parser.feed(uploaded_content)
    parser.close()

    findings = list(parser.findings)
    scan_runtime_signals(uploaded_content, findings)

    visible_blocks = [block for block in parser.blocks if block.get("type") != "image" or block.get("src")]
    metrics = dict(parser.metrics)
    metrics["visible_blocks"] = len(visible_blocks)
    metrics["external_domain_count"] = len(parser.external_domains)
    category = infer_upload_category(visible_blocks, metrics)

    decision = "guided_reconstruction"
    preservation_level = "medium"
    policy_profile = "interactive_html"
    rejection_reason = None

    finding_codes = {finding.code for finding in findings}
    if len(uploaded_content) > 1_500_000:
        decision = "reject"
        preservation_level = "none"
        policy_profile = "blocked"
        rejection_reason = "The upload is too large for reliable safe reconstruction."
        add_upload_finding(findings, "high", "oversized_upload", rejection_reason)
    elif finding_codes & {"password_field", "external_form_post", "javascript_url", "service_worker", "cookie_access"}:
        decision = "reject"
        preservation_level = "none"
        policy_profile = "blocked"
        rejection_reason = "The upload contains credential capture or unsafe runtime behavior that IterFact does not host."
    elif metrics.get("visible_blocks", 0) < 2 and (
        metrics.get("script", 0)
        or metrics.get("interactive_nodes", 0)
        or metrics.get("iframe", 0)
    ):
        decision = "reject"
        preservation_level = "none"
        policy_profile = "blocked"
        rejection_reason = "The upload is too app-like or opaque to reconstruct while preserving intent."
        add_upload_finding(findings, "medium", "opaque_runtime_bundle", rejection_reason)
    elif (
        not metrics.get("script", 0)
        and not metrics.get("iframe", 0)
        and not metrics.get("form", 0)
        and not metrics.get("interactive_nodes", 0)
        and metrics.get("external_domain_count", 0) == 0
        and metrics.get("visible_blocks", 0) >= 3
    ):
        decision = "safe_rebuild"
        preservation_level = "high"
        policy_profile = "static_html"
    elif metrics.get("visible_blocks", 0) >= 2:
        decision = "guided_reconstruction"
        preservation_level = "medium"
        policy_profile = "interactive_html"
    else:
        decision = "reject"
        preservation_level = "none"
        policy_profile = "blocked"
        rejection_reason = "IterFact could not extract enough visible structure to rebuild this upload safely."

    return UploadAnalysis(
        decision=decision,
        category=category,
        preservation_level=preservation_level,
        policy_profile=policy_profile,
        title=(parser.title_chunks[0] if parser.title_chunks else extract_html_title(uploaded_content) or prompt[:120] or "Imported HTML"),
        findings=findings,
        blocks=visible_blocks[:180],
        metrics=metrics,
        external_domains=sorted(parser.external_domains),
        rejection_reason=rejection_reason,
    )


def build_guided_reconstruction_prompt(prompt: str, analysis: UploadAnalysis) -> str:
    blocks_payload = json.dumps(analysis.blocks[:60], ensure_ascii=True)
    findings_payload = json.dumps(
        [
            {
                "severity": finding.severity,
                "code": finding.code,
                "detail": finding.detail,
            }
            for finding in analysis.findings[:20]
        ],
        ensure_ascii=True,
    )
    return (
        f"{prompt.strip()}\n\n"
        "The user uploaded HTML that cannot be hosted directly. Reconstruct it as a secure IterFact while preserving the visible structure, copy, and intent.\n"
        "Do not preserve unsafe runtime behavior, external execution, or credential capture flows.\n\n"
        f"<upload_ingest decision=\"{analysis.decision}\" category=\"{analysis.category}\" preservation=\"{analysis.preservation_level}\" />\n"
        f"<upload_title>{analysis.title or 'Imported HTML'}</upload_title>\n"
        f"<upload_findings>{findings_payload}</upload_findings>\n"
        f"<upload_blocks>{blocks_payload}</upload_blocks>\n"
        "Use the extracted blocks as the source of truth for visible content. Preserve headings, text, lists, table meaning, and obvious interactions in a secure IterFact-native form."
    )


def build_section_regeneration_prompt(
    *,
    original_prompt: str,
    plan: dict[str, Any],
    section_index: int,
    current_section: dict[str, Any],
    updated_prompt: str | None,
    structured_data: dict[str, Any] | None = None,
) -> str:
    section_titles = [
        section.get("title") or f"Section {index}"
        for index, section in enumerate(plan.get("sections", []) or [], start=1)
    ]
    neighbor_before = section_titles[section_index - 2] if section_index > 1 else None
    neighbor_after = section_titles[section_index] if section_index < len(section_titles) else None
    narrative_spine = build_narrative_spine_snapshot(plan)
    updated_instruction = (
        updated_prompt.strip()
        if updated_prompt
        else "Refresh this section so it is sharper, clearer, and more visually effective while staying consistent with the rest of the artifact."
    )

    message = (
        f"Original document request:\n{original_prompt.strip()}\n\n"
        "You are regenerating exactly one section of an existing IterFact artifact.\n"
        "Preserve the surrounding document's pacing, tone, and data fidelity.\n"
        "Do not rewrite the other sections. Return only one JSON object with a single top-level key named `section`.\n\n"
        f"Document title: {plan.get('meta', {}).get('title') or 'IterFact Document'}\n"
        f"Document description: {plan.get('meta', {}).get('description') or ''}\n"
        f"Wrapper engine: {plan.get('wrapper', {}).get('engine') or 'unknown'}\n"
        f"Theme: {plan.get('theme') or 'default'}\n"
        f"Section count: {len(plan.get('sections', []) or [])}\n"
        f"All section titles: {json.dumps(section_titles, ensure_ascii=True)}\n"
        f"Narrative spine: {json.dumps(narrative_spine, ensure_ascii=True)}\n"
        f"Target section index: {section_index}\n"
        f"Previous section title: {neighbor_before or 'none'}\n"
        f"Next section title: {neighbor_after or 'none'}\n\n"
        f"<current_section>\n{json.dumps(current_section, ensure_ascii=True, indent=2)}\n</current_section>\n\n"
        f"Updated instructions for this section:\n{updated_instruction}\n"
    )

    if structured_data:
        message += f"\n\n<structured_data>\n{json.dumps(structured_data, ensure_ascii=True, indent=2)}\n</structured_data>"

    message += (
        "\n\nReturn schema:\n"
        '{\n  "section": {\n    "title": "...",\n    "content": {"headline": "...", "body": "..."},\n    "components": []\n  }\n}\n\n'
        "Important rules:\n"
        "- keep numbers exact when structured data is present\n"
        "- do not emit undefined, null, or NaN in visible content\n"
        "- prefer charts over tables when the request clearly calls for a chart\n"
        "- make the section feel complete and well-paced, not sparse\n"
        "- do not include markdown fences or commentary"
    )
    return message


def build_section_append_prompt(
    *,
    original_prompt: str,
    plan: dict[str, Any],
    draft_blueprint: dict[str, Any] | None,
    chunk: SourceChunk,
    target_index: int,
    updated_prompt: str | None = None,
    section_title_hint: str | None = None,
    structured_data: dict[str, Any] | None = None,
    engine_override: str | None = None,
) -> str:
    current_titles = [
        section.get("title") or f"Section {index}"
        for index, section in enumerate(plan.get("sections", []) or [], start=1)
    ]
    blueprint_sections = draft_blueprint.get("sections", []) if isinstance(draft_blueprint, dict) else []
    planned_titles = [
        section.get("title") or f"Section {index}"
        for index, section in enumerate(blueprint_sections or [], start=1)
    ]
    planned_shell = blueprint_sections[target_index - 1] if target_index - 1 < len(blueprint_sections or []) else None
    preferred_title = (
        compact_whitespace(section_title_hint)
        or (compact_whitespace(str(planned_shell.get("title") or "")) if isinstance(planned_shell, dict) else "")
        or chunk.title
    )
    append_instruction = (
        updated_prompt.strip()
        if updated_prompt
        else "Build a strong IterFact section from this source chunk while preserving the original facts, structure, and named entities."
    )

    message = (
        f"Original document request:\n{original_prompt.strip()}\n\n"
        "You are appending exactly one section to a chunk-built IterFact artifact.\n"
        "Preserve the source chunk's factual content, terminology, and argument structure.\n"
        "Prefer using the source chunk as the source of truth over generic filler.\n"
        "Return only one JSON object with a single top-level key named `section`.\n\n"
        f"Document title: {plan.get('meta', {}).get('title') or 'IterFact Document'}\n"
        f"Document description: {plan.get('meta', {}).get('description') or ''}\n"
        f"Wrapper engine: {plan.get('wrapper', {}).get('engine') or 'unknown'}\n"
        f"Requested primary engine override: {engine_override or 'none'}\n"
        f"Theme: {plan.get('theme') or 'default'}\n"
        f"Sections already appended: {len(current_titles)}\n"
        f"Current section titles: {json.dumps(current_titles, ensure_ascii=True)}\n"
        f"Planned section titles: {json.dumps(planned_titles, ensure_ascii=True)}\n"
        f"Target section index: {target_index}\n"
        f"Preferred section title: {preferred_title}\n\n"
    )

    if isinstance(planned_shell, dict):
        message += f"<planned_section_shell>\n{json.dumps(planned_shell, ensure_ascii=True, indent=2)}\n</planned_section_shell>\n\n"

    message += (
        f"<source_chunk title=\"{escape(chunk.title, quote=True)}\" index=\"{chunk.index}\""
        f" char_count=\"{len(chunk.body)}\">\n{chunk.body}\n</source_chunk>\n\n"
        f"Additional instructions for this section:\n{append_instruction}\n"
    )

    if structured_data:
        message += f"\n\n<structured_data>\n{json.dumps(structured_data, ensure_ascii=True, indent=2)}\n</structured_data>"

    message += (
        "\n\nReturn schema:\n"
        '{\n  "section": {\n    "title": "...",\n    "content": {"headline": "...", "body": "..."},\n    "components": []\n  }\n}\n\n'
        "Important rules:\n"
        "- preserve important source facts, names, metrics, and comparisons from source_chunk\n"
        "- keep numbers exact when structured data is present\n"
        "- do not emit undefined, null, or NaN in visible content\n"
        "- only add a component when it materially improves comprehension of this source chunk\n"
        "- do not summarize away critical evidence if the chunk is already a focused section\n"
        "- if Requested primary engine override is not none, use that exact engine in this section's components and populate its data from source_chunk instead of relying on engine demo defaults or placeholder data\n"
        "- never leave a requested engine component with empty data if source_chunk contains the needed content\n"
        "- do not include markdown fences or commentary"
    )
    return message


QUIZ_QUESTION_HEADER_RE = re.compile(
    r"^(?:question|q)\s*(\d+)\b(?:\s*[-:]\s*difficulty\s*[:\-]?\s*([^:]+))?(?:\s*[:\-]\s*(.*))?$",
    flags=re.IGNORECASE,
)
QUIZ_OPTION_RE = re.compile(r"^\s*([A-Z])[\)\.\:]\s*(.+?)\s*$")
QUIZ_SCORING_RE = re.compile(r"^\s*(\d+)\s*-\s*(\d+)\s*correct\s*:\s*(.+?)\s*$", flags=re.IGNORECASE)


def strip_correct_marker(text: str) -> tuple[str, bool]:
    value = compact_whitespace(text)
    marked_correct = bool(re.search(r"[\[(]?\s*correct\s*[\])]?", value, flags=re.IGNORECASE))
    cleaned = re.sub(r"\s*[\[(]?\s*correct\s*[\])]?\s*$", "", value, flags=re.IGNORECASE).strip()
    return cleaned, marked_correct


def parse_quiz_payload_from_source_text(source_text: str) -> dict[str, Any] | None:
    lines = [line.strip() for line in normalize_source_text(source_text).splitlines()]
    questions: list[dict[str, Any]] = []
    scoring_ranges: list[tuple[int, int, str]] = []
    current: dict[str, Any] | None = None
    in_scoring_block = False

    def flush_current() -> None:
        nonlocal current
        if not isinstance(current, dict):
            current = None
            return
        question_text = compact_whitespace(" ".join(current.get("stem_parts") or []))
        options = [compact_whitespace(str(option)) for option in (current.get("options") or []) if compact_whitespace(str(option))]
        correct_index = current.get("correct_index")
        if question_text and len(options) >= 2 and isinstance(correct_index, int) and 0 <= correct_index < len(options):
            explanation_parts = []
            difficulty = compact_whitespace(str(current.get("difficulty") or ""))
            if difficulty:
                explanation_parts.append(f"Difficulty: {difficulty}.")
            explanation_parts.extend(
                compact_whitespace(str(part))
                for part in (current.get("explanation_parts") or [])
                if compact_whitespace(str(part))
            )
            if not explanation_parts:
                explanation_parts.append(f"Correct answer: {options[correct_index]}.")
            questions.append(
                {
                    "question": question_text,
                    "options": options,
                    "correct": correct_index,
                    "explanation": " ".join(explanation_parts),
                }
            )
        current = None

    for raw_line in lines:
        line = compact_whitespace(raw_line)
        if not line:
            continue

        if "scoring" in line.lower() and "result" in line.lower():
            flush_current()
            in_scoring_block = True
            continue

        if in_scoring_block:
            scoring_match = QUIZ_SCORING_RE.match(line)
            if scoring_match:
                minimum = int(scoring_match.group(1))
                maximum = int(scoring_match.group(2))
                label = compact_whitespace(scoring_match.group(3).strip().strip("\"'"))
                if label:
                    scoring_ranges.append((minimum, maximum, label))
            continue

        question_match = QUIZ_QUESTION_HEADER_RE.match(line)
        if question_match:
            flush_current()
            current = {
                "number": int(question_match.group(1)),
                "difficulty": compact_whitespace(question_match.group(2) or ""),
                "stem_parts": [],
                "options": [],
                "correct_index": None,
                "explanation_parts": [],
            }
            inline_question_text = compact_whitespace(question_match.group(3) or "")
            if inline_question_text:
                current["stem_parts"].append(inline_question_text)
            continue

        if not isinstance(current, dict):
            continue

        option_match = QUIZ_OPTION_RE.match(line)
        if option_match:
            option_text, marked_correct = strip_correct_marker(option_match.group(2))
            current.setdefault("options", []).append(option_text)
            if marked_correct:
                current["correct_index"] = len(current["options"]) - 1
            continue

        if current.get("options"):
            current.setdefault("explanation_parts", []).append(line)
        else:
            current.setdefault("stem_parts", []).append(line)

    flush_current()

    if len(questions) < 2:
        return None

    total_questions = max(1, len(questions))
    results: dict[str, str] = {}
    for minimum, _maximum, label in sorted(scoring_ranges, key=lambda item: item[0], reverse=True):
        threshold = max(0, min(100, int((minimum / total_questions) * 100)))
        results[str(threshold)] = label
    if "0" not in results:
        results["0"] = "Complete the quiz to see your result."

    return {
        "questions": questions,
        "results": results,
    }


def infer_org_department(title: str) -> str:
    lower = compact_whitespace(title).lower()
    if any(token in lower for token in ("cto", "engineer", "engineering", "developer", "product")):
        return "Engineering"
    if any(token in lower for token in ("sales", "account executive", "revenue")):
        return "Sales"
    if any(token in lower for token in ("marketing", "growth", "brand")):
        return "Marketing"
    if any(token in lower for token in ("finance", "controller", "accounting", "cfo")):
        return "Finance"
    if any(token in lower for token in ("operations", "ops", "coo")):
        return "Operations"
    return "Leadership"


def initials_for_name(name: str) -> str:
    tokens = [token[:1].upper() for token in re.findall(r"[A-Za-z0-9]+", name)]
    if not tokens:
        return "P"
    return "".join(tokens[:2])


def parse_org_chart_payload_from_source_text(source_text: str) -> dict[str, Any] | None:
    normalized = normalize_source_text(source_text)
    raw_entries = [part.strip() for part in re.split(r"[\n;]+", normalized) if part.strip()]
    if len(raw_entries) == 1 and raw_entries[0].count(",") >= 1:
        raw_entries = [part.strip() for part in raw_entries[0].split(",") if part.strip()]

    people: list[dict[str, Any]] = []
    for index, entry in enumerate(raw_entries, start=1):
        if ":" not in entry:
            continue
        title, name = entry.split(":", 1)
        clean_title = compact_whitespace(title)
        clean_name = compact_whitespace(name.strip().strip("\"'"))
        if not clean_title or not clean_name:
            continue
        person_id = slugify(f"{clean_name}-{clean_title}") or f"person-{index}"
        people.append(
            {
                "id": person_id,
                "name": clean_name,
                "title": clean_title,
                "dept": infer_org_department(clean_title),
                "reports_to": None,
                "avatar": initials_for_name(clean_name),
                "bio": f"{clean_name} serves as {clean_title}.",
                "email": "",
            }
        )

    if len(people) < 2:
        return None

    root_id = people[0]["id"]
    for index, person in enumerate(people):
        person["reports_to"] = None if index == 0 else root_id

    return {"people": people}


def build_source_contract_terms(engine_id: str, payload: dict[str, Any]) -> list[str]:
    normalized = normalize_engine_id(engine_id) or engine_id
    if normalized == "09":
        questions = payload.get("questions") if isinstance(payload.get("questions"), list) else []
        results = payload.get("results") if isinstance(payload.get("results"), dict) else {}
        terms: list[str] = []
        for question in questions[:4]:
            if not isinstance(question, dict):
                continue
            question_text = compact_whitespace(str(question.get("question") or ""))
            if question_text:
                terms.append(question_text)
            options = [compact_whitespace(str(option)) for option in (question.get("options") or []) if compact_whitespace(str(option))]
            correct_index = question.get("correct")
            if isinstance(correct_index, int) and 0 <= correct_index < len(options):
                terms.append(options[correct_index])
        terms.extend(
            compact_whitespace(str(value))
            for value in results.values()
            if compact_whitespace(str(value))
        )
        return list(dict.fromkeys(term for term in terms if term))[:12]

    if normalized == "19":
        people = payload.get("people") if isinstance(payload.get("people"), list) else []
        return list(
            dict.fromkeys(
                compact_whitespace(str(person.get("name") or ""))
                for person in people
                if isinstance(person, dict) and compact_whitespace(str(person.get("name") or ""))
            )
        )[:12]

    return []


def build_deterministic_section_from_source_chunk(
    *,
    chunk: SourceChunk,
    engine_override: str | None,
    target_index: int,
    section_title_hint: str | None = None,
) -> tuple[dict[str, Any], dict[str, Any]] | None:
    normalized_engine = normalize_engine_id(engine_override)
    if normalized_engine not in {"09", "19"}:
        return None

    component_payload: dict[str, Any] | None = None
    role = "Primary interactive component"
    default_headline = compact_whitespace(section_title_hint) or compact_whitespace(chunk.title) or f"Section {target_index}"
    default_body = compact_whitespace(chunk.body)

    if normalized_engine == "09":
        component_payload = parse_quiz_payload_from_source_text(chunk.body)
        if not component_payload:
            return None
        question_total = len(component_payload.get("questions") or [])
        role = "Interactive quiz built from caller-provided source text"
        default_body = (
            f"Work through {question_total} caller-provided questions and see the scored result at the end."
            if question_total
            else "Work through the caller-provided questions and see the scored result at the end."
        )
    elif normalized_engine == "19":
        component_payload = parse_org_chart_payload_from_source_text(chunk.body)
        if not component_payload:
            return None
        people_total = len(component_payload.get("people") or [])
        role = "Team directory built from caller-provided source text"
        default_body = (
            f"Browse {people_total} caller-provided people records in an interactive org chart."
            if people_total
            else "Browse the caller-provided people records in an interactive org chart."
        )

    source_contract_terms = build_source_contract_terms(normalized_engine, component_payload or {})
    section_title = compact_whitespace(section_title_hint) or compact_whitespace(chunk.title) or f"Section {target_index}"
    section = {
        "title": section_title,
        "content": {
            "headline": default_headline,
            "body": default_body,
        },
        "components": [
            {
                "engine": normalized_engine,
                "instance_id": f"{slugify(section_title) or 'section'}-{normalized_engine}-{target_index}",
                "role": role,
                "data": component_payload or {},
            }
        ],
        "source_contract": {
            "mode": "deterministic_engine_override",
            "required_terms": source_contract_terms,
        },
    }
    payload = {
        "model": None,
        "usage": {},
        "repair_applied": False,
        "retry_strategy": f"deterministic-section-{normalized_engine}",
        "_provider_events": [],
        "deterministic_engine_override": normalized_engine,
    }
    return section, payload


def evaluate_source_contract_render(plan: dict[str, Any], html: str) -> tuple[list[dict[str, Any]], int]:
    warnings: list[dict[str, Any]] = []
    penalty = 0
    matched_html = _normalize_contract_match_text(html)
    missing_sections: list[dict[str, Any]] = []

    for index, section in enumerate(plan.get("sections", []) or [], start=1):
        if not isinstance(section, dict):
            continue
        source_contract = section.get("source_contract") if isinstance(section.get("source_contract"), dict) else {}
        required_terms = [
            compact_whitespace(str(term))
            for term in (source_contract.get("required_terms") or [])
            if compact_whitespace(str(term))
        ]
        if not required_terms:
            continue
        matches = [term for term in required_terms if _normalize_contract_match_text(term) in matched_html]
        if matches:
            continue
        missing_sections.append(
            {
                "section_index": index,
                "title": section.get("title") or f"Section {index}",
                "required_terms": required_terms[:6],
            }
        )

    if missing_sections:
        warnings.append(
            {
                "type": "source_data_not_rendered",
                "severity": "high",
                "message": (
                    "Caller-provided source terms did not appear anywhere in the rendered artifact HTML. "
                    "This usually means the render fell back to template demo data instead of using the appended source text."
                ),
                "sections": missing_sections,
            }
        )
        penalty += 18

    return warnings, penalty


def safe_image_src(src: str | None) -> str | None:
    value = (src or "").strip()
    if not value:
        return None
    parsed = urlparse(value)
    if parsed.scheme in {"http", "https", "data"}:
        return value
    return None


def build_safe_rebuild_html(analysis: UploadAnalysis) -> str:
    title = analysis.title or "Imported IterFact"
    body_parts: list[str] = []
    first_heading_used = False
    for block in analysis.blocks[:140]:
        block_type = block.get("type")
        if block_type == "heading":
            text = compact_whitespace(block.get("text"))
            if not text:
                continue
            if not first_heading_used:
                body_parts.append(f"<h1>{escape(text)}</h1>")
                first_heading_used = True
            else:
                level = min(max(int(block.get("level") or 2), 2), 3)
                body_parts.append(f"<h{level}>{escape(text)}</h{level}>")
        elif block_type == "paragraph":
            text = compact_whitespace(block.get("text"))
            if text:
                body_parts.append(f"<p>{escape(text)}</p>")
        elif block_type == "quote":
            text = compact_whitespace(block.get("text"))
            if text:
                body_parts.append(f"<blockquote>{escape(text)}</blockquote>")
        elif block_type == "list":
            items = [compact_whitespace(item) for item in block.get("items", []) if compact_whitespace(item)]
            if items:
                tag = "ol" if block.get("ordered") else "ul"
                body_parts.append(
                    f"<{tag}>"
                    + "".join(f"<li>{escape(item)}</li>" for item in items[:20])
                    + f"</{tag}>"
                )
        elif block_type == "table":
            rows = block.get("rows") or []
            if rows:
                table_rows = []
                for row in rows[:20]:
                    cells = "".join(f"<td>{escape(compact_whitespace(cell))}</td>" for cell in row[:8])
                    table_rows.append(f"<tr>{cells}</tr>")
                body_parts.append(f"<div class=\"ifx-table-wrap\"><table>{''.join(table_rows)}</table></div>")
        elif block_type == "image":
            src = safe_image_src(block.get("src"))
            if src:
                alt = compact_whitespace(block.get("alt"))
                caption = f"<figcaption>{escape(alt)}</figcaption>" if alt else ""
                body_parts.append(f"<figure><img src=\"{escape(src, quote=True)}\" alt=\"{escape(alt)}\" loading=\"lazy\" />{caption}</figure>")

    if not body_parts:
        body_parts.append("<p>IterFact could not safely preserve enough visible structure from this upload to rebuild it deterministically.</p>")

    findings_note = ""
    if analysis.findings:
        findings_note = (
            "<aside class=\"ifx-import-note\">"
            "<strong>Imported with safe rebuild.</strong> "
            "IterFact reconstructed the visible structure and removed unsupported runtime behavior."
            "</aside>"
        )

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{escape(title)}</title>
  <style>
    :root {{
      color-scheme: light;
      --ifx-bg: #f7f4ee;
      --ifx-paper: #fffdf9;
      --ifx-text: #161616;
      --ifx-muted: #5f605d;
      --ifx-border: rgba(0,0,0,0.08);
      --ifx-accent: #1a7a4e;
    }}
    * {{ box-sizing: border-box; }}
    body {{
      margin: 0;
      font-family: Inter, system-ui, sans-serif;
      background: radial-gradient(circle at top left, rgba(136, 195, 171, 0.18), transparent 35%), var(--ifx-bg);
      color: var(--ifx-text);
    }}
    main {{
      width: min(880px, calc(100vw - 40px));
      margin: 48px auto 72px;
      background: var(--ifx-paper);
      border: 1px solid var(--ifx-border);
      border-radius: 28px;
      padding: 40px;
      box-shadow: 0 24px 48px rgba(0,0,0,0.08);
    }}
    h1, h2, h3 {{
      margin: 0 0 16px;
      line-height: 0.96;
      letter-spacing: -0.04em;
    }}
    h1 {{ font-size: clamp(2.6rem, 5vw, 4.8rem); }}
    h2 {{ font-size: clamp(1.6rem, 2.6vw, 2.4rem); margin-top: 36px; }}
    h3 {{ font-size: 1.2rem; margin-top: 28px; }}
    p, li, blockquote, td {{
      font-size: 1.04rem;
      line-height: 1.75;
      color: var(--ifx-text);
    }}
    p, ul, ol, blockquote, figure, .ifx-table-wrap {{ margin: 0 0 20px; }}
    blockquote {{
      margin-left: 0;
      padding: 18px 20px;
      border-left: 4px solid rgba(26,122,78,0.5);
      background: rgba(26,122,78,0.05);
      border-radius: 16px;
    }}
    ul, ol {{ padding-left: 22px; }}
    figure {{
      border: 1px solid var(--ifx-border);
      border-radius: 22px;
      padding: 14px;
      background: rgba(255,255,255,0.74);
    }}
    img {{
      max-width: 100%;
      display: block;
      border-radius: 16px;
    }}
    figcaption {{
      margin-top: 10px;
      color: var(--ifx-muted);
      font-size: 0.94rem;
    }}
    .ifx-table-wrap {{
      overflow-x: auto;
      border: 1px solid var(--ifx-border);
      border-radius: 18px;
      background: rgba(255,255,255,0.76);
    }}
    table {{
      width: 100%;
      border-collapse: collapse;
      min-width: 460px;
    }}
    td {{
      border-bottom: 1px solid var(--ifx-border);
      padding: 12px 14px;
      vertical-align: top;
    }}
    tr:last-child td {{ border-bottom: none; }}
    .ifx-import-note {{
      margin-bottom: 24px;
      border: 1px solid rgba(26,122,78,0.18);
      border-radius: 16px;
      background: rgba(26,122,78,0.06);
      padding: 14px 16px;
      color: var(--ifx-muted);
      font-size: 0.95rem;
    }}
    @media (max-width: 720px) {{
      main {{
        width: calc(100vw - 24px);
        margin: 18px auto 40px;
        padding: 22px 18px;
        border-radius: 22px;
      }}
    }}
  </style>
</head>
<body>
  <main>
    {findings_note}
    {''.join(body_parts)}
  </main>
</body>
</html>"""


def infer_created_category(prompt: str, plan: dict[str, Any], upload_analysis: UploadAnalysis | None = None) -> str:
    if upload_analysis is not None:
        return upload_analysis.category
    lower = prompt.lower()
    format_name = (plan.get("meta", {}).get("document_format") or "").lower()
    if "earnings" in lower or "financial" in lower or "scenario" in lower or "data" in format_name:
        return "data_report"
    if "research" in lower or "paper" in lower:
        return "research_artifact"
    if "proposal" in lower or "client" in lower:
        return "client_artifact"
    if "story" in lower or "media" in lower or "gallery" in format_name:
        return "media_story"
    return "interactive_artifact"

TRANSPORT_SECURITY = TransportSecuritySettings(
    enable_dns_rebinding_protection=True,
    allowed_hosts=[
        "mcp.iterfact.com",
        "iterfact-mcp-production.up.railway.app",
        "127.0.0.1:*",
        "localhost:*",
        "[::1]:*",
    ],
    allowed_origins=[
        "https://mcp.iterfact.com",
        "https://iterfact.com",
        "https://a.iterfact.com",
        "https://iterfact-mcp-production.up.railway.app",
        "https://claude.ai",
        "https://claude.com",
        "http://127.0.0.1:*",
        "http://localhost:*",
        "http://[::1]:*",
    ],
)

MCP_CORS_ORIGINS = [
    "https://claude.ai",
    "https://claude.com",
    "https://mcp.iterfact.com",
    "https://iterfact.com",
    "https://a.iterfact.com",
    "https://iterfact-mcp-production.up.railway.app",
]


class BearerAuthMiddleware(BaseHTTPMiddleware):
    def __init__(self, app: Any, token: str, admin_token: str = "") -> None:
        super().__init__(app)
        self.token = token
        self.admin_token = admin_token

    async def dispatch(self, request: Request, call_next: Any) -> Response:
        request.state.iterfact_admin = False
        request.state.iterfact_identity_mode = "authless"

        if not (self.token or self.admin_token) or not request.url.path.startswith("/mcp"):
            return await call_next(request)

        auth_header = request.headers.get("authorization", "")
        expected = f"Bearer {self.token}" if self.token else None
        expected_admin = f"Bearer {self.admin_token}" if self.admin_token else None

        if expected_admin and auth_header == expected_admin:
            request.state.iterfact_admin = True
            request.state.iterfact_identity_mode = "admin_bearer"
            return await call_next(request)

        if expected and auth_header == expected:
            request.state.iterfact_identity_mode = "bearer"
            return await call_next(request)

        # Keep the public connector surface callable without auth headers.
        # Admin passthrough remains locked behind the admin bearer token.
        if auth_header and (expected or expected_admin):
            return JSONResponse({"error": "Unauthorized"}, status_code=401)
        return await call_next(request)


def request_is_admin(ctx: Context | None) -> bool:
    request = ctx.request_context.request if ctx and ctx.request_context else None
    state = getattr(request, "state", None)
    return bool(getattr(state, "iterfact_admin", False))


def request_identity_mode(ctx: Context | None) -> str:
    request = ctx.request_context.request if ctx and ctx.request_context else None
    state = getattr(request, "state", None)
    value = getattr(state, "iterfact_identity_mode", None)
    if isinstance(value, str) and value:
        return value
    if SETTINGS.bearer_token or SETTINGS.admin_bearer_token:
        return "bearer"
    return "authless"


def slugify(value: str) -> str:
    return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") or "document"


def strip_markdown_fences(text: str) -> str:
    return re.sub(r"^\s*```json?\s*|\s*```\s*$", "", text.strip(), flags=re.IGNORECASE | re.MULTILINE)


def extract_json_object(text: str) -> str | None:
    start = text.find("{")
    if start == -1:
        return None

    depth = 0
    in_string = False
    escaped = False

    for index, char in enumerate(text[start:], start):
        if escaped:
            escaped = False
            continue

        if in_string and char == "\\":
            escaped = True
            continue

        if char == '"':
            in_string = not in_string
            continue

        if in_string:
            continue

        if char == "{":
            depth += 1
        elif char == "}":
            depth -= 1
            if depth == 0:
                return text[start : index + 1]

    return None


def strip_trailing_commas(text: str) -> str:
    return re.sub(r",(\s*[}\]])", r"\1", text)


def normalize_engine_id(value: str | None) -> str | None:
    if not value:
        return value
    match = re.match(r"^(\d{2})(?:-.+)?$", value.strip())
    if match:
        return match.group(1)
    return value


def normalize_plan(plan: dict[str, Any]) -> dict[str, Any]:
    wrapper = plan.get("wrapper", {})
    if isinstance(wrapper, dict):
        wrapper["engine"] = normalize_engine_id(wrapper.get("engine"))

    for section in plan.get("sections", []) or []:
        for component in section.get("components", []) or []:
            component["engine"] = normalize_engine_id(component.get("engine"))

    meta = plan.get("meta")
    if not isinstance(meta, dict):
        meta = {}
        plan["meta"] = meta
    meta["narrative_spine"] = normalize_narrative_spine(plan)

    return plan


def build_sections_from_story_spine(
    raw_spine: dict[str, Any] | None,
    section_count_requested: int | None,
) -> list[dict[str, Any]]:
    if not isinstance(raw_spine, dict):
        raw_spine = {}
    raw_section_jobs = raw_spine.get("section_jobs")
    sections: list[dict[str, Any]] = []
    if isinstance(raw_section_jobs, list):
        for index, item in enumerate(raw_section_jobs, start=1):
            if isinstance(item, dict):
                title = compact_whitespace(
                    str(item.get("title") or item.get("heading") or f"Section {index}")
                ) or f"Section {index}"
                job = compact_whitespace(str(item.get("job") or item.get("role") or ""))
            else:
                title = f"Section {index}"
                job = compact_whitespace(str(item or ""))
            sections.append(
                {
                    "title": title,
                    "content": {
                        "headline": title,
                        "body": job,
                    },
                    "components": [],
                }
            )
    if sections:
        return sections

    requested = max(1, min(int(section_count_requested or 5), 12))
    return [
        {
            "title": f"Section {index}",
            "content": {
                "headline": f"Section {index}",
                "body": "",
            },
            "components": [],
        }
        for index in range(1, requested + 1)
    ]


def prepare_client_supplied_artifact_plan(
    *,
    artifact_plan: dict[str, Any],
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    require_sections: bool,
) -> dict[str, Any]:
    if not isinstance(artifact_plan, dict):
        raise ValueError("artifact_plan must be a JSON object when supplied.")

    plan = normalize_plan(copy.deepcopy(artifact_plan))
    meta = plan.get("meta")
    if not isinstance(meta, dict):
        meta = {}
        plan["meta"] = meta

    meta["planning_source"] = "client_supplied_plan"
    meta["template_match"] = meta.get("template_match") or template_hint

    if theme_hint:
        plan["theme"] = theme_hint
    plan["theme"] = compact_whitespace(str(plan.get("theme") or theme_hint or "obsidian-cyan")) or "obsidian-cyan"

    if is_wrapper_engine(engine_override):
        plan.setdefault("wrapper", {})["engine"] = engine_override
    elif not isinstance(plan.get("wrapper"), dict):
        plan["wrapper"] = {}

    wrapper_engine = normalize_engine_id(plan.get("wrapper", {}).get("engine"))
    if not wrapper_engine:
        wrapper_engine = infer_chunked_draft_wrapper_engine(prompt, template_hint, engine_override)
        plan.setdefault("wrapper", {})["engine"] = wrapper_engine

    sections = plan.get("sections")
    if not isinstance(sections, list):
        sections = []
        plan["sections"] = sections
    normalized_sections: list[dict[str, Any]] = []
    for section in sections:
        if isinstance(section, dict):
            normalized_sections.append(normalize_section(section))
    plan["sections"] = normalized_sections

    if not normalized_sections and not require_sections:
        plan["sections"] = build_sections_from_story_spine(meta.get("narrative_spine"), section_count_requested)

    plan = apply_primary_engine_constraints(plan, engine_override or wrapper_engine, structured_data)

    meta = plan.get("meta") if isinstance(plan.get("meta"), dict) else {}
    title = compact_whitespace(str(meta.get("title") or ""))
    if not title:
        meta["title"] = infer_source_document_title(prompt, None) or prompt.strip()[:120] or "IterFact Document"
    description = compact_whitespace(str(meta.get("description") or ""))
    if not description:
        meta["description"] = "Client-supplied story plan prepared in Claude and rendered by IterFact."
    if not compact_whitespace(str(meta.get("document_format") or "")):
        meta["document_format"] = friendly_wrapper_name(plan.get("wrapper", {}).get("engine"))

    if require_sections and not (plan.get("sections") or []):
        raise ValueError("artifact_plan must include at least one section when used for generate_document.")

    validate_plan(plan)
    return plan


def build_source_chunks_from_plan_sections(plan: dict[str, Any]) -> list[SourceChunk]:
    sections = plan.get("sections") if isinstance(plan.get("sections"), list) else []
    source_chunks: list[SourceChunk] = []
    for index, section in enumerate(sections, start=1):
        if not isinstance(section, dict):
            continue
        content = section.get("content") if isinstance(section.get("content"), dict) else {}
        title = (
            compact_whitespace(str(section.get("title") or ""))
            or compact_whitespace(str(content.get("headline") or ""))
            or f"Section {index}"
        )
        body = compact_whitespace(
            str(
                content.get("body")
                or content.get("headline")
                or section.get("summary")
                or section.get("job")
                or ""
            )
        )
        source_chunks.append(
            SourceChunk(
                index=index,
                title=title,
                body=body,
                heading_level=None,
                source_heading=title,
            )
        )
    return source_chunks


def build_client_supplied_plan_payload(
    *,
    prompt: str,
    template_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    generation_mode: str,
    wrapper_engine: str | None,
) -> dict[str, Any]:
    return {
        "model": None,
        "usage": {},
        "repair_applied": False,
        "retry_strategy": "client-supplied-plan",
        "_provider_events": [],
        "prompt_stack_bundle_version": PROMPT_STACK_BRIDGE_VERSION,
        "active_prompt_modules": resolve_active_prompt_modules(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode=generation_mode,
        ),
        "plan_quality_audit": {
            "score_penalty": 0,
            "critical": False,
            "should_retry": False,
            "feedback_lines": [],
            "warnings": [],
            "planner_source": "client_supplied_plan",
            "wrapper_engine": wrapper_engine,
        },
        "client_supplied_plan": True,
    }


def derive_section_job_label(section: dict[str, Any], index: int) -> str:
    title = compact_whitespace(str(section.get("title") or ""))
    content = section.get("content") if isinstance(section.get("content"), dict) else {}
    headline = compact_whitespace(str(content.get("headline") or "")) if isinstance(content, dict) else ""
    component_engines = [
        str(component.get("engine") or "").strip()
        for component in (section.get("components") or [])
        if isinstance(component, dict) and str(component.get("engine") or "").strip()
    ]
    if component_engines:
        engine = component_engines[0]
        if engine == "03":
            return title or "Establish the numbers that frame the rest of the artifact"
        if engine == "08":
            return title or "Let the reader manipulate the model and see the tradeoffs"
        if engine == "17":
            return title or "Pressure-test scenarios before the recommendation"
        if engine == "07":
            return title or "Compare the options and make the decision boundaries obvious"
        if engine == "11":
            return title or "Clarify plans, packages, or commercial choices"
        if engine == "39":
            return title or "Ground the argument in visible proof from real customers"
        if engine == "44":
            return title or "Rank the field, reveal the range, and let the reader challenge the order"
    if index == 1:
        return title or headline or "Open with the core claim and orient the reader"
    return title or headline or f"Advance the argument in section {index}"


def normalize_narrative_key_entities(values: Any) -> list[str]:
    if not isinstance(values, list):
        return []
    normalized: list[str] = []
    seen: set[str] = set()
    for value in values:
        text = compact_whitespace(str(value or ""))
        key = text.lower()
        if not text or key in seen:
            continue
        seen.add(key)
        normalized.append(text)
    return normalized[:8]


def normalize_narrative_claim_map(values: Any) -> list[dict[str, Any]]:
    if not isinstance(values, list):
        return []
    normalized: list[dict[str, Any]] = []
    for item in values:
        if isinstance(item, dict):
            claim = compact_whitespace(str(item.get("claim") or item.get("title") or item.get("statement") or ""))
            evidence = compact_whitespace(str(item.get("evidence") or item.get("support") or item.get("why") or ""))
            raw_support_sections = item.get("support_sections")
        else:
            claim = compact_whitespace(str(item or ""))
            evidence = ""
            raw_support_sections = None
        if not claim:
            continue
        support_sections: list[int] = []
        if isinstance(raw_support_sections, list):
            for raw_index in raw_support_sections:
                try:
                    section_index = int(raw_index)
                except (TypeError, ValueError):
                    continue
                if section_index > 0 and section_index not in support_sections:
                    support_sections.append(section_index)
        normalized.append(
            {
                "claim": claim,
                "support_sections": support_sections,
                "evidence": evidence or None,
            }
        )
    return normalized[:8]


def derive_narrative_claim_map(
    *,
    core_thesis: str,
    throughlines: list[str],
    section_jobs: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    claim_map: list[dict[str, Any]] = []
    if core_thesis:
        support_sections = []
        if section_jobs:
            support_sections = [1]
            if len(section_jobs) > 1:
                support_sections.append(len(section_jobs))
        claim_map.append(
            {
                "claim": core_thesis,
                "support_sections": support_sections,
                "evidence": "The opening and close should reinforce the same thesis.",
            }
        )
    for index, throughline in enumerate(throughlines[:3], start=1):
        support_sections: list[int] = []
        if index <= len(section_jobs):
            support_sections.append(index)
        if len(section_jobs) > index and len(support_sections) < 2:
            support_sections.append(index + 1)
        claim_map.append(
            {
                "claim": throughline,
                "support_sections": support_sections,
                "evidence": "Keep this throughline stable across the supporting sections.",
            }
        )
    return normalize_narrative_claim_map(claim_map)


def derive_narrative_spine(plan: dict[str, Any]) -> dict[str, Any]:
    meta = plan.get("meta") if isinstance(plan.get("meta"), dict) else {}
    sections = [section for section in (plan.get("sections") or []) if isinstance(section, dict)]
    title = compact_whitespace(str(meta.get("title") or "IterFact Document"))
    description = compact_whitespace(str(meta.get("description") or ""))
    throughlines: list[str] = []
    must_keep_facts: list[str] = []
    key_entities: list[str] = []
    section_jobs: list[dict[str, Any]] = []

    for index, section in enumerate(sections, start=1):
        section_title = compact_whitespace(str(section.get("title") or ""))
        section_jobs.append(
            {
                "section_index": index,
                "title": section_title or f"Section {index}",
                "job": derive_section_job_label(section, index),
            }
        )
        if section_title and len(throughlines) < 4:
            throughlines.append(section_title)
        if section_title and len(key_entities) < 6 and section_title.lower() not in {item.lower() for item in key_entities}:
            key_entities.append(section_title)

        content = section.get("content") if isinstance(section.get("content"), dict) else {}
        for key in ("headline", "body", "caption"):
            text = compact_whitespace(str(content.get(key) or "")) if isinstance(content, dict) else ""
            if text and len(must_keep_facts) < 5:
                must_keep_facts.append(text[:140])
            if text and len(key_entities) < 6:
                candidate = text[:80]
                if candidate.lower() not in {item.lower() for item in key_entities}:
                    key_entities.append(candidate)

    core_thesis = description or title
    key_entities = normalize_narrative_key_entities(key_entities)
    claim_map = derive_narrative_claim_map(
        core_thesis=core_thesis,
        throughlines=throughlines[:4],
        section_jobs=section_jobs,
    )

    return {
        "source": "derived",
        "core_thesis": core_thesis,
        "audience": "Decision-makers reviewing this artifact",
        "desired_action": "Understand the argument, trust the evidence, and take the next clear step.",
        "throughlines": throughlines[:4],
        "must_keep_facts": must_keep_facts[:5],
        "must_not_claim": [],
        "key_entities": key_entities,
        "claim_map": claim_map,
        "closing_obligation": "End by resolving what the reader should conclude or do next based on the evidence above.",
        "section_jobs": section_jobs,
    }


def normalize_narrative_spine(plan: dict[str, Any]) -> dict[str, Any]:
    meta = plan.get("meta") if isinstance(plan.get("meta"), dict) else {}
    raw_spine = meta.get("narrative_spine")
    if not isinstance(raw_spine, dict):
        raw_spine = plan.get("narrative_spine")
    derived = derive_narrative_spine(plan)
    if not isinstance(raw_spine, dict):
        return derived

    normalized = dict(derived)
    normalized["source"] = compact_whitespace(str(raw_spine.get("source") or "planned")) or "planned"
    for key in ("core_thesis", "audience", "desired_action", "closing_obligation"):
        value = compact_whitespace(str(raw_spine.get(key) or ""))
        if value:
            normalized[key] = value
    for key in ("throughlines", "must_keep_facts", "must_not_claim"):
        value = raw_spine.get(key)
        if isinstance(value, list):
            normalized[key] = [compact_whitespace(str(item)) for item in value if compact_whitespace(str(item))]
    key_entities = normalize_narrative_key_entities(raw_spine.get("key_entities"))
    if key_entities:
        normalized["key_entities"] = key_entities
    claim_map = normalize_narrative_claim_map(raw_spine.get("claim_map"))
    if claim_map:
        normalized["claim_map"] = claim_map
    raw_section_jobs = raw_spine.get("section_jobs")
    if isinstance(raw_section_jobs, list):
        normalized_jobs: list[dict[str, Any]] = []
        for index, item in enumerate(raw_section_jobs, start=1):
            if isinstance(item, dict):
                title = compact_whitespace(str(item.get("title") or f"Section {index}"))
                job = compact_whitespace(str(item.get("job") or item.get("role") or title))
                section_index = item.get("section_index")
                try:
                    section_index_value = int(section_index)
                except (TypeError, ValueError):
                    section_index_value = index
            else:
                title = f"Section {index}"
                job = compact_whitespace(str(item))
                section_index_value = index
            if not job:
                continue
            normalized_jobs.append(
                {
                    "section_index": section_index_value,
                    "title": title,
                    "job": job,
                }
            )
        if normalized_jobs:
            normalized["section_jobs"] = normalized_jobs
    return normalized


def extract_text_blocks(payload: dict[str, Any]) -> str:
    blocks = payload.get("content", [])
    parts: list[str] = []
    for block in blocks:
        if isinstance(block, dict) and block.get("type") == "text":
            parts.append(block.get("text", ""))
    return "\n".join(parts).strip()


def looks_like_full_html_document(text: str | None) -> bool:
    if not text:
        return False
    lowered = text.lstrip().lower()
    return (
        lowered.startswith("<!doctype html")
        or lowered.startswith("<html")
        or ("<html" in lowered and "</html>" in lowered)
        or ("<body" in lowered and "</body>" in lowered)
    )


def wants_uploaded_html_ingest(prompt: str, template_hint: str | None, uploaded_content: str | None) -> bool:
    hint = (template_hint or "").strip().lower()
    if hint in {"raw_html", "passthrough", "custom_html", "host_html", "import_html"}:
        return True
    return looks_like_full_html_document(uploaded_content)


def normalize_source_text(text: str) -> str:
    normalized = text.replace("\r\n", "\n").replace("\r", "\n")
    normalized = re.sub(r"[ \t]+\n", "\n", normalized)
    normalized = re.sub(r"\n{3,}", "\n\n", normalized)
    return normalized.strip()


def clip_excerpt(text: str, max_chars: int) -> str:
    compact = normalize_source_text(text)
    if len(compact) <= max_chars:
        return compact
    clipped = compact[:max_chars]
    if " " in clipped:
        clipped = clipped.rsplit(" ", 1)[0]
    clipped = clipped.rstrip(" ,;:")
    return f"{clipped} ..."


def clip_tail_excerpt(text: str, max_chars: int) -> str:
    compact = normalize_source_text(text)
    if len(compact) <= max_chars:
        return compact
    clipped = compact[-max_chars:]
    first_space = clipped.find(" ")
    if 0 < first_space < max_chars // 3:
        clipped = clipped[first_space + 1 :]
    return f"... {clipped.strip()}"


def clip_middle_excerpt(text: str, max_chars: int) -> str:
    compact = normalize_source_text(text)
    if len(compact) <= max_chars:
        return compact
    start = max(0, (len(compact) - max_chars) // 2)
    clipped = compact[start : start + max_chars]
    first_space = clipped.find(" ")
    if 0 < first_space < max_chars // 3:
        clipped = clipped[first_space + 1 :]
    if " " in clipped:
        clipped = clipped.rsplit(" ", 1)[0]
    return f"... {clipped.strip()} ..."


def extract_source_headings(text: str) -> list[dict[str, Any]]:
    lines = normalize_source_text(text).splitlines()
    headings: list[dict[str, Any]] = []
    index = 0
    while index < len(lines):
        line = lines[index].strip()
        if not line:
            index += 1
            continue

        markdown_match = re.match(r"^(#{1,6})\s+(.+?)\s*#*\s*$", line)
        if markdown_match:
            title = re.sub(r"\s+", " ", markdown_match.group(2)).strip(" -#\t")
            if title:
                headings.append(
                    {
                        "line_index": index,
                        "level": len(markdown_match.group(1)),
                        "title": title,
                    }
                )
            index += 1
            continue

        if index + 1 < len(lines):
            underline = lines[index + 1].strip()
            if re.fullmatch(r"=+", underline):
                headings.append({"line_index": index, "level": 1, "title": line})
                index += 2
                continue
            if re.fullmatch(r"-{3,}", underline):
                headings.append({"line_index": index, "level": 2, "title": line})
                index += 2
                continue

        index += 1

    return headings


def select_source_headings(headings: list[dict[str, Any]], limit: int) -> list[dict[str, Any]]:
    if len(headings) <= limit:
        return headings

    front_count = min(len(headings), max(1, limit - 3))
    back_count = max(0, limit - front_count)
    selected: list[dict[str, Any]] = []
    seen: set[int] = set()

    for candidate in [*headings[:front_count], *headings[-back_count:]]:
        line_index = int(candidate.get("line_index", -1))
        if line_index in seen:
            continue
        seen.add(line_index)
        selected.append(candidate)

    return sorted(selected, key=lambda heading: int(heading.get("line_index", 0)))


def build_compacted_uploaded_content_summary(uploaded_content: str) -> PlannerUploadContext:
    normalized = normalize_source_text(uploaded_content)
    headings = extract_source_headings(normalized)
    selected_headings = select_source_headings(headings, PLANNER_UPLOADED_CONTENT_HEADING_LIMIT)
    lines = normalized.splitlines()

    summary_lines = [
        f"Original uploaded content length: {len(uploaded_content)} characters.",
        "Planner compaction applied so IterFact can still return a valid JSON assembly plan.",
        "Preserve the source structure and semantic meaning from this summary. Do not replace it with a generic outline.",
    ]

    if selected_headings:
        summary_lines.append(f"Detected markdown headings: {len(headings)}.")
        summary_lines.append("")
        summary_lines.append("Document outline:")
        for position, heading in enumerate(selected_headings, start=1):
            indent = "  " * max(0, int(heading.get("level", 1)) - 1)
            summary_lines.append(f"{position}. {indent}{heading.get('title')}")

        summary_lines.append("")
        summary_lines.append("Representative section excerpts:")
        for position, heading in enumerate(selected_headings, start=1):
            start = int(heading.get("line_index", 0))
            next_heading = next(
                (
                    int(candidate.get("line_index", len(lines)))
                    for candidate in headings
                    if int(candidate.get("line_index", len(lines))) > start
                ),
                len(lines),
            )
            section_text = "\n".join(lines[start:next_heading]).strip()
            excerpt = clip_excerpt(section_text, PLANNER_UPLOADED_CONTENT_SECTION_EXCERPT_MAX_CHARS)
            summary_lines.append("")
            summary_lines.append(f"[Section {position}: {heading.get('title')}]")
            summary_lines.append(excerpt)
    else:
        summary_lines.append("No markdown heading outline detected.")
        summary_lines.append("")
        summary_lines.append("Middle excerpt:")
        summary_lines.append(clip_middle_excerpt(normalized, PLANNER_UPLOADED_CONTENT_MIDDLE_EXCERPT_MAX_CHARS))

    summary_lines.append("")
    summary_lines.append("Opening excerpt:")
    summary_lines.append(clip_excerpt(normalized, PLANNER_UPLOADED_CONTENT_OPENING_EXCERPT_MAX_CHARS))
    summary_lines.append("")
    summary_lines.append("Closing excerpt:")
    summary_lines.append(clip_tail_excerpt(normalized, PLANNER_UPLOADED_CONTENT_CLOSING_EXCERPT_MAX_CHARS))

    message_block = "<uploaded_content_summary>\n" + "\n".join(summary_lines) + "\n</uploaded_content_summary>"
    return PlannerUploadContext(
        message_block=message_block,
        strategy="outline_and_excerpts" if selected_headings else "opening_middle_closing_excerpts",
        compacted=True,
        original_length=len(uploaded_content),
        detected_heading_count=len(headings),
        preserved_heading_count=len(selected_headings),
    )


def build_planner_upload_context(uploaded_content: str | None) -> PlannerUploadContext | None:
    if not uploaded_content:
        return None

    normalized = normalize_source_text(uploaded_content)
    if not normalized:
        return None

    headings = extract_source_headings(normalized)
    if len(normalized) <= PLANNER_UPLOADED_CONTENT_INLINE_MAX_CHARS:
        return PlannerUploadContext(
            message_block=f"<uploaded_content>\n{normalized}\n</uploaded_content>",
            strategy="full_text",
            compacted=False,
            original_length=len(uploaded_content),
            detected_heading_count=len(headings),
            preserved_heading_count=len(headings),
        )

    return build_compacted_uploaded_content_summary(uploaded_content)


def build_planner_user_message(
    *,
    prompt: str,
    template_hint: str | None = None,
    uploaded_content: str | None = None,
    upload_analysis: UploadAnalysis | None = None,
    structured_data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    parse_error: str | None = None,
    quality_feedback: str | None = None,
    generation_mode: str | None = None,
) -> tuple[str, PlannerUploadContext | None]:
    user_message = prompt.strip()
    upload_context = build_planner_upload_context(uploaded_content)
    if upload_context:
        user_message += f"\n\n{upload_context.message_block}"
        if upload_context.compacted:
            user_message += (
                "\n\nPreserve the structure, headings, and semantic meaning from uploaded_content_summary. "
                "Use its detected outline as the source of truth where possible. "
                "Do not replace it with invented filler, quizzes, launch checklists, or unrelated examples."
            )
            if not section_count and upload_context.preserved_heading_count >= 3:
                user_message += (
                    f"\n\nDetected {upload_context.preserved_heading_count} source headings in the compacted summary. "
                    "Preserve that section order unless two adjacent headings clearly belong together."
                )
        else:
            user_message += (
                "\n\nPreserve the actual text, headings, and semantic meaning from uploaded_content. "
                "Do not replace it with invented filler, quizzes, launch checklists, or unrelated examples."
            )
    if template_hint:
        user_message += (
            f'\n\nThe user selected the "{template_hint}" template. '
            "Use the matching recipe as a starting point but adapt to the specific request."
        )
    if structured_data:
        user_message += f"\n\n<structured_data>\n{json.dumps(structured_data, ensure_ascii=True, indent=2)}\n</structured_data>"
    if parse_error:
        user_message += (
            "\n\nThe previous response was invalid or truncated. "
            f"Problem: {parse_error}\n"
            "Rebuild the plan from scratch as a more compact valid JSON object. "
            "Do not include markdown fences or any commentary."
        )
    if quality_feedback:
        user_message += (
            "\n\nThe previous plan was valid JSON but weak or misaligned. "
            "Rebuild the plan from scratch using this critique as a hard correction layer:\n"
            f"{quality_feedback}"
        )
    user_message += "\n\n" + build_planner_guidance_with_profiles(
        prompt,
        template_hint,
        upload_analysis,
        structured_data,
        engine_override,
        section_count,
        generation_mode,
        bool(uploaded_content),
    )
    user_message += (
        "\n\nReturn only one valid JSON object for the assembly plan. "
        "Do not wrap it in markdown fences and do not include commentary before or after the JSON."
    )
    return user_message, upload_context


def annotate_payload_with_upload_context(payload: dict[str, Any], upload_context: PlannerUploadContext | None) -> None:
    payload["uploaded_content_compacted"] = bool(upload_context and upload_context.compacted)
    payload["uploaded_content_strategy"] = upload_context.strategy if upload_context else None
    payload["uploaded_heading_count_detected"] = upload_context.detected_heading_count if upload_context else 0
    payload["uploaded_heading_count_preserved"] = upload_context.preserved_heading_count if upload_context else 0


def chunk_title_to_slug(title: str) -> str:
    return slugify(title) if title else "section"


def split_long_chunk_body(body: str, max_chars: int = 4_600) -> list[str]:
    normalized = normalize_source_text(body)
    if len(normalized) <= max_chars:
        return [normalized]

    paragraphs = [part.strip() for part in re.split(r"\n\s*\n", normalized) if part.strip()]
    if not paragraphs:
        return [clip_excerpt(normalized, max_chars)]

    chunks: list[str] = []
    current: list[str] = []
    current_length = 0
    for paragraph in paragraphs:
        paragraph_length = len(paragraph)
        proposed_length = current_length + paragraph_length + (2 if current else 0)
        if current and proposed_length > max_chars:
            chunks.append("\n\n".join(current))
            current = [paragraph]
            current_length = paragraph_length
        else:
            current.append(paragraph)
            current_length = proposed_length

    if current:
        chunks.append("\n\n".join(current))

    normalized_chunks: list[str] = []
    for chunk in chunks:
        if len(chunk) <= max_chars:
            normalized_chunks.append(chunk)
            continue
        start = 0
        while start < len(chunk):
            normalized_chunks.append(chunk[start : start + max_chars].strip())
            start += max_chars

    return [chunk for chunk in normalized_chunks if chunk]


def choose_chunk_heading_level(headings: list[dict[str, Any]]) -> int | None:
    if not headings:
        return None

    level_counts: dict[int, int] = {}
    for heading in headings:
        level = int(heading.get("level", 0) or 0)
        if level:
            level_counts[level] = level_counts.get(level, 0) + 1

    for preferred_level in (2, 1, 3, 4):
        if level_counts.get(preferred_level, 0) >= 2:
            return preferred_level

    ordered = sorted((level for level in level_counts if level_counts[level] >= 1))
    return ordered[0] if ordered else None


def merge_source_chunks(chunks: list[SourceChunk], max_chunks: int = 12) -> list[SourceChunk]:
    if len(chunks) <= max_chunks:
        return chunks

    group_size = max(2, int((len(chunks) + max_chunks - 1) / max_chunks))
    merged: list[SourceChunk] = []
    for index in range(0, len(chunks), group_size):
        group = chunks[index : index + group_size]
        if not group:
            continue
        first = group[0]
        last = group[-1]
        title = first.title if len(group) == 1 else f"{first.title} + {last.title}"
        body = "\n\n".join(chunk.body for chunk in group if chunk.body.strip())
        merged.append(
            SourceChunk(
                index=len(merged) + 1,
                title=title,
                body=body,
                heading_level=first.heading_level,
                source_heading=first.source_heading,
            )
        )

    return merged


def build_source_chunks_from_text(source_text: str, *, max_chunks: int = 12) -> list[SourceChunk]:
    normalized = normalize_source_text(source_text)
    if not normalized:
        return []

    headings = extract_source_headings(normalized)
    lines = normalized.splitlines()
    chosen_level = choose_chunk_heading_level(headings)
    raw_chunks: list[SourceChunk] = []

    if chosen_level is not None:
        section_heads = [heading for heading in headings if int(heading.get("level", 0) or 0) == chosen_level]
        for section_head in section_heads:
            start = int(section_head.get("line_index", 0))
            next_heading = next(
                (
                    int(candidate.get("line_index", len(lines)))
                    for candidate in section_heads
                    if int(candidate.get("line_index", len(lines))) > start
                ),
                len(lines),
            )
            section_text = "\n".join(lines[start:next_heading]).strip()
            if not section_text:
                continue
            title = compact_whitespace(str(section_head.get("title") or "")) or f"Section {len(raw_chunks) + 1}"
            parts = split_long_chunk_body(section_text)
            for part_index, part in enumerate(parts, start=1):
                part_title = title if len(parts) == 1 else f"{title} (Part {part_index})"
                raw_chunks.append(
                    SourceChunk(
                        index=len(raw_chunks) + 1,
                        title=part_title,
                        body=part,
                        heading_level=chosen_level,
                        source_heading=title,
                    )
                )

    if not raw_chunks:
        title = compact_whitespace(normalized.splitlines()[0]) or "Source Material"
        parts = split_long_chunk_body(normalized)
        for part_index, part in enumerate(parts, start=1):
            part_title = title if len(parts) == 1 else f"{title} (Part {part_index})"
            raw_chunks.append(
                SourceChunk(
                    index=len(raw_chunks) + 1,
                    title=part_title,
                    body=part,
                    heading_level=None,
                    source_heading=title,
                )
            )

    merged_chunks = merge_source_chunks(raw_chunks, max_chunks=max_chunks)
    return [
        SourceChunk(
            index=index,
            title=chunk.title,
            body=chunk.body,
            heading_level=chunk.heading_level,
            source_heading=chunk.source_heading,
        )
        for index, chunk in enumerate(merged_chunks, start=1)
    ]


def build_placeholder_source_chunks(count: int) -> list[SourceChunk]:
    normalized_count = max(1, min(count, 12))
    return [
        SourceChunk(
            index=index,
            title=f"Section {index}",
            body=f"Placeholder source chunk for section {index}. Replace this by calling append_section with real source content.",
            heading_level=None,
            source_heading=None,
        )
        for index in range(1, normalized_count + 1)
    ]


def infer_source_document_title(prompt: str, source_text: str) -> str:
    headings = extract_source_headings(source_text)
    if headings:
        first_title = compact_whitespace(str(headings[0].get("title") or ""))
        if first_title:
            return first_title
    first_line = compact_whitespace(source_text.splitlines()[0] if source_text.splitlines() else "")
    if first_line:
        return first_line[:120]
    return prompt.strip()[:120] or "IterFact Draft"


def should_use_chunked_uploaded_document_builder(prompt: str, template_hint: str | None, uploaded_content: str | None) -> bool:
    if not uploaded_content or looks_like_full_html_document(uploaded_content):
        return False

    normalized = normalize_source_text(uploaded_content)
    if not normalized:
        return False

    headings = extract_source_headings(normalized)
    lower_prompt = prompt.lower()
    research_like = template_hint in {"research_report", "financial_briefing"} or any(
        token in lower_prompt
        for token in (
            "research",
            "report",
            "analysis",
            "market",
            "competitive",
            "strategy",
            "partnership",
            "notion",
            "enterprise",
            "findings",
            "deep dive",
        )
    )
    if len(normalized) >= 22_000:
        return True
    if research_like and len(normalized) >= 14_000:
        return True
    if research_like and len(headings) >= 5:
        return True
    return False


def should_background_freeform_generation(
    prompt: str,
    template_hint: str | None,
    structured_data: dict[str, Any] | None,
    section_count_requested: int | None,
    uploaded_content: str | None,
) -> bool:
    if uploaded_content:
        return False

    prompt_text = compact_whitespace(prompt)
    lower = prompt_text.lower()
    prompt_length = len(prompt_text)
    structured_size = len(json.dumps(structured_data, ensure_ascii=True)) if isinstance(structured_data, dict) else 0
    research_like = template_hint in {"research_report", "financial_briefing"} or any(
        token in lower
        for token in (
            "deep dive",
            "deep-dive",
            "research",
            "report",
            "analysis",
            "comprehensive",
            "detailed",
            "white paper",
            "whitepaper",
            "market study",
            "competitive review",
            "briefing",
        )
    )

    if section_count_requested is not None and section_count_requested >= 8:
        return True
    if structured_size >= 12_000:
        return True
    if prompt_length >= 2_000:
        return True
    if research_like and prompt_length >= 1_000:
        return True
    return False


def extract_html_title(html: str | None) -> str | None:
    if not html:
        return None
    match = re.search(r"<title[^>]*>(.*?)</title>", html, flags=re.IGNORECASE | re.DOTALL)
    if not match:
        return None
    title = re.sub(r"\s+", " ", match.group(1)).strip()
    return title or None


def parse_plan_text(raw_text: str) -> dict[str, Any]:
    cleaned = strip_markdown_fences(raw_text)
    candidates: list[str] = []

    for candidate in [cleaned, extract_json_object(cleaned)]:
        if not candidate:
            continue
        candidate = candidate.strip()
        if candidate and candidate not in candidates:
            candidates.append(candidate)

        without_trailing_commas = strip_trailing_commas(candidate).strip()
        if without_trailing_commas and without_trailing_commas not in candidates:
            candidates.append(without_trailing_commas)

    errors: list[str] = []
    for candidate in candidates:
        try:
            return normalize_plan(json.loads(candidate))
        except json.JSONDecodeError as exc:
            errors.append(f"{exc.msg} at line {exc.lineno} column {exc.colno}")

    error_summary = "; ".join(errors) if errors else "No JSON object found in planner output"
    raise ValueError(error_summary)


def normalize_section(section: dict[str, Any]) -> dict[str, Any]:
    if not isinstance(section, dict):
        raise ValueError("Section must be a JSON object.")
    for component in section.get("components", []) or []:
        if isinstance(component, dict):
            component["engine"] = normalize_engine_id(component.get("engine"))
    return section


def parse_section_text(raw_text: str) -> dict[str, Any]:
    cleaned = strip_markdown_fences(raw_text)
    candidates: list[str] = []

    for candidate in [cleaned, extract_json_object(cleaned)]:
        if not candidate:
            continue
        candidate = candidate.strip()
        if candidate and candidate not in candidates:
            candidates.append(candidate)

        without_trailing_commas = strip_trailing_commas(candidate).strip()
        if without_trailing_commas and without_trailing_commas not in candidates:
            candidates.append(without_trailing_commas)

    errors: list[str] = []
    for candidate in candidates:
        try:
            payload = json.loads(candidate)
        except json.JSONDecodeError as exc:
            errors.append(f"{exc.msg} at line {exc.lineno} column {exc.colno}")
            continue

        if isinstance(payload, dict) and isinstance(payload.get("section"), dict):
            return normalize_section(payload["section"])
        if isinstance(payload, dict):
            return normalize_section(payload)
        errors.append("Section output was not a JSON object")

    error_summary = "; ".join(errors) if errors else "No JSON object found in section output"
    raise ValueError(error_summary)


def validate_plan(plan: dict[str, Any]) -> None:
    errors: list[str] = []

    if not plan.get("meta", {}).get("title"):
        errors.append("Missing meta.title")
    if not plan.get("theme"):
        errors.append("Missing theme")
    if plan.get("wrapper", {}).get("engine") not in VALID_WRAPPERS:
        errors.append("Missing or invalid wrapper.engine")
    sections = plan.get("sections")
    if not isinstance(sections, list) or not sections:
        errors.append("Missing or empty sections")

    if errors:
        raise ValueError("; ".join(errors))


def validate_section(section: dict[str, Any]) -> None:
    if not isinstance(section, dict):
        raise ValueError("Section must be a JSON object.")

    content = section.get("content") or {}
    components = section.get("components") or []
    title = str(section.get("title") or "").strip()
    headline = str(content.get("headline") or "").strip() if isinstance(content, dict) else ""
    body = str(content.get("body") or "").strip() if isinstance(content, dict) else ""

    if not title and not headline and not body and not components:
        raise ValueError("Section must contain visible content or components.")
    if components and not isinstance(components, list):
        raise ValueError("Section components must be a list when present.")


def parse_artifact_critic_text(raw_text: str) -> dict[str, Any]:
    cleaned = strip_markdown_fences(raw_text)
    candidates: list[str] = []

    for candidate in [cleaned, extract_json_object(cleaned)]:
        if not candidate:
            continue
        candidate = candidate.strip()
        if candidate and candidate not in candidates:
            candidates.append(candidate)

        without_trailing_commas = strip_trailing_commas(candidate).strip()
        if without_trailing_commas and without_trailing_commas not in candidates:
            candidates.append(without_trailing_commas)

    errors: list[str] = []
    for candidate in candidates:
        try:
            payload = json.loads(candidate)
        except json.JSONDecodeError as exc:
            errors.append(f"{exc.msg} at line {exc.lineno} column {exc.colno}")
            continue

        if not isinstance(payload, dict):
            errors.append("Critic output was not a JSON object.")
            continue

        repairs: list[dict[str, Any]] = []
        for repair in payload.get("section_repairs") or []:
            if not isinstance(repair, dict):
                continue
            try:
                section_index = int(repair.get("section_index"))
            except (TypeError, ValueError):
                continue
            updated_prompt = compact_whitespace(str(repair.get("updated_prompt") or ""))
            reason = compact_whitespace(str(repair.get("reason") or ""))
            severity = compact_whitespace(str(repair.get("severity") or "medium")).lower() or "medium"
            if section_index < 1 or not updated_prompt:
                continue
            repairs.append(
                {
                    "section_index": section_index,
                    "updated_prompt": updated_prompt,
                    "reason": reason,
                    "severity": severity,
                }
            )

        normalized = {
            "ship": bool(payload.get("ship")),
            "overall_diagnosis": compact_whitespace(str(payload.get("overall_diagnosis") or "")),
            "keep": [compact_whitespace(str(item)) for item in (payload.get("keep") or []) if compact_whitespace(str(item))],
            "fix": [compact_whitespace(str(item)) for item in (payload.get("fix") or []) if compact_whitespace(str(item))],
            "unsupported_claims": [
                compact_whitespace(str(item))
                for item in (payload.get("unsupported_claims") or [])
                if compact_whitespace(str(item))
            ],
            "artifact_level_advice": [
                compact_whitespace(str(item))
                for item in (payload.get("artifact_level_advice") or [])
                if compact_whitespace(str(item))
            ],
            "section_repairs": repairs,
            "residual_risk": [
                compact_whitespace(str(item))
                for item in (payload.get("residual_risk") or [])
                if compact_whitespace(str(item))
            ],
        }
        return normalized

    error_summary = "; ".join(errors) if errors else "No JSON object found in artifact critic output"
    raise ValueError(error_summary)


def clip_for_prompt(text: str | None, max_chars: int) -> str:
    normalized = compact_whitespace(text or "")
    if len(normalized) <= max_chars:
        return normalized
    clip = max(0, max_chars - 3)
    return normalized[:clip].rstrip() + "..."


def build_section_snapshot_for_critic(section: dict[str, Any], index: int) -> dict[str, Any]:
    content = section.get("content") if isinstance(section.get("content"), dict) else {}
    component_engines = [
        str(component.get("engine") or "").strip()
        for component in (section.get("components") or [])
        if isinstance(component, dict) and str(component.get("engine") or "").strip()
    ]
    return {
        "section_index": index,
        "title": compact_whitespace(str(section.get("title") or "")),
        "headline": clip_for_prompt(content.get("headline"), 180) if isinstance(content, dict) else "",
        "body_excerpt": clip_for_prompt(content.get("body"), 420) if isinstance(content, dict) else "",
        "component_engines": component_engines,
    }


def build_plan_snapshots_for_critic(plan: dict[str, Any]) -> list[dict[str, Any]]:
    return [
        build_section_snapshot_for_critic(section, index)
        for index, section in enumerate(plan.get("sections", []) or [], start=1)
        if isinstance(section, dict)
    ]


def build_narrative_spine_snapshot(plan: dict[str, Any]) -> dict[str, Any]:
    meta = plan.get("meta") if isinstance(plan.get("meta"), dict) else {}
    spine = meta.get("narrative_spine")
    if not isinstance(spine, dict):
        return {}
    snapshot = {
        "source": compact_whitespace(str(spine.get("source") or "")),
        "core_thesis": clip_for_prompt(spine.get("core_thesis"), 260),
        "audience": clip_for_prompt(spine.get("audience"), 180),
        "desired_action": clip_for_prompt(spine.get("desired_action"), 180),
        "throughlines": [clip_for_prompt(item, 140) for item in (spine.get("throughlines") or [])[:4]],
        "must_keep_facts": [clip_for_prompt(item, 160) for item in (spine.get("must_keep_facts") or [])[:6]],
        "must_not_claim": [clip_for_prompt(item, 120) for item in (spine.get("must_not_claim") or [])[:6]],
        "key_entities": [clip_for_prompt(item, 120) for item in (spine.get("key_entities") or [])[:6]],
        "claim_map": [],
        "closing_obligation": clip_for_prompt(spine.get("closing_obligation"), 220),
        "section_jobs": [],
    }
    for item in (spine.get("claim_map") or [])[:6]:
        if not isinstance(item, dict):
            continue
        snapshot["claim_map"].append(
            {
                "claim": clip_for_prompt(item.get("claim"), 180),
                "support_sections": [int(index) for index in (item.get("support_sections") or []) if str(index).strip().isdigit()][:6],
                "evidence": clip_for_prompt(item.get("evidence"), 180),
            }
        )
    for item in (spine.get("section_jobs") or [])[:12]:
        if not isinstance(item, dict):
            continue
        snapshot["section_jobs"].append(
            {
                "section_index": item.get("section_index"),
                "title": clip_for_prompt(item.get("title"), 120),
                "job": clip_for_prompt(item.get("job"), 160),
            }
        )
    return snapshot


def build_rendered_text_excerpt(render_validation_summary: dict[str, Any], max_chars: int = 6000) -> str:
    headless = render_validation_summary.get("headless_validation") if isinstance(render_validation_summary, dict) else {}
    standalone = headless.get("standalone") if isinstance(headless, dict) else {}
    body_text = standalone.get("bodyText") if isinstance(standalone, dict) else ""
    return clip_for_prompt(str(body_text or ""), max_chars)


def _normalize_contract_match_text(text: str) -> str:
    return re.sub(r"\s+", " ", (text or "").lower()).strip()


def _claim_has_visible_support(claim: str, visible_text: str) -> bool:
    narrative_match_stopwords = {
        "about",
        "their",
        "there",
        "which",
        "would",
        "could",
        "should",
        "these",
        "those",
        "across",
        "through",
        "under",
        "after",
        "before",
        "because",
    }
    normalized_claim = _normalize_contract_match_text(claim)
    normalized_visible = _normalize_contract_match_text(visible_text)
    if not normalized_claim or not normalized_visible:
        return False
    if normalized_claim in normalized_visible:
        return True
    tokens = [token for token in re.findall(r"[a-z0-9]{5,}", normalized_claim) if token not in narrative_match_stopwords]
    if not tokens:
        return False
    matched = sum(1 for token in tokens if token in normalized_visible)
    return matched >= max(1, min(2, len(tokens)))


def evaluate_narrative_contract_render(
    plan: dict[str, Any],
    render_validation_summary: dict[str, Any],
) -> tuple[list[dict[str, Any]], int]:
    warnings: list[dict[str, Any]] = []
    penalty = 0
    spine = build_narrative_spine_snapshot(plan)
    if not spine:
        return warnings, penalty

    visible_text = build_rendered_text_excerpt(render_validation_summary, max_chars=12000)
    if not visible_text:
        return warnings, penalty

    key_entities = [str(item).strip() for item in (spine.get("key_entities") or []) if str(item).strip()]
    missing_entities = [entity for entity in key_entities if not _claim_has_visible_support(entity, visible_text)]
    if key_entities and len(missing_entities) >= (len(key_entities) // 2 + 1):
        warnings.append(
            {
                "type": "narrative_entity_consistency_risk",
                "severity": "medium",
                "message": (
                    "Several named entities or anchor phrases from the narrative spine are not visible in the rendered artifact. "
                    "This suggests the final output may have drifted away from the intended story contract."
                ),
                "missing_entities": missing_entities[:6],
            }
        )
        penalty += 5

    claim_map = [item for item in (spine.get("claim_map") or []) if isinstance(item, dict)]
    weak_claims = [
        item.get("claim")
        for item in claim_map
        if str(item.get("claim") or "").strip() and not _claim_has_visible_support(str(item.get("claim") or ""), visible_text)
    ]
    if claim_map and len(weak_claims) >= (len(claim_map) // 2 + 1):
        warnings.append(
            {
                "type": "narrative_claim_visibility_gap",
                "severity": "medium",
                "message": (
                    "Too many claim-map items are not clearly reflected in the rendered text. "
                    "The artifact may no longer support the intended argument consistently from section to section."
                ),
                "claims": weak_claims[:6],
            }
        )
        penalty += 5

    return warnings, penalty


def summarize_warnings_for_critic(warnings: list[dict[str, Any]], max_items: int = 10) -> list[str]:
    summarized: list[str] = []
    for warning in warnings[:max_items]:
        if not isinstance(warning, dict):
            continue
        severity = compact_whitespace(str(warning.get("severity") or "warning")).lower()
        message = compact_whitespace(str(warning.get("message") or ""))
        if not message:
            continue
        summarized.append(f"{severity}: {message}")
    return summarized


def section_indexes_using_engines(plan: dict[str, Any], engine_ids: set[str]) -> list[int]:
    indexes: list[int] = []
    for index, section in enumerate(plan.get("sections", []) or [], start=1):
        if not isinstance(section, dict):
            continue
        component_engines = {
            str(component.get("engine") or "").strip()
            for component in (section.get("components") or [])
            if isinstance(component, dict)
        }
        if component_engines & engine_ids:
            indexes.append(index)
    return indexes


def _normalize_claim_key(text: str | None) -> str:
    return re.sub(r"\s+", " ", str(text or "").strip().lower())


def section_indexes_for_claim(plan: dict[str, Any], claim_text: str) -> list[int]:
    spine = build_narrative_spine_snapshot(plan)
    target = _normalize_claim_key(claim_text)
    if not target:
        return []
    for item in spine.get("claim_map") or []:
        if not isinstance(item, dict):
            continue
        if _normalize_claim_key(item.get("claim")) != target:
            continue
        indexes: list[int] = []
        for raw_index in item.get("support_sections") or []:
            try:
                index = int(raw_index)
            except (TypeError, ValueError):
                continue
            if index > 0 and index not in indexes:
                indexes.append(index)
        return indexes
    return []


def section_indexes_for_entities(plan: dict[str, Any], entities: list[str]) -> list[int]:
    if not entities:
        return []
    lowered_entities = [_normalize_claim_key(entity) for entity in entities if _normalize_claim_key(entity)]
    if not lowered_entities:
        return []
    indexes: list[int] = []
    for snapshot in build_plan_snapshots_for_critic(plan):
        haystack = " ".join(
            [
                str(snapshot.get("title") or ""),
                str(snapshot.get("headline") or ""),
                str(snapshot.get("body_excerpt") or ""),
            ]
        ).lower()
        if any(entity in haystack for entity in lowered_entities):
            section_index = int(snapshot.get("section_index") or 0)
            if section_index > 0 and section_index not in indexes:
                indexes.append(section_index)
    return indexes


def sanitize_plan_for_render(node: Any) -> Any:
    if isinstance(node, dict):
        cleaned: dict[str, Any] = {}
        for key, value in node.items():
            if value is None:
                continue
            sanitized_value = sanitize_plan_for_render(value)
            if sanitized_value is None:
                continue
            if isinstance(sanitized_value, str) and not sanitized_value.strip() and key in {
                "change",
                "direction",
                "notes",
                "note",
                "subtitle",
                "caption",
                "footnote",
            }:
                continue
            if key == "metrics" and isinstance(sanitized_value, list):
                sanitized_value = [
                    item
                    for item in sanitized_value
                    if isinstance(item, dict) and compact_whitespace(str(item.get("label") or "")) and compact_whitespace(str(item.get("value") or ""))
                ]
            if key == "counters" and isinstance(sanitized_value, list):
                sanitized_value = [
                    item
                    for item in sanitized_value
                    if isinstance(item, dict) and compact_whitespace(str(item.get("label") or "")) and item.get("value") is not None
                ]
            if isinstance(sanitized_value, dict) and not sanitized_value:
                continue
            if isinstance(sanitized_value, list) and not sanitized_value and key in {"metrics", "counters", "tabs", "annotations"}:
                continue
            cleaned[key] = sanitized_value
        return cleaned
    if isinstance(node, list):
        cleaned_items = [sanitize_plan_for_render(value) for value in node]
        return [value for value in cleaned_items if value is not None]
    return node


def normalize_engine_override(value: str | None) -> str | None:
    normalized = normalize_engine_id(value)
    if not normalized:
        return None
    if normalized not in ENGINE_CATALOG:
        raise ValueError("engine_override must match a known IterFact engine id.")
    return normalized


def is_wrapper_engine(engine_id: str | None) -> bool:
    return bool(engine_id and normalize_engine_id(engine_id) in VALID_WRAPPERS)


def plan_honors_engine_override(plan: dict[str, Any], engine_override: str | None) -> bool:
    normalized = normalize_engine_id(engine_override)
    if not normalized:
        return True
    if normalized in VALID_WRAPPERS:
        return normalize_engine_id(plan.get("wrapper", {}).get("engine")) == normalized
    return plan_uses_engine(plan, {normalized})


def engine_override_feedback_line(engine_override: str | None) -> str | None:
    normalized = normalize_engine_id(engine_override)
    if not normalized:
        return None
    if normalized in VALID_WRAPPERS:
        return f"Use wrapper engine {normalized} exactly. Do not substitute a different wrapper."
    return (
        f"Use engine {normalized} as the primary interactive component. "
        "Place it in the first section, make it central to the artifact, and do not substitute a different engine. "
        "Use the least intrusive wrapper that matches the request. Prefer wrapper 12 for editorial or report-like artifacts, "
        "use wrapper 02 only for true slides, wrapper 21 only for demos or walkthroughs, wrapper 24 only for event pages, "
        "and wrapper 25 only for signing or acceptance flows."
    )


def normalize_section_count(value: int | None) -> int | None:
    if value is None:
        return None
    try:
        normalized = int(value)
    except (TypeError, ValueError) as exc:
        raise ValueError("section_count must be an integer between 1 and 12.") from exc
    if normalized < 1 or normalized > 12:
        raise ValueError("section_count must be between 1 and 12.")
    return normalized


def sanitize_structured_data(value: dict[str, Any] | None) -> dict[str, Any] | None:
    if value is None:
        return None
    if not isinstance(value, dict):
        raise ValueError("data must be a JSON object with top-level keys.")
    normalized = json.loads(json.dumps(value))
    payload = json.dumps(normalized, ensure_ascii=True)
    if len(payload) > 75_000:
        raise ValueError("data is too large. Keep structured data under 75 KB per request.")
    return normalized


def structured_data_keys(value: dict[str, Any] | None) -> list[str]:
    if not isinstance(value, dict):
        return []
    return sorted(str(key) for key in value.keys())


def plan_empty_sections(plan: dict[str, Any]) -> list[int]:
    empty: list[int] = []
    for index, section in enumerate(plan.get("sections", []) or [], start=1):
        content = section.get("content") or {}
        components = section.get("components") or []
        headline = str(content.get("headline") or "").strip()
        body = str(content.get("body") or "").strip()
        if not headline and not body and not components:
            empty.append(index)
    return empty


def describe_chart_expectation(plan: dict[str, Any]) -> bool:
    charty_engine_ids = {"03", "29", "31", "32"}
    for section in plan.get("sections", []) or []:
        for component in section.get("components", []) or []:
            engine_id = normalize_engine_id(component.get("engine"))
            if engine_id in charty_engine_ids:
                return True
    return False


def plan_uses_engine(plan: dict[str, Any], engine_ids: set[str]) -> bool:
    normalized_ids = {normalize_engine_id(engine_id) for engine_id in engine_ids}
    if normalize_engine_id(plan.get("wrapper", {}).get("engine")) in normalized_ids:
        return True
    for section in plan.get("sections", []) or []:
        for component in section.get("components", []) or []:
            if normalize_engine_id(component.get("engine")) in normalized_ids:
                return True
    return False


def section_has_visible_content(section: dict[str, Any]) -> bool:
    content = section.get("content") or {}
    return bool(
        str(section.get("title") or "").strip()
        or str(content.get("headline") or "").strip()
        or str(content.get("body") or "").strip()
        or (section.get("components") or [])
    )


def extract_primary_engine_payload(engine_id: str, structured_data: dict[str, Any] | None) -> dict[str, Any] | None:
    if not isinstance(structured_data, dict):
        return None

    normalized = normalize_engine_id(engine_id)
    if normalized == "08":
        candidate = structured_data.get("calculator") if isinstance(structured_data.get("calculator"), dict) else structured_data
        payload = {key: candidate.get(key) for key in ("inputs", "outputs", "formula") if key in candidate}
        return payload or None
    if normalized == "03":
        candidate = structured_data.get("dashboard") if isinstance(structured_data.get("dashboard"), dict) else structured_data
        payload: dict[str, Any] = {}
        if "metrics" in candidate:
            payload["metrics"] = candidate.get("metrics")
        elif "kpis" in candidate:
            payload["metrics"] = candidate.get("kpis")
        if "charts" in candidate:
            payload["charts"] = candidate.get("charts")
        return payload or None
    if normalized == "17":
        candidate = structured_data.get("scenario_modeler") if isinstance(structured_data.get("scenario_modeler"), dict) else structured_data
        payload = {key: candidate.get(key) for key in ("variables", "scenarios", "outputs") if key in candidate}
        return payload or None
    if normalized == "44":
        candidate = structured_data.get("ranking_chart") if isinstance(structured_data.get("ranking_chart"), dict) else structured_data
        payload = {key: candidate.get(key) for key in ("items", "sort_options", "config") if key in candidate}
        return payload or None
    return None


def infer_primary_engine_from_request(
    prompt: str,
    template_hint: str | None,
    structured_data: dict[str, Any] | None,
) -> str | None:
    lower_prompt = (prompt or "").lower()
    lower_template = (template_hint or "").lower()
    structured_keys = {str(key).strip().lower() for key in structured_data.keys()} if isinstance(structured_data, dict) else set()

    if (
        (isinstance(structured_data, dict) and isinstance(structured_data.get("calculator"), dict))
        or {"inputs", "outputs"} <= structured_keys
        or any(token in lower_prompt for token in ("calculator", "roi", "payback", "estimator"))
        or lower_template in {"calculator", "roi_calculator", "roi"}
    ):
        return "08"

    if (
        (isinstance(structured_data, dict) and isinstance(structured_data.get("ranking_chart"), dict))
        or ("sort_options" in structured_keys and "items" in structured_keys)
        or any(
            token in lower_prompt
            for token in (
                "ranking chart",
                "interactive ranking",
                "ranked bar",
                "leaderboard",
                "league table",
                "market size ranking",
                "market sizing ranking",
                "future arenas",
                "rank order",
                "sort by",
                "re-sort",
            )
        )
        or lower_template in {"ranking_chart", "interactive_ranking", "ranking", "leaderboard"}
    ):
        return "44"

    if (
        (isinstance(structured_data, dict) and isinstance(structured_data.get("dashboard"), dict))
        or ("charts" in structured_keys and ({"metrics", "kpis"} & structured_keys))
        or any(token in lower_prompt for token in ("dashboard", "kpi", "mrr", "bar chart", "line chart"))
        or lower_template in {"dashboard", "kpi_dashboard", "analytics"}
    ):
        return "03"

    if (
        (isinstance(structured_data, dict) and isinstance(structured_data.get("scenario_modeler"), dict))
        or {"variables", "scenarios", "outputs"} <= structured_keys
        or any(token in lower_prompt for token in ("scenario", "what-if", "what if", "modeler", "forecast"))
        or lower_template in {"scenario_modeler", "scenario", "forecast"}
    ):
        return "17"

    if (
        ({"testimonials", "locations"} <= structured_keys)
        or (
            "testimonials" in structured_keys
            and ("locations" in structured_keys or "global_footprint" in structured_keys or "globe" in structured_keys)
        )
        or any(
            token in lower_prompt
            for token in (
                "testimonial globe",
                "globe testimonials",
                "social proof globe",
                "global proof",
                "global social proof",
                "customer footprint",
                "global footprint",
                "proof orbit",
                "rotating testimonials",
                "customer stories globe",
            )
        )
        or (
            "testimonial" in lower_prompt
            and any(token in lower_prompt for token in ("globe", "global", "footprint", "orbit", "world"))
        )
        or lower_template in {"globe_testimonials", "global_proof", "social_proof"}
    ):
        return "39"

    return None


def apply_primary_engine_constraints(
    plan: dict[str, Any],
    engine_override: str | None,
    structured_data: dict[str, Any] | None,
    *,
    create_placeholder_section: bool = True,
) -> dict[str, Any]:
    normalized = normalize_engine_id(engine_override)
    if not normalized or normalized in VALID_WRAPPERS:
        return plan

    wrapper_engine = normalize_engine_id(plan.get("wrapper", {}).get("engine"))
    if wrapper_engine not in VALID_WRAPPERS:
        plan.setdefault("wrapper", {})["engine"] = "12"
    plan.setdefault("meta", {})["primary_engine_constraint"] = normalized
    sections = plan.get("sections") or []
    if not sections:
        if not create_placeholder_section:
            return plan
        sections = [
            {
                "id": "primary-section",
                "title": "Interactive Model",
                "content": {"headline": "", "body": ""},
                "components": [],
            }
        ]
        plan["sections"] = sections

    primary_component: dict[str, Any] | None = None
    for section in sections:
        components = [component for component in (section.get("components") or []) if normalize_engine_id(component.get("engine")) == normalized]
        if components and primary_component is None:
            primary_component = components[0]
        section["components"] = components

    if primary_component is None:
        first_section = sections[0]
        primary_component = {
            "engine": normalized,
            "instance_id": f"primary-{normalized}",
            "role": "Primary interactive component",
            "data": {},
        }
        first_section.setdefault("components", []).insert(0, primary_component)

    payload = extract_primary_engine_payload(normalized, structured_data)
    if payload:
        existing_data = primary_component.get("data") if isinstance(primary_component.get("data"), dict) else {}
        primary_component["data"] = {**existing_data, **payload}

    filtered_sections = [section for section in sections if section_has_visible_content(section)]
    plan["sections"] = filtered_sections or sections
    return plan


def count_native_support_occurrences(html: str, tag_name: str) -> int:
    literal_count = len(re.findall(rf"<{re.escape(tag_name)}\b", html, flags=re.IGNORECASE))
    creation_count = len(
        re.findall(rf"createElement\(\s*['\"]{re.escape(tag_name)}['\"]\s*\)", html, flags=re.IGNORECASE)
    )
    return literal_count + creation_count


def strip_validation_exempt_blocks(html: str) -> str:
    stripped = re.sub(
        r"/\*\s*ITERFACT_DESIGN_TOKENS_START\s*\*/[\s\S]*?/\*\s*ITERFACT_DESIGN_TOKENS_END\s*\*/",
        "",
        html,
        flags=re.IGNORECASE,
    )
    stripped = re.sub(
        r"/\*\s*ITERFACT_THEME_COMPAT_START\s*\*/[\s\S]*?/\*\s*ITERFACT_THEME_COMPAT_END\s*\*/",
        "",
        stripped,
        flags=re.IGNORECASE,
    )
    return stripped


def inspect_rendered_html_structure(html: str) -> dict[str, int]:
    validation_html = strip_validation_exempt_blocks(html)
    image_tags = re.findall(r"<img\b[^>]*>", html, flags=re.IGNORECASE)
    iframe_tags = re.findall(r"<iframe\b[^>]*>", html, flags=re.IGNORECASE)
    image_missing_src_count = 0
    image_placeholder_src_count = 0

    for tag in image_tags:
        src_match = re.search(
            r"""\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))""",
            tag,
            flags=re.IGNORECASE,
        )
        if not src_match:
            image_missing_src_count += 1
            continue

        src_value = next((group for group in src_match.groups() if group is not None), "")
        normalized_src = src_value.strip().lower()
        if not normalized_src:
            image_missing_src_count += 1
            continue

        if any(token in normalized_src for token in VISIBLE_PLACEHOLDER_TOKENS):
            image_placeholder_src_count += 1

    canvas_count = len(re.findall(r"<canvas\b", html, flags=re.IGNORECASE))
    chart_surface_count = canvas_count + html.lower().count("bar-group")
    z_index_values = [int(value) for value in re.findall(r"z-index\s*:\s*(-?\d+)", validation_html, flags=re.IGNORECASE)]
    hardcoded_hexes = [match.group(0).lower() for match in HEX_COLOR_PATTERN.finditer(validation_html)]
    disallowed_hexes = [color for color in hardcoded_hexes if color not in VALID_VISUALIZER_RAMP_HEXES]
    return {
        "canvas_count": canvas_count,
        "chart_surface_count": chart_surface_count,
        "details_count": len(re.findall(r"<details\b", html, flags=re.IGNORECASE)),
        "dialog_count": len(re.findall(r"<dialog\b", html, flags=re.IGNORECASE)),
        "datalist_count": len(re.findall(r"<datalist\b", html, flags=re.IGNORECASE)),
        "native_details_count": count_native_support_occurrences(html, "details"),
        "native_dialog_count": count_native_support_occurrences(html, "dialog"),
        "native_datalist_count": count_native_support_occurrences(html, "datalist"),
        "image_count": len(image_tags),
        "iframe_count": len(iframe_tags),
        "image_missing_src_count": image_missing_src_count,
        "image_placeholder_src_count": image_placeholder_src_count,
        "lazy_media_count": len(
            re.findall(
                r"<(?:img|iframe)\b[^>]*\bloading\s*=\s*(?:\"lazy\"|'lazy'|lazy)",
                html,
                flags=re.IGNORECASE,
            )
        ),
        "inline_event_handler_count": len(re.findall(r"\son[a-z]+\s*=", validation_html, flags=re.IGNORECASE)),
        "viewport_media_query_count": len(re.findall(r"@media\s*\(\s*(?:max|min)-width", validation_html, flags=re.IGNORECASE)),
        "viewport_unit_count": len(re.findall(r"(?<![\w-])(?:100|[8-9]\d)(?:\.\d+)?(?:vh|vw)\b", validation_html, flags=re.IGNORECASE)),
        "fixed_position_count": len(re.findall(r"position\s*:\s*fixed", validation_html, flags=re.IGNORECASE)),
        "container_query_count": len(re.findall(r"@container\b", validation_html, flags=re.IGNORECASE)),
        "content_visibility_count": len(re.findall(r"content-visibility\s*:\s*auto", validation_html, flags=re.IGNORECASE)),
        "tabular_nums_count": len(re.findall(r"tabular-nums", validation_html, flags=re.IGNORECASE)),
        "gradient_count": len(re.findall(r"gradient\s*\(", validation_html, flags=re.IGNORECASE)),
        "shadow_count": len(re.findall(r"(?:box-shadow|text-shadow)\s*:", validation_html, flags=re.IGNORECASE)),
        "blur_glow_count": len(re.findall(r"(?:filter\s*:\s*[^;]*blur\(|drop-shadow\(|glow)", validation_html, flags=re.IGNORECASE)),
        "heavy_font_weight_count": len(re.findall(r"font-weight\s*:\s*(?:[6-9]00|5[1-9]\d)\b", validation_html, flags=re.IGNORECASE)),
        "uppercase_text_transform_count": len(re.findall(r"text-transform\s*:\s*uppercase", validation_html, flags=re.IGNORECASE)),
        "disallowed_hardcoded_color_count": len(disallowed_hexes),
        "large_z_index_count": sum(1 for value in z_index_values if value >= 1000),
        "max_z_index": max(z_index_values) if z_index_values else 0,
        "section_tag_count": len(re.findall(r"<section\b", html, flags=re.IGNORECASE)),
        "window_top_ref_count": len(re.findall(r"window\.top\b", validation_html, flags=re.IGNORECASE)),
        "window_parent_ref_count": len(re.findall(r"window\.parent\b", validation_html, flags=re.IGNORECASE)),
    }


def derive_engine_supports(engine: dict[str, Any]) -> list[str]:
    folder = engine.get("folder", "")
    engine_key = folder[:2] if folder[:2].isdigit() else ""
    if engine_key in ENGINE_SUPPORTS_OVERRIDES:
        return ENGINE_SUPPORTS_OVERRIDES[engine_key]

    supports: list[str] = []
    for item in [*(engine.get("tags") or []), *(engine.get("core_libs") or [])]:
        normalized = str(item).strip().lower().replace(" ", "_")
        if normalized:
            supports.append(normalized)
    return sorted(dict.fromkeys(supports))


def derive_engine_best_for(engine: dict[str, Any]) -> str:
    folder = engine.get("folder", "")
    engine_key = folder[:2] if folder[:2].isdigit() else ""
    if engine_key in ENGINE_BEST_FOR_OVERRIDES:
        return ENGINE_BEST_FOR_OVERRIDES[engine_key]
    verticals = engine.get("compatible_verticals") or []
    if verticals:
        return ", ".join(str(vertical).replace("-", " ") for vertical in verticals[:3])
    return str(engine.get("category") or "general interactive artifacts")


def sanitize_rendered_html(html: str) -> tuple[str, dict[str, int]]:
    counts = {token: 0 for token in VISIBLE_PLACEHOLDER_TOKENS}
    segments = re.split(r"(<script\b[\s\S]*?</script>|<style\b[\s\S]*?</style>)", html, flags=re.IGNORECASE)

    def clean_text_segments(markup: str) -> str:
        def strip_empty_text_tag(match: re.Match[str]) -> str:
            attrs = match.group(2) or ""
            if 'id="' in attrs or "data-ifx-keep-empty" in attrs:
                return match.group(0)
            return ""

        def replace_text(match: re.Match[str]) -> str:
            raw_text = match.group(1)
            if not raw_text or not raw_text.strip():
                return match.group(0)

            updated = raw_text
            for token in VISIBLE_PLACEHOLDER_TOKENS:
                pattern = re.compile(rf"(?<![\w-]){re.escape(token)}(?![\w-])", re.IGNORECASE)
                hits = len(pattern.findall(updated))
                if hits:
                    counts[token] += hits
                    updated = pattern.sub("", updated)

            if updated != raw_text:
                updated = re.sub(r"\s{2,}", " ", updated)
            return f">{updated}<"

        cleaned = re.sub(r">([^<]+)<", replace_text, markup)
        for tag in EMPTY_TEXT_TAGS:
            cleaned = re.sub(
                rf"<({tag})\b([^>]*)>\s*</\1>",
                strip_empty_text_tag,
                cleaned,
                flags=re.IGNORECASE,
            )
        return cleaned

    rebuilt: list[str] = []
    for index, segment in enumerate(segments):
        if index % 2 == 1:
            rebuilt.append(segment)
        else:
            rebuilt.append(clean_text_segments(segment))
    return "".join(rebuilt), counts


def analyze_visible_runtime_text(text: str) -> dict[str, Any]:
    normalized_text = re.sub(r"\s+", " ", text or "").strip()
    placeholder_matches = HEADLESS_VISIBLE_FAILURE_PATTERN.findall(normalized_text)
    float_matches = UNFORMATTED_FLOAT_PATTERN.findall(normalized_text)
    return {
        "placeholder_match_count": len(placeholder_matches),
        "placeholder_samples": list(dict.fromkeys(match.strip() for match in placeholder_matches if match.strip()))[:8],
        "unformatted_float_count": len(float_matches),
        "unformatted_float_samples": list(dict.fromkeys(float_matches))[:8],
        "text_length": len(normalized_text),
    }


def coerce_number(value: Any, fallback: float = 0) -> float:
    try:
        numeric = float(value)
    except (TypeError, ValueError):
        return fallback
    return numeric if numeric == numeric and numeric not in (float("inf"), float("-inf")) else fallback


def collect_runtime_dom_snapshot(target: Any) -> dict[str, Any]:
    return target.evaluate(
        """
        () => {
          const body = document.body;
          const docEl = document.documentElement;
          const outputSelectors = '.result-value, .result-number, .metric-value, .metric-number, .kpi-card .value, .kpi-card .change, .scenario-value, .scenario-output, .calc-output, [data-output], output';
          const nodes = Array.from(document.querySelectorAll('section, .ifx-sec, .ifx-chapter, .ifx-infosec, .ifx-slide, .ifx-component, .chart-card, .metric, .result-item'));
          const emptySections = nodes.filter((node) => {
            const rect = node.getBoundingClientRect();
            const text = (node.innerText || '').trim();
            const hasVisual = !!node.querySelector('img, canvas, svg, video, iframe, table');
            return rect.height >= 180 && text.length < 12 && !hasVisual;
          }).length;
          const outputNodes = Array.from(document.querySelectorAll(outputSelectors));
          const metricSnapshot = outputNodes
            .slice(0, 8)
            .map((node) => (node.innerText || '').trim())
            .filter(Boolean)
            .join(' | ');
          return {
            bodyText: body ? (body.innerText || '') : '',
            scrollWidth: Math.max(body ? body.scrollWidth : 0, docEl ? docEl.scrollWidth : 0),
            viewportWidth: window.innerWidth || (docEl ? docEl.clientWidth : 0) || 0,
            emptySectionCount: emptySections,
            inputCount: document.querySelectorAll('input, select, textarea').length,
            rangeCount: document.querySelectorAll('input[type="range"]').length,
            outputNodeCount: outputNodes.length,
            metricSnapshot: metricSnapshot,
            sectionCount: nodes.length
          };
        }
        """
    )


def probe_runtime_interaction(page: Any, target: Any, baseline_snapshot: str) -> dict[str, Any]:
    first_range = target.query_selector("input[type=range]")
    if not first_range:
        return {"attempted": False, "changed": None}

    if not compact_whitespace(baseline_snapshot or ""):
        return {
            "attempted": False,
            "changed": None,
            "reason": "No recognizable metric output snapshot was present for interaction validation.",
        }

    current_value = coerce_number(first_range.evaluate("node => Number(node.value)"), 0)
    min_value = coerce_number(first_range.evaluate("node => Number(node.min || 0)"), 0)
    max_value = coerce_number(first_range.evaluate("node => Number(node.max || 0)"), current_value)
    step_value = max(coerce_number(first_range.evaluate("node => Number(node.step || 1)"), 1), 0.1)

    next_value = max_value if current_value < max_value else min_value
    if abs(next_value - current_value) < step_value:
        next_value = current_value + step_value if current_value + step_value <= max_value else current_value - step_value

    first_range.evaluate(
        """(node, value) => {
            node.value = String(value);
            node.dispatchEvent(new Event('input', { bubbles: true }));
            node.dispatchEvent(new Event('change', { bubbles: true }));
        }""",
        next_value,
    )
    page.wait_for_timeout(180)
    updated_snapshot = collect_runtime_dom_snapshot(target)
    changed = bool(updated_snapshot.get("metricSnapshot")) and updated_snapshot.get("metricSnapshot") != baseline_snapshot
    return {
        "attempted": True,
        "changed": changed,
        "before": baseline_snapshot,
        "after": updated_snapshot.get("metricSnapshot"),
    }


def classify_headless_issue(issue: str) -> tuple[str, int, bool]:
    normalized = compact_whitespace(issue).lower()
    if "page error" in normalized or "console error" in normalized:
        return "critical", 24, True
    if "contains nan" in normalized or "undefined" in normalized or "[object object]" in normalized:
        return "high", 18, False
    if "visually empty sections" in normalized:
        return "high", 14, False
    if "overflows horizontally" in normalized:
        return "high", 12, False
    if "interactive inputs did not update" in normalized:
        return "high", 10, False
    return "medium", 8, False


def _run_headless_artifact_validation_impl(html: str) -> dict[str, Any]:
    summary: dict[str, Any] = {
        "enabled": SETTINGS.headless_validation_enabled,
        "available": bool(sync_playwright),
        "critical_failure": False,
        "issues": [],
        "page_errors": [],
        "console_errors": [],
        "availability_warning": None,
    }

    if not SETTINGS.headless_validation_enabled:
        return summary

    if sync_playwright is None:
        summary["availability_warning"] = "Headless validation is enabled but Playwright is not installed."
        return summary

    try:
        with sync_playwright() as playwright:
            browser = playwright.chromium.launch(
                headless=True,
                args=["--disable-dev-shm-usage", "--no-sandbox"],
            )
            try:
                page_errors: list[str] = []
                console_errors: list[str] = []

                def capture_console(message: Any) -> None:
                    if getattr(message, "type", "") == "error":
                        console_errors.append(str(message.text))

                page = browser.new_page(viewport={"width": 1280, "height": 900})
                page.on("console", capture_console)
                page.on("pageerror", lambda error: page_errors.append(str(error)))
                page.set_content(html, wait_until="domcontentloaded")
                with contextlib.suppress(PlaywrightError):
                    page.wait_for_load_state("networkidle", timeout=SETTINGS.headless_validation_timeout_ms)
                page.wait_for_timeout(250)

                standalone_dom = collect_runtime_dom_snapshot(page)
                standalone_text = analyze_visible_runtime_text(standalone_dom.get("bodyText") or "")
                standalone_interaction = probe_runtime_interaction(page, page, standalone_dom.get("metricSnapshot") or "")

                iframe_page = browser.new_page(viewport={"width": 460, "height": 720})
                iframe_page.on("console", capture_console)
                iframe_page.on("pageerror", lambda error: page_errors.append(str(error)))
                iframe_page.set_content(
                    "<!DOCTYPE html><html><body style='margin:0;background:#fff;'>"
                    "<div style='width:400px;height:600px;overflow:hidden;'><iframe id='artifact-frame' "
                    "style='width:100%;height:100%;border:0;display:block;'></iframe></div></body></html>",
                    wait_until="domcontentloaded",
                )
                iframe_page.evaluate(
                    """(markup) => {
                        const frame = document.getElementById('artifact-frame');
                        frame.srcdoc = markup;
                    }""",
                    html,
                )
                frame_handle = iframe_page.query_selector("#artifact-frame")
                if frame_handle is None:
                    raise RuntimeError("Headless iframe validation could not create the artifact iframe.")
                frame = frame_handle.content_frame()
                if frame is None:
                    raise RuntimeError("Headless iframe validation could not access the artifact iframe content.")

                with contextlib.suppress(PlaywrightError):
                    iframe_page.wait_for_timeout(600)
                iframe_dom = collect_runtime_dom_snapshot(frame)
                iframe_text = analyze_visible_runtime_text(iframe_dom.get("bodyText") or "")
                iframe_interaction = probe_runtime_interaction(iframe_page, frame, iframe_dom.get("metricSnapshot") or "")

                summary.update(
                    {
                        "standalone": {
                            **standalone_dom,
                            **standalone_text,
                            "interaction": standalone_interaction,
                        },
                        "iframe": {
                            **iframe_dom,
                            **iframe_text,
                            "interaction": iframe_interaction,
                        },
                        "page_errors": page_errors[:10],
                        "console_errors": console_errors[:10],
                    }
                )

                issues: list[str] = []
                if page_errors:
                    issues.append(f"Standalone runtime threw {len(page_errors)} page error(s).")
                if console_errors:
                    issues.append(f"Standalone runtime emitted {len(console_errors)} console error(s).")
                if standalone_text["placeholder_match_count"] or iframe_text["placeholder_match_count"]:
                    issues.append("Visible runtime text still contains NaN, undefined, null, or [object Object].")
                if standalone_text["unformatted_float_count"] or iframe_text["unformatted_float_count"]:
                    issues.append("Visible runtime text still contains long unformatted floating-point numbers.")
                if coerce_number(standalone_dom.get("emptySectionCount"), 0) > 0:
                    issues.append("Standalone runtime produced visually empty sections.")
                if coerce_number(iframe_dom.get("emptySectionCount"), 0) > 0:
                    issues.append("Iframe runtime produced visually empty sections.")
                if coerce_number(iframe_dom.get("scrollWidth"), 0) > coerce_number(iframe_dom.get("viewportWidth"), 0) + 4:
                    issues.append("Artifact overflows horizontally inside a 400px iframe.")
                if standalone_interaction.get("attempted") and standalone_interaction.get("changed") is False:
                    issues.append("Standalone interactive inputs did not update the visible outputs.")
                if iframe_interaction.get("attempted") and iframe_interaction.get("changed") is False:
                    issues.append("Embedded interactive inputs did not update the visible outputs.")

                summary["issues"] = issues
                summary["critical_failure"] = any(classify_headless_issue(str(issue))[2] for issue in issues)
                return summary
            finally:
                browser.close()
    except Exception as exc:
        summary["availability_warning"] = f"Headless validation could not start: {exc}"
        summary["available"] = False
        return summary


def run_headless_artifact_validation(html: str) -> dict[str, Any]:
    summary: dict[str, Any] = {
        "enabled": SETTINGS.headless_validation_enabled,
        "available": bool(sync_playwright),
        "critical_failure": False,
        "issues": [],
        "page_errors": [],
        "console_errors": [],
        "availability_warning": None,
    }

    if not SETTINGS.headless_validation_enabled:
        return summary

    if sync_playwright is None:
        summary["availability_warning"] = "Headless validation is enabled but Playwright is not installed."
        return summary

    result_holder: dict[str, Any] = {}
    error_holder: dict[str, Exception] = {}

    def worker() -> None:
        try:
            result_holder["value"] = _run_headless_artifact_validation_impl(html)
        except Exception as exc:  # noqa: BLE001
            error_holder["error"] = exc

    thread = threading.Thread(target=worker, name="iterfact-headless-validation", daemon=True)
    thread.start()
    thread.join(timeout=max(15, int(SETTINGS.headless_validation_timeout_ms / 1000) * 4))

    if thread.is_alive():
        summary["availability_warning"] = "Headless validation timed out before the browser checks completed."
        summary["available"] = False
        return summary

    if "error" in error_holder:
        summary["availability_warning"] = f"Headless validation could not start: {error_holder['error']}"
        summary["available"] = False
        return summary

    return result_holder.get("value") or summary


def run_threaded_capture_job(
    *,
    name: str,
    timeout_seconds: int,
    worker: Callable[[], Any],
) -> Any:
    result_holder: dict[str, Any] = {}
    error_holder: dict[str, Exception] = {}

    def target() -> None:
        try:
            result_holder["value"] = worker()
        except Exception as exc:  # noqa: BLE001
            error_holder["error"] = exc

    thread = threading.Thread(target=target, name=name, daemon=True)
    thread.start()
    thread.join(timeout=max(1, int(timeout_seconds)))

    if thread.is_alive():
        raise TimeoutError(f"{name} timed out after {timeout_seconds}s")

    if "error" in error_holder:
        raise error_holder["error"]

    return result_holder.get("value")


def validate_rendered_artifact(
    *,
    html: str,
    plan: dict[str, Any],
    requested_section_count: int | None,
    engine_override: str | None,
) -> tuple[str, list[dict[str, Any]], int, dict[str, Any]]:
    warnings: list[dict[str, Any]] = []
    quality_score = 100
    critical_failure = False
    failure_reasons: list[str] = []
    sanitized_html, placeholder_counts = sanitize_rendered_html(html)
    headless_summary = run_headless_artifact_validation(sanitized_html)
    render_structure = inspect_rendered_html_structure(sanitized_html)

    removed_total = sum(placeholder_counts.values())
    if removed_total:
        for token, count in placeholder_counts.items():
            if not count:
                continue
            warnings.append(
                {
                    "type": f"{token}_detected",
                    "message": f"Removed {count} visible '{token}' placeholder token(s) from the rendered output.",
                    "count": count,
                }
            )
        warnings.append(
            {
                "type": "render_sanitized",
                "message": (
                    "Removed visible placeholder tokens from the rendered output "
                    f"(undefined={placeholder_counts['undefined']}, NaN={placeholder_counts['nan']}, null={placeholder_counts['null']})."
                ),
            }
        )
        quality_score -= min(removed_total * 6, 18)

    empty_sections = plan_empty_sections(plan)
    if empty_sections:
        warnings.append(
            {
                "type": "empty_section",
                "message": f"Planner returned empty sections: {', '.join(str(index) for index in empty_sections)}.",
                "sections": empty_sections,
                "count": len(empty_sections),
            }
        )
        quality_score -= min(len(empty_sections) * 10, 20)

    if requested_section_count is not None and len(plan.get("sections", [])) != requested_section_count:
        warnings.append(
            {
                "type": "section_count_mismatch",
                "message": (
                    f"Requested {requested_section_count} sections, but the plan rendered {len(plan.get('sections', []))}."
                ),
                "requested": requested_section_count,
                "rendered": len(plan.get("sections", [])),
            }
        )
        quality_score -= 8

    if engine_override and not plan_honors_engine_override(plan, engine_override):
        warnings.append(
            {
                "type": "engine_override_mismatch",
                "message": f"The final plan did not honor the requested engine override {engine_override}.",
            }
        )
        quality_score -= 20

    chart_expected = describe_chart_expectation(plan)
    if chart_expected and render_structure["chart_surface_count"] == 0:
        warnings.append(
            {
                "type": "chart_surface_missing",
                "message": "This plan expected a chart-capable component, but the rendered artifact exposes no obvious chart surface.",
                "canvas_count": render_structure["canvas_count"],
                "chart_surface_count": render_structure["chart_surface_count"],
            }
        )
        quality_score -= 12

    if render_structure["image_missing_src_count"]:
        warnings.append(
            {
                "type": "image_src_missing",
                "message": f"{render_structure['image_missing_src_count']} image(s) rendered without a usable src attribute.",
                "count": render_structure["image_missing_src_count"],
            }
        )
        quality_score -= min(render_structure["image_missing_src_count"] * 6, 18)

    if render_structure["image_placeholder_src_count"]:
        warnings.append(
            {
                "type": "image_placeholder_src",
                "message": f"{render_structure['image_placeholder_src_count']} image src value(s) still contain placeholder tokens.",
                "count": render_structure["image_placeholder_src_count"],
            }
        )
        quality_score -= min(render_structure["image_placeholder_src_count"] * 6, 18)

    if render_structure["inline_event_handler_count"]:
        warnings.append(
            {
                "type": "inline_event_handler",
                "message": (
                    f"{render_structure['inline_event_handler_count']} inline event handler(s) remain in the artifact source. "
                    "Prefer native browser behavior or addEventListener-style wiring over onclick/onchange handlers."
                ),
                "count": render_structure["inline_event_handler_count"],
            }
        )
        quality_score -= min(render_structure["inline_event_handler_count"] * 2, 16)

    if render_structure["large_z_index_count"]:
        warnings.append(
            {
                "type": "large_z_index_stack",
                "message": (
                    f"{render_structure['large_z_index_count']} large z-index declaration(s) were detected "
                    f"(max z-index {render_structure['max_z_index']}). Prefer native top-layer primitives when overlays are needed."
                ),
                "count": render_structure["large_z_index_count"],
                "max_z_index": render_structure["max_z_index"],
            }
        )
        quality_score -= min(render_structure["large_z_index_count"] * 4, 12)

    if render_structure["fixed_position_count"]:
        warnings.append(
            {
                "type": "fixed_position_detected",
                "message": (
                    f"{render_structure['fixed_position_count']} fixed-position declaration(s) were detected. "
                    "These often behave poorly in embedded iframe contexts."
                ),
                "count": render_structure["fixed_position_count"],
            }
        )
        quality_score -= min(render_structure["fixed_position_count"] * 2, 10)

    if render_structure["viewport_unit_count"]:
        warnings.append(
            {
                "type": "viewport_unit_detected",
                "message": (
                    f"{render_structure['viewport_unit_count']} vh/vw viewport unit declaration(s) were detected. "
                    "Container-relative sizing is safer for embeds."
                ),
                "count": render_structure["viewport_unit_count"],
            }
        )
        quality_score -= min(render_structure["viewport_unit_count"] * 2, 10)

    if render_structure["gradient_count"]:
        warnings.append(
            {
                "type": "design_gradient_detected",
                "message": (
                    f"{render_structure['gradient_count']} gradient declaration(s) were detected. "
                    "The Visualizer design system prefers flat surfaces with no gradients."
                ),
                "count": render_structure["gradient_count"],
            }
        )
        quality_score -= min(render_structure["gradient_count"] * 2, 10)

    if render_structure["shadow_count"]:
        warnings.append(
            {
                "type": "design_shadow_detected",
                "message": (
                    f"{render_structure['shadow_count']} shadow declaration(s) were detected. "
                    "The Visualizer design system prefers flat surfaces with no drop shadows."
                ),
                "count": render_structure["shadow_count"],
            }
        )
        quality_score -= min(render_structure["shadow_count"] * 2, 10)

    if render_structure["blur_glow_count"]:
        warnings.append(
            {
                "type": "design_blur_glow_detected",
                "message": (
                    f"{render_structure['blur_glow_count']} blur/glow effect declaration(s) were detected. "
                    "The Visualizer design system avoids blur, glow, and neon effects."
                ),
                "count": render_structure["blur_glow_count"],
            }
        )
        quality_score -= min(render_structure["blur_glow_count"] * 2, 8)

    if render_structure["heavy_font_weight_count"]:
        warnings.append(
            {
                "type": "design_font_weight_violation",
                "message": (
                    f"{render_structure['heavy_font_weight_count']} font-weight declaration(s) above 500 were detected. "
                    "The Visualizer design system allows only 400 and 500."
                ),
                "count": render_structure["heavy_font_weight_count"],
            }
        )
        quality_score -= min(render_structure["heavy_font_weight_count"] * 2, 10)

    if render_structure["uppercase_text_transform_count"]:
        warnings.append(
            {
                "type": "design_uppercase_transform_detected",
                "message": (
                    f"{render_structure['uppercase_text_transform_count']} uppercase text-transform declaration(s) were detected. "
                    "The Visualizer design system prefers sentence case."
                ),
                "count": render_structure["uppercase_text_transform_count"],
            }
        )
        quality_score -= min(render_structure["uppercase_text_transform_count"] * 2, 8)

    if render_structure["disallowed_hardcoded_color_count"]:
        warnings.append(
            {
                "type": "design_hardcoded_color_detected",
                "message": (
                    f"{render_structure['disallowed_hardcoded_color_count']} hardcoded hex color declaration(s) were detected outside the approved Visualizer ramp set. "
                    "Prefer shared CSS tokens or approved ramp values."
                ),
                "count": render_structure["disallowed_hardcoded_color_count"],
            }
        )
        quality_score -= min(render_structure["disallowed_hardcoded_color_count"], 10)

    if render_structure["window_top_ref_count"] or render_structure["window_parent_ref_count"]:
        warnings.append(
            {
                "type": "cross_window_reference_detected",
                "message": (
                    "The artifact references window.top or window.parent. "
                    "That is risky for embeddable artifacts and should be removed."
                ),
                "window_top_ref_count": render_structure["window_top_ref_count"],
                "window_parent_ref_count": render_structure["window_parent_ref_count"],
            }
        )
        quality_score -= 16
        critical_failure = True
        failure_reasons.append("Artifact references window.top or window.parent, which breaks embed safety.")

    if chart_expected and render_structure["viewport_media_query_count"] and not render_structure["container_query_count"]:
        warnings.append(
            {
                "type": "container_query_missing",
                "message": (
                    "This artifact relies on viewport width media queries without any container query support. "
                    "The prompt stack prefers container-aware adaptation for chart and card internals."
                ),
                "viewport_media_query_count": render_structure["viewport_media_query_count"],
            }
        )
        quality_score -= 4

    if chart_expected and not render_structure["tabular_nums_count"]:
        warnings.append(
            {
                "type": "numeric_typography_missing",
                "message": (
                    "This artifact appears data-heavy but does not expose tabular number styling. "
                    "The prompt stack prefers tabular-nums for dashboards, KPIs, and tables."
                ),
            }
        )
        quality_score -= 4

    if render_structure["image_count"] + render_structure["iframe_count"] >= 3 and not render_structure["lazy_media_count"]:
        warnings.append(
            {
                "type": "lazy_loading_missing",
                "message": (
                    "This artifact includes multiple images or iframes without any loading='lazy' usage. "
                    "Consider lazy-loading non-critical media for better initial performance."
                ),
                "image_count": render_structure["image_count"],
                "iframe_count": render_structure["iframe_count"],
            }
        )
        quality_score -= 4

    narrative_contract_warnings, narrative_contract_penalty = evaluate_narrative_contract_render(
        plan,
        {"headless_validation": headless_summary},
    )
    if narrative_contract_warnings:
        warnings.extend(narrative_contract_warnings)
        quality_score -= narrative_contract_penalty

    source_contract_warnings, source_contract_penalty = evaluate_source_contract_render(plan, sanitized_html)
    if source_contract_warnings:
        warnings.extend(source_contract_warnings)
        quality_score -= source_contract_penalty

    if plan_uses_engine(plan, {"22"}) and not render_structure["native_details_count"]:
        warnings.append(
            {
                "type": "native_disclosure_missing",
                "message": (
                    "The plan uses the FAQ/disclosure engine but the source does not expose native details support. "
                    "Prefer details/summary for expandable reference content."
                ),
            }
        )
        quality_score -= 6

    if headless_summary.get("enabled") and not headless_summary.get("available"):
        warnings.append(
            {
                "type": "headless_validation_unavailable",
                "message": headless_summary.get("availability_warning")
                or "Headless validation is enabled but unavailable in this runtime.",
            }
        )
        quality_score -= 10

    headless_fatal_issues: list[str] = []
    for issue in headless_summary.get("issues") or []:
        severity, penalty, fatal = classify_headless_issue(str(issue))
        warnings.append(
            {
                "type": "headless_validation_failure",
                "severity": severity,
                "message": issue,
            }
        )
        quality_score -= penalty
        if fatal:
            headless_fatal_issues.append(str(issue))
    if headless_fatal_issues:
        critical_failure = True
        failure_reasons.extend(headless_fatal_issues)
        quality_score = 0

    if critical_failure:
        quality_score = 0
    quality_score = max(0, quality_score)
    validation_summary = {
        "placeholder_counts": placeholder_counts,
        "empty_sections": empty_sections,
        "requested_section_count": requested_section_count,
        "rendered_section_count": len(plan.get("sections", [])),
        "engine_override": engine_override,
        "chart_expected": chart_expected,
        "render_structure": render_structure,
        "headless_validation": headless_summary,
        "critical_failure": critical_failure,
        "failure_reasons": list(dict.fromkeys(reason for reason in failure_reasons if reason)),
        "warning_count": len(warnings),
    }
    return sanitized_html, warnings, quality_score, validation_summary


def apply_prompt_stack_plan_audit(
    *,
    prompt: str,
    template_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    generation_mode: str | None,
    plan: dict[str, Any],
    payload: dict[str, Any] | None,
    warnings: list[dict[str, Any]],
    quality_score: int,
    render_validation_summary: dict[str, Any],
) -> tuple[list[dict[str, Any]], int, dict[str, Any], list[str], dict[str, Any]]:
    active_modules = list((payload or {}).get("active_prompt_modules") or [])
    if not active_modules:
        active_modules = resolve_active_prompt_modules(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode=generation_mode,
        )

    plan_audit = (payload or {}).get("plan_quality_audit")
    if not isinstance(plan_audit, dict):
        uploaded_heading_count = 0
        if payload:
            uploaded_heading_count = int(
                payload.get("uploaded_heading_count_preserved") or payload.get("uploaded_heading_count_detected") or 0
            )
        plan_audit = audit_plan_against_prompt_stack(
            plan=plan,
            prompt=prompt,
            template_hint=template_hint,
            structured_data=structured_data,
            uploaded_heading_count=uploaded_heading_count,
            active_modules=active_modules,
        )

    merged_warnings = list(warnings)
    for finding in plan_audit.get("warnings") or []:
        merged_warnings.append(
            {
                "type": finding.get("type") or "plan_quality_audit",
                "severity": finding.get("severity") or "medium",
                "message": finding.get("message") or "The plan quality audit flagged a prompt-stack issue.",
                "penalty": finding.get("penalty"),
            }
        )

    merged_quality_score = max(0, quality_score - int(plan_audit.get("score_penalty") or 0))
    updated_summary = dict(render_validation_summary)
    updated_summary["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
    updated_summary["active_prompt_modules"] = active_modules
    updated_summary["plan_quality_audit"] = plan_audit
    return merged_warnings, merged_quality_score, updated_summary, active_modules, plan_audit


def enforce_render_validation_or_raise(
    warnings: list[dict[str, Any]],
    quality_score: int,
    render_validation_summary: dict[str, Any],
    *,
    allow_low_quality_publish: bool = False,
) -> None:
    minimum_quality = max(0, int(getattr(SETTINGS, "minimum_publish_quality_score", 75) or 75))
    critical_messages = [
        str(warning.get("message"))
        for warning in warnings
        if str(warning.get("severity") or "").lower() == "critical" and warning.get("message")
    ]
    critical_messages.extend(str(reason) for reason in render_validation_summary.get("failure_reasons") or [])
    fatal_failure = bool(render_validation_summary.get("critical_failure")) or bool(critical_messages)

    if allow_low_quality_publish:
        if not fatal_failure:
            return
    else:
        critical = fatal_failure or quality_score <= 0 or quality_score < minimum_quality
        if not critical:
            return

    if quality_score < minimum_quality:
        critical_messages.append(
            f"Quality score {quality_score} is below the minimum publish threshold of {minimum_quality}."
        )
    deduped = list(dict.fromkeys(message for message in critical_messages if message))
    detail = "; ".join(deduped[:8]) or "Render validation failed."
    raise ValueError(f"Render validation failed. {detail}")


class CriticTimeoutError(TimeoutError):
    """Raised when the editorial critic exceeds its allowed wall-clock budget."""


async def call_anthropic(
    system: str,
    user_message: str,
    *,
    max_tokens: int,
    temperature: float = 0.3,
    model: str | None = None,
    step_name: str,
    timeout_seconds: float | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    routing_reason = "explicit_model" if model else "default_model"
    if model:
        resolved_model = (model or SETTINGS.anthropic_model).strip() or SETTINGS.anthropic_model
    elif SETTINGS.anthropic_smart_routing_enabled:
        resolved_model, routing_reason = route_anthropic_model(
            step_name=step_name,
            user_message=user_message,
            default_model=SETTINGS.anthropic_model,
            fast_model=SETTINGS.anthropic_fast_model,
        )
    else:
        resolved_model = SETTINGS.anthropic_model
    request_body = {
        "model": resolved_model,
        "max_tokens": max_tokens,
        "temperature": temperature,
        "system": system,
        "messages": [{"role": "user", "content": user_message}],
    }

    headers = {
        "x-api-key": SETTINGS.anthropic_api_key,
        "anthropic-version": SETTINGS.anthropic_version,
        "content-type": "application/json",
    }

    started_at = datetime.now(timezone.utc)
    started_perf = time.perf_counter()
    async with httpx.AsyncClient(timeout=timeout_seconds or SETTINGS.anthropic_request_timeout_seconds) as client:
        response = await client.post(
            "https://api.anthropic.com/v1/messages",
            headers=headers,
            json=request_body,
        )
        response.raise_for_status()
        payload = response.json()

    completed_at = datetime.now(timezone.utc)
    usage = serializable_usage(payload.get("usage"))
    provider_event = {
        "provider": "anthropic",
        "step_name": step_name,
        "model": payload.get("model") or resolved_model,
        "routing_reason": routing_reason,
        "api_key_label": SETTINGS.anthropic_api_key_label,
        "workspace_id": None,
        "service_tier": payload.get("service_tier"),
        "provider_request_id": response.headers.get("request-id") or response.headers.get("anthropic-request-id"),
        "usage_bucket_date": started_at.date().isoformat(),
        "started_at": started_at.isoformat(),
        "completed_at": completed_at.isoformat(),
        "latency_ms": int((time.perf_counter() - started_perf) * 1000),
        "raw_usage": usage,
        "formula_cost_usd": estimate_anthropic_cost(usage),
    }
    tracker = TOKEN_TRACKER_VAR.get()
    if tracker is not None:
        tracker.record(
            model=str(provider_event.get("model") or resolved_model),
            input_tokens=usage.get("input_tokens", 0),
            output_tokens=usage.get("output_tokens", 0),
            step_name=step_name,
            latency_ms=provider_event.get("latency_ms"),
        )
    return payload, provider_event


def combine_usage(*payloads: dict[str, Any]) -> dict[str, int] | None:
    totals: dict[str, int] = {}
    found = False
    for payload in payloads:
        usage = payload.get("usage") if isinstance(payload, dict) else None
        if not isinstance(usage, dict):
            continue
        found = True
        for key, value in usage.items():
            if isinstance(value, int):
                totals[key] = totals.get(key, 0) + value
    return totals if found else None


def sanitize_telemetry_context(raw: dict[str, Any] | None) -> dict[str, str | None]:
    if not isinstance(raw, dict):
        return {
            "actor_key": None,
            "source_surface": "mcp",
            "source_lane": None,
            "source_mode": None,
            "user_id": None,
        }

    def clean(key: str) -> str | None:
        value = raw.get(key)
        if value is None:
            return None
        text = str(value).strip()
        return text[:160] if text else None

    return {
        "actor_key": clean("actor_key"),
        "source_surface": clean("source_surface") or "mcp",
        "source_lane": clean("source_lane"),
        "source_mode": clean("source_mode"),
        "user_id": clean("user_id"),
    }


def provider_usage_fields(usage: dict[str, Any] | None) -> dict[str, int]:
    if not isinstance(usage, dict):
        return {
            "input_tokens": 0,
            "output_tokens": 0,
            "cache_creation_input_tokens": 0,
            "cache_read_input_tokens": 0,
        }

    def read_int(key: str) -> int:
        value = usage.get(key)
        return int(value) if isinstance(value, int) else 0

    return {
        "input_tokens": read_int("input_tokens"),
        "output_tokens": read_int("output_tokens"),
        "cache_creation_input_tokens": read_int("cache_creation_input_tokens"),
        "cache_read_input_tokens": read_int("cache_read_input_tokens"),
    }


def now_utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


async def insert_supabase_rows(table: str, rows: list[dict[str, Any]]) -> None:
    if not SETTINGS.has_supabase_telemetry or not rows:
        return

    headers = {
        "apikey": SETTINGS.supabase_service_key,
        "authorization": f"Bearer {SETTINGS.supabase_service_key}",
        "content-type": "application/json",
        "prefer": "return=minimal",
    }

    async with httpx.AsyncClient(timeout=30) as client:
        response = await client.post(
            f"{SETTINGS.supabase_url}/rest/v1/{table}",
            headers=headers,
            json=rows,
        )
        response.raise_for_status()


async def fetch_supabase_rows(
    table: str,
    *,
    select: str = "*",
    params: dict[str, Any] | None = None,
) -> list[dict[str, Any]]:
    if not SETTINGS.has_supabase_telemetry:
        return []

    headers = {
        "apikey": SETTINGS.supabase_service_key,
        "authorization": f"Bearer {SETTINGS.supabase_service_key}",
    }
    query_params = {"select": select}
    if isinstance(params, dict):
        query_params.update({key: value for key, value in params.items() if value is not None})

    async with httpx.AsyncClient(timeout=30) as client:
        response = await client.get(
            f"{SETTINGS.supabase_url}/rest/v1/{table}",
            headers=headers,
            params=query_params,
        )
        response.raise_for_status()
        payload = response.json()
        return payload if isinstance(payload, list) else []


async def sync_generation_event_to_supabase(
    *,
    generation_event: dict[str, Any],
    provider_events: list[dict[str, Any]],
) -> None:
    if not SETTINGS.has_supabase_telemetry:
        return

    run_row = {
        "id": generation_event["event_id"],
        "source_surface": generation_event.get("source_surface") or "mcp",
        "actor_key": generation_event.get("actor_key"),
        "user_id": generation_event.get("user_id"),
        "request_id": generation_event.get("request_id"),
        "client_id": generation_event.get("client_id"),
        "identity_mode": generation_event.get("identity_mode"),
        "prompt_hash": generation_event.get("prompt_hash"),
        "prompt_preview": generation_event.get("prompt_preview"),
        "prompt_length": generation_event.get("prompt_length") or 0,
        "template_hint": generation_event.get("template_hint"),
        "theme_hint": generation_event.get("theme_hint"),
        "structured_data_present": generation_event.get("structured_data_present") or False,
        "structured_data_keys": generation_event.get("structured_data_keys") or [],
        "engine_override_requested": generation_event.get("engine_override_requested"),
        "section_count_requested": generation_event.get("section_count_requested"),
        "generation_mode": generation_event.get("generation_mode"),
        "interaction_mode": generation_event.get("interaction_mode"),
        "source_lane": generation_event.get("source_lane"),
        "source_mode": generation_event.get("source_mode"),
        "uploaded_content_present": generation_event.get("uploaded_content_present") or False,
        "uploaded_length": generation_event.get("uploaded_length") or 0,
        "upload_ingest_decision": generation_event.get("upload_ingest_decision") or "not_applicable",
        "upload_ingest_category": generation_event.get("upload_ingest_category"),
        "upload_preservation_level": generation_event.get("upload_preservation_level"),
        "upload_policy_profile": generation_event.get("upload_policy_profile"),
        "upload_reconstruction_status": generation_event.get("upload_reconstruction_status") or "not_applicable",
        "upload_rejection_reason": generation_event.get("upload_rejection_reason"),
        "upload_findings_counts": generation_event.get("upload_findings_counts") or {},
        "upload_external_domains": generation_event.get("upload_external_domains") or [],
        "upload_analysis_summary": generation_event.get("upload_analysis_summary") or {},
        "artifact_id": generation_event.get("artifact_id"),
        "artifact_family_key": generation_event.get("artifact_family_key"),
        "created_category": generation_event.get("created_category"),
        "title": generation_event.get("title"),
        "description": generation_event.get("description"),
        "template_match": generation_event.get("template_match"),
        "document_format": generation_event.get("document_format"),
        "wrapper_engine_id": generation_event.get("wrapper_engine_id"),
        "theme_slug": generation_event.get("theme_slug"),
        "theme_name": generation_event.get("theme_name"),
        "warnings": generation_event.get("warnings") or [],
        "quality_score": generation_event.get("quality_score"),
        "render_validation_summary": generation_event.get("render_validation_summary") or {},
        "section_count": generation_event.get("section_count") or 0,
        "section_titles": generation_event.get("section_titles") or [],
        "storage": generation_event.get("storage"),
        "local_path": generation_event.get("local_path"),
        "object_key": generation_event.get("object_key"),
        "preview_url": generation_event.get("preview_url"),
        "download_url": generation_event.get("preview_url"),
        "planner_model": generation_event.get("planner_model"),
        "planner_repaired": generation_event.get("planner_repaired") or False,
        "retry_strategy": generation_event.get("retry_strategy"),
        "raw_usage": generation_event.get("usage") or {},
        "formula_cost_usd": generation_event.get("estimated_cost_usd"),
        "reconciled_cost_usd": None,
        "cost_reconciliation_status": "unreconciled" if provider_events else "not_available",
        "planning_ms": (generation_event.get("durations_ms") or {}).get("planning"),
        "assembly_ms": (generation_event.get("durations_ms") or {}).get("assembling"),
        "uploading_ms": (generation_event.get("durations_ms") or {}).get("uploading"),
        "total_ms": (generation_event.get("durations_ms") or {}).get("total"),
        "status": "succeeded",
        "failure_stage": None,
        "failure_type": None,
        "failure_message": None,
        "created_at": generation_event.get("timestamp_utc") or now_utc_iso(),
        "completed_at": generation_event.get("timestamp_utc") or now_utc_iso(),
    }

    engine_rows = [
        {
            "generation_run_id": generation_event["event_id"],
            "artifact_id": generation_event.get("artifact_id"),
            "engine_id": engine.get("engine_id"),
            "engine_name": engine.get("engine_name"),
            "engine_category": engine.get("engine_category"),
            "usage_role": engine.get("usage_role"),
            "instance_id": engine.get("instance_id"),
            "section_id": engine.get("section_id"),
            "section_title": engine.get("section_title"),
        }
        for engine in generation_event.get("engines_used", [])
    ]

    provider_rows = []
    for provider_event in provider_events:
        usage = provider_usage_fields(provider_event.get("raw_usage"))
        provider_rows.append(
            {
                "generation_run_id": generation_event["event_id"],
                "source_surface": generation_event.get("source_surface") or "mcp",
                "actor_key": generation_event.get("actor_key"),
                "user_id": generation_event.get("user_id"),
                "artifact_id": generation_event.get("artifact_id"),
                "provider": provider_event.get("provider") or "anthropic",
                "step_name": provider_event.get("step_name"),
                "model": provider_event.get("model"),
                "api_key_label": provider_event.get("api_key_label"),
                "workspace_id": provider_event.get("workspace_id"),
                "service_tier": provider_event.get("service_tier"),
                "provider_request_id": provider_event.get("provider_request_id"),
                "usage_bucket_date": provider_event.get("usage_bucket_date"),
                "started_at": provider_event.get("started_at"),
                "completed_at": provider_event.get("completed_at"),
                "latency_ms": provider_event.get("latency_ms"),
                "input_tokens": usage["input_tokens"],
                "output_tokens": usage["output_tokens"],
                "cache_creation_input_tokens": usage["cache_creation_input_tokens"],
                "cache_read_input_tokens": usage["cache_read_input_tokens"],
                "raw_usage": provider_event.get("raw_usage") or {},
                "formula_cost_usd": provider_event.get("formula_cost_usd"),
                "reconciled_cost_usd": None,
                "reconciliation_status": "unreconciled",
            }
        )

    finding_rows = [
        {
            "generation_run_id": generation_event["event_id"],
            "severity": finding.get("severity"),
            "finding_code": finding.get("code"),
            "detail": finding.get("detail"),
            "evidence": finding.get("evidence"),
        }
        for finding in generation_event.get("upload_findings", [])
    ]

    await insert_supabase_rows("generation_runs", [run_row])
    if engine_rows:
        await insert_supabase_rows("generation_run_engines", engine_rows)
    if provider_rows:
        await insert_supabase_rows("provider_usage_events", provider_rows)
    if finding_rows:
        await insert_supabase_rows("upload_analysis_findings", finding_rows)


def request_client_ip_hash(request: Request) -> str | None:
    raw_ip = (
        request.headers.get("cf-connecting-ip")
        or request.headers.get("x-forwarded-for", "").split(",")[0].strip()
        or request.client.host if request.client else ""
    )
    normalized = compact_whitespace(str(raw_ip or ""))
    if not normalized:
        return None
    return hashlib.sha256(normalized.encode("utf-8")).hexdigest()


ALLOWED_VIEWER_KINDS = {
    "owner",
    "recipient",
    "library_member",
    "audience",
    "site_visitor",
    "unknown",
}


def sanitize_uuid_text(raw: Any) -> str | None:
    value = compact_whitespace(str(raw or ""))
    if not value:
        return None
    with contextlib.suppress(Exception):
        return str(uuid.UUID(value))
    return None


def normalize_viewer_kind(raw: Any) -> str | None:
    value = compact_whitespace(str(raw or "")).lower().replace("-", "_").replace(" ", "_")
    if value in ALLOWED_VIEWER_KINDS:
        return value
    return None


def sanitize_engagement_metadata(raw: Any) -> dict[str, Any]:
    if not isinstance(raw, dict):
        return {}

    cleaned: dict[str, Any] = {}
    for key, value in raw.items():
        normalized_key = compact_whitespace(str(key or "")).lower().replace(" ", "_")
        if not normalized_key:
            continue
        if isinstance(value, (str, int, float, bool)) or value is None:
            cleaned[normalized_key] = value
        elif isinstance(value, list):
            cleaned[normalized_key] = [
                item
                for item in value
                if isinstance(item, (str, int, float, bool)) or item is None
            ][:24]
        elif isinstance(value, dict):
            nested: dict[str, Any] = {}
            for nested_key, nested_value in value.items():
                normalized_nested_key = compact_whitespace(str(nested_key or "")).lower().replace(" ", "_")
                if not normalized_nested_key:
                    continue
                if isinstance(nested_value, (str, int, float, bool)) or nested_value is None:
                    nested[normalized_nested_key] = nested_value
            if nested:
                cleaned[normalized_key] = nested
    return cleaned


def sanitize_view_url(raw: Any) -> str | None:
    value = compact_whitespace(str(raw or ""))
    if not value:
        return None

    try:
        parsed = urlparse(value)
    except Exception:
        return value.split("#", 1)[0].split("?", 1)[0] or None

    clean_path = parsed.path or "/"
    if parsed.scheme and parsed.netloc:
        return urlunparse((parsed.scheme, parsed.netloc, clean_path, "", "", ""))

    return clean_path or None


def build_view_event_payload(
    *,
    request: Request,
    body: dict[str, Any],
) -> dict[str, Any]:
    timestamp = now_utc_iso()
    referrer = sanitize_view_url(body.get("referrer") or request.headers.get("referer") or "")
    parsed_referrer = urlparse(referrer) if referrer else None
    viewport = body.get("viewport") if isinstance(body.get("viewport"), dict) else {}
    path = compact_whitespace(str(body.get("path") or ""))
    url = sanitize_view_url(body.get("url") or "")
    host = urlparse(url).netloc if url else compact_whitespace(str(request.headers.get("origin") or ""))
    surface = compact_whitespace(str(body.get("surface") or "artifact")).lower() or "artifact"
    event_name = compact_whitespace(str(body.get("event") or "view")).lower() or "view"
    session_id = compact_whitespace(str(body.get("session_id") or body.get("sessionId") or ""))
    artifact_id = compact_whitespace(str(body.get("artifact_id") or body.get("artifactId") or ""))
    title = compact_whitespace(str(body.get("title") or ""))
    source = compact_whitespace(str(body.get("source") or "browser_beacon")) or "browser_beacon"
    share_invite_id = sanitize_uuid_text(body.get("share_invite_id") or body.get("shareInviteId"))
    actor_user_id = sanitize_uuid_text(body.get("actor_user_id") or body.get("actorUserId"))
    metadata = sanitize_engagement_metadata(body.get("metadata"))
    viewer_kind = normalize_viewer_kind(body.get("viewer_kind") or body.get("viewerKind"))
    if not viewer_kind:
        if surface == "site":
            viewer_kind = "site_visitor"
        elif share_invite_id:
            viewer_kind = "recipient"
        elif source == "iterate_shell_library":
            viewer_kind = "owner"
        elif surface == "artifact":
            viewer_kind = "audience"
        else:
            viewer_kind = "unknown"

    def read_int(value: Any) -> int | None:
        try:
            return max(0, int(value))
        except (TypeError, ValueError):
            return None

    return {
        "id": str(uuid.uuid4()),
        "event_name": event_name,
        "surface": surface,
        "source": source,
        "artifact_id": artifact_id or None,
        "share_invite_id": share_invite_id,
        "actor_user_id": actor_user_id,
        "viewer_kind": viewer_kind,
        "session_id": session_id or None,
        "path": path or None,
        "url": url or None,
        "host": host or None,
        "title": title or None,
        "referrer": referrer or None,
        "referrer_host": parsed_referrer.netloc if parsed_referrer and parsed_referrer.netloc else None,
        "metadata": metadata,
        "viewport_w": read_int(viewport.get("w") or viewport.get("width")),
        "viewport_h": read_int(viewport.get("h") or viewport.get("height")),
        "user_agent": compact_whitespace(str(request.headers.get("user-agent") or "")) or None,
        "ip_hash": request_client_ip_hash(request),
        "occurred_at": timestamp,
        "created_at": timestamp,
    }


async def sync_view_event_to_supabase(event: dict[str, Any]) -> None:
    if not SETTINGS.has_supabase_telemetry:
        return
    await insert_supabase_rows("view_events", [event])


def build_engagement_event_payload(
    *,
    request: Request,
    body: dict[str, Any],
) -> dict[str, Any]:
    timestamp = now_utc_iso()
    referrer = sanitize_view_url(body.get("referrer") or request.headers.get("referer") or "")
    parsed_referrer = urlparse(referrer) if referrer else None
    path = compact_whitespace(str(body.get("path") or ""))
    url = sanitize_view_url(body.get("url") or "")
    host = urlparse(url).netloc if url else compact_whitespace(str(request.headers.get("origin") or ""))
    surface = compact_whitespace(str(body.get("surface") or "artifact")).lower() or "artifact"
    source = compact_whitespace(str(body.get("source") or "browser_beacon")) or "browser_beacon"
    event_name = compact_whitespace(str(body.get("event") or body.get("event_name") or "interaction")).lower()
    event_name = event_name or "interaction"
    session_id = compact_whitespace(str(body.get("session_id") or body.get("sessionId") or ""))
    artifact_id = compact_whitespace(str(body.get("artifact_id") or body.get("artifactId") or ""))
    event_key = compact_whitespace(str(body.get("event_key") or body.get("eventKey") or "")).lower().replace(" ", "_")
    event_label = compact_whitespace(str(body.get("event_label") or body.get("eventLabel") or ""))
    title = compact_whitespace(str(body.get("title") or ""))
    share_invite_id = sanitize_uuid_text(body.get("share_invite_id") or body.get("shareInviteId"))
    actor_user_id = sanitize_uuid_text(body.get("actor_user_id") or body.get("actorUserId"))
    metadata = sanitize_engagement_metadata(body.get("metadata"))
    viewer_kind = normalize_viewer_kind(body.get("viewer_kind") or body.get("viewerKind"))
    if not viewer_kind:
        if surface == "site":
            viewer_kind = "site_visitor"
        elif share_invite_id:
            viewer_kind = "recipient"
        elif source == "iterate_shell_library":
            viewer_kind = "owner"
        elif surface == "artifact":
            viewer_kind = "audience"
        else:
            viewer_kind = "unknown"

    return {
        "id": str(uuid.uuid4()),
        "event_name": event_name,
        "surface": surface,
        "source": source,
        "artifact_id": artifact_id or None,
        "share_invite_id": share_invite_id,
        "actor_user_id": actor_user_id,
        "viewer_kind": viewer_kind,
        "session_id": session_id or None,
        "event_key": event_key or None,
        "event_label": event_label or None,
        "path": path or None,
        "url": url or None,
        "host": host or None,
        "referrer": referrer or None,
        "referrer_host": parsed_referrer.netloc if parsed_referrer and parsed_referrer.netloc else None,
        "metadata": metadata,
        "occurred_at": timestamp,
        "created_at": timestamp,
    }


def build_engagement_event_from_view_event(view_event: dict[str, Any]) -> dict[str, Any]:
    return {
        "id": str(uuid.uuid4()),
        "event_name": view_event.get("event_name") or "view",
        "surface": view_event.get("surface") or "artifact",
        "source": view_event.get("source"),
        "artifact_id": view_event.get("artifact_id"),
        "share_invite_id": view_event.get("share_invite_id"),
        "actor_user_id": view_event.get("actor_user_id"),
        "viewer_kind": view_event.get("viewer_kind") or "unknown",
        "session_id": view_event.get("session_id"),
        "event_key": None,
        "event_label": view_event.get("title"),
        "path": view_event.get("path"),
        "url": view_event.get("url"),
        "host": view_event.get("host"),
        "referrer": view_event.get("referrer"),
        "referrer_host": view_event.get("referrer_host"),
        "metadata": view_event.get("metadata") or {},
        "occurred_at": view_event.get("occurred_at") or now_utc_iso(),
        "created_at": view_event.get("created_at") or now_utc_iso(),
    }


async def sync_engagement_event_to_supabase(event: dict[str, Any]) -> None:
    if not SETTINGS.has_supabase_telemetry:
        return
    await insert_supabase_rows("engagement_events", [event])


async def sync_failure_event_to_supabase(failure_event: dict[str, Any]) -> None:
    if not SETTINGS.has_supabase_telemetry:
        return

    provider_events = merge_provider_events(failure_event.get("provider_events") or [])
    tracker = token_tracker_from_provider_events(provider_events)

    row = {
        "id": failure_event["event_id"],
        "source_surface": failure_event.get("source_surface") or "mcp",
        "actor_key": failure_event.get("actor_key"),
        "user_id": failure_event.get("user_id"),
        "request_id": failure_event.get("request_id"),
        "client_id": failure_event.get("client_id"),
        "identity_mode": failure_event.get("identity_mode"),
        "prompt_hash": failure_event.get("prompt_hash"),
        "prompt_preview": failure_event.get("prompt_preview"),
        "prompt_length": failure_event.get("prompt_length") or 0,
        "template_hint": failure_event.get("template_hint"),
        "theme_hint": failure_event.get("theme_hint"),
        "structured_data_present": failure_event.get("structured_data_present") or False,
        "structured_data_keys": failure_event.get("structured_data_keys") or [],
        "engine_override_requested": failure_event.get("engine_override_requested"),
        "section_count_requested": failure_event.get("section_count_requested"),
        "generation_mode": failure_event.get("generation_mode"),
        "interaction_mode": failure_event.get("interaction_mode"),
        "source_lane": failure_event.get("source_lane"),
        "source_mode": failure_event.get("source_mode"),
        "uploaded_content_present": failure_event.get("uploaded_content_present") or False,
        "uploaded_length": failure_event.get("uploaded_length") or 0,
        "upload_ingest_decision": failure_event.get("upload_ingest_decision") or "not_applicable",
        "upload_ingest_category": failure_event.get("upload_ingest_category"),
        "upload_preservation_level": failure_event.get("upload_preservation_level"),
        "upload_policy_profile": failure_event.get("upload_policy_profile"),
        "upload_reconstruction_status": failure_event.get("upload_reconstruction_status") or "not_applicable",
        "upload_rejection_reason": failure_event.get("upload_rejection_reason"),
        "upload_findings_counts": failure_event.get("upload_findings_counts") or {},
        "upload_external_domains": failure_event.get("upload_external_domains") or [],
        "upload_analysis_summary": failure_event.get("upload_analysis_summary") or {},
        "artifact_id": None,
        "artifact_family_key": None,
        "created_category": failure_event.get("created_category"),
        "title": None,
        "description": None,
        "template_match": None,
        "document_format": None,
        "wrapper_engine_id": None,
        "theme_slug": None,
        "theme_name": None,
        "warnings": failure_event.get("warnings") or [],
        "quality_score": failure_event.get("quality_score"),
        "render_validation_summary": failure_event.get("render_validation_summary") or {},
        "section_count": 0,
        "section_titles": [],
        "storage": None,
        "local_path": None,
        "object_key": None,
        "preview_url": None,
        "download_url": None,
        "planner_model": None,
        "planner_repaired": False,
        "retry_strategy": None,
        "raw_usage": {},
        "formula_cost_usd": tracker.estimated_cost_usd if tracker.total_calls else None,
        "reconciled_cost_usd": None,
        "cost_reconciliation_status": "unreconciled" if provider_events else "not_available",
        "planning_ms": (failure_event.get("durations_ms") or {}).get("planning"),
        "assembly_ms": (failure_event.get("durations_ms") or {}).get("assembling"),
        "uploading_ms": (failure_event.get("durations_ms") or {}).get("uploading"),
        "total_ms": (failure_event.get("durations_ms") or {}).get("total"),
        "status": "failed",
        "failure_stage": failure_event.get("stage"),
        "failure_type": failure_event.get("error_type"),
        "failure_message": failure_event.get("error_message"),
        "created_at": failure_event.get("timestamp_utc") or now_utc_iso(),
        "completed_at": failure_event.get("timestamp_utc") or now_utc_iso(),
    }

    await insert_supabase_rows("generation_runs", [row])
    provider_rows = []
    for provider_event in provider_events:
        usage = provider_usage_fields(provider_event.get("raw_usage"))
        provider_rows.append(
            {
                "generation_run_id": failure_event["event_id"],
                "source_surface": failure_event.get("source_surface") or "mcp",
                "actor_key": failure_event.get("actor_key"),
                "user_id": failure_event.get("user_id"),
                "artifact_id": failure_event.get("artifact_id"),
                "provider": provider_event.get("provider") or "anthropic",
                "step_name": provider_event.get("step_name"),
                "model": provider_event.get("model"),
                "api_key_label": provider_event.get("api_key_label"),
                "workspace_id": provider_event.get("workspace_id"),
                "service_tier": provider_event.get("service_tier"),
                "provider_request_id": provider_event.get("provider_request_id"),
                "usage_bucket_date": provider_event.get("usage_bucket_date"),
                "started_at": provider_event.get("started_at"),
                "completed_at": provider_event.get("completed_at"),
                "latency_ms": provider_event.get("latency_ms"),
                "input_tokens": usage["input_tokens"],
                "output_tokens": usage["output_tokens"],
                "cache_creation_input_tokens": usage["cache_creation_input_tokens"],
                "cache_read_input_tokens": usage["cache_read_input_tokens"],
                "raw_usage": provider_event.get("raw_usage") or {},
                "formula_cost_usd": provider_event.get("formula_cost_usd"),
                "reconciled_cost_usd": None,
                "reconciliation_status": "unreconciled",
            }
        )
    if provider_rows:
        await insert_supabase_rows("provider_usage_events", provider_rows)
    finding_rows = [
        {
            "generation_run_id": failure_event["event_id"],
            "severity": finding.get("severity"),
            "finding_code": finding.get("code"),
            "detail": finding.get("detail"),
            "evidence": finding.get("evidence"),
        }
        for finding in failure_event.get("upload_findings", [])
    ]
    if finding_rows:
        await insert_supabase_rows("upload_analysis_findings", finding_rows)


async def repair_plan_with_anthropic(
    raw_text: str,
    parse_error: str,
    progress_callback: Callable[[int, str], None] | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if progress_callback is not None:
        with contextlib.suppress(Exception):
            progress_callback(32, "Planner retry also failed. Attempting JSON repair.")
    repair_system = (
        "You repair malformed JSON for an IterFact assembly plan. "
        "Return exactly one valid JSON object. Do not include markdown fences, explanation, or extra text."
    )
    repair_user = (
        "The previous planner output was not valid JSON.\n"
        f"Parsing error: {parse_error}\n\n"
        "Repair this into valid JSON only while preserving the intended structure and values.\n\n"
        "<planner_output>\n"
        f"{raw_text}\n"
        "</planner_output>"
    )

    payload, provider_event = await call_anthropic(
        repair_system,
        repair_user,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0,
        step_name="json_repair",
    )
    payload["_provider_events"] = [provider_event]
    plan = parse_plan_text(extract_text_blocks(payload))
    validate_plan(plan)
    return plan, payload


async def repair_section_with_anthropic(raw_text: str, parse_error: str) -> tuple[dict[str, Any], dict[str, Any]]:
    repair_system = (
        "You repair malformed JSON for one IterFact section. "
        "Return exactly one valid JSON object. Do not include markdown fences, explanation, or extra text."
    )
    repair_user = (
        "The previous section builder output was not valid JSON.\n"
        f"Parsing error: {parse_error}\n\n"
        "Repair this into valid JSON only while preserving the intended section structure and values.\n\n"
        "<section_output>\n"
        f"{raw_text}\n"
        "</section_output>"
    )

    payload, provider_event = await call_anthropic(
        repair_system,
        repair_user,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0,
        step_name="section_json_repair",
    )
    payload["_provider_events"] = [provider_event]
    section = parse_section_text(extract_text_blocks(payload))
    validate_section(section)
    return section, payload


def build_planner_guidance(prompt: str, template_hint: str | None = None) -> str:
    return build_planner_guidance_with_profiles(prompt, template_hint, None, None, None, None)


def should_apply_data_visual_guidance(
    prompt: str,
    template_hint: str | None = None,
    upload_analysis: UploadAnalysis | None = None,
) -> bool:
    lower = prompt.lower()
    keyword_hits = [
        "analytics",
        "analysis",
        "benchmark",
        "bridge",
        "calculator",
        "chart",
        "compare",
        "comparison",
        "comp",
        "dashboard",
        "data",
        "earnings",
        "evidence",
        "findings",
        "financial",
        "forecast",
        "kpi",
        "matrix",
        "metric",
        "model",
        "numbers",
        "performance",
        "pricing",
        "report",
        "results",
        "revenue",
        "roi",
        "scenario",
        "score",
        "summary",
        "table",
        "telemetry",
        "trend",
        "variance",
    ]
    if any(keyword in lower for keyword in keyword_hits):
        return True

    if template_hint in {"client_proposal", "financial_briefing", "research_report"}:
        return True

    if upload_analysis and upload_analysis.category in {"data_report", "interactive_surface"}:
        return True

    return False


def prompt_contains_any(prompt: str, keywords: tuple[str, ...]) -> bool:
    lower = prompt.lower()
    return any(keyword in lower for keyword in keywords)


def build_prompt_stack_bridge_lines(
    prompt: str,
    template_hint: str | None = None,
    structured_data: dict[str, Any] | None = None,
    uploaded_content_present: bool = False,
    generation_mode: str | None = None,
) -> list[str]:
    active_modules = resolve_active_prompt_modules(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content="present" if uploaded_content_present else None,
        structured_data=structured_data,
        generation_mode=generation_mode,
    )
    lines = [
        (
            f"Apply the compact IterFact prompt-stack bridge ({PROMPT_STACK_BRIDGE_VERSION}). "
            "Prioritize strong information design, clear section jobs, source fidelity, and framework-aware compositions over generic filler sections."
        ),
        f"Active runtime prompt modules: {', '.join(active_modules)}.",
        "Give each section one primary job: KPI summary, chart, comparison, timeline, tool, evidence block, or next step.",
        "Lead with the sharpest claim, insight, or decision surface before background context.",
        (
            "Limit dense analytical sections to one dominant framework plus supporting context. "
            "Use supporting notes, tabs, or drill-downs instead of piling multiple complex frameworks into the same viewport."
        ),
        "When a section contains quantitative claims, include the benchmark, delta, target, or takeaway whenever the prompt supports it.",
        "If a comparison would sprawl horizontally, narrow the visible set or use tabs, filters, or a smaller comparison slice instead of a bloated wide table.",
        (
            "Prefer chaptered or scrollytelling wrappers for report-style artifacts. "
            "Use the slide-deck wrapper only when the request is explicitly speaker-led, board-facing, investor-facing, or asks for slides."
        ),
        "Wrapper fit hierarchy: use 12 for most editorial/report-like artifacts, 18 only for explicitly immersive scrollytelling, 21 only for walkthroughs or demos, 24 only for event/countdown pages, and 25 only for contracts or acceptance flows.",
        "When an engine already supports structured comparison or semantic disclosure, preserve that pattern instead of inventing extra interaction for its own sake.",
    ]
    lines.extend(
        build_runtime_planner_lines(
            active_modules=active_modules,
            prompt=prompt,
            template_hint=template_hint,
            structured_data=structured_data,
            uploaded_content_present=uploaded_content_present,
        )
    )

    if prompt_contains_any(
        prompt,
        (
            "strategy",
            "strategic",
            "swot",
            "positioning",
            "competitive",
            "competitor",
            "competition",
            "market map",
            "five forces",
            "value chain",
            "deal book",
            "dealbook",
        ),
    ):
        lines.append(
            "This is a strategy or competitive brief. Prefer compositions built from engines 03, 07, 05, 26, 31, and 17. "
            "Think: KPI bar -> comparison view -> timeline or roadmap -> risk, decision, or next-step summary."
        )

    if prompt_contains_any(
        prompt,
        (
            "financial",
            "finance",
            "earnings",
            "revenue",
            "ebitda",
            "cash flow",
            "cashflow",
            "variance",
            "waterfall",
            "bridge",
            "budget",
            "forecast",
            "scenario",
            "three statement",
            "three-statement",
        ),
    ) or template_hint in {"financial_briefing", "research_report"}:
        lines.append(
            "This is a financial or results-heavy narrative. Prefer KPI summary, waterfall bridge, scenario modeling, and reference tables. "
            "Engines 03, 29, 17, 26, and 31 are preferred over generic slide fillers."
        )

    if prompt_contains_any(
        prompt,
        (
            "roadmap",
            "timeline",
            "rollout",
            "implementation",
            "milestones",
            "gantt",
            "phase",
            "phases",
            "launch plan",
            "project plan",
        ),
    ):
        lines.append(
            "This is a rollout or implementation plan. Prefer gantt, milestone timeline, checklist or progress tracking, and a concise decision summary. "
            "Engines 31, 05, 28, 37, and 26 are preferred."
        )

    if prompt_contains_any(
        prompt,
        (
            "faq",
            "policy",
            "policies",
            "terms",
            "handbook",
            "knowledge base",
            "questions",
            "disclosure",
        ),
    ):
        lines.append(
            "This is a disclosure, FAQ, or policy-heavy request. Prefer engine 22 or engine 25 over long undifferentiated prose blocks."
        )

    if prompt_contains_any(
        prompt,
        (
            "calculator",
            "estimator",
            "configurator",
            "what-if",
            "what if",
            "scenario model",
            "scenario modeling",
        ),
    ):
        lines.append(
            "Only choose calculator or modeler engines when user inputs materially change outputs. "
            "Do not add interactive controls just because they are available."
        )

    if structured_data:
        lines.append(
            "Let the structured data shape drive the composition. Use tables for reference detail, charts for trends and comparisons, and scenario engines only when the user needs adjustable assumptions."
        )

    return lines


def build_planner_guidance_with_profiles(
    prompt: str,
    template_hint: str | None = None,
    upload_analysis: UploadAnalysis | None = None,
    structured_data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    generation_mode: str | None = None,
    uploaded_content_present: bool = False,
) -> str:
    lower = prompt.lower()
    lines = [
        "Keep the plan compact and production-oriented.",
        "Favor 4 to 7 sections unless the user clearly asks for something larger.",
        "Keep headlines, body text, notes, and role strings concise.",
        "Return only the fields needed to render a strong artifact.",
        "Always return meta.narrative_spine with core_thesis, audience, desired_action, throughlines, must_keep_facts, must_not_claim, key_entities, claim_map, closing_obligation, and section_jobs so the whole artifact can stay coherent across revisions.",
        "Prefer premium interactive formats over a conventional slide deck by default.",
        "Only use the slide deck wrapper when the request is clearly speaker-led, board-facing, investor-facing, or explicitly asks for slides.",
        "Default to wrapper 12 for most report-like, proposal-like, memo-like, and editorial artifacts unless the prompt clearly calls for a different wrapper behavior.",
        "Use wrapper 18 only when the prompt clearly wants immersive scrollytelling or cinematic pacing. Do not use it as a generic fallback.",
        "Use wrapper 21 only when the user explicitly wants a walkthrough, demo, onboarding flow, or guided product tour.",
        "Use wrapper 24 only for invitation, RSVP, launch, webinar, or countdown experiences.",
        "Use wrapper 25 only when the artifact genuinely needs signature, acceptance, agreement, or policy acknowledgment behavior.",
        "Treat motion and reveal effects as progressive enhancement. Content must remain visible and readable if JavaScript fails.",
        "Do not hide the whole document behind opacity-zero scroll reveal logic.",
        "Do not reinterpret the request into a quiz, flashcard, map, or educational demo unless the user explicitly asks for that.",
        "Never let missing values render as undefined, NaN, or null in visible copy. If a slot cannot be filled, use a sensible fallback or skip that component.",
    ]
    lines.extend(
        build_prompt_stack_bridge_lines(
            prompt,
            template_hint,
            structured_data,
            bool(upload_analysis) or uploaded_content_present,
            generation_mode,
        )
    )

    if any(keyword in lower for keyword in ["iterfact", "interactive artifact", "static files", "why static", "product story", "explain"]):
        lines.append(
            "This request is a product or category narrative. Prefer a chaptered report, scrollytelling report, guided walkthrough, or guided document over a standard slide deck."
        )

    if any(keyword in lower for keyword in ["bug", "audit", "analytics", "telemetry", "report", "usage", "dashboard"]):
        lines.append(
            "This request should feel like a sharp product or analytics report. Favor strong information design, visible charts, and clean narrative structure over generic pitch deck slides."
        )

    if any(keyword in lower for keyword in ["homepage", "landing page", "product demo", "walkthrough"]):
        lines.append("Lean toward a narrative or demo-first wrapper, not slides.")

    if template_hint:
        lines.append(f'Honor the selected template direction "{template_hint}" without exposing internal engine labels.')

    if engine_override:
        override_line = engine_override_feedback_line(engine_override)
        if override_line:
            lines.append(override_line)

    if section_count is not None:
        lines.append(f"Target exactly {section_count} sections unless preserving fidelity requires a clearly explained exception.")

    if structured_data:
        lines.append(
            "Structured data is provided separately. Treat those values as the source of truth. Do not approximate, interpolate, or rewrite the provided numbers."
        )
        keys = ", ".join(structured_data_keys(structured_data))
        if keys:
            lines.append(f"Structured data keys: {keys}.")
        lines.append(
            "If charts are requested and structured data is present, build chart datasets directly from the provided JSON instead of interpreting prose descriptions."
        )

    lines.extend(
        [
            "Use explicit chart vocabulary when helpful: line, bar, horizontal_bar, stacked_bar, paired_horizontal_bar, donut, scatter, area, waterfall, gauge.",
            "When the prompt clearly asks for a chart, do not fall back to a table unless no chart-capable option is available.",
            "For rankings and category comparisons, prefer horizontal bars.",
            "For positive and negative financial values, prefer bar charts with positive/negative color logic.",
            "For timelines and time series, include annotation markers when the prompt calls out milestones or discrete events.",
            "Make sections feel full and deliberate. If a section is sparse, pair the main visual with KPIs or a concise takeaway instead of leaving dead space.",
            "Chart titles and chart-adjacent takeaways should communicate the insight, not just repeat the metric name.",
            "Prefer direct labels over detached legends when the chart has four or fewer series and the labeling stays readable.",
            "KPI sections should include benchmark, delta, target, or comparison context whenever the prompt provides it or strongly implies it.",
            "Keep related comparison charts on a shared scale whenever possible, and start bar charts at zero.",
            "Avoid pie or donut charts with more than five slices, dual-axis charts, and rainbow color scales unless the request explicitly requires them.",
        ]
    )

    if should_apply_data_visual_guidance(prompt, template_hint, upload_analysis):
        lines.append(
            "This request is data-heavy, comparison-heavy, or results-heavy. Apply the stored IterFact data-visual cognition guidance profile when choosing wrappers, engines, hierarchy, chart types, and narrative flow."
        )
        if DATA_VISUAL_GUIDANCE:
            lines.append(DATA_VISUAL_GUIDANCE)

    return "\n".join(lines)


async def retry_planner_from_original_request(
    prompt: str,
    parse_error: str,
    template_hint: str | None = None,
    uploaded_content: str | None = None,
    upload_analysis: UploadAnalysis | None = None,
    structured_data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    generation_mode: str | None = None,
    progress_callback: Callable[[int, str], None] | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if progress_callback is not None:
        with contextlib.suppress(Exception):
            progress_callback(28, "Planner returned invalid JSON. Retrying from the original request.")
    retry_message, upload_context = build_planner_user_message(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content=uploaded_content,
        upload_analysis=upload_analysis,
        structured_data=structured_data,
        engine_override=engine_override,
        section_count=section_count,
        parse_error=parse_error,
        generation_mode=generation_mode,
    )

    payload, provider_event = await call_anthropic(
        SYSTEM_PROMPT,
        retry_message,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0.2,
        step_name="planner_retry",
    )
    payload["_provider_events"] = [provider_event]
    annotate_payload_with_upload_context(payload, upload_context)
    plan = parse_plan_text(extract_text_blocks(payload))
    validate_plan(plan)
    return plan, payload


def build_quality_feedback_block(feedback_lines: list[str], audit: dict[str, Any]) -> str:
    lines = [
        f"- Prompt-stack audit penalty: {audit.get('score_penalty', 0)}",
        f"- Critical issues detected: {'yes' if audit.get('critical') else 'no'}",
    ]
    for line in feedback_lines[:8]:
        lines.append(f"- {line}")
    lines.append("- Keep the plan compact, specific, and directly aligned to the user's requested format.")
    return "\n".join(lines)


async def retry_planner_from_quality_feedback(
    *,
    prompt: str,
    template_hint: str | None = None,
    uploaded_content: str | None = None,
    upload_analysis: UploadAnalysis | None = None,
    structured_data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    quality_feedback: str,
    generation_mode: str | None = None,
    progress_callback: Callable[[int, str], None] | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if progress_callback is not None:
        with contextlib.suppress(Exception):
            progress_callback(26, "Planner quality audit requested a corrective retry.")
    retry_message, upload_context = build_planner_user_message(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content=uploaded_content,
        upload_analysis=upload_analysis,
        structured_data=structured_data,
        engine_override=engine_override,
        section_count=section_count,
        quality_feedback=quality_feedback,
        generation_mode=generation_mode,
    )

    payload, provider_event = await call_anthropic(
        SYSTEM_PROMPT,
        retry_message,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0.2,
        step_name="planner_quality_retry",
    )
    payload["_provider_events"] = [provider_event]
    annotate_payload_with_upload_context(payload, upload_context)
    plan = parse_plan_text(extract_text_blocks(payload))
    validate_plan(plan)
    return plan, payload


async def call_planner(
    prompt: str,
    template_hint: str | None = None,
    uploaded_content: str | None = None,
    upload_analysis: UploadAnalysis | None = None,
    structured_data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    generation_mode: str | None = None,
    progress_callback: Callable[[int, str], None] | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if not SETTINGS.anthropic_api_key:
        raise RuntimeError("ANTHROPIC_API_KEY is required")
    if progress_callback is not None:
        with contextlib.suppress(Exception):
            progress_callback(20, "Planner is drafting the artifact structure.")

    user_message, upload_context = build_planner_user_message(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content=uploaded_content,
        upload_analysis=upload_analysis,
        structured_data=structured_data,
        engine_override=engine_override,
        section_count=section_count,
        generation_mode=generation_mode,
    )

    payload, provider_event = await call_anthropic(
        SYSTEM_PROMPT,
        user_message,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0.3,
        step_name="planner",
    )
    payload["_provider_events"] = [provider_event]
    annotate_payload_with_upload_context(payload, upload_context)
    active_modules = resolve_active_prompt_modules(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content=uploaded_content,
        structured_data=structured_data,
        generation_mode=generation_mode,
    )

    raw_text = extract_text_blocks(payload)
    try:
        plan = parse_plan_text(raw_text)
        validate_plan(plan)
        audit = audit_plan_against_prompt_stack(
            plan=plan,
            prompt=prompt,
            template_hint=template_hint,
            structured_data=structured_data,
            uploaded_heading_count=upload_context.preserved_heading_count if upload_context else 0,
            active_modules=active_modules,
        )
        if engine_override and not plan_honors_engine_override(plan, engine_override):
            feedback_line = engine_override_feedback_line(engine_override)
            override_warning = {
                "type": "engine_override_mismatch",
                "severity": "critical",
                "message": f"The planner did not honor the requested engine override {engine_override}.",
                "penalty": 35,
            }
            audit.setdefault("warnings", []).append(override_warning)
            audit["score_penalty"] = int(audit.get("score_penalty") or 0) + 35
            audit["critical"] = True
            audit["should_retry"] = True
            existing_feedback = list(audit.get("feedback_lines") or [])
            if feedback_line:
                audit["feedback_lines"] = [feedback_line, *existing_feedback]
        payload["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
        payload["active_prompt_modules"] = active_modules
        payload["plan_quality_audit"] = audit
        if audit.get("should_retry") and audit.get("feedback_lines"):
            quality_feedback = build_quality_feedback_block(audit.get("feedback_lines") or [], audit)
            LOGGER.info("Planner quality audit requested one corrective retry: %s", quality_feedback)
            retried_plan, retry_payload = await retry_planner_from_quality_feedback(
                prompt=prompt,
                template_hint=template_hint,
                uploaded_content=uploaded_content,
                upload_analysis=upload_analysis,
                structured_data=structured_data,
                engine_override=engine_override,
                section_count=section_count,
                quality_feedback=quality_feedback,
                generation_mode=generation_mode,
                progress_callback=progress_callback,
            )
            retry_payload["usage"] = combine_usage(payload, retry_payload)
            retry_payload["repair_applied"] = True
            retry_payload["retry_strategy"] = "quality-critic"
            retry_payload["_provider_events"] = [
                *(payload.get("_provider_events") or []),
                *(retry_payload.get("_provider_events") or []),
            ]
            retried_audit = audit_plan_against_prompt_stack(
                plan=retried_plan,
                prompt=prompt,
                template_hint=template_hint,
                structured_data=structured_data,
                uploaded_heading_count=upload_context.preserved_heading_count if upload_context else 0,
                active_modules=active_modules,
            )
            retry_payload["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
            retry_payload["active_prompt_modules"] = active_modules
            retry_payload["plan_quality_audit"] = retried_audit
            if engine_override and not plan_honors_engine_override(retried_plan, engine_override):
                raise ValueError(f"Planner failed to honor requested engine override {engine_override}.")
            return retried_plan, retry_payload
        if engine_override and not plan_honors_engine_override(plan, engine_override):
            raise ValueError(f"Planner failed to honor requested engine override {engine_override}.")
        return plan, payload
    except ValueError as exc:
        LOGGER.warning("Planner JSON parse/validation failed, retrying from original request: %s", exc)
        LOGGER.debug("Planner raw output snippet: %s", raw_text[:1200])
        try:
            retried_plan, retry_payload = await retry_planner_from_original_request(
                prompt=prompt,
                parse_error=str(exc),
                template_hint=template_hint,
                uploaded_content=uploaded_content,
                upload_analysis=upload_analysis,
                structured_data=structured_data,
                engine_override=engine_override,
                section_count=section_count,
                generation_mode=generation_mode,
                progress_callback=progress_callback,
            )
            retry_payload["usage"] = combine_usage(payload, retry_payload)
            retry_payload["repair_applied"] = True
            retry_payload["retry_strategy"] = "original-request-retry"
            retry_payload["_provider_events"] = [
                *(payload.get("_provider_events") or []),
                *(retry_payload.get("_provider_events") or []),
            ]
            retry_payload["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
            retry_payload["active_prompt_modules"] = active_modules
            retry_payload["plan_quality_audit"] = audit_plan_against_prompt_stack(
                plan=retried_plan,
                prompt=prompt,
                template_hint=template_hint,
                structured_data=structured_data,
                uploaded_heading_count=upload_context.preserved_heading_count if upload_context else 0,
                active_modules=active_modules,
            )
            if engine_override and not plan_honors_engine_override(retried_plan, engine_override):
                raise ValueError(f"Planner failed to honor requested engine override {engine_override}.")
            return retried_plan, retry_payload
        except ValueError as retry_exc:
            LOGGER.warning("Compact retry also failed, attempting JSON repair: %s", retry_exc)
            try:
                repaired_plan, repair_payload = await repair_plan_with_anthropic(
                    raw_text,
                    str(retry_exc),
                    progress_callback=progress_callback,
                )
                repair_payload["usage"] = combine_usage(payload, repair_payload)
                repair_payload["repair_applied"] = True
                repair_payload["retry_strategy"] = "json-repair"
                repair_payload["_provider_events"] = [
                    *(payload.get("_provider_events") or []),
                    *(repair_payload.get("_provider_events") or []),
                ]
                annotate_payload_with_upload_context(repair_payload, upload_context)
                repair_payload["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
                repair_payload["active_prompt_modules"] = active_modules
                repair_payload["plan_quality_audit"] = audit_plan_against_prompt_stack(
                    plan=repaired_plan,
                    prompt=prompt,
                    template_hint=template_hint,
                    structured_data=structured_data,
                    uploaded_heading_count=upload_context.preserved_heading_count if upload_context else 0,
                    active_modules=active_modules,
                )
                return repaired_plan, repair_payload
            except ValueError:
                if upload_context and upload_context.compacted:
                    raise ValueError(
                        "The uploaded source was too large or structurally dense for single-shot planning. "
                        "IterFact compacted the source context, but the planner still returned invalid JSON. "
                        "Try a shorter excerpt or split the document into sections."
                    ) from retry_exc
                raise


async def generate_section_from_source_chunk(
    *,
    original_prompt: str,
    template_hint: str | None,
    plan: dict[str, Any],
    draft_blueprint: dict[str, Any] | None,
    chunk: SourceChunk,
    target_index: int,
    updated_prompt: str | None,
    section_title_hint: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if not SETTINGS.anthropic_api_key:
        raise RuntimeError("ANTHROPIC_API_KEY is required")

    user_message = build_section_append_prompt(
        original_prompt=original_prompt,
        plan=plan,
        draft_blueprint=draft_blueprint,
        chunk=chunk,
        target_index=target_index,
        updated_prompt=updated_prompt,
        section_title_hint=section_title_hint,
        structured_data=structured_data,
        engine_override=engine_override,
    )
    user_message += "\n\n" + build_planner_guidance_with_profiles(
        original_prompt,
        template_hint,
        None,
        structured_data,
        engine_override,
        None,
    )

    payload, provider_event = await call_anthropic(
        SYSTEM_PROMPT,
        user_message,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0.2,
        step_name="section_append",
    )
    payload["_provider_events"] = [provider_event]

    raw_text = extract_text_blocks(payload)
    try:
        section = parse_section_text(raw_text)
        validate_section(section)
        return section, payload
    except ValueError as exc:
        LOGGER.warning("Section append JSON parse/validation failed, attempting section repair: %s", exc)
        repaired_section, repair_payload = await repair_section_with_anthropic(raw_text, str(exc))
        repair_payload["usage"] = combine_usage(payload, repair_payload)
        repair_payload["repair_applied"] = True
        repair_payload["retry_strategy"] = "section-json-repair"
        repair_payload["_provider_events"] = [
            *(payload.get("_provider_events") or []),
            *(repair_payload.get("_provider_events") or []),
        ]
        return repaired_section, repair_payload


async def regenerate_single_section_with_anthropic(
    *,
    original_prompt: str,
    template_hint: str | None,
    plan: dict[str, Any],
    section_index: int,
    current_section: dict[str, Any],
    updated_prompt: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if not SETTINGS.anthropic_api_key:
        raise RuntimeError("ANTHROPIC_API_KEY is required")

    user_message = build_section_regeneration_prompt(
        original_prompt=original_prompt,
        plan=plan,
        section_index=section_index,
        current_section=current_section,
        updated_prompt=updated_prompt,
        structured_data=structured_data,
    )
    user_message += "\n\n" + build_planner_guidance_with_profiles(
        original_prompt,
        template_hint,
        None,
        structured_data,
        engine_override,
        len(plan.get("sections", []) or []),
    )

    payload, provider_event = await call_anthropic(
        SYSTEM_PROMPT,
        user_message,
        max_tokens=SETTINGS.anthropic_max_tokens,
        temperature=0.2,
        step_name="section_regeneration",
    )
    payload["_provider_events"] = [provider_event]

    regenerated_section = parse_section_text(extract_text_blocks(payload))
    validate_section(regenerated_section)
    return regenerated_section, payload


def build_feedback_plan_revision_prompt(
    *,
    original_prompt: str,
    template_hint: str | None,
    current_plan: dict[str, Any],
    selected_feedback: list[dict[str, Any]],
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    additional_instruction: str | None = None,
    quality_feedback: str | None = None,
) -> str:
    active_modules = resolve_active_prompt_modules(
        prompt=original_prompt,
        template_hint=template_hint,
        uploaded_content=None,
        structured_data=structured_data,
        generation_mode="feedback_revision",
    )
    feedback_snapshot = [
        {
            "id": note.get("id"),
            "type": note.get("type"),
            "text": note.get("text"),
            "target_label": note.get("target_label"),
            "section_hint": note.get("section_hint"),
            "selection_excerpt": note.get("selection_excerpt"),
        }
        for note in selected_feedback
    ]
    message = (
        f"Original artifact request:\n{original_prompt.strip()}\n\n"
        "You are revising an existing IterFact assembly plan in place based on feedback left on the published artifact.\n"
        "Do not start from zero. Preserve strong sections, preserve exact numbers unless feedback explicitly changes them, and keep the artifact coherent as one document.\n\n"
        f"Template hint: {template_hint or 'none'}\n"
        f"Requested engine override: {engine_override or 'none'}\n"
        f"Document title: {current_plan.get('meta', {}).get('title') or 'IterFact Document'}\n"
        f"Wrapper engine: {current_plan.get('wrapper', {}).get('engine') or 'unknown'}\n"
        f"Theme: {current_plan.get('theme') or 'default'}\n\n"
        f"<narrative_spine>\n{json.dumps(build_narrative_spine_snapshot(current_plan), ensure_ascii=True, indent=2)}\n</narrative_spine>\n\n"
        f"<current_plan>\n{json.dumps(current_plan, ensure_ascii=True, indent=2)}\n</current_plan>\n\n"
        f"<feedback_notes>\n{json.dumps(feedback_snapshot, ensure_ascii=True, indent=2)}\n</feedback_notes>\n"
    )
    if not feedback_snapshot:
        message += (
            "\nNo anchored feedback notes were selected for this revision request. "
            "Treat this as a prompt-driven whole-document revision while preserving the strongest parts of the current artifact.\n"
        )
    if structured_data:
        message += f"\n<structured_data>\n{json.dumps(structured_data, ensure_ascii=True, indent=2)}\n</structured_data>\n"
    if additional_instruction:
        message += f"\nAdditional revision instruction:\n{additional_instruction.strip()}\n"
    if quality_feedback:
        message += (
            "\nThe previous feedback-based revision was still weak or misaligned. Rebuild the revised plan again using this correction layer:\n"
            f"{quality_feedback}\n"
        )
    message += (
        "\nRequirements:\n"
        "- Keep the strongest existing sections unless the feedback clearly asks to replace or remove them.\n"
        "- If feedback implies adding or removing a section, revise the section list carefully instead of pretending nothing changed.\n"
        "- Maintain or improve meta.narrative_spine so the artifact still has one coherent thesis, throughlines, key entities, claim map, and close.\n"
        "- Keep section titles, section jobs, and evidence progression specific. Avoid generic overview/summary filler.\n"
        "- Return only one valid JSON assembly plan and no commentary.\n\n"
    )
    message += build_planner_guidance_with_profiles(
        original_prompt,
        template_hint,
        None,
        structured_data,
        engine_override,
        len(current_plan.get("sections", []) or []),
        "feedback_revision",
        False,
    )
    message += "\n\n" + "\n".join(build_runtime_planner_lines(
        active_modules=active_modules,
        prompt=original_prompt,
        template_hint=template_hint,
        structured_data=structured_data,
        uploaded_content_present=False,
    ))
    message += "\n\nReturn only one valid JSON object for the revised assembly plan."
    return message


async def revise_plan_from_feedback_with_anthropic(
    *,
    original_prompt: str,
    template_hint: str | None,
    current_plan: dict[str, Any],
    selected_feedback: list[dict[str, Any]],
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    additional_instruction: str | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if not SETTINGS.anthropic_api_key:
        raise RuntimeError("ANTHROPIC_API_KEY is required")

    active_modules = resolve_active_prompt_modules(
        prompt=original_prompt,
        template_hint=template_hint,
        uploaded_content=None,
        structured_data=structured_data,
        generation_mode="feedback_revision",
    )
    quality_feedback: str | None = None
    previous_payload: dict[str, Any] | None = None

    for attempt in range(1, 3):
        user_message = build_feedback_plan_revision_prompt(
            original_prompt=original_prompt,
            template_hint=template_hint,
            current_plan=current_plan,
            selected_feedback=selected_feedback,
            structured_data=structured_data,
            engine_override=engine_override,
            additional_instruction=additional_instruction,
            quality_feedback=quality_feedback,
        )
        payload, provider_event = await call_anthropic(
            SYSTEM_PROMPT,
            user_message,
            max_tokens=SETTINGS.anthropic_max_tokens,
            temperature=0.2,
            step_name="feedback_plan_revision" if attempt == 1 else "feedback_plan_revision_retry",
        )
        payload["_provider_events"] = [provider_event]
        plan = parse_plan_text(extract_text_blocks(payload))
        validate_plan(plan)
        audit = audit_plan_against_prompt_stack(
            plan=plan,
            prompt=original_prompt,
            template_hint=template_hint,
            structured_data=structured_data,
            uploaded_heading_count=0,
            active_modules=active_modules,
        )
        payload["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
        payload["active_prompt_modules"] = active_modules
        payload["plan_quality_audit"] = audit
        payload["repair_applied"] = True
        payload["retry_strategy"] = "feedback_plan_revision" if attempt == 1 else "feedback_plan_revision_retry"
        if previous_payload is not None:
            payload["usage"] = combine_usage(previous_payload, payload)
            payload["_provider_events"] = [
                *(previous_payload.get("_provider_events") or []),
                *(payload.get("_provider_events") or []),
            ]
        if not audit.get("should_retry") or not audit.get("feedback_lines"):
            return plan, payload
        quality_feedback = build_quality_feedback_block(audit.get("feedback_lines") or [], audit)
        previous_payload = payload

    return plan, payload


ITERFACT_CRITIC_SYSTEM_PROMPT = (
    "You are the IterFact editorial critic and revision planner. "
    "Review the rendered artifact as a skeptical operator would. "
    "Diagnose the highest-severity weaknesses, preserve strong sections, "
    "and return only valid JSON that matches the requested schema."
)

ITERFACT_AGENTIC_CRITIC_SYSTEM_PROMPT = (
    "You are the IterFact agentic critic. "
    "The server already ran deterministic validation plus quick visual checks. "
    "Use those findings to produce compact, actionable JSON for targeted section repair only. "
    "Do not rewrite the whole artifact and do not return markdown."
)


def merge_payload_metadata(base_payload: dict[str, Any] | None, *other_payloads: dict[str, Any] | None) -> dict[str, Any]:
    valid_payloads = [payload for payload in [base_payload, *other_payloads] if isinstance(payload, dict)]
    if not valid_payloads:
        return {}

    merged = dict(valid_payloads[0])
    merged["model"] = next((payload.get("model") for payload in valid_payloads if payload.get("model")), merged.get("model"))
    merged["usage"] = combine_usage(*valid_payloads) or {}
    merged["repair_applied"] = any(bool(payload.get("repair_applied")) for payload in valid_payloads) or bool(
        merged.get("repair_applied")
    )
    merged["_provider_events"] = [event for payload in valid_payloads for event in (payload.get("_provider_events") or [])]
    merged["uploaded_content_compacted"] = any(bool(payload.get("uploaded_content_compacted")) for payload in valid_payloads)
    merged["uploaded_content_strategy"] = next(
        (payload.get("uploaded_content_strategy") for payload in valid_payloads if payload.get("uploaded_content_strategy")),
        merged.get("uploaded_content_strategy"),
    )
    merged["uploaded_heading_count_detected"] = max(
        [int(payload.get("uploaded_heading_count_detected") or 0) for payload in valid_payloads] or [0]
    )
    merged["uploaded_heading_count_preserved"] = max(
        [int(payload.get("uploaded_heading_count_preserved") or 0) for payload in valid_payloads] or [0]
    )
    merged["retry_strategy"] = next(
        (payload.get("retry_strategy") for payload in reversed(valid_payloads) if payload.get("retry_strategy")),
        merged.get("retry_strategy"),
    )
    return merged


def payload_without_stale_plan_audit(payload: dict[str, Any] | None) -> dict[str, Any] | None:
    if not isinstance(payload, dict):
        return payload
    refreshed = dict(payload)
    refreshed.pop("plan_quality_audit", None)
    return refreshed


def iterative_refinement_summary(
    payload: dict[str, Any] | None,
    render_validation_summary: dict[str, Any] | None = None,
) -> dict[str, Any]:
    if isinstance(payload, dict) and isinstance(payload.get("iterative_refinement"), dict):
        return dict(payload.get("iterative_refinement") or {})
    if isinstance(render_validation_summary, dict) and isinstance(render_validation_summary.get("iterative_refinement"), dict):
        return dict(render_validation_summary.get("iterative_refinement") or {})
    return {}


def critic_outcome_flags(
    payload: dict[str, Any] | None,
    render_validation_summary: dict[str, Any] | None = None,
) -> tuple[bool, bool]:
    refinement_summary = iterative_refinement_summary(payload, render_validation_summary)
    critic_skipped = bool(refinement_summary.get("critic_skipped") or (render_validation_summary or {}).get("critic_skipped"))
    critic_timed_out = bool(refinement_summary.get("critic_timed_out") or (render_validation_summary or {}).get("critic_timed_out"))
    return critic_skipped, critic_timed_out


def public_quality_score(
    quality_score: int | None,
    payload: dict[str, Any] | None,
    render_validation_summary: dict[str, Any] | None = None,
) -> int | None:
    critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
    if critic_skipped or critic_timed_out:
        return None
    return quality_score


def allow_nonfatal_low_quality_publish(
    payload: dict[str, Any] | None,
    render_validation_summary: dict[str, Any] | None = None,
) -> bool:
    critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
    if critic_skipped or critic_timed_out:
        return True
    if bool((render_validation_summary or {}).get("critical_failure")):
        return False
    refinement_summary = iterative_refinement_summary(payload, render_validation_summary)
    return bool(int(refinement_summary.get("passes_run") or 0) > 0)


def build_critic_public_notes(
    payload: dict[str, Any] | None,
    render_validation_summary: dict[str, Any] | None = None,
) -> list[str]:
    refinement_summary = iterative_refinement_summary(payload, render_validation_summary)
    critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
    timeout_seconds = refinement_summary.get("critic_timeout_seconds")
    notes: list[str] = []
    if critic_skipped:
        notes.append(
            "Critic was skipped by caller request. The artifact passed delivery validation but was published without editorial design review."
        )
    if critic_timed_out:
        label = f" after {int(timeout_seconds)}s" if isinstance(timeout_seconds, (int, float)) and timeout_seconds else ""
        notes.append(
            f"Critic evaluation timed out{label}. The artifact passed delivery validation and was published without editorial design review."
        )
    if (
        not critic_skipped
        and not critic_timed_out
        and allow_nonfatal_low_quality_publish(payload, render_validation_summary)
        and isinstance(render_validation_summary, dict)
        and not render_validation_summary.get("critical_failure")
    ):
        notes.append(
            "IterFact published this artifact below the normal quality threshold because iterative refinement finished without any fatal validation failures."
        )
    return notes


def should_run_iterative_refinement(
    *,
    generation_mode: str | None,
    plan: dict[str, Any],
    warnings: list[dict[str, Any]],
    quality_score: int,
    render_validation_summary: dict[str, Any],
) -> bool:
    if not SETTINGS.iterative_refinement_enabled:
        return False
    if generation_mode in {"admin_passthrough_html", "safe_rebuild_html"}:
        return False
    if not isinstance(plan.get("sections"), list) or not plan.get("sections"):
        return False

    plan_audit = render_validation_summary.get("plan_quality_audit") if isinstance(render_validation_summary, dict) else {}
    plan_penalty = int(plan_audit.get("score_penalty") or 0) if isinstance(plan_audit, dict) else 0
    critical_failure = bool(render_validation_summary.get("critical_failure"))
    high_warning = any(str(warning.get("severity") or "").lower() in {"critical", "high"} for warning in warnings)
    warning_types = {str(warning.get("type") or "").lower() for warning in warnings if isinstance(warning, dict)}
    if warning_types and warning_types.issubset({"headless_validation_unavailable"}) and not critical_failure and plan_penalty == 0:
        return False

    return any(
        [
            critical_failure,
            quality_score < SETTINGS.iterative_refinement_min_quality,
            high_warning,
            plan_penalty >= 6,
            bool(
                warning_types
                & {
                    "chart_surface_missing",
                    "container_query_missing",
                    "numeric_typography_missing",
                    "native_disclosure_missing",
                    "generic_section_pattern",
                    "narrative_claim_visibility_gap",
                    "narrative_entity_consistency_risk",
                    "source_specificity_loss",
                    "source_outline_drift",
                    "parallax_overuse",
                }
            ),
        ]
    )


def build_heuristic_section_repairs(
    *,
    plan: dict[str, Any],
    warnings: list[dict[str, Any]],
    render_validation_summary: dict[str, Any],
) -> list[dict[str, Any]]:
    repairs: list[dict[str, Any]] = []
    warning_types = {str(warning.get("type") or "").lower() for warning in warnings if isinstance(warning, dict)}
    headless = render_validation_summary.get("headless_validation") if isinstance(render_validation_summary, dict) else {}
    failure_reasons = [str(reason) for reason in (render_validation_summary.get("failure_reasons") or [])]
    failure_blob = " ".join(failure_reasons + [str(issue) for issue in (headless.get("issues") or [])]).lower()

    def add_engine_repair(engine_ids: set[str], *, reason: str, updated_prompt: str, severity: str = "high") -> None:
        for section_index in section_indexes_using_engines(plan, engine_ids):
            repairs.append(
                {
                    "section_index": section_index,
                    "severity": severity,
                    "reason": reason,
                    "updated_prompt": updated_prompt,
                }
            )

    if "chart_surface_missing" in warning_types or "dashboard_mismatch" in warning_types:
        add_engine_repair(
            {"03", "29", "32"},
            reason="The rendered artifact is missing a clear dashboard/chart surface where one was expected.",
            updated_prompt=(
                "Repair this section into a true chart- or KPI-first analytical surface. Use exact provided numbers, direct labels, "
                "and a visible chart or KPI treatment instead of narrative filler. Do not invent deltas or baselines."
            ),
        )

    if "native_disclosure_missing" in warning_types:
        add_engine_repair(
            {"22"},
            reason="The FAQ or disclosure section lost its native expandable disclosure behavior.",
            updated_prompt=(
                "Repair this section so it behaves like a native FAQ/disclosure block with clear question-and-answer content. "
                "Prefer native disclosure semantics and remove any undefined or placeholder copy."
            ),
        )

    if any(token in failure_blob for token in ("nan", "undefined", "null", "floating-point", "interactive inputs did not update")):
        add_engine_repair(
            {"08", "17"},
            reason="The calculator or modeler surface rendered broken values or failed to update when inputs changed.",
            updated_prompt=(
                "Repair this calculator/modeler section. Use explicit inputs, outputs, and formula configuration; ensure input events update visible outputs; "
                "never let NaN, undefined, null, Infinity, or long raw floats reach visible output; preserve exact units, defaults, ranges, and formulas from the request."
            ),
            severity="critical",
        )

    for warning in warnings:
        if not isinstance(warning, dict):
            continue
        warning_type = str(warning.get("type") or "").lower()
        if warning_type == "narrative_claim_visibility_gap":
            missing_claims = [
                compact_whitespace(str(item))
                for item in (warning.get("claims") or [])
                if compact_whitespace(str(item))
            ]
            for claim in missing_claims:
                target_indexes = section_indexes_for_claim(plan, claim)
                if not target_indexes:
                    continue
                for section_index in target_indexes:
                    repairs.append(
                        {
                            "section_index": section_index,
                            "severity": "high",
                            "reason": f"The rendered artifact no longer clearly supports this narrative claim: {claim}",
                            "updated_prompt": (
                                f"Repair this section so it clearly supports the narrative claim '{claim}'. "
                                "Preserve exact facts and figures, reinforce the same thesis as the rest of the artifact, "
                                "and make the supporting evidence explicit instead of implied."
                            ),
                        }
                    )
        elif warning_type == "narrative_entity_consistency_risk":
            missing_entities = [
                compact_whitespace(str(item))
                for item in (warning.get("missing_entities") or [])
                if compact_whitespace(str(item))
            ]
            target_indexes = section_indexes_for_entities(plan, missing_entities)
            for section_index in target_indexes[:3]:
                repairs.append(
                    {
                        "section_index": section_index,
                        "severity": "medium",
                        "reason": "Named entities or anchor phrases from the narrative contract are missing or drifting in this section.",
                        "updated_prompt": (
                            "Repair this section so the named entities, product names, metrics, and anchor phrases stay consistent with the narrative spine. "
                            f"Reinforce these anchors where they matter: {', '.join(missing_entities[:4])}."
                        ),
                    }
                )

    deduped: list[dict[str, Any]] = []
    seen_indexes: set[int] = set()
    for repair in repairs:
        section_index = int(repair.get("section_index") or 0)
        if section_index < 1 or section_index in seen_indexes:
            continue
        seen_indexes.add(section_index)
        deduped.append(repair)
    return deduped


def build_artifact_critic_prompt(
    *,
    prompt: str,
    template_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    generation_mode: str | None,
    plan: dict[str, Any],
    warnings: list[dict[str, Any]],
    quality_score: int,
    render_validation_summary: dict[str, Any],
) -> str:
    active_modules = render_validation_summary.get("active_prompt_modules") if isinstance(render_validation_summary, dict) else None
    if not isinstance(active_modules, list):
        active_modules = resolve_active_prompt_modules(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode=generation_mode,
        )

    critic_lines = build_runtime_critic_lines(
        active_modules=active_modules,
        prompt=prompt,
        structured_data=structured_data,
        uploaded_content_present=bool(uploaded_content),
    )
    visible_text = build_rendered_text_excerpt(render_validation_summary)
    warning_lines = summarize_warnings_for_critic(warnings)
    section_snapshots = build_plan_snapshots_for_critic(plan)
    narrative_spine = build_narrative_spine_snapshot(plan)
    headless = render_validation_summary.get("headless_validation") if isinstance(render_validation_summary, dict) else {}
    standalone = headless.get("standalone") if isinstance(headless.get("standalone"), dict) else {}
    iframe = headless.get("iframe") if isinstance(headless.get("iframe"), dict) else {}

    message = (
        f"Original artifact request:\n{prompt.strip()}\n\n"
        "You are reviewing a first rendered IterFact draft before it is allowed to publish.\n"
        "Diagnose the weakest sections, preserve strong sections, and return only JSON.\n\n"
        f"Template hint: {template_hint or 'none'}\n"
        f"Generation mode: {generation_mode or 'planned_render'}\n"
        f"Document title: {plan.get('meta', {}).get('title') or 'IterFact Document'}\n"
        f"Wrapper engine: {plan.get('wrapper', {}).get('engine') or 'unknown'}\n"
        f"Theme: {plan.get('theme') or 'default'}\n"
        f"Section count: {len(plan.get('sections', []) or [])}\n"
        f"Quality score before revision: {quality_score}\n"
        f"Active prompt modules: {', '.join(str(module) for module in active_modules)}\n\n"
        "Critic rules:\n- "
        + "\n- ".join(critic_lines[:18])
        + "\n\n"
        f"Current warning summary:\n{chr(10).join('- ' + line for line in warning_lines) if warning_lines else '- none'}\n\n"
        f"Headless metric snapshot: standalone={compact_whitespace(str(standalone.get('metricSnapshot') or 'n/a'))}; "
        f"iframe={compact_whitespace(str(iframe.get('metricSnapshot') or 'n/a'))}\n"
        f"Failure reasons: {json.dumps(render_validation_summary.get('failure_reasons') or [], ensure_ascii=True)}\n\n"
        f"<rendered_visible_text>\n{visible_text or '[no visible text snapshot available]'}\n</rendered_visible_text>\n\n"
        f"<narrative_spine>\n{json.dumps(narrative_spine, ensure_ascii=True, indent=2)}\n</narrative_spine>\n\n"
        f"<section_snapshots>\n{json.dumps(section_snapshots, ensure_ascii=True, indent=2)}\n</section_snapshots>\n"
    )

    if structured_data:
        message += f"\n<structured_data>\n{json.dumps(structured_data, ensure_ascii=True, indent=2)}\n</structured_data>\n"

    message += (
        "\nReturn exactly this JSON schema:\n"
        "{\n"
        '  "ship": false,\n'
        '  "overall_diagnosis": "...",\n'
        '  "keep": ["..."],\n'
        '  "fix": ["..."],\n'
        '  "unsupported_claims": ["..."],\n'
        '  "artifact_level_advice": ["..."],\n'
        '  "section_repairs": [\n'
        "    {\n"
        '      "section_index": 1,\n'
        '      "severity": "critical",\n'
        '      "reason": "...",\n'
        '      "updated_prompt": "Concrete instructions to regenerate only this section."\n'
        "    }\n"
        "  ],\n"
        '  "residual_risk": ["..."]\n'
        "}\n\n"
        "Requirements:\n"
        "- Recommend at most three section repairs.\n"
        "- Only include section indexes that exist in section_snapshots.\n"
        "- If the artifact is already ready to ship, set ship=true and leave section_repairs empty.\n"
        "- If a section uses exact numbers or formulas, tell the rewrite to preserve them exactly.\n"
        "- Do not rewrite the whole artifact. Target the weakest sections only.\n"
        "- When a claim in narrative_spine.claim_map is unsupported, list it in unsupported_claims and target the supporting sections named in claim_map.support_sections.\n"
        "- If named entities or anchor phrases drift, repair the sections that introduce, support, or close the argument so those entities stay stable.\n"
        "- Do not include markdown fences or commentary."
    )
    return message


def build_agentic_critic_prompt(
    *,
    prompt: str,
    template_hint: str | None,
    generation_mode: str | None,
    plan: dict[str, Any],
    warnings: list[dict[str, Any]],
    quality_score: int,
    render_validation_summary: dict[str, Any],
    quick_warnings: list[str],
    visual_warnings: list[str],
) -> str:
    visible_text = clip_for_prompt(build_rendered_text_excerpt(render_validation_summary), 3200)
    warning_lines = summarize_warnings_for_critic(warnings)[:8]
    section_snapshots = build_plan_snapshots_for_critic(plan)[:6]
    narrative_spine = build_narrative_spine_snapshot(plan)
    headless = render_validation_summary.get("headless_validation") if isinstance(render_validation_summary, dict) else {}
    standalone = headless.get("standalone") if isinstance(headless.get("standalone"), dict) else {}
    iframe = headless.get("iframe") if isinstance(headless.get("iframe"), dict) else {}

    message = (
        f"Original artifact request:\n{prompt.strip()}\n\n"
        "You are reviewing a rendered IterFact draft using compact evidence from deterministic validation and visual checks.\n"
        "Return only one JSON object using the exact schema below.\n\n"
        f"Template hint: {template_hint or 'none'}\n"
        f"Generation mode: {generation_mode or 'planned_render'}\n"
        f"Document title: {plan.get('meta', {}).get('title') or 'IterFact Document'}\n"
        f"Wrapper engine: {plan.get('wrapper', {}).get('engine') or 'unknown'}\n"
        f"Section count: {len(plan.get('sections', []) or [])}\n"
        f"Current quality score before critic repair: {quality_score}\n"
        f"Known deterministic warnings:\n{chr(10).join('- ' + line for line in warning_lines) if warning_lines else '- none'}\n\n"
        f"Quick rule warnings:\n{chr(10).join('- ' + line for line in quick_warnings) if quick_warnings else '- none'}\n\n"
        f"Visual warnings:\n{chr(10).join('- ' + line for line in visual_warnings) if visual_warnings else '- none'}\n\n"
        f"Headless metric snapshot: standalone={compact_whitespace(str(standalone.get('metricSnapshot') or 'n/a'))}; "
        f"iframe={compact_whitespace(str(iframe.get('metricSnapshot') or 'n/a'))}\n"
        f"Failure reasons: {json.dumps(render_validation_summary.get('failure_reasons') or [], ensure_ascii=True)}\n\n"
        f"<rendered_visible_text>\n{visible_text or '[no visible text snapshot available]'}\n</rendered_visible_text>\n\n"
        f"<narrative_spine>\n{json.dumps(narrative_spine, ensure_ascii=True, indent=2)}\n</narrative_spine>\n\n"
        f"<section_snapshots>\n{json.dumps(section_snapshots, ensure_ascii=True, indent=2)}\n</section_snapshots>\n\n"
        "Return exactly this JSON schema:\n"
        "{\n"
        '  "ship": false,\n'
        '  "overall_diagnosis": "...",\n'
        '  "keep": ["..."],\n'
        '  "fix": ["..."],\n'
        '  "unsupported_claims": ["..."],\n'
        '  "artifact_level_advice": ["..."],\n'
        '  "section_repairs": [\n'
        "    {\n"
        '      "section_index": 1,\n'
        '      "severity": "critical",\n'
        '      "reason": "...",\n'
        '      "updated_prompt": "Concrete instructions to regenerate only this section."\n'
        "    }\n"
        "  ],\n"
        '  "residual_risk": ["..."]\n'
        "}\n\n"
        "Requirements:\n"
        "- Recommend at most three section repairs.\n"
        "- Only include section indexes that exist in section_snapshots.\n"
        "- Preserve exact figures and formulas when a section carries numeric evidence.\n"
        "- If the artifact is ready to ship, set ship=true and leave section_repairs empty.\n"
        "- If the quick or visual warnings show layout breakage, reflect that in artifact_level_advice or residual_risk.\n"
        "- When a claim in narrative_spine.claim_map is unsupported, list it in unsupported_claims and target the supporting sections named there.\n"
        "- Do not include markdown fences or commentary."
    )
    return message


def merge_agentic_review_signals(
    critique: dict[str, Any],
    *,
    quick_warnings: list[str],
    visual_warnings: list[str],
) -> dict[str, Any]:
    normalized = parse_artifact_critic_text(json.dumps(critique, ensure_ascii=True))
    extra_lines = [line for line in [*quick_warnings, *visual_warnings] if line]
    if extra_lines:
        normalized["artifact_level_advice"] = list(
            dict.fromkeys([*(normalized.get("artifact_level_advice") or []), *extra_lines[:8]])
        )
        normalized["residual_risk"] = list(
            dict.fromkeys([*(normalized.get("residual_risk") or []), *extra_lines[:6]])
        )
    if any("Horizontal overflow" in line for line in visual_warnings):
        normalized["ship"] = False
    return normalized


async def critique_rendered_artifact(
    *,
    html: str,
    prompt: str,
    template_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    generation_mode: str | None,
    plan: dict[str, Any],
    warnings: list[dict[str, Any]],
    quality_score: int,
    render_validation_summary: dict[str, Any],
    pass_number: int,
    timeout_seconds: int | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if SETTINGS.agentic_critic_enabled:
        quick_warnings: list[str] = []
        visual_warnings: list[str] = []
        try:
            _, quick_warnings = await run_quick_checks(html)
            if SETTINGS.agentic_critic_visual_enabled:
                visual_timeout = max(4, min(int(timeout_seconds or SETTINGS.agentic_critic_timeout_seconds), 8))
                try:
                    _, visual_warnings = await asyncio.wait_for(run_visual_checks(html), timeout=visual_timeout)
                except asyncio.TimeoutError:
                    visual_warnings = [f"Visual check timed out after {visual_timeout}s"]
                except Exception as exc:  # noqa: BLE001
                    visual_warnings = [f"Visual check skipped: {exc}"]

            review_prompt = build_agentic_critic_prompt(
                prompt=prompt,
                template_hint=template_hint,
                generation_mode=generation_mode,
                plan=plan,
                warnings=warnings,
                quality_score=quality_score,
                render_validation_summary=render_validation_summary,
                quick_warnings=quick_warnings,
                visual_warnings=visual_warnings,
            )

            if SETTINGS.agent_sdk_critic_enabled and sdk_backend_available():
                review_timeout = int(timeout_seconds or SETTINGS.agentic_critic_timeout_seconds)
                sdk_result = await run_sdk_review(
                    review_prompt,
                    system_prompt=ITERFACT_AGENTIC_CRITIC_SYSTEM_PROMPT,
                    model=SETTINGS.agentic_critic_model,
                    timeout_seconds=review_timeout,
                )
                critique = merge_agentic_review_signals(
                    parse_artifact_critic_text(str(sdk_result.get("parsed") or "")),
                    quick_warnings=quick_warnings,
                    visual_warnings=visual_warnings,
                )
                payload = {
                    "artifact_critic": critique,
                    "agentic_critic": {
                        "backend": "sdk",
                        "sdk_available": True,
                        "quick_warnings": quick_warnings,
                        "visual_warnings": visual_warnings,
                        "raw_text": sdk_result.get("raw_text"),
                    },
                    "_provider_events": [],
                }
                return critique, payload

            review_timeout = float(timeout_seconds or SETTINGS.agentic_critic_timeout_seconds)
            payload, provider_event = await call_anthropic(
                ITERFACT_AGENTIC_CRITIC_SYSTEM_PROMPT,
                review_prompt,
                max_tokens=SETTINGS.anthropic_critic_max_tokens,
                temperature=0.1,
                model=SETTINGS.agentic_critic_model,
                step_name=f"agentic_artifact_critic_pass_{pass_number}",
                timeout_seconds=review_timeout,
            )
            payload["_provider_events"] = [provider_event]
            critique = merge_agentic_review_signals(
                parse_artifact_critic_text(extract_text_blocks(payload)),
                quick_warnings=quick_warnings,
                visual_warnings=visual_warnings,
            )
            payload["artifact_critic"] = critique
            payload["agentic_critic"] = {
                "backend": "anthropic",
                "sdk_available": sdk_backend_available(),
                "quick_warnings": quick_warnings,
                "visual_warnings": visual_warnings,
            }
            return critique, payload
        except (asyncio.TimeoutError, httpx.TimeoutException) as exc:
            timeout_label = int(timeout_seconds or SETTINGS.agentic_critic_timeout_seconds)
            raise CriticTimeoutError(f"Agentic critic timed out after {timeout_label}s.") from exc
        except CriticTimeoutError:
            raise
        except Exception as exc:  # noqa: BLE001
            LOGGER.warning("Agentic critic failed; falling back to legacy critic on pass %s: %s", pass_number, exc)

    user_message = build_artifact_critic_prompt(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content=uploaded_content,
        structured_data=structured_data,
        generation_mode=generation_mode,
        plan=plan,
        warnings=warnings,
        quality_score=quality_score,
        render_validation_summary=render_validation_summary,
    )
    try:
        payload, provider_event = await call_anthropic(
            ITERFACT_CRITIC_SYSTEM_PROMPT,
            user_message,
            max_tokens=SETTINGS.anthropic_critic_max_tokens,
            temperature=0.1,
            model=SETTINGS.anthropic_critic_model,
            step_name=f"artifact_critic_pass_{pass_number}",
            timeout_seconds=float(timeout_seconds) if timeout_seconds else None,
        )
    except (asyncio.TimeoutError, httpx.TimeoutException) as exc:
        timeout_label = int(timeout_seconds) if timeout_seconds else SETTINGS.anthropic_critic_timeout_seconds
        raise CriticTimeoutError(f"Critic evaluation timed out after {timeout_label}s.") from exc
    payload["_provider_events"] = [provider_event]
    critique = parse_artifact_critic_text(extract_text_blocks(payload))
    payload["artifact_critic"] = critique
    return critique, payload


def merge_section_repair_candidates(
    heuristic_repairs: list[dict[str, Any]],
    critic_repairs: list[dict[str, Any]],
    *,
    max_sections: int,
) -> list[dict[str, Any]]:
    ranked: dict[int, dict[str, Any]] = {}
    severity_rank = {"critical": 3, "high": 2, "medium": 1, "low": 0}

    for repair in [*heuristic_repairs, *critic_repairs]:
        if not isinstance(repair, dict):
            continue
        try:
            section_index = int(repair.get("section_index"))
        except (TypeError, ValueError):
            continue
        if section_index < 1:
            continue
        normalized = {
            "section_index": section_index,
            "severity": compact_whitespace(str(repair.get("severity") or "medium")).lower() or "medium",
            "reason": compact_whitespace(str(repair.get("reason") or "")),
            "updated_prompt": compact_whitespace(str(repair.get("updated_prompt") or "")),
        }
        if not normalized["updated_prompt"]:
            continue

        existing = ranked.get(section_index)
        if existing is None:
            ranked[section_index] = normalized
            continue

        merged_reason = compact_whitespace("; ".join(filter(None, [existing.get("reason"), normalized.get("reason")])))
        merged_prompt = compact_whitespace(
            "\n".join(
                dict.fromkeys(
                    item
                    for item in [existing.get("updated_prompt"), normalized.get("updated_prompt")]
                    if item
                )
            )
        )
        preferred = normalized if severity_rank.get(normalized["severity"], 1) >= severity_rank.get(existing["severity"], 1) else existing
        ranked[section_index] = {
            **preferred,
            "reason": merged_reason,
            "updated_prompt": merged_prompt,
        }

    ordered = sorted(
        ranked.values(),
        key=lambda repair: (-severity_rank.get(str(repair.get("severity") or "medium"), 1), int(repair.get("section_index") or 0)),
    )
    return ordered[: max(1, max_sections)]


async def render_plan_with_iterative_refinement(
    *,
    prompt: str,
    template_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    generation_mode: str | None,
    plan: dict[str, Any],
    payload: dict[str, Any] | None,
    requested_section_count: int | None,
    engine_override: str | None,
    resume_checkpoint: dict[str, Any] | None = None,
    checkpoint_writer: Callable[[dict[str, Any]], None] | None = None,
    progress_callback: Callable[[int, str], None] | None = None,
    skip_critic: bool = False,
    critic_timeout_seconds: int | None = None,
) -> tuple[dict[str, Any], str, dict[str, Any], list[dict[str, Any]], int, dict[str, Any]]:
    checkpoint = normalize_render_checkpoint_state(resume_checkpoint)
    resume_plan = checkpoint.get("plan") if isinstance(checkpoint.get("plan"), dict) else plan
    resume_payload = checkpoint.get("payload") if isinstance(checkpoint.get("payload"), dict) else payload
    current_plan = normalize_plan(copy.deepcopy(resume_plan))
    current_payload = dict(resume_payload or {})
    refinement_history = list(checkpoint.get("refinement_history") or [])
    current_html = ""
    current_warnings: list[dict[str, Any]] = []
    current_quality_score = 100
    current_summary: dict[str, Any] = {}
    stop_reason = "quality_gate_passed"
    pass_number = int(checkpoint.get("pass_number") or 0)
    critic_timed_out = bool(checkpoint.get("critic_timed_out"))

    def update_refinement_progress(progress: int, message: str) -> None:
        if progress_callback is None:
            return
        with contextlib.suppress(Exception):
            progress_callback(progress, message)

    while True:
        current_plan = normalize_plan(sanitize_plan_for_render(current_plan))
        current_html = assemble(current_plan, str(SETTINGS.vault_path / "engines"))
        current_html, current_warnings, current_quality_score, current_summary = validate_rendered_artifact(
            html=current_html,
            plan=current_plan,
            requested_section_count=requested_section_count,
            engine_override=engine_override,
        )
        current_warnings, current_quality_score, current_summary, active_modules, plan_audit = apply_prompt_stack_plan_audit(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode=generation_mode,
            plan=current_plan,
            payload=payload_without_stale_plan_audit(current_payload),
            warnings=current_warnings,
            quality_score=current_quality_score,
            render_validation_summary=current_summary,
        )
        current_payload["prompt_stack_bundle_version"] = PROMPT_STACK_BRIDGE_VERSION
        current_payload["active_prompt_modules"] = active_modules
        current_payload["plan_quality_audit"] = plan_audit
        update_refinement_progress(58, "Base render complete. Evaluating quality and deciding whether refinement is needed.")

        if skip_critic:
            stop_reason = "critic_skipped_by_caller"
            break

        needs_refinement = should_run_iterative_refinement(
            generation_mode=generation_mode,
            plan=current_plan,
            warnings=current_warnings,
            quality_score=current_quality_score,
            render_validation_summary=current_summary,
        )
        if not needs_refinement:
            stop_reason = "quality_gate_passed"
            break
        if pass_number >= max(0, SETTINGS.iterative_refinement_max_passes):
            stop_reason = "max_passes_reached"
            break

        pass_number += 1
        update_refinement_progress(
            min(69, 59 + (pass_number * 4)),
            f"Critiquing render pass {pass_number} of {max(1, SETTINGS.iterative_refinement_max_passes)}.",
        )
        pass_record: dict[str, Any] = {
            "pass_number": pass_number,
            "quality_before": current_quality_score,
            "warning_count_before": len(current_warnings),
            "failure_reasons_before": list(current_summary.get("failure_reasons") or []),
        }

        heuristic_repairs = build_heuristic_section_repairs(
            plan=current_plan,
            warnings=current_warnings,
            render_validation_summary=current_summary,
        )
        critique: dict[str, Any] = {}
        critic_payload: dict[str, Any] | None = None
        try:
            critique, critic_payload = await critique_rendered_artifact(
                html=current_html,
                prompt=prompt,
                template_hint=template_hint,
                uploaded_content=uploaded_content,
                structured_data=structured_data,
                generation_mode=generation_mode,
                plan=current_plan,
                warnings=current_warnings,
                quality_score=current_quality_score,
                render_validation_summary=current_summary,
                pass_number=pass_number,
                timeout_seconds=critic_timeout_seconds,
            )
            pass_record["overall_diagnosis"] = critique.get("overall_diagnosis")
            pass_record["fix"] = critique.get("fix") or []
        except CriticTimeoutError as exc:
            critic_timed_out = True
            pass_record["critic_error"] = str(exc)
            pass_record["critic_timeout_seconds"] = critic_timeout_seconds or SETTINGS.anthropic_critic_timeout_seconds
            refinement_history.append(pass_record)
            stop_reason = "critic_timed_out"
            break
        except Exception as exc:  # noqa: BLE001
            LOGGER.warning("Iterative artifact critic failed on pass %s: %s", pass_number, exc)
            pass_record["critic_error"] = str(exc)

        selected_repairs = merge_section_repair_candidates(
            heuristic_repairs,
            critique.get("section_repairs") or [],
            max_sections=SETTINGS.iterative_refinement_max_sections_per_pass,
        )
        if not selected_repairs:
            stop_reason = "critic_found_no_actionable_repairs"
            if critic_payload is not None:
                current_payload = merge_payload_metadata(current_payload, critic_payload)
            refinement_history.append(pass_record)
            break

        update_refinement_progress(
            min(70, 61 + (pass_number * 4)),
            f"Applying critic-guided repairs to {len(selected_repairs)} section(s).",
        )
        regenerated_payloads: list[dict[str, Any]] = []
        repaired_sections: list[dict[str, Any]] = []
        regeneration_failures: list[str] = []
        for repair in selected_repairs:
            section_index = int(repair.get("section_index") or 0)
            if section_index < 1 or section_index > len(current_plan.get("sections", []) or []):
                continue
            current_section = current_plan["sections"][section_index - 1]
            try:
                regenerated_section, regen_payload = await regenerate_single_section_with_anthropic(
                    original_prompt=prompt,
                    template_hint=template_hint,
                    plan=current_plan,
                    section_index=section_index,
                    current_section=current_section,
                    updated_prompt=repair.get("updated_prompt"),
                    structured_data=structured_data,
                    engine_override=engine_override,
                )
                current_plan["sections"][section_index - 1] = regenerated_section
                regenerated_payloads.append(regen_payload)
                repaired_sections.append(
                    {
                        "section_index": section_index,
                        "title": current_plan["sections"][section_index - 1].get("title"),
                        "severity": repair.get("severity"),
                        "reason": repair.get("reason"),
                    }
                )
            except Exception as exc:  # noqa: BLE001
                regeneration_failures.append(f"Section {section_index}: {exc}")

        pass_record["repaired_sections"] = repaired_sections
        if regeneration_failures:
            pass_record["regeneration_failures"] = regeneration_failures
        refinement_history.append(pass_record)

        merged_payloads: list[dict[str, Any]] = [current_payload]
        if critic_payload is not None:
            critic_payload["retry_strategy"] = "iterative-refinement-critic"
            merged_payloads.append(critic_payload)
        for regen_payload in regenerated_payloads:
            regen_payload["retry_strategy"] = "iterative-refinement-section"
            regen_payload["repair_applied"] = True
            merged_payloads.append(regen_payload)
        current_payload = merge_payload_metadata(*merged_payloads)
        current_payload["repair_applied"] = True
        current_payload["retry_strategy"] = "iterative-refinement"
        update_refinement_progress(
            min(71, 62 + (pass_number * 4)),
            f"Re-rendering after critic pass {pass_number}.",
        )
        if checkpoint_writer is not None:
            checkpoint_writer(
                build_render_checkpoint(
                    "iterative_refinement",
                    plan=current_plan,
                    payload=current_payload,
                    pass_number=pass_number,
                    refinement_history=refinement_history,
                )
            )

        if not repaired_sections:
            stop_reason = "regeneration_failed"
            break

    refinement_summary = {
        "enabled": bool(SETTINGS.iterative_refinement_enabled and not skip_critic),
        "applied": bool(refinement_history),
        "passes_run": len(refinement_history),
        "max_passes": SETTINGS.iterative_refinement_max_passes,
        "max_sections_per_pass": SETTINGS.iterative_refinement_max_sections_per_pass,
        "stop_reason": stop_reason,
        "critic_skipped": bool(skip_critic),
        "critic_timed_out": bool(critic_timed_out),
        "critic_timeout_seconds": int(critic_timeout_seconds or SETTINGS.anthropic_critic_timeout_seconds),
        "history": refinement_history,
    }
    current_summary["iterative_refinement"] = refinement_summary
    current_summary["critic_skipped"] = bool(skip_critic)
    current_summary["critic_timed_out"] = bool(critic_timed_out)
    current_payload["iterative_refinement"] = refinement_summary
    current_payload["critic_skipped"] = bool(skip_critic)
    current_payload["critic_timed_out"] = bool(critic_timed_out)
    return current_plan, current_html, current_payload, current_warnings, current_quality_score, current_summary


def write_local_output(title: str, html: str, artifact_id: str | None = None) -> tuple[str, Path]:
    artifact_id = artifact_id or str(uuid.uuid4())
    stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
    file_name = f"{stamp}-{slugify(title)}.html"
    artifact_dir = SETTINGS.output_dir / artifact_id
    artifact_dir.mkdir(parents=True, exist_ok=True)
    output_path = artifact_dir / file_name
    delivery_html, optimization_report = optimize_artifact_html(
        inject_ifx_state_runtime(html, artifact_id)
    )
    output_path.write_text(delivery_html, encoding="utf-8")
    if optimization_report.get("optimized"):
        LOGGER.info(
            "Optimized artifact %s before publish: %.1f%% smaller (%s -> %s bytes)",
            artifact_id,
            float(optimization_report.get("saved_percent") or 0.0),
            int(optimization_report.get("original_bytes") or 0),
            int(optimization_report.get("optimized_bytes") or 0),
        )
    preview_assets = ensure_library_preview_assets(output_path, delivery_html)
    if preview_assets:
        LOGGER.info(
            "Captured %s cached preview asset(s) for artifact %s",
            len(preview_assets),
            artifact_id,
        )
    return artifact_id, output_path


def inject_ifx_state_runtime(html: str, artifact_id: str) -> str:
    if 'id="ifx-state-runtime"' in html:
        return html

    state_endpoint = f"{SETTINGS.public_app_base_url}/api/artifacts/state/{artifact_id}"
    script = f"""
<script id="ifx-state-runtime">
(() => {{
  const artifactId = {json.dumps(artifact_id)};
  const stateEndpoint = {json.dumps(state_endpoint)};
  const namespace = window.ifx = window.ifx || {{}};
  if (namespace.state && typeof namespace.state.get === 'function') {{
    return;
  }}

  const pending = new Map();
  const timers = new Map();

  function normalizeKey(key) {{
    return typeof key === 'string' && key.trim() ? key.trim() : '_default';
  }}

  function buildReadUrl(key) {{
    const url = new URL(stateEndpoint);
    url.searchParams.set('key', normalizeKey(key));
    return url.toString();
  }}

  async function getState(key) {{
    try {{
      const response = await fetch(buildReadUrl(key), {{
        method: 'GET',
        cache: 'no-store',
        credentials: 'include',
        headers: {{ Accept: 'application/json' }},
        mode: 'cors',
      }});
      if (!response.ok) return null;
      const payload = await response.json();
      return payload && typeof payload === 'object' ? payload.state_value ?? null : null;
    }} catch (error) {{
      console.warn('IterFact state get failed', error);
      return null;
    }}
  }}

  async function writeState(value, key, keepalive) {{
    try {{
      const response = await fetch(stateEndpoint, {{
        method: keepalive ? 'POST' : 'PUT',
        cache: 'no-store',
        credentials: 'include',
        keepalive: Boolean(keepalive),
        headers: {{
          'Content-Type': 'application/json',
          Accept: 'application/json',
        }},
        mode: 'cors',
        body: JSON.stringify({{
          key: normalizeKey(key),
          value: value ?? {{}},
        }}),
      }});
      return response.ok;
    }} catch (error) {{
      console.warn('IterFact state save failed', error);
      return false;
    }}
  }}

  function flushPending() {{
    const queued = Array.from(pending.entries());
    pending.clear();
    for (const [key, value] of queued) {{
      void writeState(value, key, true);
    }}
  }}

  function saveDebounced(value, key, delay) {{
    const normalizedKey = normalizeKey(key);
    const wait = Number.isFinite(delay) ? Math.max(0, Number(delay)) : 1000;
    pending.set(normalizedKey, value ?? {{}});
    const existing = timers.get(normalizedKey);
    if (existing) {{
      clearTimeout(existing);
    }}
    const timer = setTimeout(() => {{
      const latest = pending.get(normalizedKey);
      pending.delete(normalizedKey);
      timers.delete(normalizedKey);
      void writeState(latest ?? {{}}, normalizedKey, false);
    }}, wait);
    timers.set(normalizedKey, timer);
    return true;
  }}

  window.addEventListener('pagehide', flushPending);
  window.addEventListener('beforeunload', flushPending);
  document.addEventListener('visibilitychange', () => {{
    if (document.visibilityState === 'hidden') {{
      flushPending();
    }}
  }});

  namespace.state = {{
    __iterfactVersion: '2026-03-24',
    artifactId,
    get: getState,
    set(value, key) {{
      return writeState(value, key, false);
    }},
    save: saveDebounced,
    flush: flushPending,
  }};
}})();
</script>
"""

    lower_html = html.lower()
    body_index = lower_html.rfind("</body>")
    if body_index >= 0:
        return f"{html[:body_index]}{script}{html[body_index:]}"
    return f"{html}{script}"


def build_library_preview_asset_paths(local_path: Path) -> dict[str, Any]:
    stem = local_path.stem
    parent = local_path.parent
    poster = parent / f"{stem}.preview-poster.png"
    frames = [parent / f"{stem}.preview-frame-{index}.png" for index in range(1, 4)]
    return {"poster": poster, "frames": frames}


def existing_library_preview_sidecars(local_path: Path) -> list[Path]:
    asset_paths = build_library_preview_asset_paths(local_path)
    return [
        candidate
        for candidate in [asset_paths["poster"], *asset_paths["frames"]]
        if candidate.exists()
    ]


def _remove_preview_sidecars(paths: list[Path]) -> None:
    for path in paths:
        with contextlib.suppress(FileNotFoundError):
            path.unlink()


def derive_library_preview_title(html: str, local_path: Path) -> str:
    title_match = re.search(r"<title[^>]*>(.*?)</title>", html, flags=re.IGNORECASE | re.DOTALL)
    if title_match:
        title = compact_whitespace(unescape(title_match.group(1)))
        if title:
            return title

    og_title_match = re.search(
        r'<meta[^>]+property=["\']og:title["\'][^>]+content=["\'](.*?)["\']',
        html,
        flags=re.IGNORECASE | re.DOTALL,
    )
    if og_title_match:
        title = compact_whitespace(unescape(og_title_match.group(1)))
        if title:
            return title

    stem = local_path.stem.replace("-", " ").replace("_", " ").strip()
    return compact_whitespace(stem.title()) or "IterFact preview"


def build_library_preview_fallback_markup(title: str) -> str:
    safe_title = escape(compact_whitespace(title) or "IterFact preview")
    return f"""<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{safe_title}</title>
    <style>
      :root {{
        color-scheme: dark;
      }}
      * {{
        box-sizing: border-box;
      }}
      body {{
        margin: 0;
        width: 960px;
        height: 600px;
        overflow: hidden;
        background: #13272a;
        color: rgba(250, 247, 241, 0.94);
        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      }}
      .frame {{
        position: relative;
        width: 100%;
        height: 100%;
        padding: 36px;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        background:
          radial-gradient(circle at 82% 88%, rgba(250, 247, 241, 0.08), transparent 30%),
          linear-gradient(180deg, rgba(19, 39, 42, 0.94), #13272a);
      }}
      .frame::after {{
        content: "";
        position: absolute;
        inset: auto -12% -24% 42%;
        height: 72%;
        border-radius: 999px;
        background: rgba(250, 247, 241, 0.06);
        transform: rotate(-10deg);
      }}
      .chrome {{
        position: relative;
        z-index: 1;
        display: flex;
        align-items: center;
        justify-content: space-between;
      }}
      .brand {{
        font-size: 28px;
        font-weight: 500;
        letter-spacing: -0.04em;
        color: rgba(102, 209, 184, 0.9);
      }}
      .badge {{
        min-height: 42px;
        padding: 0 18px;
        border-radius: 999px;
        background: rgba(250, 247, 241, 0.12);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-size: 24px;
        color: rgba(250, 247, 241, 0.9);
      }}
      .content {{
        position: relative;
        z-index: 1;
        display: grid;
        gap: 28px;
      }}
      .bars {{
        display: flex;
        align-items: end;
        gap: 16px;
        height: 220px;
      }}
      .bar {{
        width: 52px;
        border-radius: 999px 999px 18px 18px;
        opacity: 0.92;
      }}
      .eyebrow {{
        font-size: 22px;
        letter-spacing: 0.02em;
        color: rgba(102, 209, 184, 0.82);
      }}
      .title {{
        margin-top: 12px;
        max-width: 720px;
        font-size: 52px;
        line-height: 1.02;
        letter-spacing: -0.05em;
        color: rgba(250, 247, 241, 0.96);
      }}
    </style>
  </head>
  <body>
    <div class="frame">
      <div class="chrome">
        <div class="brand">iterFact</div>
        <div class="badge">HTML</div>
      </div>
      <div class="content">
        <div class="bars" aria-hidden="true">
          <span class="bar" style="height: 96px; background: #66d1b8;"></span>
          <span class="bar" style="height: 124px; background: #fbbf24;"></span>
          <span class="bar" style="height: 154px; background: #60a5fa;"></span>
          <span class="bar" style="height: 182px; background: #f87171;"></span>
          <span class="bar" style="height: 208px; background: #a78bfa;"></span>
        </div>
        <div>
          <div class="eyebrow">Interactive document</div>
          <div class="title">{safe_title}</div>
        </div>
      </div>
    </div>
  </body>
</html>
"""


def copy_bundled_library_preview_poster(local_path: Path) -> list[Path]:
    if local_path.suffix.lower() != ".html":
        return []

    asset_paths = build_library_preview_asset_paths(local_path)
    poster_path = asset_paths["poster"]
    source_path = STATIC_DIR / "library-preview-fallback.png"
    if not source_path.exists():
        return []

    with contextlib.suppress(FileNotFoundError):
        poster_path.unlink()

    try:
        poster_path.write_bytes(source_path.read_bytes())
    except Exception as exc:  # pragma: no cover - disk write best effort
        LOGGER.warning(
            "Unable to copy bundled library preview poster for %s: %s",
            local_path,
            exc,
        )
        with contextlib.suppress(FileNotFoundError):
            poster_path.unlink()
        return []

    return [poster_path] if poster_path.exists() else []


def capture_library_preview_fallback_poster(local_path: Path, title: str) -> list[Path]:
    if local_path.suffix.lower() != ".html":
        return []

    if not SETTINGS.library_preview_capture_enabled or sync_playwright is None:
        return copy_bundled_library_preview_poster(local_path)

    asset_paths = build_library_preview_asset_paths(local_path)
    poster_path = asset_paths["poster"]
    fallback_markup = build_library_preview_fallback_markup(title)

    with contextlib.suppress(FileNotFoundError):
        poster_path.unlink()

    def worker() -> list[Path]:
        with sync_playwright() as playwright:
            browser = playwright.chromium.launch(
                headless=True,
                args=["--disable-dev-shm-usage", "--no-sandbox"],
            )
            try:
                page = browser.new_page(viewport={"width": 960, "height": 600})
                page.set_content(fallback_markup, wait_until="load")
                page.wait_for_timeout(120)
                page.screenshot(
                    path=str(poster_path),
                    type="png",
                    full_page=False,
                )
            finally:
                browser.close()
        return [poster_path] if poster_path.exists() else []

    try:
        return run_threaded_capture_job(
            name="iterfact-library-preview-fallback",
            timeout_seconds=max(10, int(SETTINGS.library_preview_capture_timeout_ms / 1000) * 4),
            worker=worker,
        )
    except Exception as exc:  # pragma: no cover - best effort capture
        LOGGER.warning(
            "Unable to capture fallback library preview poster for %s: %s",
            local_path,
            exc,
        )
        with contextlib.suppress(FileNotFoundError):
            poster_path.unlink()
        return copy_bundled_library_preview_poster(local_path)

    return []

    return []


def capture_cached_library_preview_assets(local_path: Path) -> list[Path]:
    if not SETTINGS.library_preview_capture_enabled:
        return []
    if sync_playwright is None:
        return []
    if local_path.suffix.lower() != ".html":
        return []

    asset_paths = build_library_preview_asset_paths(local_path)
    all_paths = [asset_paths["poster"], *asset_paths["frames"]]
    _remove_preview_sidecars(all_paths)

    def worker() -> list[Path]:
        with sync_playwright() as playwright:
            browser = playwright.chromium.launch(
                headless=True,
                args=["--disable-dev-shm-usage", "--no-sandbox"],
            )
            try:
                page = browser.new_page(viewport={"width": 960, "height": 600})
                page.goto(local_path.resolve().as_uri(), wait_until="domcontentloaded")
                with contextlib.suppress(PlaywrightError):
                    page.wait_for_load_state(
                        "networkidle",
                        timeout=SETTINGS.library_preview_capture_timeout_ms,
                    )
                page.wait_for_timeout(350)
                page.screenshot(
                    path=str(asset_paths["poster"]),
                    type="png",
                    full_page=False,
                )
                for frame_path in asset_paths["frames"]:
                    page.wait_for_timeout(240)
                    page.screenshot(path=str(frame_path), type="png", full_page=False)
            finally:
                browser.close()
        return [path for path in all_paths if path.exists()]

    try:
        return run_threaded_capture_job(
            name="iterfact-library-preview-capture",
            timeout_seconds=max(10, int(SETTINGS.library_preview_capture_timeout_ms / 1000) * 4),
            worker=worker,
        )
    except Exception as exc:  # pragma: no cover - best effort capture
        LOGGER.warning(
            "Unable to capture cached library preview assets for %s: %s",
            local_path,
            exc,
        )
        _remove_preview_sidecars(all_paths)
        return []


def ensure_library_preview_assets(local_path: Path, html: str | None = None) -> list[Path]:
    normalized_html = html
    if normalized_html is None:
        try:
            normalized_html = local_path.read_text(encoding="utf-8")
        except Exception:
            normalized_html = ""

    if extract_body_residue_without_delivery_shells(normalized_html):
        real_assets = capture_cached_library_preview_assets(local_path)
        if real_assets:
            return real_assets

    fallback_title = derive_library_preview_title(normalized_html, local_path)
    return capture_library_preview_fallback_poster(local_path, fallback_title)


def make_r2_client():
    return boto3.client(
        "s3",
        endpoint_url=f"https://{SETTINGS.r2_account_id}.r2.cloudflarestorage.com",
        aws_access_key_id=SETTINGS.r2_access_key_id,
        aws_secret_access_key=SETTINGS.r2_secret_access_key,
        region_name="auto",
    )


def build_r2_immutable_backup_key(object_key: str) -> str:
    key_path = PurePosixPath(object_key)
    stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S-%f")
    return str(key_path.parent / "_versions" / f"{stamp}-{key_path.name}")


def backup_existing_r2_object(client: Any, object_key: str) -> str | None:
    if not SETTINGS.has_r2:
        return None

    try:
        response = client.get_object(Bucket=SETTINGS.r2_bucket, Key=object_key)
    except Exception:
        return None

    backup_key = build_r2_immutable_backup_key(object_key)
    body = response["Body"].read()
    content_type = compact_whitespace(str(response.get("ContentType") or "")) or guess_upload_content_type(Path(object_key))
    client.put_object(
        Bucket=SETTINGS.r2_bucket,
        Key=backup_key,
        Body=body,
        ContentType=content_type,
    )
    return backup_key


def upload_published_html_backup_to_r2(client: Any, local_path: Path, artifact_id: str) -> str | None:
    if local_path.suffix.lower() != ".html":
        return None
    backup_key = f"{SETTINGS.r2_prefix}/{artifact_id}/_source/{local_path.name}"
    client.upload_file(
        str(local_path),
        SETTINGS.r2_bucket,
        backup_key,
        ExtraArgs={"ContentType": guess_upload_content_type(local_path)},
    )
    return backup_key


def guess_upload_content_type(path: Path) -> str:
    if path.suffix.lower() == ".html":
        return "text/html; charset=utf-8"

    guessed, _ = mimetypes.guess_type(path.name)
    if guessed:
        return guessed
    return "application/octet-stream"


ALLOWED_FEEDBACK_NOTE_TYPES = {
    "change copy",
    "update number",
    "add section",
    "remove section",
    "visual fix",
    "general note",
}


def normalize_feedback_note_type(value: str | None) -> str:
    lowered = compact_whitespace(value or "").lower()
    aliases = {
        "revision": "general note",
        "note": "general note",
        "comment": "general note",
        "copy": "change copy",
        "text": "change copy",
        "number": "update number",
        "metric": "update number",
        "visual": "visual fix",
        "design": "visual fix",
    }
    lowered = aliases.get(lowered, lowered)
    return lowered if lowered in ALLOWED_FEEDBACK_NOTE_TYPES else "general note"


def normalize_feedback_note(note: dict[str, Any], *, fallback_source: str = "iterate_link") -> dict[str, Any]:
    note_id = compact_whitespace(str(note.get("id") or f"ifx-note-{uuid.uuid4()}"))
    text = compact_whitespace(
        str(
            note.get("text")
            or note.get("body")
            or note.get("comment")
            or note.get("message")
            or note.get("content")
            or ""
        )
    )
    if not text:
        raise ValueError("Feedback note text is required.")
    created_at = compact_whitespace(str(note.get("created_at") or note.get("createdAt") or datetime.now(timezone.utc).isoformat()))
    status = compact_whitespace(str(note.get("status") or "open")).lower() or "open"
    if status not in {"open", "applied", "dismissed"}:
        status = "open"
    return {
        "id": note_id,
        "status": status,
        "type": normalize_feedback_note_type(str(note.get("type") or "")),
        "text": text,
        "author": compact_whitespace(str(note.get("author") or note.get("user") or note.get("created_by") or "")) or None,
        "target_id": compact_whitespace(str(note.get("target_id") or note.get("targetId") or "")) or None,
        "target_label": compact_whitespace(str(note.get("target_label") or note.get("targetLabel") or "")) or None,
        "section_hint": compact_whitespace(str(note.get("section_hint") or note.get("sectionHint") or "")) or None,
        "selection_excerpt": compact_whitespace(str(note.get("selection_excerpt") or note.get("selectionExcerpt") or "")) or None,
        "source": compact_whitespace(str(note.get("source") or fallback_source)) or fallback_source,
        "created_at": created_at,
        "updated_at": compact_whitespace(str(note.get("updated_at") or note.get("updatedAt") or created_at)) or created_at,
        "applied_at": compact_whitespace(str(note.get("applied_at") or note.get("appliedAt") or "")) or None,
        "dismissed_at": compact_whitespace(str(note.get("dismissed_at") or note.get("dismissedAt") or "")) or None,
    }


def normalize_feedback_notes(notes: Any) -> list[dict[str, Any]]:
    normalized: list[dict[str, Any]] = []
    if not isinstance(notes, list):
        return normalized
    for item in notes:
        if not isinstance(item, dict):
            continue
        with contextlib.suppress(Exception):
            normalized.append(normalize_feedback_note(item, fallback_source=str(item.get("source") or "iterate_link")))
    return normalized


def get_feedback_notes_from_state(state: dict[str, Any], *, include_resolved: bool = False) -> list[dict[str, Any]]:
    notes = normalize_feedback_notes(state.get("feedback_notes"))
    if include_resolved:
        return notes
    return [note for note in notes if str(note.get("status") or "open") == "open"]


def summarize_feedback_notes(notes: list[dict[str, Any]]) -> dict[str, Any]:
    summary = {
        "total": len(notes),
        "open": 0,
        "applied": 0,
        "dismissed": 0,
        "latest_at": None,
    }
    for note in notes:
        status = str(note.get("status") or "open")
        if status == "applied":
            summary["applied"] += 1
        elif status == "dismissed":
            summary["dismissed"] += 1
        else:
            summary["open"] += 1
        latest = str(note.get("updated_at") or note.get("created_at") or "")
        if latest and (summary["latest_at"] is None or latest > summary["latest_at"]):
            summary["latest_at"] = latest
    return summary


def append_artifact_feedback_note(artifact_id: str, note: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
    state_path = artifact_state_path(artifact_id)
    with ARTIFACT_STATE_LOCK:
        if not state_path.exists():
            raise FileNotFoundError(f"No artifact state exists for {artifact_id}.")
        state = json.loads(state_path.read_text(encoding="utf-8"))
        notes = normalize_feedback_notes(state.get("feedback_notes"))
        normalized = normalize_feedback_note(note)
        notes.append(normalized)
        state["feedback_notes"] = notes[-250:]
        state["updated_at"] = datetime.now(timezone.utc).isoformat()
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state, normalized


def update_feedback_note_statuses(artifact_id: str, note_ids: list[str], *, status: str) -> dict[str, Any]:
    desired_status = status if status in {"open", "applied", "dismissed"} else "open"
    state_path = artifact_state_path(artifact_id)
    selected_ids = {compact_whitespace(note_id) for note_id in note_ids if compact_whitespace(note_id)}
    with ARTIFACT_STATE_LOCK:
        if not state_path.exists():
            raise FileNotFoundError(f"No artifact state exists for {artifact_id}.")
        state = json.loads(state_path.read_text(encoding="utf-8"))
        notes = normalize_feedback_notes(state.get("feedback_notes"))
        now = datetime.now(timezone.utc).isoformat()
        for note in notes:
            if note.get("id") not in selected_ids:
                continue
            note["status"] = desired_status
            note["updated_at"] = now
            if desired_status == "applied":
                note["applied_at"] = now
            elif desired_status == "dismissed":
                note["dismissed_at"] = now
        state["feedback_notes"] = notes
        state["updated_at"] = now
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state


def artifact_state_path(artifact_id: str) -> Path:
    return SETTINGS.output_dir / artifact_id / ARTIFACT_STATE_FILENAME


def load_artifact_state(artifact_id: str) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    if not state_path.exists():
        raise FileNotFoundError(
            "This artifact does not have saved regeneration state yet. Regenerate it once with generate_document, then section regeneration will be available."
        )
    state = json.loads(state_path.read_text(encoding="utf-8"))
    if isinstance(state.get("plan"), dict):
        state["plan"] = normalize_plan(state["plan"])
    state["feedback_notes"] = normalize_feedback_notes(state.get("feedback_notes"))
    state["render_checkpoint"] = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
    state["version_preferences"] = normalize_artifact_version_preferences(state)
    return state


def build_public_artifact_url(
    artifact_id: str,
    html_file: str | None,
    *,
    object_key: str | None = None,
    preview_url: str | None = None,
) -> str | None:
    if preview_url:
        return str(preview_url)
    if object_key and SETTINGS.public_artifact_base_url:
        return f"{SETTINGS.public_artifact_base_url}/{object_key}"
    if html_file and SETTINGS.public_artifact_base_url:
        return f"{SETTINGS.public_artifact_base_url}/{SETTINGS.r2_prefix}/{artifact_id}/{html_file}"
    return None


def build_version_preview_assets(public_url: str | None) -> dict[str, Any]:
    if not public_url or not public_url.endswith(".html"):
        return {"poster_url": None, "frame_urls": []}
    base = public_url[:-5]
    return {
        "poster_url": f"{base}.preview-poster.png",
        "frame_urls": [f"{base}.preview-frame-{index}.png" for index in range(1, 4)],
    }


def normalize_artifact_version_preferences(state: dict[str, Any] | None) -> dict[str, Any]:
    current = state.get("current") if isinstance(state, dict) and isinstance(state.get("current"), dict) else {}
    revision_history = (
        list(state.get("revision_history") or [])
        if isinstance(state, dict)
        else []
    )
    known_files = {
        str(entry.get("html_file") or "").strip()
        for entry in [current, *revision_history]
        if isinstance(entry, dict) and str(entry.get("html_file") or "").strip()
    }
    raw_preferences = state.get("version_preferences") if isinstance(state, dict) and isinstance(state.get("version_preferences"), dict) else {}
    requested_primary = str(raw_preferences.get("primary_html_file") or "").strip()
    fallback_primary = str(current.get("html_file") or "").strip()
    primary_html_file = requested_primary if requested_primary in known_files else fallback_primary
    return {
        "primary_html_file": primary_html_file or None,
        "show_public_iterations": bool(raw_preferences.get("show_public_iterations")),
        "updated_at": raw_preferences.get("updated_at"),
    }


def describe_artifact_version_reason(version: dict[str, Any]) -> str:
    regeneration_context = (
        version.get("regeneration_context")
        if isinstance(version.get("regeneration_context"), dict)
        else {}
    )
    generation_mode = str(version.get("generation_mode") or "").strip().lower()
    if regeneration_context.get("builder_mode") == "chunked_append":
        return "Chunked build finalized"
    if generation_mode == "section_regeneration":
        section_index = regeneration_context.get("section_index")
        section_title = compact_whitespace(str(regeneration_context.get("section_title") or ""))
        if section_index and section_title:
            return f"Section {section_index} refreshed: {section_title}"
        if section_index:
            return f"Section {section_index} refreshed"
        return "Targeted section refresh"
    if generation_mode == "feedback_revision":
        return "Feedback-driven revision"
    if generation_mode == "admin_passthrough_html":
        return "Admin passthrough upload"
    if generation_mode == "template_instance_render":
        return "Generated from a template"
    if generation_mode == "chunked_planned_render":
        return "Chunked IterFact generation"
    if generation_mode in {"planned_render", "background_planned_render"}:
        return "Fresh IterFact generation"
    return "Artifact revision"


def build_artifact_version_entries(state: dict[str, Any]) -> list[dict[str, Any]]:
    artifact_id = str(state.get("artifact_id") or "")
    title = compact_whitespace(str(state.get("title") or "")) or "IterFact Artifact"
    current = state.get("current") if isinstance(state.get("current"), dict) else {}
    revision_history = list(state.get("revision_history") or [])
    version_preferences = normalize_artifact_version_preferences(state)
    primary_html_file = str(version_preferences.get("primary_html_file") or "")
    raw_entries: list[dict[str, Any]] = []
    seen_files: set[str] = set()

    for candidate in [current, *revision_history]:
        if not isinstance(candidate, dict):
            continue
        html_file = compact_whitespace(str(candidate.get("html_file") or ""))
        if not html_file or html_file in seen_files:
            continue
        seen_files.add(html_file)
        public_url = build_public_artifact_url(
            artifact_id,
            html_file,
            object_key=str(candidate.get("object_key") or "") or None,
            preview_url=str(candidate.get("preview_url") or "") or None,
        )
        preview_assets = build_version_preview_assets(public_url)
        raw_entries.append(
            {
                "html_file": html_file,
                "title": compact_whitespace(str(candidate.get("version_title") or "")) or title,
                "published_at": candidate.get("published_at") or candidate.get("updated_at") or state.get("updated_at"),
                "superseded_at": candidate.get("superseded_at"),
                "quality_score": candidate.get("quality_score"),
                "warning_count": len(candidate.get("warnings") or []) if isinstance(candidate.get("warnings"), list) else 0,
                "generation_mode": candidate.get("generation_mode"),
                "reason": describe_artifact_version_reason(candidate),
                "preview_url": public_url,
                "poster_url": preview_assets["poster_url"],
                "frame_urls": preview_assets["frame_urls"],
                "is_latest": html_file == str(current.get("html_file") or ""),
                "is_primary": html_file == primary_html_file,
            }
        )

    raw_entries.sort(
        key=lambda item: (
            str(item.get("published_at") or ""),
            str(item.get("html_file") or ""),
        ),
        reverse=True,
    )

    version_number_lookup: dict[str, int] = {}
    for index, item in enumerate(reversed(raw_entries), start=1):
        version_number_lookup[str(item.get("html_file") or "")] = index

    entries: list[dict[str, Any]] = []
    for item in raw_entries:
        html_file = str(item.get("html_file") or "")
        entries.append(
            {
                **item,
                "version_number": version_number_lookup.get(html_file, 1),
            }
        )
    return entries


def build_artifact_versions_response(state: dict[str, Any]) -> dict[str, Any]:
    artifact_id = str(state.get("artifact_id") or "")
    title = compact_whitespace(str(state.get("title") or "")) or "IterFact Artifact"
    version_preferences = normalize_artifact_version_preferences(state)
    entries = build_artifact_version_entries(state)
    primary_entry = next((entry for entry in entries if entry.get("is_primary")), None)
    latest_entry = next((entry for entry in entries if entry.get("is_latest")), None)
    return {
        "artifact_id": artifact_id,
        "title": title,
        "version_count": len(entries),
        "show_public_iterations": bool(version_preferences.get("show_public_iterations")),
        "public_iterations_url": f"{SETTINGS.public_app_base_url}/artifacts/{artifact_id}/iterations",
        "primary_html_file": version_preferences.get("primary_html_file"),
        "primary_version": primary_entry,
        "latest_version": latest_entry,
        "versions": entries,
    }


def build_artifact_section_entries(state: dict[str, Any]) -> list[dict[str, Any]]:
    plan = state.get("plan") if isinstance(state.get("plan"), dict) else {}
    sections = plan.get("sections") if isinstance(plan.get("sections"), list) else []
    entries: list[dict[str, Any]] = []

    for index, section in enumerate(sections, start=1):
        if not isinstance(section, dict):
            continue
        title = compact_whitespace(str(section.get("title") or "")) or f"Section {index}"
        entries.append(
            {
                "index": index,
                "title": title,
                "section_type": compact_whitespace(str(section.get("type") or "")) or None,
                "section_job": compact_whitespace(str(section.get("job") or "")) or None,
                "engine": compact_whitespace(str(section.get("engine") or "")) or None,
            }
        )

    return entries


def build_artifact_sections_response(state: dict[str, Any]) -> dict[str, Any]:
    entries = build_artifact_section_entries(state)
    return {
        "artifact_id": state.get("artifact_id"),
        "title": compact_whitespace(str(state.get("title") or "")) or "IterFact Artifact",
        "section_count": len(entries),
        "sections": entries,
        "updated_at": state.get("updated_at"),
    }


def update_artifact_version_preferences(
    artifact_id: str,
    updater: Callable[[dict[str, Any]], dict[str, Any]],
) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    with ARTIFACT_STATE_LOCK:
        if not state_path.exists():
            raise FileNotFoundError(f"No artifact state exists for {artifact_id}.")
        state = json.loads(state_path.read_text(encoding="utf-8"))
        preferences = normalize_artifact_version_preferences(state)
        next_preferences = updater(dict(preferences))
        next_preferences["updated_at"] = datetime.now(timezone.utc).isoformat()
        state["version_preferences"] = next_preferences
        state["updated_at"] = next_preferences["updated_at"]
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return load_artifact_state(artifact_id)


def set_artifact_primary_version(artifact_id: str, html_file: str) -> dict[str, Any]:
    normalized_html_file = compact_whitespace(str(html_file or ""))
    if not normalized_html_file:
        raise ValueError("html_file is required to promote a version.")

    state = load_artifact_state(artifact_id)
    available_files = {
        str(entry.get("html_file") or "")
        for entry in build_artifact_version_entries(state)
        if str(entry.get("html_file") or "")
    }
    if normalized_html_file not in available_files:
        raise ValueError(f"{normalized_html_file} is not a published version for artifact {artifact_id}.")

    return update_artifact_version_preferences(
        artifact_id,
        lambda preferences: {
            **preferences,
            "primary_html_file": normalized_html_file,
        },
    )


def set_artifact_public_iteration_visibility(
    artifact_id: str,
    *,
    show_public_iterations: bool,
) -> dict[str, Any]:
    return update_artifact_version_preferences(
        artifact_id,
        lambda preferences: {
            **preferences,
            "show_public_iterations": bool(show_public_iterations),
        },
    )


def default_render_job_state() -> dict[str, Any]:
    return {
        "status": "idle",
        "attempt_count": 0,
        "job_id": None,
        "mode": "chunked_finalize",
        "resume_stage": None,
        "requested_at": None,
        "started_at": None,
        "finished_at": None,
        "failed_at": None,
        "progress": 0,
        "last_message": None,
        "last_error_type": None,
        "last_error_message": None,
        "preview_url": None,
        "quality_score": None,
        "warning_count": None,
        "skip_critic_requested": False,
        "critic_skipped": False,
        "critic_timed_out": False,
        "critic_timeout_seconds": None,
    }


RENDER_CHECKPOINT_STAGE_ORDER = {
    "planned": 1,
    "iterative_refinement": 2,
    "render_refined": 3,
    "delivery_validated": 4,
    "uploaded": 5,
}


def normalize_render_checkpoint_state(render_checkpoint: dict[str, Any] | None) -> dict[str, Any]:
    if not isinstance(render_checkpoint, dict):
        return {}
    normalized = dict(render_checkpoint)
    stage = compact_whitespace(str(normalized.get("stage") or "")).lower()
    normalized["stage"] = stage or None
    pass_number = normalized.get("pass_number")
    try:
        normalized["pass_number"] = max(0, int(pass_number))
    except (TypeError, ValueError):
        normalized["pass_number"] = 0
    return normalized


def checkpoint_stage_at_least(render_checkpoint: dict[str, Any] | None, stage: str) -> bool:
    current_stage = compact_whitespace(str((render_checkpoint or {}).get("stage") or "")).lower()
    return RENDER_CHECKPOINT_STAGE_ORDER.get(current_stage, 0) >= RENDER_CHECKPOINT_STAGE_ORDER.get(stage, 0)


def checkpoint_resume_progress(stage: str | None) -> int:
    normalized_stage = compact_whitespace(str(stage or "")).lower()
    if normalized_stage == "planned":
        return 36
    if normalized_stage == "iterative_refinement":
        return 55
    if normalized_stage == "render_refined":
        return 72
    if normalized_stage == "delivery_validated":
        return 86
    if normalized_stage == "uploaded":
        return 96
    return 55


def checkpoint_resume_message(stage: str | None) -> str:
    normalized_stage = compact_whitespace(str(stage or "")).lower()
    if normalized_stage == "planned":
        return "Resuming from a saved plan checkpoint and continuing render."
    if normalized_stage == "iterative_refinement":
        return "Resuming from a saved refinement checkpoint instead of restarting the full render."
    if normalized_stage == "render_refined":
        return "Resuming from saved rendered HTML and continuing delivery validation."
    if normalized_stage == "delivery_validated":
        return "Resuming from saved validated HTML and continuing publication."
    if normalized_stage == "uploaded":
        return "Resuming from the uploaded artifact checkpoint and finalizing publication metadata."
    return "Rendering, validating, and critiquing the finalized artifact."


def build_render_checkpoint(stage: str, **fields: Any) -> dict[str, Any]:
    checkpoint = {
        "version": 1,
        "stage": compact_whitespace(stage).lower() or stage,
        "updated_at": datetime.now(timezone.utc).isoformat(),
    }
    checkpoint.update(fields)
    return checkpoint


def update_artifact_render_checkpoint(
    artifact_id: str,
    *,
    checkpoint: dict[str, Any] | None = None,
    clear: bool = False,
) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    with ARTIFACT_STATE_LOCK:
        if not state_path.exists():
            raise FileNotFoundError(f"No artifact state exists for {artifact_id}.")
        state = json.loads(state_path.read_text(encoding="utf-8"))
        if clear:
            state["render_checkpoint"] = None
        elif checkpoint is not None:
            state["render_checkpoint"] = normalize_render_checkpoint_state(checkpoint)
        state["updated_at"] = datetime.now(timezone.utc).isoformat()
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state


def build_artifact_status_url(artifact_id: str) -> str:
    return f"{SETTINGS.public_mcp_base_url}/v1/artifacts/{artifact_id}/status"


def build_artifact_next_action(state: dict[str, Any], artifact_status: str, render_job: dict[str, Any]) -> dict[str, Any]:
    artifact_id = str(state.get("artifact_id") or "")
    status_url = build_artifact_status_url(artifact_id) if artifact_id else None
    render_status = str(render_job.get("status") or "idle")

    if artifact_status in {"planning", "building", "rendering"} or render_status in {"queued", "running"}:
        return {
            "type": "poll_status",
            "recommended_tool": "get_artifact_status",
            "fallback_tool": "finalize_artifact",
            "artifact_id": artifact_id,
            "status_url": status_url,
            "safe_to_repeat_finalize": True,
            "message": (
                "Poll get_artifact_status until the artifact is published or failed. "
                "If your client does not expose get_artifact_status yet, call finalize_artifact again with the same artifact_id. "
                "No duplicate render is started while a background render job is already queued or running."
            ),
        }

    if render_status == "failed" or artifact_status == "failed":
        return {
            "type": "retry_finalize",
            "recommended_tool": "finalize_artifact",
            "artifact_id": artifact_id,
            "status_url": status_url,
            "safe_to_repeat_finalize": True,
            "message": "Review render_job.last_error_message, then call finalize_artifact with retry_failed_render=true to resume from the saved checkpoint.",
        }

    if artifact_status == "draft":
        return {
            "type": "finalize",
            "recommended_tool": "finalize_artifact",
            "artifact_id": artifact_id,
            "status_url": status_url,
            "safe_to_repeat_finalize": True,
            "message": "This artifact is still a draft. Call finalize_artifact when you are ready to render and publish it.",
        }

    return {
        "type": "complete",
        "recommended_tool": None,
        "artifact_id": artifact_id,
        "status_url": status_url,
        "safe_to_repeat_finalize": True,
        "message": "The artifact is already published. Use the preview_url directly, or call get_artifact_status again if you need the latest metadata.",
    }


def normalize_render_job_state(render_job: dict[str, Any] | None) -> dict[str, Any]:
    normalized = default_render_job_state()
    if isinstance(render_job, dict):
        normalized.update(render_job)
    attempt_count = normalized.get("attempt_count")
    try:
        normalized["attempt_count"] = max(0, int(attempt_count))
    except (TypeError, ValueError):
        normalized["attempt_count"] = 0
    progress = normalized.get("progress")
    try:
        normalized["progress"] = max(0, min(100, int(progress)))
    except (TypeError, ValueError):
        normalized["progress"] = 0
    normalized["resume_stage"] = compact_whitespace(str(normalized.get("resume_stage") or "")).lower() or None
    return normalized


def update_artifact_runtime_state(
    artifact_id: str,
    *,
    artifact_status: str | None = None,
    render_job_patch: dict[str, Any] | None = None,
) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    with ARTIFACT_STATE_LOCK:
        if not state_path.exists():
            raise FileNotFoundError(f"No artifact state exists for {artifact_id}.")
        state = json.loads(state_path.read_text(encoding="utf-8"))
        if isinstance(state.get("plan"), dict):
            state["plan"] = normalize_plan(state["plan"])
        if artifact_status:
            state["artifact_status"] = artifact_status
        render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
        if render_job_patch:
            render_job.update(render_job_patch)
        state["render_job"] = render_job
        state["updated_at"] = datetime.now(timezone.utc).isoformat()
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state


def get_active_finalize_thread(artifact_id: str) -> threading.Thread | None:
    with FINALIZE_JOB_LOCK:
        thread = ACTIVE_FINALIZE_THREADS.get(artifact_id)
        if thread is not None and not thread.is_alive():
            ACTIVE_FINALIZE_THREADS.pop(artifact_id, None)
            return None
        return thread


def register_finalize_thread(artifact_id: str, thread: threading.Thread) -> None:
    with FINALIZE_JOB_LOCK:
        ACTIVE_FINALIZE_THREADS[artifact_id] = thread


def clear_finalize_thread(artifact_id: str) -> None:
    with FINALIZE_JOB_LOCK:
        ACTIVE_FINALIZE_THREADS.pop(artifact_id, None)


def build_artifact_status_response(
    state: dict[str, Any],
    *,
    notes: list[str] | None = None,
) -> dict[str, Any]:
    plan = state.get("plan") if isinstance(state.get("plan"), dict) else None
    current = state.get("current") if isinstance(state.get("current"), dict) else {}
    draft = state.get("draft") if isinstance(state.get("draft"), dict) else {}
    render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
    render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
    artifact_status = str(state.get("artifact_status") or ("published" if current else "draft"))
    feedback_notes = normalize_feedback_notes(state.get("feedback_notes"))
    versions_payload = build_artifact_versions_response(state)
    critic_skipped = bool(current.get("critic_skipped") or render_job.get("critic_skipped"))
    critic_timed_out = bool(current.get("critic_timed_out") or render_job.get("critic_timed_out"))
    planning_source = ((plan or {}).get("meta", {}) or {}).get("planning_source") if isinstance(plan, dict) else None

    derived_notes = list(notes or [])
    if planning_source == "client_supplied_plan":
        derived_notes.append("This artifact is using a Claude-side supplied story plan, so IterFact is only handling render, validation, and publication.")
    if artifact_status == "planning":
        derived_notes.append(
            "The artifact is planning in the background. Poll get_artifact_status until it moves to rendering, published, or failed. "
            "If your client does not expose get_artifact_status yet, call finalize_artifact again with the same artifact_id to check status."
        )
    elif artifact_status == "building":
        derived_notes.append(
            "The artifact is still being assembled in the background. Poll get_artifact_status until it moves to rendering, published, or failed. "
            "If your client does not expose get_artifact_status yet, call finalize_artifact again with the same artifact_id to check status."
        )
    elif artifact_status == "rendering":
        derived_notes.append(
            "Rendering is running in the background. Poll get_artifact_status until the artifact is published or failed. "
            "If your client does not expose get_artifact_status yet, call finalize_artifact again with the same artifact_id to check status."
        )
    elif render_job.get("status") == "failed":
        derived_notes.append(
            "Finalize failed. Review render_job.last_error_message, then call finalize_artifact with retry_failed_render=true if you want to resume from the saved checkpoint."
        )
        if render_checkpoint.get("stage"):
            derived_notes.append(
                "A resumable render checkpoint was saved, so the next finalize retry can continue from the last completed stage instead of restarting from scratch."
            )
    elif artifact_status == "failed":
        derived_notes.append("The artifact failed in the background. Review render_job.last_error_message before retrying.")
    elif artifact_status == "draft":
        derived_notes.append("This artifact is still a draft. Call finalize_artifact when you are ready to publish it.")
    elif artifact_status == "published":
        derived_notes.append("The artifact is published under the same artifact id.")
    if critic_skipped:
        derived_notes.append("Critic was skipped by caller request for this render.")
    if critic_timed_out:
        derived_notes.append("Critic evaluation timed out for this render, so the artifact published without editorial review.")

    return {
        "artifact_id": state.get("artifact_id"),
        "title": state.get("title"),
        "artifact_status": artifact_status,
        "draft_status": draft.get("status") or ("published" if artifact_status == "published" else "draft"),
        "render_job": render_job,
        "next_action": build_artifact_next_action(state, artifact_status, render_job),
        "planning_source": planning_source,
        "wrapper_engine_id": (plan or {}).get("wrapper", {}).get("engine") if isinstance(plan, dict) else None,
        "primary_engine_id": normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
        "summary": build_summary(plan) if plan else None,
        "section_count": len(plan.get("sections", []) or []) if plan else 0,
        "section_titles": [section.get("title") for section in (plan.get("sections", []) if plan else []) if section.get("title")],
        "preview_url": current.get("preview_url"),
        "download_url": current.get("preview_url"),
        "version_count": versions_payload.get("version_count"),
        "primary_version_url": (versions_payload.get("primary_version") or {}).get("preview_url"),
        "show_public_iterations": versions_payload.get("show_public_iterations"),
        "quality_score": current.get("quality_score"),
        "critic_skipped": critic_skipped,
        "critic_timed_out": critic_timed_out,
        "warnings": current.get("warnings") or None,
        "feedback_summary": summarize_feedback_notes(feedback_notes),
        "updated_at": state.get("updated_at"),
        "notes": derived_notes,
    }


def persist_artifact_draft_state(
    *,
    artifact_id: str,
    title: str,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    interaction_mode: str | None,
    generation_mode: str,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    plan: dict[str, Any],
    draft_state: dict[str, Any],
    draft_blueprint: dict[str, Any] | None = None,
) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    state_path.parent.mkdir(parents=True, exist_ok=True)
    now = datetime.now(timezone.utc).isoformat()
    previous_state: dict[str, Any] = {}
    if state_path.exists():
        with contextlib.suppress(Exception):
            previous_state = json.loads(state_path.read_text(encoding="utf-8"))
    version_preferences = normalize_artifact_version_preferences(previous_state)

    state = {
        "artifact_id": artifact_id,
        "created_at": (previous_state.get("created_at") if isinstance(previous_state, dict) else None) or now,
        "updated_at": now,
        "artifact_status": "draft",
        "title": title,
        "prompt": prompt,
        "template_hint": template_hint,
        "theme_hint": theme_hint,
        "interaction_mode": interaction_mode,
        "generation_mode": generation_mode,
        "structured_data": structured_data,
        "engine_override_requested": engine_override,
        "section_count_requested": section_count_requested,
        "wrapper_engine_id": plan.get("wrapper", {}).get("engine"),
        "theme": plan.get("theme"),
        "plan": plan,
        "draft": draft_state,
        "draft_blueprint": draft_blueprint if draft_blueprint is not None else previous_state.get("draft_blueprint"),
        "render_job": normalize_render_job_state(previous_state.get("render_job") if isinstance(previous_state, dict) else None),
        "render_checkpoint": None,
        "current": previous_state.get("current"),
        "feedback_notes": normalize_feedback_notes(previous_state.get("feedback_notes")),
        "telemetry_state": previous_state.get("telemetry_state") if isinstance(previous_state.get("telemetry_state"), dict) else None,
        "revision_history": list(previous_state.get("revision_history") or []),
        "version_preferences": version_preferences,
    }
    with ARTIFACT_STATE_LOCK:
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state


def persist_artifact_state(
    *,
    artifact_id: str,
    title: str,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    interaction_mode: str | None,
    generation_mode: str,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    plan: dict[str, Any],
    local_path: Path,
    storage: str,
    object_key: str | None,
    preview_url: str | None,
    warnings: list[dict[str, Any]],
    quality_score: int | None,
    render_validation_summary: dict[str, Any],
    regeneration_context: dict[str, Any] | None = None,
    draft_state: dict[str, Any] | None = None,
    draft_blueprint: dict[str, Any] | None = None,
    critic_skipped: bool = False,
    critic_timed_out: bool = False,
    uploaded_content: str | None = None,
) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    state_path.parent.mkdir(parents=True, exist_ok=True)
    now = datetime.now(timezone.utc).isoformat()
    previous_state: dict[str, Any] = {}
    if state_path.exists():
        with contextlib.suppress(Exception):
            previous_state = json.loads(state_path.read_text(encoding="utf-8"))
    version_preferences = normalize_artifact_version_preferences(previous_state)

    revision_history = list(previous_state.get("revision_history") or [])
    previous_current = previous_state.get("current") if isinstance(previous_state, dict) else None
    if isinstance(previous_current, dict) and previous_current.get("html_file") and previous_current.get("html_file") != local_path.name:
        revision_history.insert(
            0,
            {
                **previous_current,
                "superseded_at": now,
            },
        )
        revision_history = revision_history[:24]

    state = {
        "artifact_id": artifact_id,
        "created_at": (previous_state.get("created_at") if isinstance(previous_state, dict) else None) or now,
        "updated_at": now,
        "artifact_status": "published",
        "title": title,
        "prompt": prompt,
        "template_hint": template_hint,
        "theme_hint": theme_hint,
        "interaction_mode": interaction_mode,
        "generation_mode": generation_mode,
        "structured_data": structured_data,
        "uploaded_content": uploaded_content if uploaded_content is not None else previous_state.get("uploaded_content"),
        "engine_override_requested": engine_override,
        "section_count_requested": section_count_requested,
        "wrapper_engine_id": plan.get("wrapper", {}).get("engine"),
        "theme": plan.get("theme"),
        "plan": plan,
        "draft": draft_state if draft_state is not None else previous_state.get("draft"),
        "draft_blueprint": draft_blueprint if draft_blueprint is not None else previous_state.get("draft_blueprint"),
        "render_job": normalize_render_job_state(previous_state.get("render_job") if isinstance(previous_state, dict) else None),
        "render_checkpoint": None,
        "feedback_notes": normalize_feedback_notes(previous_state.get("feedback_notes")),
        "telemetry_state": previous_state.get("telemetry_state") if isinstance(previous_state.get("telemetry_state"), dict) else None,
        "current": {
            "html_file": local_path.name,
            "local_path": str(local_path),
            "storage": storage,
            "object_key": object_key,
            "preview_url": preview_url,
            "published_at": now,
            "quality_score": quality_score,
            "critic_skipped": critic_skipped,
            "critic_timed_out": critic_timed_out,
            "warnings": warnings,
            "render_validation_summary": render_validation_summary,
            "updated_at": now,
            "generation_mode": generation_mode,
            "regeneration_context": regeneration_context,
            "version_title": title,
        },
        "revision_history": revision_history,
        "version_preferences": {
            **version_preferences,
            "primary_html_file": local_path.name,
            "updated_at": now,
        },
    }
    with ARTIFACT_STATE_LOCK:
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state


def persist_background_generation_state(
    *,
    artifact_id: str,
    title: str,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    interaction_mode: str | None,
    generation_mode: str,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    plan: dict[str, Any] | None,
    artifact_status: str,
    render_job: dict[str, Any] | None = None,
    uploaded_content: str | None = None,
) -> dict[str, Any]:
    state_path = artifact_state_path(artifact_id)
    state_path.parent.mkdir(parents=True, exist_ok=True)
    now = datetime.now(timezone.utc).isoformat()
    previous_state: dict[str, Any] = {}
    if state_path.exists():
        with contextlib.suppress(Exception):
            previous_state = json.loads(state_path.read_text(encoding="utf-8"))
    version_preferences = normalize_artifact_version_preferences(previous_state)

    state = {
        "artifact_id": artifact_id,
        "created_at": (previous_state.get("created_at") if isinstance(previous_state, dict) else None) or now,
        "updated_at": now,
        "artifact_status": artifact_status,
        "title": title,
        "prompt": prompt,
        "template_hint": template_hint,
        "theme_hint": theme_hint,
        "interaction_mode": interaction_mode,
        "generation_mode": generation_mode,
        "structured_data": structured_data,
        "uploaded_content": uploaded_content if uploaded_content is not None else previous_state.get("uploaded_content"),
        "engine_override_requested": engine_override,
        "section_count_requested": section_count_requested,
        "wrapper_engine_id": plan.get("wrapper", {}).get("engine") if isinstance(plan, dict) else None,
        "theme": plan.get("theme") if isinstance(plan, dict) else theme_hint,
        "plan": plan,
        "draft": previous_state.get("draft"),
        "draft_blueprint": previous_state.get("draft_blueprint"),
        "render_job": normalize_render_job_state(render_job if isinstance(render_job, dict) else previous_state.get("render_job") if isinstance(previous_state, dict) else None),
        "render_checkpoint": normalize_render_checkpoint_state(previous_state.get("render_checkpoint") if isinstance(previous_state.get("render_checkpoint"), dict) else None),
        "feedback_notes": normalize_feedback_notes(previous_state.get("feedback_notes")),
        "telemetry_state": previous_state.get("telemetry_state") if isinstance(previous_state.get("telemetry_state"), dict) else None,
        "current": previous_state.get("current"),
        "revision_history": list(previous_state.get("revision_history") or []),
        "version_preferences": version_preferences,
    }
    with ARTIFACT_STATE_LOCK:
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")
    return state


def record_artifact_provider_events(
    artifact_id: str,
    provider_events: list[dict[str, Any]] | None,
) -> None:
    merged_events = merge_provider_events(provider_events)
    if not merged_events:
        return

    state_path = artifact_state_path(artifact_id)
    if not state_path.exists():
        return

    with ARTIFACT_STATE_LOCK:
        state = json.loads(state_path.read_text(encoding="utf-8"))
        telemetry_state = state.get("telemetry_state") if isinstance(state.get("telemetry_state"), dict) else {}
        existing_events = telemetry_state.get("provider_events") if isinstance(telemetry_state.get("provider_events"), list) else []
        telemetry_state["provider_events"] = merge_provider_events(existing_events, merged_events)
        state["telemetry_state"] = telemetry_state
        state_path.write_text(json.dumps(state, ensure_ascii=True, indent=2), encoding="utf-8")


def telemetry_provider_events_from_state(state: dict[str, Any] | None) -> list[dict[str, Any]]:
    if not isinstance(state, dict):
        return []
    telemetry_state = state.get("telemetry_state")
    if not isinstance(telemetry_state, dict):
        return []
    provider_events = telemetry_state.get("provider_events")
    return provider_events if isinstance(provider_events, list) else []


def estimate_anthropic_cost(usage: dict[str, int] | None) -> float | None:
    if not usage:
        return None

    input_tokens = usage.get("input_tokens", 0)
    output_tokens = usage.get("output_tokens", 0)
    input_cost = (input_tokens / 1_000_000) * SETTINGS.anthropic_input_cost_per_million
    output_cost = (output_tokens / 1_000_000) * SETTINGS.anthropic_output_cost_per_million
    return round(input_cost + output_cost, 6)


def resolve_estimated_generation_cost_usd(
    *,
    generation_mode: str | None,
    provider_tracker: TokenTracker,
    usage: dict[str, int] | None,
) -> float | None:
    if provider_tracker.total_calls:
        return provider_tracker.estimated_cost_usd

    estimated = estimate_anthropic_cost(usage)
    if estimated is not None:
        return estimated

    if generation_mode == "template_instance_render":
        return 0.0

    return None


def merge_provider_events(*event_groups: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
    merged: list[dict[str, Any]] = []
    seen: set[tuple[str, str, str, str]] = set()
    for group in event_groups:
        for event in group or []:
            if not isinstance(event, dict):
                continue
            key = (
                str(event.get("provider_request_id") or ""),
                str(event.get("step_name") or ""),
                str(event.get("model") or ""),
                str(event.get("started_at") or ""),
            )
            if key in seen:
                continue
            seen.add(key)
            merged.append(event)
    return merged


def token_tracker_from_provider_events(provider_events: list[dict[str, Any]] | None) -> TokenTracker:
    tracker = TokenTracker()
    for event in provider_events or []:
        if not isinstance(event, dict):
            continue
        usage = provider_usage_fields(event.get("raw_usage"))
        tracker.record(
            model=str(event.get("model") or ""),
            input_tokens=usage["input_tokens"],
            output_tokens=usage["output_tokens"],
            step_name=str(event.get("step_name") or "anthropic"),
            latency_ms=event.get("latency_ms"),
        )
    return tracker


def estimate_text_token_count(text: str | None) -> int:
    cleaned = compact_whitespace(text or "")
    if not cleaned:
        return 0
    return max(1, int(round(len(cleaned) / 4)))


def classify_prompt_category(
    prompt: str,
    *,
    template_hint: str | None = None,
    plan: dict[str, Any] | None = None,
) -> str:
    lower = (prompt or "").lower()
    template_text = (template_hint or "").lower()
    category_text = " ".join(
        [
            lower,
            template_text,
            str((plan or {}).get("meta", {}).get("document_format") or "").lower(),
            str((plan or {}).get("meta", {}).get("template_match") or "").lower(),
        ]
    )
    if any(token in category_text for token in ("dashboard", "kpi", "mrr", "arr", "qbr", "bar chart", "line chart")):
        return "dashboard"
    if any(token in category_text for token in ("revenue", "financial", "board", "investor", "quarterly", "profit", "ebitda", "cash flow")):
        return "financial_report"
    if any(token in category_text for token in ("real estate", "property", "listing", "broker", "neighborhood", "comp")):
        return "real_estate"
    if any(token in category_text for token in ("pitch deck", "fundraise", "seed round", "series a", "deck", "slides")):
        return "pitch_deck"
    if any(token in category_text for token in ("proposal", "scope of work", "sow", "engagement letter", "statement of work")):
        return "proposal"
    if any(token in category_text for token in ("course", "lesson", "curriculum", "training", "educational", "learn")):
        return "educational"
    if any(token in category_text for token in ("campaign", "marketing", "landing page", "brand", "social proof", "testimonial")):
        return "marketing"
    return "general"


def detect_trigger_source(telemetry_context: dict[str, Any] | None, generation_mode: str) -> str:
    context = sanitize_telemetry_context(telemetry_context)
    surface = str(context.get("source_surface") or "mcp").lower()
    actor_key = str(context.get("actor_key") or "").lower()
    lane = str(context.get("source_lane") or "").lower()
    mode = str(context.get("source_mode") or generation_mode or "").lower()
    blob = " ".join([surface, actor_key, lane, mode])
    if "sdk_python" in blob or ("sdk" in blob and "python" in blob):
        return "sdk_python"
    if "sdk_api" in blob or ("sdk" in blob and "api" in blob):
        return "sdk_api"
    if "template" in blob and "direct" in blob:
        return "template_direct"
    if "claude" in blob:
        return "mcp_claude"
    if "mcp" in blob:
        return "mcp_other"
    return surface or "mcp_other"


def summarize_warning_shapes(warnings: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
    summarized: list[dict[str, Any]] = []
    for warning in warnings or []:
        if not isinstance(warning, dict):
            continue
        summarized.append(
            {
                "type": warning.get("type"),
                "severity": warning.get("severity") or "warning",
            }
        )
    return summarized


def collect_regeneration_metadata(state: dict[str, Any] | None) -> tuple[int, list[int]]:
    revision_history = list((state or {}).get("revision_history") or [])
    current = (state or {}).get("current") if isinstance((state or {}).get("current"), dict) else {}
    regenerated_sections: list[int] = []
    if isinstance(current, dict):
        regen_context = current.get("regeneration_context")
        if isinstance(regen_context, dict):
            section_index = regen_context.get("section_index")
            if isinstance(section_index, int):
                regenerated_sections.append(section_index)
    for revision in revision_history:
        if not isinstance(revision, dict):
            continue
        regen_context = revision.get("regeneration_context")
        if not isinstance(regen_context, dict):
            continue
        section_index = regen_context.get("section_index")
        if isinstance(section_index, int):
            regenerated_sections.append(section_index)
    regenerated_sections = sorted(set(regenerated_sections))
    return len(regenerated_sections), regenerated_sections


def emit_telemetry_event(payload: dict[str, Any]) -> None:
    timestamp = str(payload.get("timestamp") or now_utc_iso())
    date_key = timestamp[:10]
    append_jsonl(TELEMETRY_DIR / f"{date_key}.jsonl", payload)


def load_recent_telemetry_events(days: int = 30) -> list[dict[str, Any]]:
    safe_days = max(1, min(int(days or 30), 365))
    cutoff = datetime.now(timezone.utc).date().toordinal() - safe_days + 1
    events: list[dict[str, Any]] = []
    for path in sorted(TELEMETRY_DIR.glob("*.jsonl")):
        try:
            if len(path.stem) == 10:
                file_day = datetime.strptime(path.stem, "%Y-%m-%d").date().toordinal()
                if file_day < cutoff:
                    continue
        except ValueError:
            pass
        with contextlib.suppress(Exception):
            for line in path.read_text(encoding="utf-8").splitlines():
                if not line.strip():
                    continue
                with contextlib.suppress(Exception):
                    events.append(json.loads(line))
    return events


def build_telemetry_summary(days: int = 30) -> dict[str, Any]:
    events = load_recent_telemetry_events(days=days)
    generation_events = [event for event in events if event.get("event_type") in {"artifact_generated", "artifact_failed"}]
    regeneration_events = [event for event in events if event.get("event_type") == "artifact_regenerated"]
    successful = [event for event in generation_events if event.get("artifact_status") == "published"]
    failed = [event for event in generation_events if event.get("artifact_status") == "failed"]
    costs = [
        float(event["estimated_cost_usd"])
        for event in generation_events
        if isinstance(event.get("estimated_cost_usd"), (int, float))
    ]
    quality_scores = [
        int(event["quality_score"])
        for event in successful
        if isinstance(event.get("quality_score"), (int, float))
    ]
    avg_cost = round(sum(costs) / len(costs), 6) if costs else None
    avg_quality = round(sum(quality_scores) / len(quality_scores), 2) if quality_scores else None
    top_engines = Counter(
        str(event.get("engine_id") or "")
        for event in successful
        if event.get("engine_id")
    ).most_common(10)
    prompt_categories = Counter(
        str(event.get("prompt_category") or "general")
        for event in generation_events
    ).most_common(10)
    warning_types = Counter()
    for event in successful:
        for warning_type in event.get("warning_types") or []:
            if warning_type:
                warning_types[str(warning_type)] += 1
    trigger_sources = Counter(
        str(event.get("trigger_source") or "unknown")
        for event in generation_events
    ).most_common(10)
    template_usage = Counter(
        str(event.get("template_id") or "")
        for event in successful
        if event.get("template_id")
    ).most_common(10)
    return {
        "window_days": max(1, min(int(days or 30), 365)),
        "event_count": len(events),
        "artifact_count": len(generation_events),
        "published_count": len(successful),
        "failed_count": len(failed),
        "regeneration_event_count": len(regeneration_events),
        "average_cost_usd": avg_cost,
        "average_quality_score": avg_quality,
        "top_engines": [{"engine_id": engine_id, "count": count} for engine_id, count in top_engines],
        "top_prompt_categories": [{"category": category, "count": count} for category, count in prompt_categories],
        "top_warning_types": [{"warning_type": warning_type, "count": count} for warning_type, count in warning_types.most_common(12)],
        "trigger_sources": [{"trigger_source": source, "count": count} for source, count in trigger_sources],
        "template_usage": [{"template_id": template_id, "count": count} for template_id, count in template_usage],
    }


def _cost_value(value: Any) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0


def _int_value(value: Any) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return 0


def _bucket_day(value: Any) -> str:
    text = compact_whitespace(str(value or ""))
    if len(text) >= 10:
        return text[:10]
    return "unknown"


def _aggregate_cost_dimensions(rows: list[dict[str, Any]], key: str) -> list[dict[str, Any]]:
    buckets: dict[str, dict[str, Any]] = {}
    for row in rows:
        bucket_key = compact_whitespace(str(row.get(key) or "")) or "unknown"
        bucket = buckets.setdefault(
            bucket_key,
            {
                key: bucket_key,
                "provider_call_count": 0,
                "input_tokens": 0,
                "output_tokens": 0,
                "formula_cost_usd": 0.0,
            },
        )
        bucket["provider_call_count"] += 1
        bucket["input_tokens"] += _int_value(row.get("input_tokens"))
        bucket["output_tokens"] += _int_value(row.get("output_tokens"))
        bucket["formula_cost_usd"] += _cost_value(row.get("formula_cost_usd"))

    results = list(buckets.values())
    for item in results:
        item["formula_cost_usd"] = round(float(item["formula_cost_usd"]), 6)
    results.sort(key=lambda item: (-float(item["formula_cost_usd"]), item.get(key) or ""))
    return results


def _aggregate_run_dimensions(rows: list[dict[str, Any]], key: str) -> list[dict[str, Any]]:
    buckets: dict[str, dict[str, Any]] = {}
    for row in rows:
        bucket_key = compact_whitespace(str(row.get(key) or "")) or "unknown"
        bucket = buckets.setdefault(
            bucket_key,
            {
                key: bucket_key,
                "generation_run_count": 0,
                "published_count": 0,
                "failed_count": 0,
                "formula_cost_usd": 0.0,
            },
        )
        status = compact_whitespace(str(row.get("status") or "unknown")).lower()
        bucket["generation_run_count"] += 1
        if status == "succeeded":
            bucket["published_count"] += 1
        elif status == "failed":
            bucket["failed_count"] += 1
        bucket["formula_cost_usd"] += _cost_value(row.get("formula_cost_usd"))

    results = list(buckets.values())
    for item in results:
        item["formula_cost_usd"] = round(float(item["formula_cost_usd"]), 6)
    results.sort(key=lambda item: (-int(item["generation_run_count"]), item.get(key) or ""))
    return results


async def build_supabase_cost_audit(
    *,
    days: int = 30,
    artifact_id: str | None = None,
    limit: int = 25,
) -> dict[str, Any]:
    safe_days = max(1, min(int(days or 30), 365))
    safe_limit = max(1, min(int(limit or 25), 200))
    cutoff_iso = datetime.now(timezone.utc) - timedelta(days=safe_days)
    cutoff_text = cutoff_iso.isoformat()
    select_usage = (
        "artifact_id,step_name,model,formula_cost_usd,input_tokens,output_tokens,"
        "provider_request_id,started_at,completed_at,created_at"
    )
    usage_params: dict[str, Any] = {
        "order": "created_at.desc",
        "limit": min(max(safe_limit * 10, 200), 5000),
    }
    run_params: dict[str, Any] = {
        "order": "created_at.desc",
        "limit": safe_limit,
    }
    if artifact_id:
        usage_params["artifact_id"] = f"eq.{artifact_id}"
        run_params["artifact_id"] = f"eq.{artifact_id}"
    else:
        usage_params["created_at"] = f"gte.{cutoff_text}"
        run_params["created_at"] = f"gte.{cutoff_text}"

    provider_rows = await fetch_supabase_rows(
        "provider_usage_events",
        select=select_usage,
        params=usage_params,
    )
    run_rows = await fetch_supabase_rows(
        "generation_runs",
        select=(
            "artifact_id,title,status,formula_cost_usd,quality_score,created_at,completed_at,"
            "source_mode,generation_mode,template_hint,theme_name,preview_url"
        ),
        params=run_params,
    )

    total_cost = round(sum(_cost_value(row.get("formula_cost_usd")) for row in provider_rows), 6)
    total_input = sum(_int_value(row.get("input_tokens")) for row in provider_rows)
    total_output = sum(_int_value(row.get("output_tokens")) for row in provider_rows)
    by_day_rows = []
    day_buckets: dict[str, dict[str, Any]] = {}
    for row in provider_rows:
        day_key = _bucket_day(row.get("created_at") or row.get("completed_at") or row.get("started_at"))
        bucket = day_buckets.setdefault(
            day_key,
            {
                "bucket_date": day_key,
                "provider_call_count": 0,
                "input_tokens": 0,
                "output_tokens": 0,
                "formula_cost_usd": 0.0,
            },
        )
        bucket["provider_call_count"] += 1
        bucket["input_tokens"] += _int_value(row.get("input_tokens"))
        bucket["output_tokens"] += _int_value(row.get("output_tokens"))
        bucket["formula_cost_usd"] += _cost_value(row.get("formula_cost_usd"))
    by_day_rows = list(day_buckets.values())
    for item in by_day_rows:
        item["formula_cost_usd"] = round(float(item["formula_cost_usd"]), 6)
    by_day_rows.sort(key=lambda item: item["bucket_date"], reverse=True)

    status_counts = Counter(
        compact_whitespace(str(row.get("status") or "unknown")) or "unknown"
        for row in run_rows
    )

    return {
        "window_days": safe_days,
        "artifact_id": artifact_id,
        "recent_generation_run_count": len(run_rows),
        "provider_call_count": len(provider_rows),
        "totals": {
            "formula_cost_usd": total_cost,
            "input_tokens": total_input,
            "output_tokens": total_output,
        },
        "status_counts": [
            {"status": status, "count": count}
            for status, count in status_counts.most_common()
        ],
        "by_generation_mode": _aggregate_run_dimensions(run_rows, "generation_mode")[:20],
        "by_source_mode": _aggregate_run_dimensions(run_rows, "source_mode")[:20],
        "by_model": _aggregate_cost_dimensions(provider_rows, "model")[:20],
        "by_step": _aggregate_cost_dimensions(provider_rows, "step_name")[:20],
        "by_day": by_day_rows[: max(safe_days, 7)],
        "recent_generation_runs": run_rows[:safe_limit],
        "recent_provider_events": provider_rows[:safe_limit],
    }


def upload_to_r2(local_path: Path, artifact_id: str) -> tuple[str, str | None]:
    key = f"{SETTINGS.r2_prefix}/{artifact_id}/{local_path.name}"

    client = make_r2_client()
    backup_key = None
    if local_path.suffix.lower() == ".html":
        with contextlib.suppress(Exception):
            backup_key = backup_existing_r2_object(client, key)
    client.upload_file(
        str(local_path),
        SETTINGS.r2_bucket,
        key,
        ExtraArgs={"ContentType": guess_upload_content_type(local_path)},
    )

    for sidecar_path in existing_library_preview_sidecars(local_path):
        sidecar_key = f"{SETTINGS.r2_prefix}/{artifact_id}/{sidecar_path.name}"
        try:
            client.upload_file(
                str(sidecar_path),
                SETTINGS.r2_bucket,
                sidecar_key,
                ExtraArgs={"ContentType": guess_upload_content_type(sidecar_path)},
            )
        except Exception as exc:  # pragma: no cover - best effort sidecars
            LOGGER.warning(
                "Unable to upload cached preview sidecar %s for artifact %s: %s",
                sidecar_path.name,
                artifact_id,
                exc,
            )

    published_backup_key = None
    with contextlib.suppress(Exception):
        published_backup_key = upload_published_html_backup_to_r2(client, local_path, artifact_id)

    if backup_key:
        LOGGER.info(
            "Backed up existing R2 artifact object before overwrite: %s -> %s",
            key,
            backup_key,
        )
    if published_backup_key:
        LOGGER.info(
            "Stored immutable published HTML backup for artifact %s at %s",
            artifact_id,
            published_backup_key,
        )

    preview_url = None
    if SETTINGS.public_artifact_base_url:
        preview_url = f"{SETTINGS.public_artifact_base_url}/{key}"

    return key, preview_url


def upload_admin_source_backup_to_r2(artifact_id: str, uploaded_html: str) -> str | None:
    if not SETTINGS.has_r2:
        return None

    key = f"{SETTINGS.r2_prefix}/{artifact_id}/_source/original-upload.html"
    client = boto3.client(
        "s3",
        endpoint_url=f"https://{SETTINGS.r2_account_id}.r2.cloudflarestorage.com",
        aws_access_key_id=SETTINGS.r2_access_key_id,
        aws_secret_access_key=SETTINGS.r2_secret_access_key,
        region_name="auto",
    )
    client.put_object(
        Bucket=SETTINGS.r2_bucket,
        Key=key,
        Body=uploaded_html.encode("utf-8"),
        ContentType="text/html; charset=utf-8",
    )
    return key


def persist_admin_source_backup(artifact_id: str, uploaded_html: str) -> tuple[Path, str | None]:
    artifact_dir = SETTINGS.output_dir / artifact_id
    artifact_dir.mkdir(parents=True, exist_ok=True)
    source_path = artifact_dir / "original-upload.html"
    source_path.write_text(uploaded_html, encoding="utf-8")
    return source_path, upload_admin_source_backup_to_r2(artifact_id, uploaded_html)


def resolve_interaction_mode(prompt: str, interaction_mode: str | None) -> str | None:
    mode = (interaction_mode or "").strip().lower()
    if mode in {"iterate", "fact"}:
        return mode

    lower = prompt.lower()
    if any(phrase in lower for phrase in ["iterate mode", "comment mode", "revision mode"]):
        return "iterate"
    if any(phrase in lower for phrase in ["fact mode", "final mode", "presentation mode"]):
        return "fact"
    return None


def apply_iterate_fact_shell(html: str, artifact_id: str, default_mode: str) -> str:
    style = """
<style id="ifx-iterate-style">
  :root {
    --ifx-iterate-bg: var(--color-background-primary, rgb(255 255 255));
    --ifx-iterate-bg-secondary: var(--color-background-secondary, rgb(248 246 240));
    --ifx-iterate-border: var(--color-border-tertiary, rgba(44, 44, 42, 0.15));
    --ifx-iterate-border-strong: var(--color-border-secondary, rgba(44, 44, 42, 0.3));
    --ifx-iterate-text: var(--color-text-primary, rgb(44 44 42));
    --ifx-iterate-muted: var(--color-text-secondary, rgb(95 94 90));
    --ifx-iterate-subtle: var(--color-text-tertiary, rgb(136 135 128));
    --ifx-iterate-accent: var(--ramp-teal-600, rgb(15 110 86));
    --ifx-iterate-accent-soft: var(--ramp-teal-50, rgb(225 245 238));
  }
  .ifx-iterate-shell, .ifx-iterate-ui, .ifx-iterate-pin, .ifx-iterate-composer, .ifx-iterate-drawer {
    font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif) !important;
  }
  .ifx-iterate-shell {
    position: sticky;
    top: 0.75rem;
    z-index: 48;
    display: grid;
    gap: 0.75rem;
    max-width: min(1120px, calc(100% - 2rem));
    margin: 0.9rem auto 0;
    padding: 0 1rem;
    container-type: inline-size;
  }
  .ifx-iterate-ui {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-start;
    padding: 0.75rem 0.85rem;
    border-radius: var(--border-radius-lg, 12px);
    background: var(--ifx-iterate-bg);
    color: var(--ifx-iterate-text);
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
  }
  .ifx-iterate-brand {
    font-size: 12px;
    color: var(--ifx-iterate-muted);
    padding-right: 0.25rem;
  }
  .ifx-iterate-toggle {
    appearance: none;
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
    background: transparent;
    color: var(--ifx-iterate-text);
    padding: 0.45rem 0.75rem;
    border-radius: var(--border-radius-md, 8px);
    cursor: pointer;
    font-size: 13px;
    font-weight: 400;
  }
  .ifx-iterate-toggle.is-active {
    background: var(--ifx-iterate-bg-secondary);
    border-color: var(--ifx-iterate-border-strong);
    color: var(--ifx-iterate-accent);
  }
  .ifx-iterate-status {
    font-size: 12px;
    color: var(--ifx-iterate-muted);
    margin-left: auto;
  }
  .ifx-iterate-target {
    outline: 2px solid rgba(102, 209, 184, 0.22) !important;
    outline-offset: 4px !important;
    cursor: crosshair !important;
    transition: outline-color 120ms ease;
  }
  .ifx-iterate-target:hover {
    outline-color: rgba(102, 209, 184, 0.85) !important;
  }
  .ifx-fact-mode .ifx-iterate-target {
    outline: none !important;
    cursor: auto !important;
  }
  .ifx-iterate-pin {
    position: absolute;
    z-index: 44;
    width: 28px;
    height: 28px;
    border-radius: 999px;
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border-strong);
    background: var(--ifx-iterate-bg);
    color: var(--ifx-iterate-text);
    display: grid;
    place-items: center;
    font-size: 12px;
    font-weight: 500;
  }
  .ifx-fact-mode .ifx-iterate-pin,
  .ifx-fact-mode .ifx-iterate-drawer,
  .ifx-fact-mode .ifx-iterate-composer {
    display: none !important;
  }
  .ifx-iterate-drawer {
    display: grid;
    gap: 0.75rem;
    padding: 0.9rem;
    border-radius: var(--border-radius-lg, 12px);
    background: var(--ifx-iterate-bg);
    color: var(--ifx-iterate-text);
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
  }
  .ifx-iterate-drawer h2 {
    margin: 0 0 6px;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0;
  }
  .ifx-iterate-drawer p {
    margin: 0 0 14px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--ifx-iterate-muted);
  }
  .ifx-iterate-empty {
    padding: 12px 0 6px;
    font-size: 13px;
    color: var(--ifx-iterate-muted);
  }
  .ifx-iterate-comment {
    padding: 12px;
    border-radius: var(--border-radius-md, 8px);
    background: var(--ifx-iterate-bg-secondary);
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
    margin-bottom: 10px;
  }
  .ifx-iterate-comment-header {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    align-items: center;
    margin-bottom: 8px;
  }
  .ifx-iterate-comment-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border-radius: var(--border-radius-md, 8px);
    padding: 4px 8px;
    background: var(--ifx-iterate-accent-soft);
    color: var(--ifx-iterate-accent);
    font-size: 11px;
    font-weight: 500;
  }
  .ifx-iterate-comment-target {
    font-size: 12px;
    color: var(--ifx-iterate-muted);
  }
  .ifx-iterate-comment-body {
    margin: 0 0 8px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--ifx-iterate-text);
    white-space: pre-wrap;
  }
  .ifx-iterate-comment-actions {
    display: flex;
    gap: 8px;
  }
  .ifx-iterate-link {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--ifx-iterate-accent);
    padding: 0;
    cursor: pointer;
    font-size: 12px;
    font-weight: 500;
  }
  .ifx-iterate-composer {
    width: 100%;
    border-radius: var(--border-radius-lg, 12px);
    padding: 14px;
    background: var(--ifx-iterate-bg);
    color: var(--ifx-iterate-text);
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
    display: none;
  }
  .ifx-iterate-composer.is-open {
    display: block;
  }
  .ifx-iterate-composer-head {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    align-items: center;
    margin-bottom: 10px;
  }
  .ifx-iterate-composer-title {
    font-size: 13px;
    font-weight: 500;
  }
  .ifx-iterate-composer-meta {
    font-size: 12px;
    color: var(--ifx-iterate-muted);
  }
  .ifx-iterate-composer-grid {
    display: grid;
    grid-template-columns: 160px 1fr;
    gap: 10px;
  }
  .ifx-iterate-composer select,
  .ifx-iterate-composer textarea {
    width: 100%;
    border-radius: var(--border-radius-md, 8px);
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
    background: var(--ifx-iterate-bg-secondary);
    color: var(--ifx-iterate-text);
    padding: 10px 12px;
    font-size: 13px;
  }
  .ifx-iterate-composer textarea {
    min-height: 112px;
    resize: vertical;
  }
  .ifx-iterate-composer-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 10px;
  }
  .ifx-iterate-button {
    appearance: none;
    border: var(--border-width-hairline, 0.5px) solid var(--ifx-iterate-border);
    background: transparent;
    color: var(--ifx-iterate-text);
    padding: 9px 12px;
    border-radius: var(--border-radius-md, 8px);
    cursor: pointer;
    font-size: 13px;
    font-weight: 400;
  }
  .ifx-iterate-button.is-primary {
    background: var(--ifx-iterate-bg-secondary);
    border-color: var(--ifx-iterate-border-strong);
    color: var(--ifx-iterate-accent);
  }
  @container (max-width: 40rem) {
    .ifx-iterate-composer-grid {
      grid-template-columns: 1fr;
    }
    .ifx-iterate-ui {
      align-items: flex-start;
    }
    .ifx-iterate-status {
      width: 100%;
      margin-left: 0;
    }
  }
</style>
"""
    script = f"""
<script id="ifx-iterate-script">
(() => {{
  const artifactId = {json.dumps(artifact_id)};
  const defaultMode = {json.dumps(default_mode)};
  const feedbackBaseUrl = {json.dumps(SETTINGS.public_mcp_base_url)};
  const storageKey = `ifx:iterate:${{artifactId}}`;
  const state = {{
    mode: defaultMode,
    comments: [],
    selectedTargetId: null,
  }};
  const uiRefs = {{}};

  function loadState() {{
    try {{
      const raw = localStorage.getItem(storageKey);
      if (!raw) return;
      const parsed = JSON.parse(raw);
      if (parsed && Array.isArray(parsed.comments)) state.comments = parsed.comments;
      if (parsed && (parsed.mode === 'iterate' || parsed.mode === 'fact')) state.mode = parsed.mode;
    }} catch (err) {{
      console.warn('IterFact iterate state load failed', err);
    }}
  }}

  function saveState() {{
    try {{
      localStorage.setItem(storageKey, JSON.stringify({{ mode: state.mode, comments: state.comments }}));
    }} catch (err) {{
      console.warn('IterFact iterate state save failed', err);
    }}
  }}

  function feedbackEndpoint() {{
    return `${{feedbackBaseUrl}}/v1/artifacts/${{artifactId}}/feedback`;
  }}

  function feedbackNoteEndpoint(noteId) {{
    return `${{feedbackBaseUrl}}/v1/artifacts/${{artifactId}}/feedback/${{encodeURIComponent(noteId)}}`;
  }}

  function canUseRemoteFeedback() {{
    try {{
      return window.location.protocol === 'http:' || window.location.protocol === 'https:';
    }} catch (err) {{
      return false;
    }}
  }}

  function telemetryViewEndpoint() {{
    return `${{feedbackBaseUrl}}/v1/telemetry/view`;
  }}

  function telemetryEngagementEndpoint() {{
    return `${{feedbackBaseUrl}}/v1/telemetry/engagement`;
  }}

  function artifactViewSessionId() {{
    const key = `ifx:view:${{artifactId}}`;
    try {{
      const existing = sessionStorage.getItem(key);
      if (existing) return existing;
      const created = `view-${{Date.now()}}-${{Math.random().toString(36).slice(2, 8)}}`;
      sessionStorage.setItem(key, created);
      return created;
    }} catch (err) {{
      return `view-${{Date.now()}}-${{Math.random().toString(36).slice(2, 8)}}`;
    }}
  }}

  function sanitizeTelemetryUrl(raw) {{
    if (!raw) return '';
    try {{
      const parsed = new URL(raw);
      return `${{parsed.origin}}${{parsed.pathname}}`;
    }} catch (err) {{
      return String(raw).split('#')[0].split('?')[0];
    }}
  }}

  function sanitizeDocumentReferrer() {{
    try {{
      return sanitizeTelemetryUrl(document.referrer || '');
    }} catch (err) {{
      return '';
    }}
  }}

  function artifactTelemetryContext() {{
    let currentUrl;
    try {{
      currentUrl = new URL(window.location.href);
    }} catch (err) {{
      return null;
    }}
    const embedMode = currentUrl.searchParams.get('ifx_embed');
    const openedFromLibrary =
      currentUrl.searchParams.get('ifx_source') === 'library' ||
      currentUrl.searchParams.get('ifx_saved') === '1';
    const requestedViewerKind = (currentUrl.searchParams.get('ifx_viewer') || '')
      .trim()
      .toLowerCase()
      .replace(/-/g, '_');
    const shareInviteId = (currentUrl.searchParams.get('ifx_share_invite_id') || '').trim() || null;
    const allowedViewerKinds = ['owner', 'recipient', 'library_member', 'audience', 'site_visitor', 'unknown'];
    let viewerKind = allowedViewerKinds.includes(requestedViewerKind) ? requestedViewerKind : null;
    if (!viewerKind) {{
      if (shareInviteId) {{
        viewerKind = 'recipient';
      }} else if (openedFromLibrary) {{
        viewerKind = 'owner';
      }} else {{
        viewerKind = 'audience';
      }}
    }}
    const telemetrySource = embedMode
      ? 'iterate_shell_embed'
      : openedFromLibrary
        ? 'iterate_shell_library'
        : 'iterate_shell';
    return {{
      currentUrl,
      embedMode,
      openedFromLibrary,
      shareInviteId,
      viewerKind,
      telemetrySource,
    }};
  }}

  function buildArtifactTelemetryBase(context) {{
    return {{
      surface: 'artifact',
      source: context.telemetrySource,
      artifact_id: artifactId,
      share_invite_id: context.shareInviteId,
      viewer_kind: context.viewerKind,
      session_id: artifactViewSessionId(),
      path: context.currentUrl.pathname,
      url: sanitizeTelemetryUrl(window.location.href),
      title: document.title || '',
      referrer: sanitizeDocumentReferrer(),
      viewport: {{
        w: window.innerWidth || null,
        h: window.innerHeight || null,
      }},
    }};
  }}

  function sendArtifactTelemetry(endpoint, payload, label) {{
    try {{
      const blob = new Blob([JSON.stringify(payload)], {{ type: 'application/json' }});
      if (navigator.sendBeacon) {{
        navigator.sendBeacon(endpoint, blob);
        return;
      }}
    }} catch (err) {{
      console.warn(`IterFact ${{label}} beacon failed`, err);
    }}
    fetch(endpoint, {{
      method: 'POST',
      headers: {{ 'content-type': 'application/json' }},
      body: JSON.stringify(payload),
      keepalive: true,
      credentials: 'omit',
    }}).catch(() => undefined);
  }}

  function emitArtifactView() {{
    if (!canUseRemoteFeedback()) return;
    const context = artifactTelemetryContext();
    if (!context || context.embedMode === 'thumb') return;
    const payload = {{
      event: 'view',
      ...buildArtifactTelemetryBase(context),
      metadata: {{
        opened_from_library: context.openedFromLibrary,
      }},
    }};
    sendArtifactTelemetry(telemetryViewEndpoint(), payload, 'view');
  }}

  const recentTelemetryKeys = new Map();

  function shouldEmitTelemetry(key, windowMs = 1200) {{
    const now = Date.now();
    const previous = recentTelemetryKeys.get(key);
    if (typeof previous === 'number' && now - previous < windowMs) {{
      return false;
    }}
    recentTelemetryKeys.set(key, now);
    if (recentTelemetryKeys.size > 120) {{
      const cutoff = now - 10000;
      for (const [entryKey, entryValue] of recentTelemetryKeys.entries()) {{
        if (entryValue < cutoff) recentTelemetryKeys.delete(entryKey);
      }}
    }}
    return true;
  }}

  function normalizeTelemetryKey(value) {{
    return String(value || '')
      .trim()
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '_')
      .replace(/^_+|_+$/g, '')
      .slice(0, 64);
  }}

  function safeNodeText(node) {{
    if (!(node instanceof HTMLElement)) return '';
    const parts = [
      node.getAttribute('data-ifx-label'),
      node.getAttribute('aria-label'),
      node.getAttribute('title'),
      node.getAttribute('alt'),
      node.innerText,
      node.textContent,
    ];
    return parts
      .map((part) => String(part || '').replace(/\\s+/g, ' ').trim())
      .find(Boolean) || '';
  }}

  function isTelemetryUiNode(node) {{
    return node instanceof HTMLElement &&
      Boolean(node.closest('.ifx-iterate-ui, .ifx-iterate-drawer, .ifx-iterate-composer, .ifx-iterate-pin'));
  }}

  function classifyCtaInteraction(target) {{
    if (!(target instanceof HTMLElement) || isTelemetryUiNode(target)) return null;
    const explicit = target.closest('[data-ifx-cta]');
    if (explicit instanceof HTMLElement) {{
      const explicitKey = normalizeTelemetryKey(explicit.getAttribute('data-ifx-cta'));
      if (explicitKey) {{
        return {{
          eventKey: explicitKey,
          eventLabel: safeNodeText(explicit) || explicitKey.replace(/_/g, ' '),
        }};
      }}
    }}

    const actionable = target.closest('a, button, [role="button"]');
    if (!(actionable instanceof HTMLElement)) return null;
    const label = safeNodeText(actionable);
    const href = actionable instanceof HTMLAnchorElement ? actionable.href || '' : '';
    const combined = `${{label}} ${{href}}`.toLowerCase();
    if (!combined) return null;

    if (combined.includes('contact agent') || combined.includes('contact listing agent') || combined.includes('contact broker')) {{
      return {{ eventKey: 'contact_agent', eventLabel: label || 'Contact agent' }};
    }}
    if (combined.includes('book showing') || combined.includes('schedule showing') || combined.includes('schedule tour') || combined.includes('book tour')) {{
      return {{ eventKey: 'book_showing', eventLabel: label || 'Book showing' }};
    }}
    if (combined.includes('request comps') || combined.includes('request comparable') || combined.includes('get comps')) {{
      return {{ eventKey: 'request_comps', eventLabel: label || 'Request comps' }};
    }}
    if (combined.includes('copy link') || combined.includes('copy url')) {{
      return {{ eventKey: 'copied_link', eventLabel: label || 'Copy link' }};
    }}

    return null;
  }}

  function classifyModuleInteraction(target, trigger) {{
    if (!(target instanceof HTMLElement) || isTelemetryUiNode(target)) return null;
    const explicit = target.closest('[data-ifx-module]');
    if (explicit instanceof HTMLElement) {{
      const explicitKey = normalizeTelemetryKey(explicit.getAttribute('data-ifx-module'));
      if (explicitKey) {{
        return {{
          eventKey: explicitKey,
          eventLabel: safeNodeText(explicit) || explicitKey.replace(/_/g, ' '),
          metadata: {{ trigger }},
        }};
      }}
    }}

    const host =
      target.closest('details, summary, button, select, input, [role="button"], [data-map], .map, [class*="map"], [id*="map"]') ||
      target;
    if (!(host instanceof HTMLElement)) return null;
    const contextText = [
      safeNodeText(host),
      host.id,
      host.className,
      host.getAttribute('name'),
      host.getAttribute('data-section'),
      safeNodeText(host.closest('section, article, form, details')),
    ].join(' ').toLowerCase();

    if (!contextText) return null;

    if (
      contextText.includes('mortgage') ||
      contextText.includes('monthly payment') ||
      contextText.includes('payment calculator') ||
      contextText.includes('principal') ||
      contextText.includes('interest rate')
    ) {{
      return {{
        eventKey: 'payment_calculator_used',
        eventLabel: safeNodeText(host) || 'Payment calculator used',
        metadata: {{ trigger }},
      }};
    }}

    if (
      contextText.includes('pricing scenario') ||
      contextText.includes('scenario') ||
      contextText.includes('price scenario') ||
      contextText.includes('what if') ||
      contextText.includes('price toggle')
    ) {{
      return {{
        eventKey: 'pricing_scenario_toggled',
        eventLabel: safeNodeText(host) || 'Pricing scenario toggled',
        metadata: {{ trigger }},
      }};
    }}

    if (
      contextText.includes('map') ||
      contextText.includes('location') ||
      contextText.includes('neighborhood')
    ) {{
      return {{
        eventKey: 'map_section_expanded',
        eventLabel: safeNodeText(host) || 'Map section expanded',
        metadata: {{ trigger }},
      }};
    }}

    return null;
  }}

  function emitArtifactEngagement(eventName, details = {{}}) {{
    if (!canUseRemoteFeedback()) return;
    const context = artifactTelemetryContext();
    if (!context || context.embedMode === 'thumb') return;
    if (
      details.audienceOnly &&
      (context.viewerKind === 'owner' || context.viewerKind === 'library_member')
    ) {{
      return;
    }}
    const payload = {{
      event: eventName,
      ...buildArtifactTelemetryBase(context),
      event_key: details.eventKey || null,
      event_label: details.eventLabel || null,
      metadata: details.metadata && typeof details.metadata === 'object' ? details.metadata : {{}},
    }};
    sendArtifactTelemetry(telemetryEngagementEndpoint(), payload, 'engagement');
  }}

  function wireEngagementTracking() {{
    document.addEventListener('click', (event) => {{
      const target = event.target;
      if (!(target instanceof HTMLElement)) return;

      const cta = classifyCtaInteraction(target);
      if (cta && shouldEmitTelemetry(`cta:${{cta.eventKey}}:${{cta.eventLabel}}`, 800)) {{
        emitArtifactEngagement('cta_click', {{
          audienceOnly: true,
          eventKey: cta.eventKey,
          eventLabel: cta.eventLabel,
        }});
      }}

      const moduleInteraction = classifyModuleInteraction(target, 'click');
      if (
        moduleInteraction &&
        shouldEmitTelemetry(`module:${{moduleInteraction.eventKey}}:click`, 800)
      ) {{
        emitArtifactEngagement('module_interaction', {{
          audienceOnly: true,
          eventKey: moduleInteraction.eventKey,
          eventLabel: moduleInteraction.eventLabel,
          metadata: moduleInteraction.metadata,
        }});
      }}
    }}, true);

    const emitModuleFromField = (eventName) => (event) => {{
      const target = event.target;
      if (!(target instanceof HTMLElement)) return;
      const moduleInteraction = classifyModuleInteraction(target, eventName);
      if (
        moduleInteraction &&
        shouldEmitTelemetry(`module:${{moduleInteraction.eventKey}}:${{eventName}}`, 1200)
      ) {{
        emitArtifactEngagement('module_interaction', {{
          audienceOnly: true,
          eventKey: moduleInteraction.eventKey,
          eventLabel: moduleInteraction.eventLabel,
          metadata: moduleInteraction.metadata,
        }});
      }}
    }};

    document.addEventListener('change', emitModuleFromField('change'), true);
    document.addEventListener('input', emitModuleFromField('input'), true);
  }}

  function getAnnotatableNodes() {{
    const selector = [
      'h1','h2','h3','h4','h5','h6','p','li','blockquote','table','canvas','svg','img',
      'section','article','.card','.kpi-card','.metric-card','.stat-card','.feature-card'
    ].join(',');
    return Array.from(document.querySelectorAll(selector)).filter((node) => {{
      if (!(node instanceof HTMLElement)) return false;
      if (node.closest('.ifx-iterate-ui, .ifx-iterate-drawer, .ifx-iterate-composer')) return false;
      const rect = node.getBoundingClientRect();
      if (rect.width < 80 || rect.height < 20) return false;
      const style = window.getComputedStyle(node);
      if (style.display === 'none' || style.visibility === 'hidden') return false;
      return true;
    }});
  }}

  function nodeLabel(node) {{
    const text = (node.innerText || node.getAttribute('alt') || '').replace(/\\s+/g, ' ').trim();
    if (text) return text.slice(0, 72);
    return node.tagName.toLowerCase();
  }}

  function nearestSectionHint(node) {{
    let current = node;
    while (current && current !== document.body) {{
      if (!(current instanceof HTMLElement)) break;
      const heading = current.querySelector('h1, h2, h3, h4, h5, h6');
      if (heading instanceof HTMLElement) {{
        const text = (heading.innerText || '').replace(/\\s+/g, ' ').trim();
        if (text) return text.slice(0, 120);
      }}
      current = current.parentElement;
    }}
    const preceding = node instanceof HTMLElement ? node.previousElementSibling : null;
    if (preceding instanceof HTMLElement) {{
      const text = (preceding.innerText || '').replace(/\\s+/g, ' ').trim();
      if (text) return text.slice(0, 120);
    }}
    return null;
  }}

  function normalizeComment(comment) {{
    return {{
      id: String(comment.id || `ifx-comment-${{Date.now()}}-${{Math.random().toString(36).slice(2, 7)}}`),
      status: String(comment.status || 'open'),
      targetId: comment.targetId || comment.target_id || null,
      targetLabel: comment.targetLabel || comment.target_label || '',
      sectionHint: comment.sectionHint || comment.section_hint || null,
      selectionExcerpt: comment.selectionExcerpt || comment.selection_excerpt || null,
      type: String(comment.type || 'general note'),
      text: String(comment.text || ''),
      createdAt: comment.createdAt || comment.created_at || new Date().toISOString(),
      updatedAt: comment.updatedAt || comment.updated_at || new Date().toISOString(),
      source: comment.source || 'iterate_link',
    }};
  }}

  async function hydrateRemoteComments() {{
    if (!canUseRemoteFeedback()) return;
    try {{
      const response = await fetch(feedbackEndpoint(), {{
        method: 'GET',
        headers: {{ 'accept': 'application/json' }},
        credentials: 'omit',
      }});
      if (!response.ok) return;
      const payload = await response.json();
      if (!payload || !Array.isArray(payload.notes)) return;
      state.comments = payload.notes.map(normalizeComment);
      render();
    }} catch (err) {{
      console.warn('IterFact iterate feedback load failed', err);
    }}
  }}

  async function persistComment(comment) {{
    if (!canUseRemoteFeedback()) return null;
    try {{
      const response = await fetch(feedbackEndpoint(), {{
        method: 'POST',
        headers: {{ 'content-type': 'application/json', 'accept': 'application/json' }},
        credentials: 'omit',
        body: JSON.stringify(comment),
      }});
      if (!response.ok) return null;
      const payload = await response.json();
      return payload && payload.note ? normalizeComment(payload.note) : null;
    }} catch (err) {{
      console.warn('IterFact iterate feedback save failed', err);
      return null;
    }}
  }}

  async function dismissComment(commentId) {{
    if (!canUseRemoteFeedback()) return false;
    try {{
      const response = await fetch(feedbackNoteEndpoint(commentId), {{
        method: 'PATCH',
        headers: {{ 'content-type': 'application/json', 'accept': 'application/json' }},
        credentials: 'omit',
        body: JSON.stringify({{ status: 'dismissed' }}),
      }});
      return response.ok;
    }} catch (err) {{
      console.warn('IterFact iterate feedback dismiss failed', err);
      return false;
    }}
  }}

  function ensureTargetIds() {{
    let index = 1;
    for (const node of getAnnotatableNodes()) {{
      if (!node.dataset.ifxIterId) {{
        node.dataset.ifxIterId = `ifx-region-${{index++}}`;
      }}
      node.classList.add('ifx-iterate-target');
      node.dataset.ifxIterLabel = node.dataset.ifxIterLabel || nodeLabel(node);
    }}
  }}

  function buildUi() {{
    const host = document.createElement('div');
    host.className = 'ifx-iterate-shell';
    document.body.prepend(host);
    uiRefs.host = host;

    const ui = document.createElement('div');
    ui.className = 'ifx-iterate-ui';
    ui.innerHTML = `
      <div class="ifx-iterate-brand">IterFact</div>
      <button class="ifx-iterate-toggle" data-mode="iterate">Iterate</button>
      <button class="ifx-iterate-toggle" data-mode="fact">Fact</button>
      <div class="ifx-iterate-status">Click any section to leave a targeted note.</div>
    `;
    host.appendChild(ui);
    uiRefs.toolbar = ui;
    uiRefs.iterateButton = ui.querySelector('[data-mode="iterate"]');
    uiRefs.factButton = ui.querySelector('[data-mode="fact"]');

    const drawer = document.createElement('aside');
    drawer.className = 'ifx-iterate-drawer';
    drawer.innerHTML = `
      <h2>Iterate Notes</h2>
      <p>Leave targeted feedback on any visible region. Switch to Fact to hide the iterate layer instantly.</p>
      <div class="ifx-iterate-list"></div>
    `;
    host.appendChild(drawer);
    uiRefs.drawer = drawer;
    uiRefs.commentList = drawer.querySelector('.ifx-iterate-list');

    const composer = document.createElement('div');
    composer.className = 'ifx-iterate-composer';
    composer.innerHTML = `
      <div class="ifx-iterate-composer-head">
        <div>
          <div class="ifx-iterate-composer-title">Add iterate note</div>
          <div class="ifx-iterate-composer-meta"></div>
        </div>
      </div>
      <div class="ifx-iterate-composer-grid">
        <select class="ifx-iterate-type">
          <option value="change copy">Change copy</option>
          <option value="update number">Update number</option>
          <option value="add section">Add section</option>
          <option value="remove section">Remove section</option>
          <option value="visual fix">Visual fix</option>
          <option value="general note">General note</option>
        </select>
        <textarea class="ifx-iterate-text" placeholder="What should change here?"></textarea>
      </div>
      <div class="ifx-iterate-composer-actions">
        <button class="ifx-iterate-button" data-action="cancel">Cancel</button>
        <button class="ifx-iterate-button is-primary" data-action="save">Save note</button>
      </div>
    `;
    host.appendChild(composer);
    uiRefs.composer = composer;
    uiRefs.composerMeta = composer.querySelector('.ifx-iterate-composer-meta');
    uiRefs.composerType = composer.querySelector('.ifx-iterate-type');
    uiRefs.composerText = composer.querySelector('.ifx-iterate-text');
  }}

  function closeComposer() {{
    state.selectedTargetId = null;
    uiRefs.composer.classList.remove('is-open');
    uiRefs.composerText.value = '';
  }}

  function openComposer(target) {{
    if (!target) return;
    state.selectedTargetId = target.dataset.ifxIterId;
    uiRefs.composerMeta.textContent = target.dataset.ifxIterLabel || target.tagName.toLowerCase();
    uiRefs.composer.classList.add('is-open');
    uiRefs.composerText.focus();
  }}

  function findTarget(targetId) {{
    return document.querySelector(`[data-ifx-iter-id="${{CSS.escape(targetId)}}"]`);
  }}

  function renderPins() {{
    document.querySelectorAll('.ifx-iterate-pin').forEach((node) => node.remove());
    if (state.mode !== 'iterate') return;
    state.comments.forEach((comment, idx) => {{
      const target = findTarget(comment.targetId);
      if (!target) return;
      const rect = target.getBoundingClientRect();
      const pin = document.createElement('button');
      pin.className = 'ifx-iterate-pin';
      pin.textContent = String(idx + 1);
      pin.type = 'button';
      pin.style.left = `${{window.scrollX + rect.right - 14}}px`;
      pin.style.top = `${{window.scrollY + rect.top - 14}}px`;
      pin.title = comment.type;
      pin.addEventListener('click', () => {{
        target.scrollIntoView({{ behavior: 'smooth', block: 'center' }});
      }});
      document.body.appendChild(pin);
    }});
  }}

  function renderList() {{
    if (!uiRefs.commentList) return;
    uiRefs.commentList.innerHTML = '';
    if (!state.comments.length) {{
      const empty = document.createElement('div');
      empty.className = 'ifx-iterate-empty';
      empty.textContent = 'No iterate notes yet. Click any visible section to leave one.';
      uiRefs.commentList.appendChild(empty);
      return;
    }}

    state.comments.forEach((comment, idx) => {{
      const card = document.createElement('div');
      card.className = 'ifx-iterate-comment';
      card.innerHTML = `
        <div class="ifx-iterate-comment-header">
          <span class="ifx-iterate-comment-chip">#${{idx + 1}} ${{comment.type}}</span>
          <span class="ifx-iterate-comment-target">${{comment.targetLabel}}</span>
        </div>
        <p class="ifx-iterate-comment-body">${{comment.text.replace(/[&<>]/g, (m) => ({{'&':'&amp;','<':'&lt;','>':'&gt;'}}[m]))}}</p>
        <div class="ifx-iterate-comment-actions">
          <button type="button" class="ifx-iterate-link" data-action="jump">Jump to area</button>
          <button type="button" class="ifx-iterate-link" data-action="remove">Remove</button>
        </div>
      `;
      card.querySelector('[data-action="jump"]').addEventListener('click', () => {{
        const target = findTarget(comment.targetId);
        if (target) target.scrollIntoView({{ behavior: 'smooth', block: 'center' }});
      }});
      card.querySelector('[data-action="remove"]').addEventListener('click', () => {{
        dismissComment(comment.id).then((ok) => {{
          state.comments = state.comments.filter((item) => item.id !== comment.id);
          if (!ok) {{
            console.warn('IterFact iterate note dismissal only updated local state.');
          }}
          saveState();
          render();
        }});
      }});
      uiRefs.commentList.appendChild(card);
    }});
  }}

  function applyMode() {{
    document.documentElement.classList.toggle('ifx-fact-mode', state.mode === 'fact');
    uiRefs.iterateButton.classList.toggle('is-active', state.mode === 'iterate');
    uiRefs.factButton.classList.toggle('is-active', state.mode === 'fact');
    if (state.mode === 'fact') closeComposer();
  }}

  function render() {{
    applyMode();
    renderList();
    renderPins();
    saveState();
  }}

  function wireEvents() {{
    uiRefs.iterateButton.addEventListener('click', () => {{
      state.mode = 'iterate';
      render();
    }});
    uiRefs.factButton.addEventListener('click', () => {{
      state.mode = 'fact';
      render();
    }});

    document.addEventListener('click', (event) => {{
      const target = event.target;
      if (!(target instanceof HTMLElement)) return;
      if (target.closest('.ifx-iterate-ui, .ifx-iterate-drawer, .ifx-iterate-composer, .ifx-iterate-pin')) return;
      if (state.mode !== 'iterate') return;
      const region = target.closest('[data-ifx-iter-id]');
      if (!region) return;
      event.preventDefault();
      openComposer(region);
    }}, true);

    uiRefs.composer.querySelector('[data-action="cancel"]').addEventListener('click', () => closeComposer());
    uiRefs.composer.querySelector('[data-action="save"]').addEventListener('click', () => {{
      const target = state.selectedTargetId ? findTarget(state.selectedTargetId) : null;
      const text = uiRefs.composerText.value.trim();
      if (!target || !text) return;
      const comment = normalizeComment({{
        id: `ifx-comment-${{Date.now()}}-${{Math.random().toString(36).slice(2, 7)}}`,
        targetId: target.dataset.ifxIterId,
        targetLabel: target.dataset.ifxIterLabel || target.tagName.toLowerCase(),
        sectionHint: nearestSectionHint(target),
        selectionExcerpt: (target.innerText || '').replace(/\\s+/g, ' ').trim().slice(0, 160) || null,
        type: uiRefs.composerType.value,
        text,
        createdAt: new Date().toISOString(),
      }});
      state.comments.push(comment);
      closeComposer();
      render();
      persistComment(comment).then((saved) => {{
        if (!saved) return;
        state.comments = state.comments.map((item) => item.id === comment.id ? saved : item);
        saveState();
        render();
      }});
    }});

    window.addEventListener('scroll', () => renderPins(), {{ passive: true }});
    window.addEventListener('resize', () => renderPins());
  }}

  loadState();
  emitArtifactView();
  ensureTargetIds();
  buildUi();
  wireEngagementTracking();
  wireEvents();
  render();
  hydrateRemoteComments();
}})();
</script>
"""

    if "</head>" in html:
        html = html.replace("</head>", style + "\n</head>", 1)
    else:
        html = style + html

    if "</body>" in html:
        html = html.replace("</body>", script + "\n</body>", 1)
    else:
        html += script

    return html


def strip_marker_comments(html: str, *, start_marker: str, end_marker: str) -> str:
    pattern = (
        rf"<!--\s*{re.escape(start_marker)}\s*-->\s*"
        rf"|<!--\s*{re.escape(end_marker)}\s*-->\s*"
    )
    return re.sub(pattern, "", html, flags=re.IGNORECASE)


def strip_tag_block_by_id(html: str, *, tag_name: str, element_id: str) -> str:
    pattern = (
        rf"<{tag_name}\b(?=[^>]*\bid=(['\"])"
        rf"{re.escape(element_id)}\1)[^>]*>[\s\S]*?</{tag_name}>\s*"
    )
    return re.sub(pattern, "", html, flags=re.IGNORECASE)


def strip_balanced_element_by_id(html: str, *, tag_name: str, element_id: str) -> str:
    opening_pattern = re.compile(
        rf"<{tag_name}\b(?=[^>]*\bid=(['\"]){re.escape(element_id)}\1)[^>]*>",
        flags=re.IGNORECASE,
    )
    tag_pattern = re.compile(rf"<(/?){tag_name}\b[^>]*>", flags=re.IGNORECASE)

    current = html
    while True:
        opening_match = opening_pattern.search(current)
        if not opening_match:
            return current

        depth = 1
        for tag_match in tag_pattern.finditer(current, opening_match.end()):
            if tag_match.group(1):
                depth -= 1
                if depth == 0:
                    current = current[: opening_match.start()] + current[tag_match.end() :]
                    break
            else:
                depth += 1
        else:
            return current


def extract_body_residue_without_delivery_shells(html: str) -> str:
    body_match = re.search(r"<body\b[^>]*>([\s\S]*?)</body>", html, flags=re.IGNORECASE)
    residue = body_match.group(1) if body_match else html
    residue = strip_marker_comments(
        residue,
        start_marker="IFX_LIBRARY_SHELL_START",
        end_marker="IFX_LIBRARY_SHELL_END",
    )
    residue = strip_marker_comments(
        residue,
        start_marker="IFX_NEXT_MOVE_SHELL_START",
        end_marker="IFX_NEXT_MOVE_SHELL_END",
    )
    residue = strip_balanced_element_by_id(residue, tag_name="div", element_id="ifx-library-save-ui")
    residue = strip_balanced_element_by_id(residue, tag_name="div", element_id="ifx-next-move-ui")
    residue = strip_tag_block_by_id(residue, tag_name="script", element_id="ifx-library-save-script")
    residue = strip_tag_block_by_id(residue, tag_name="script", element_id="ifx-next-move-script")
    residue = strip_tag_block_by_id(residue, tag_name="script", element_id="ifx-iterate-script")
    residue = re.sub(r"<!--[\s\S]*?-->", " ", residue)
    residue = re.sub(r"\s+", " ", residue).strip()
    return residue


def assert_delivery_shell_preserves_body(original_html: str, candidate_html: str, *, artifact_id: str) -> None:
    original_residue = extract_body_residue_without_delivery_shells(original_html)
    candidate_residue = extract_body_residue_without_delivery_shells(candidate_html)
    if original_residue and not candidate_residue:
        raise ValueError(
            f"Delivery shell rewrite would remove the artifact body for {artifact_id}. "
            "Refusing to overwrite the published artifact."
        )


def strip_library_shell(html: str) -> str:
    cleaned = strip_marker_comments(
        html,
        start_marker="IFX_LIBRARY_SHELL_START",
        end_marker="IFX_LIBRARY_SHELL_END",
    )
    cleaned = strip_balanced_element_by_id(cleaned, tag_name="div", element_id="ifx-library-save-ui")
    cleaned = strip_tag_block_by_id(cleaned, tag_name="style", element_id="ifx-library-style")
    cleaned = strip_tag_block_by_id(cleaned, tag_name="script", element_id="ifx-library-save-script")
    return cleaned


def apply_library_shell(html: str, artifact_id: str, artifact_title: str) -> str:
    html = strip_library_shell(html)

    style = """
<style id="ifx-library-style">
  .ifx-library-save-ui, .ifx-library-save-ui * {
    box-sizing: border-box !important;
    font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif) !important;
  }
  .ifx-library-save-ui {
    max-width: min(1120px, calc(100% - 2rem));
    margin: 0.9rem auto 0;
    padding: 0 1rem;
    container-type: inline-size;
  }
  .ifx-library-save-card {
    margin-left: auto;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    width: min(100%, 28rem);
    padding: 0.75rem 0.9rem;
    border-radius: var(--border-radius-lg, 12px);
    border: var(--border-width-hairline, 0.5px) solid var(--color-border-tertiary, rgba(44, 44, 42, 0.15));
    background: var(--color-background-primary, rgb(255 255 255));
    color: var(--color-text-primary, rgb(44 44 42));
  }
  .ifx-library-save-meta {
    display: grid;
    gap: 0.18rem;
    min-width: 0;
  }
  .ifx-library-save-kicker {
    font-size: 12px;
    color: var(--color-text-tertiary, rgb(136 135 128));
  }
  .ifx-library-save-title {
    margin-top: 0;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.35;
    color: var(--color-text-primary, rgb(44 44 42));
  }
  .ifx-library-save-copy,
  .ifx-library-save-note {
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-text-secondary, rgb(95 94 90));
  }
  .ifx-library-save-note {
    display: none;
  }
  .ifx-library-save-row {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
  }
  .ifx-library-save-button {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 34px;
    padding: 0 0.85rem;
    border-radius: var(--border-radius-md, 8px);
    border: var(--border-width-hairline, 0.5px) solid var(--color-border-secondary, rgba(44, 44, 42, 0.3));
    background: transparent;
    color: var(--color-text-primary, rgb(44 44 42));
    text-decoration: none;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 400;
    transition: background 140ms ease, border-color 140ms ease;
  }
  .ifx-library-save-button:hover,
  .ifx-library-save-button:focus-visible {
    background: var(--color-background-secondary, rgb(248 246 240));
    border-color: var(--color-border-primary, rgba(44, 44, 42, 0.4));
  }
  @container (max-width: 36rem) {
    .ifx-library-save-card {
      width: 100%;
      display: grid;
    }
    .ifx-library-save-row {
      justify-content: flex-start;
    }
    .ifx-library-save-note {
      display: block;
    }
  }
</style>
"""
    markup = """
<!-- IFX_LIBRARY_SHELL_START -->
<div class="ifx-library-save-ui ifx-library-shell" id="ifx-library-save-ui" data-ifx-library-version="5">
  <div class="ifx-library-save-card">
    <div class="ifx-library-save-meta">
      <div class="ifx-library-save-kicker">Free library</div>
      <div class="ifx-library-save-title">Save this artifact to IterFact</div>
      <div class="ifx-library-save-copy">
        Keep this IterFact one click away instead of losing it in browser history or a chat thread.
      </div>
      <div class="ifx-library-save-note">Only live IterFacts can be saved.</div>
    </div>
    <div class="ifx-library-save-row">
      <a class="ifx-library-save-button" id="ifx-library-save-button" href="https://iterfact.com/library">Save to library</a>
    </div>
  </div>
</div>
"""
    script = f"""
<script id="ifx-library-save-script">
(() => {{
  const root = document.getElementById("ifx-library-save-ui");
  const button = document.getElementById("ifx-library-save-button");
  const kicker = root?.querySelector(".ifx-library-save-kicker");
  const title = root?.querySelector(".ifx-library-save-title");
  const copy = root?.querySelector(".ifx-library-save-copy");
  const note = root?.querySelector(".ifx-library-save-note");
  if (!button || !root || !kicker || !title || !copy || !note) return;
  const currentUrl = new URL(window.location.href);
  const canonicalUrl = new URL(window.location.href);
  canonicalUrl.searchParams.delete("ifx_source");
  canonicalUrl.searchParams.delete("ifx_saved");
  canonicalUrl.searchParams.delete("ifx_embed");
  const embedMode = currentUrl.searchParams.get("ifx_embed");
  if (embedMode === "thumb") {{
    root.style.display = "none";
    return;
  }}
  const openedFromLibrary =
    currentUrl.searchParams.get("ifx_source") === "library" ||
    currentUrl.searchParams.get("ifx_saved") === "1";

  if (openedFromLibrary) {{
    kicker.textContent = "Shared link";
    title.textContent = "Share this IterFact";
    copy.textContent = "Send the clean artifact link to another reader or IterFact user.";
    note.textContent = "Anyone with the link can open it and save their own copy.";
    button.textContent =
      typeof navigator !== "undefined" && typeof navigator.share === "function"
        ? "Share"
        : "Copy link";
    button.href = canonicalUrl.toString();
    button.addEventListener("click", async (event) => {{
      event.preventDefault();
      try {{
        if (typeof navigator !== "undefined" && typeof navigator.share === "function") {{
          await navigator.share({{
            title: document.title || {json.dumps(artifact_title or "IterFact Artifact")},
            url: canonicalUrl.toString(),
          }});
          note.textContent = "Shared from your library.";
          return;
        }}

        if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {{
          await navigator.clipboard.writeText(canonicalUrl.toString());
          note.textContent = "Link copied.";
          return;
        }}

        window.prompt("Copy this IterFact link", canonicalUrl.toString());
      }} catch (error) {{
        if (!(error instanceof Error) || error.message !== "Share canceled") {{
          note.textContent = "Unable to share right now.";
        }}
      }}
    }});
    return;
  }}

  const params = new URLSearchParams({{
    claim_artifact_id: {json.dumps(artifact_id)},
    claim_title: {json.dumps(artifact_title or "IterFact Artifact")},
    claim_origin: "iterfact_artifact",
    claim_format: "html",
    claim_preview: "dark",
    claim_provenance: "Saved from a live IterFact artifact",
    claim_summary: "Saved from an IterFact-hosted artifact.",
    claim_tab: "saved",
    claim_tags: "artifact,iterfact",
    claim_visibility: "public",
    claim_status: "published",
  }});
  params.set("claim_url", canonicalUrl.toString());
  button.href = "https://iterfact.com/library?" + params.toString();
}})();
</script>
<!-- IFX_LIBRARY_SHELL_END -->
"""

    if "</head>" in html:
        html = html.replace("</head>", style + "\n</head>", 1)
    else:
        html = style + html

    if re.search(r"<body[^>]*>", html, flags=re.IGNORECASE):
        html = re.sub(r"(<body[^>]*>)", r"\1\n" + markup, html, count=1, flags=re.IGNORECASE)
    else:
        html = markup + html

    if "</body>" in html:
        return html.replace("</body>", script + "\n</body>", 1)
    return html + script


def normalize_next_move_href(value: str | None) -> str | None:
    href = compact_whitespace(value or "")
    if not href:
        return None
    if href.startswith("/"):
        return f"{SETTINGS.public_app_base_url.rstrip('/')}{href}"
    if href.startswith("http://") or href.startswith("https://"):
        return href
    return None


def build_default_next_move_context(
    *,
    artifact_title: str,
    prompt: str,
    template_hint: str | None,
    generation_mode: str | None,
    plan: dict[str, Any],
) -> dict[str, str]:
    wrapper_name = friendly_wrapper_name(plan.get("wrapper", {}).get("engine"))
    format_name = plan.get("meta", {}).get("document_format") or wrapper_name or "interactive artifact"
    keyword_haystack = " ".join(
        [
            compact_whitespace(artifact_title),
            compact_whitespace(prompt),
            compact_whitespace(template_hint or ""),
            compact_whitespace(str(format_name)),
        ]
    ).lower()

    if generation_mode == "template_instance_render" or template_hint:
        return {
            "title": "After this sample, run the real template",
            "copy": "This artifact is the live proof. The next useful move is to run the same template with your own inputs and keep the output in your workspace.",
            "primary_label": "Use this template with your data",
            "primary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/templates",
            "secondary_label": "Browse companion templates",
            "secondary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/templates",
        }

    if any(keyword in keyword_haystack for keyword in ["calculator", "roi", "pricing", "budget", "mortgage", "model"]):
        return {
            "title": "Turn this into your own decision tool",
            "copy": f"This {format_name} works best when the next move is tied to your own numbers and assumptions.",
            "primary_label": "Use a template with your data",
            "primary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/templates",
            "secondary_label": "Start a fresh IterFact",
            "secondary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/create",
        }

    if any(keyword in keyword_haystack for keyword in ["quiz", "assessment", "test", "score"]):
        return {
            "title": "Keep the momentum going",
            "copy": "The next smart move is to open a companion format or make your own version while the interaction is still fresh.",
            "primary_label": "Open another live IterFact",
            "primary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/atlas",
            "secondary_label": "Use this format with your data",
            "secondary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/templates",
        }

    return {
        "title": "If this helped, take the next useful move",
        "copy": "Open the companion format, start with a template, or keep exploring live IterFacts instead of letting the thread die here.",
        "primary_label": "Use a template with your data",
        "primary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/templates",
        "secondary_label": "Open Atlas",
        "secondary_href": f"{SETTINGS.public_app_base_url.rstrip('/')}/atlas",
    }


def strip_next_move_shell(html: str) -> str:
    cleaned = strip_marker_comments(
        html,
        start_marker="IFX_NEXT_MOVE_SHELL_START",
        end_marker="IFX_NEXT_MOVE_SHELL_END",
    )
    cleaned = strip_balanced_element_by_id(cleaned, tag_name="div", element_id="ifx-next-move-ui")
    cleaned = strip_tag_block_by_id(cleaned, tag_name="style", element_id="ifx-next-move-style")
    cleaned = strip_tag_block_by_id(cleaned, tag_name="script", element_id="ifx-next-move-script")
    return cleaned


def apply_next_move_shell(
    html: str,
    *,
    artifact_id: str,
    artifact_title: str,
    prompt: str,
    template_hint: str | None,
    generation_mode: str | None,
    plan: dict[str, Any],
) -> str:
    html = strip_next_move_shell(html)

    defaults = build_default_next_move_context(
        artifact_title=artifact_title,
        prompt=prompt,
        template_hint=template_hint,
        generation_mode=generation_mode,
        plan=plan,
    )

    style = """
<style id="ifx-next-move-style">
  .ifx-next-move-ui, .ifx-next-move-ui * {
    box-sizing: border-box !important;
    font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif) !important;
  }
  .ifx-next-move-ui {
    max-width: min(1120px, calc(100% - 2rem));
    margin: 2.6rem auto 0;
    padding: 0 1rem 2.4rem;
    container-type: inline-size;
  }
  .ifx-next-move-card {
    display: grid;
    gap: 0.95rem;
    padding: 1rem 1.05rem;
    border-radius: var(--border-radius-lg, 12px);
    border: var(--border-width-hairline, 0.5px) solid var(--color-border-tertiary, rgba(44, 44, 42, 0.15));
    background: var(--color-background-primary, rgb(255 255 255));
    color: var(--color-text-primary, rgb(44 44 42));
  }
  .ifx-next-move-meta {
    display: grid;
    gap: 0.32rem;
    min-width: 0;
  }
  .ifx-next-move-kicker {
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-tertiary, rgb(136 135 128));
  }
  .ifx-next-move-title {
    font-size: 16px;
    font-weight: 500;
    line-height: 1.32;
    color: var(--color-text-primary, rgb(44 44 42));
  }
  .ifx-next-move-copy {
    font-size: 13px;
    line-height: 1.6;
    color: var(--color-text-secondary, rgb(95 94 90));
  }
  .ifx-next-move-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.7rem;
  }
  .ifx-next-move-button {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 36px;
    padding: 0 0.9rem;
    border-radius: 999px;
    border: var(--border-width-hairline, 0.5px) solid var(--color-border-secondary, rgba(44, 44, 42, 0.3));
    text-decoration: none;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 500;
    transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
  }
  .ifx-next-move-button-primary {
    background: #183d2f;
    border-color: #183d2f;
    color: #f7f1e6;
  }
  .ifx-next-move-button-secondary {
    background: transparent;
    color: var(--color-text-primary, rgb(44 44 42));
  }
  .ifx-next-move-button:hover,
  .ifx-next-move-button:focus-visible {
    border-color: var(--color-border-primary, rgba(44, 44, 42, 0.4));
  }
  .ifx-next-move-button-secondary:hover,
  .ifx-next-move-button-secondary:focus-visible {
    background: var(--color-background-secondary, rgb(248 246 240));
  }
  @container (max-width: 36rem) {
    .ifx-next-move-card {
      padding: 0.95rem;
    }
    .ifx-next-move-actions {
      display: grid;
    }
    .ifx-next-move-button {
      width: 100%;
    }
  }
</style>
"""
    markup = """
<!-- IFX_NEXT_MOVE_SHELL_START -->
<div class="ifx-next-move-ui ifx-next-move-shell" id="ifx-next-move-ui" data-ifx-next-version="1">
  <div class="ifx-next-move-card">
    <div class="ifx-next-move-meta">
      <div class="ifx-next-move-kicker">Next IterFact</div>
      <div class="ifx-next-move-title" id="ifx-next-move-title"></div>
      <div class="ifx-next-move-copy" id="ifx-next-move-copy"></div>
    </div>
    <div class="ifx-next-move-actions">
      <a class="ifx-next-move-button ifx-next-move-button-primary" id="ifx-next-move-primary" data-ifx-cta="next_move_primary" href="https://iterfact.com/templates">Use a template with your data</a>
      <a class="ifx-next-move-button ifx-next-move-button-secondary" id="ifx-next-move-secondary" data-ifx-cta="next_move_secondary" href="https://iterfact.com/atlas">Open Atlas</a>
    </div>
  </div>
</div>
"""
    script = f"""
<script id="ifx-next-move-script">
(() => {{
  const root = document.getElementById("ifx-next-move-ui");
  const titleEl = document.getElementById("ifx-next-move-title");
  const copyEl = document.getElementById("ifx-next-move-copy");
  const primary = document.getElementById("ifx-next-move-primary");
  const secondary = document.getElementById("ifx-next-move-secondary");
  if (!root || !titleEl || !copyEl || !primary || !secondary) return;

  const currentUrl = new URL(window.location.href);
  if (currentUrl.searchParams.get("ifx_embed") === "thumb") {{
    root.style.display = "none";
    return;
  }}

  const appBase = {json.dumps(SETTINGS.public_app_base_url.rstrip("/"))};
  const defaults = {{
    title: {json.dumps(defaults["title"])},
    copy: {json.dumps(defaults["copy"])},
    primaryLabel: {json.dumps(defaults["primary_label"])},
    primaryHref: {json.dumps(defaults["primary_href"])},
    secondaryLabel: {json.dumps(defaults["secondary_label"])},
    secondaryHref: {json.dumps(defaults["secondary_href"])},
  }};

  function readParam(name, fallback) {{
    const raw = (currentUrl.searchParams.get(name) || "").trim();
    return raw || fallback;
  }}

  function normalizeHref(raw, fallback) {{
    const next = (raw || "").trim();
    if (!next) return fallback;
    if (next.startsWith("/")) {{
      return new URL(next, appBase).toString();
    }}
    try {{
      const parsed = new URL(next);
      if (parsed.protocol === "http:" || parsed.protocol === "https:") {{
        return parsed.toString();
      }}
    }} catch (error) {{}}
    return fallback;
  }}

  let nextTitle = readParam("ifx_next_title", defaults.title);
  let nextCopy = readParam("ifx_next_copy", defaults.copy);
  let primaryLabel = readParam("ifx_next_label", defaults.primaryLabel);
  let primaryHref = normalizeHref(currentUrl.searchParams.get("ifx_next_href"), defaults.primaryHref);
  let secondaryLabel = readParam("ifx_next_secondary_label", defaults.secondaryLabel);
  let secondaryHref = normalizeHref(currentUrl.searchParams.get("ifx_next_secondary_href"), defaults.secondaryHref);

  const openedFromLibrary =
    currentUrl.searchParams.get("ifx_source") === "library" ||
    currentUrl.searchParams.get("ifx_saved") === "1";
  if (openedFromLibrary) {{
    nextTitle = "Keep the momentum going in IterFact";
    nextCopy = "You already have the artifact. The next useful move is to make another one or head back to your workspace.";
    primaryLabel = "Create another IterFact";
    primaryHref = appBase + "/create";
    secondaryLabel = "Open your library";
    secondaryHref = appBase + "/library";
  }}

  titleEl.textContent = nextTitle;
  copyEl.textContent = nextCopy;
  primary.textContent = primaryLabel;
  primary.href = primaryHref;
  secondary.textContent = secondaryLabel;
  secondary.href = secondaryHref;

  const target = window.frameElement ? "_blank" : "_self";
  primary.target = target;
  secondary.target = target;
  primary.rel = "noreferrer";
  secondary.rel = "noreferrer";
}})();
</script>
<!-- IFX_NEXT_MOVE_SHELL_END -->
"""

    if "</head>" in html:
        html = html.replace("</head>", style + "\n</head>", 1)
    else:
        html = style + html

    if "</body>" in html:
        return html.replace("</body>", markup + "\n" + script + "\n</body>", 1)
    return html + markup + script


def apply_artifact_delivery_shells(
    html: str,
    *,
    artifact_id: str,
    artifact_title: str,
    interaction_mode: str | None,
    generation_mode: str | None,
    prompt: str,
    template_hint: str | None,
    plan: dict[str, Any],
) -> str:
    final_html = html
    if interaction_mode:
        final_html = apply_iterate_fact_shell(final_html, artifact_id, interaction_mode)
    if generation_mode != "admin_passthrough_html":
        final_html = apply_library_shell(final_html, artifact_id, artifact_title)
        final_html = apply_next_move_shell(
            final_html,
            artifact_id=artifact_id,
            artifact_title=artifact_title,
            prompt=prompt,
            template_hint=template_hint,
            generation_mode=generation_mode,
            plan=plan,
        )
    assert_delivery_shell_preserves_body(html, final_html, artifact_id=artifact_id)
    return final_html


def validate_delivery_ready_artifact(
    *,
    html: str,
    artifact_id: str,
    artifact_title: str,
    interaction_mode: str | None,
    plan: dict[str, Any],
    requested_section_count: int | None,
    engine_override: str | None,
    prompt: str,
    template_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    generation_mode: str | None,
    payload: dict[str, Any] | None,
) -> tuple[str, list[dict[str, Any]], int, dict[str, Any]]:
    final_html = apply_artifact_delivery_shells(
        html,
        artifact_id=artifact_id,
        artifact_title=artifact_title,
        interaction_mode=interaction_mode,
        generation_mode=generation_mode,
        prompt=prompt,
        template_hint=template_hint,
        plan=plan,
    )
    final_html, warnings, quality_score, render_validation_summary = validate_rendered_artifact(
        html=final_html,
        plan=plan,
        requested_section_count=requested_section_count,
        engine_override=engine_override,
    )
    warnings, quality_score, render_validation_summary, _, _ = apply_prompt_stack_plan_audit(
        prompt=prompt,
        template_hint=template_hint,
        uploaded_content=uploaded_content,
        structured_data=structured_data,
        generation_mode=generation_mode,
        plan=plan,
        payload=payload,
        warnings=warnings,
        quality_score=quality_score,
        render_validation_summary=render_validation_summary,
    )
    return final_html, warnings, quality_score, render_validation_summary


def collect_engine_usage(plan: dict[str, Any]) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    wrapper_engine = normalize_engine_id(plan.get("wrapper", {}).get("engine"))
    if wrapper_engine:
        engine_meta = ENGINE_CATALOG.get(wrapper_engine, {})
        rows.append(
            {
                "engine_id": wrapper_engine,
                "engine_folder": engine_meta.get("folder"),
                "engine_name": engine_meta.get("name"),
                "engine_category": engine_meta.get("category"),
                "usage_role": "wrapper",
                "instance_id": "wrapper",
                "section_id": None,
                "section_title": None,
            }
        )

    for section in plan.get("sections", []) or []:
        for component in section.get("components", []) or []:
            engine_id = normalize_engine_id(component.get("engine"))
            if not engine_id:
                continue
            engine_meta = ENGINE_CATALOG.get(engine_id, {})
            rows.append(
                {
                    "engine_id": engine_id,
                    "engine_folder": engine_meta.get("folder"),
                    "engine_name": engine_meta.get("name"),
                    "engine_category": engine_meta.get("category"),
                    "usage_role": "component",
                    "instance_id": component.get("instance_id"),
                    "section_id": section.get("id"),
                    "section_title": section.get("title"),
                }
            )
    return rows


def artifact_family_key(title: str) -> str:
    normalized = title.lower()
    normalized = re.sub(r"\bq[1-4]\b", " ", normalized)
    normalized = re.sub(r"\b20\d{2}\b", " ", normalized)
    normalized = re.sub(r"\b(v|version)\s*\d+\b", " ", normalized)
    normalized = re.sub(r"\s+", " ", normalized).strip()
    return slugify(normalized)


def prompt_hash(prompt: str) -> str:
    return hashlib.sha256(prompt.encode("utf-8")).hexdigest()


def append_jsonl(path: Path, payload: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps(payload, ensure_ascii=True) + "\n")


def friendly_wrapper_name(engine_id: str | None) -> str:
    if not engine_id:
        return "interactive document"
    return WRAPPER_LABELS.get(engine_id, "interactive document")


def friendly_theme_name(theme_slug: str | None) -> str | None:
    if not theme_slug:
        return None
    return THEME_LABELS.get(theme_slug, theme_slug.replace("-", " ").title())


def indefinite_article(value: str) -> str:
    return "an" if value[:1].lower() in {"a", "e", "i", "o", "u"} else "a"


def build_summary(plan: dict[str, Any]) -> str:
    format_name = plan.get("meta", {}).get("document_format") or friendly_wrapper_name(plan.get("wrapper", {}).get("engine"))
    section_count = len(plan.get("sections", []))
    theme_name = friendly_theme_name(plan.get("theme"))
    template_match = plan.get("meta", {}).get("template_match")
    if template_match == "safe_html_rebuild":
        return "Safely rebuilt the uploaded HTML into an IterFact-native artifact while preserving visible structure and intent."
    if template_match == "guided_html_reconstruction":
        return "Reconstructed the uploaded HTML into a new IterFact using extracted structure and visible content instead of hosting the original runtime."
    summary = f"Built as {indefinite_article(format_name)} {format_name} with {section_count} section"
    if section_count != 1:
        summary += "s"
    if theme_name:
        summary += f" in the {theme_name} theme"
    return summary + "."


def build_follow_up_suggestions(prompt: str, plan: dict[str, Any]) -> list[str]:
    lower = prompt.lower()
    if plan.get("meta", {}).get("template_match") == "safe_html_rebuild":
        return [
            "Ask IterFact to turn this rebuilt import into a more structured narrative report.",
            "Ask IterFact to create a more visual version with stronger chaptering and proof points.",
            "Ask IterFact to rebuild the same source into a more data-heavy or more presentation-ready version.",
        ]
    if plan.get("meta", {}).get("template_match") == "guided_html_reconstruction":
        return [
            "Ask IterFact to make the reconstructed version more faithful to the original layout.",
            "Ask IterFact to simplify the reconstruction into a cleaner executive summary.",
            "Ask IterFact to turn the same source into a more interactive explainer with clearer next steps.",
        ]

    if any(keyword in lower for keyword in ["iterfact", "interactive artifact", "static files", "product story", "why static"]):
        return [
            "Try a more editorial scrollytelling version with a stronger category narrative.",
            "Try a guided product walkthrough that shows how IterFact turns static files into interactive artifacts.",
            "Try a sharper proof-first version focused on buyer pain, before-and-after examples, and why PDFs fail.",
        ]

    if any(keyword in lower for keyword in ["pitch deck", "fundraising", "raise", "series a", "pre-seed", "seed"]):
        return [
            "Try a tighter investor version with fewer sections and stronger proof points.",
            "Try a founder-story version with more narrative momentum and less dashboard weight.",
            "Try a demo-led version that shows the product experience before the market slide.",
        ]

    if any(keyword in lower for keyword in ["board", "investor update", "earnings", "quarterly"]):
        return [
            "Try a shorter executive-summary version for quick review.",
            "Try a more data-heavy version with deeper KPI and scenario coverage.",
            "Try a narrative version that frames the numbers around three key decisions.",
        ]

    return [
        "Try a more narrative version with stronger pacing.",
        "Try a more visual version with fewer words and clearer comparisons.",
        "Try a shorter executive version focused on the three most important points.",
    ]

def build_safe_rebuild_plan(prompt: str, analysis: UploadAnalysis) -> dict[str, Any]:
    derived_title = analysis.title or prompt.strip()[:120] or "IterFact Imported Artifact"
    return {
        "meta": {
            "title": derived_title,
            "description": "Safely rebuilt from uploaded HTML while preserving visible structure and intent.",
            "document_format": "rebuilt interactive artifact",
            "template_match": "safe_html_rebuild",
        },
        "theme": None,
        "wrapper": {"engine": None},
        "sections": [],
    }


def build_passthrough_upload_plan(prompt: str, analysis: UploadAnalysis | None, title: str) -> dict[str, Any]:
    derived_title = compact_whitespace(title) or prompt.strip()[:120] or "IterFact Passthrough Upload"
    category = analysis.category if analysis else "passthrough_html"
    return {
        "meta": {
            "title": derived_title,
            "description": "Admin passthrough upload. Original HTML runtime was hosted without IterFact reconstruction.",
            "document_format": "passthrough html artifact",
            "template_match": "admin_passthrough_upload",
        },
        "theme": None,
        "wrapper": {"engine": None},
        "sections": [
            {
                "title": "Original Upload",
                "content": {
                    "headline": "Original HTML passthrough",
                    "body": (
                        f"Hosted the original uploaded HTML runtime directly under admin override. "
                        f"Detected category: {category.replace('_', ' ')}."
                    ),
                },
            }
        ],
    }


def build_guided_reconstruction_plan(prompt: str, analysis: UploadAnalysis, plan: dict[str, Any]) -> dict[str, Any]:
    resolved = dict(plan)
    resolved_meta = dict(resolved.get("meta", {}))
    resolved_meta.setdefault("title", analysis.title or prompt.strip()[:120] or "IterFact Reconstructed Artifact")
    resolved_meta["description"] = (
        "Guided reconstruction from uploaded HTML. Visible content and intent were preserved, while unsupported runtime behavior was rebuilt inside IterFact."
    )
    resolved_meta["template_match"] = "guided_html_reconstruction"
    resolved["meta"] = resolved_meta
    return resolved


def serializable_usage(usage: dict[str, Any] | None) -> dict[str, int]:
    if not isinstance(usage, dict):
        return {}
    return {key: int(value) for key, value in usage.items() if isinstance(value, int)}


def build_generation_event(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    generation_mode: str,
    interaction_mode: str | None,
    telemetry_context: dict[str, str | None],
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    warnings: list[dict[str, Any]],
    quality_score: int | None,
    render_validation_summary: dict[str, Any],
    ctx: Context | None,
    plan: dict[str, Any],
    payload: dict[str, Any],
    artifact_id: str,
    storage: str,
    local_path: Path,
    object_key: str | None,
    preview_url: str | None,
    durations_ms: dict[str, int],
    upload_analysis: UploadAnalysis | None = None,
) -> dict[str, Any]:
    provider_events = merge_provider_events(payload.get("_provider_events") or [])
    provider_tracker = token_tracker_from_provider_events(provider_events)
    usage = serializable_usage(payload.get("usage"))
    if provider_tracker.total_calls:
        usage = {
            "input_tokens": provider_tracker.total_input_tokens,
            "output_tokens": provider_tracker.total_output_tokens,
        }
    title = plan.get("meta", {}).get("title", "IterFact Document")
    engine_usage = collect_engine_usage(plan)
    if upload_analysis is not None and upload_analysis.decision == "safe_rebuild":
        engine_usage.insert(
            0,
            {
                "engine_id": "safe-rebuild-renderer",
                "engine_name": "Safe Rebuild Renderer",
                "engine_category": "ingest",
                "usage_role": "wrapper",
                "instance_id": "safe-rebuild-renderer",
                "section_id": None,
                "section_title": None,
            },
        )
    unique_engine_ids = sorted({row["engine_id"] for row in engine_usage if row.get("engine_id")})
    client_id = ctx.client_id if ctx is not None else None
    request_id = ctx.request_id if ctx is not None else None
    event_time = datetime.now(timezone.utc)
    document_format = plan.get("meta", {}).get("document_format") or friendly_wrapper_name(plan.get("wrapper", {}).get("engine"))

    return {
        "event_type": "generation_succeeded",
        "event_id": str(uuid.uuid4()),
        "timestamp_utc": event_time.isoformat(),
        "request_id": request_id,
        "client_id": client_id,
        "identity_mode": request_identity_mode(ctx),
        "source_surface": telemetry_context.get("source_surface") or "mcp",
        "actor_key": telemetry_context.get("actor_key"),
        "user_id": telemetry_context.get("user_id"),
        "prompt_hash": prompt_hash(prompt),
        "prompt_preview": prompt[:240],
        "prompt_length": len(prompt),
        "prompt_token_estimate": estimate_text_token_count(prompt),
        "prompt_category": classify_prompt_category(prompt, template_hint=template_hint, plan=plan),
        "template_hint": template_hint,
        "theme_hint": theme_hint,
        "structured_data_present": bool(structured_data),
        "structured_data_keys": structured_data_keys(structured_data),
        "engine_override_requested": engine_override,
        "section_count_requested": section_count_requested,
        "generation_mode": generation_mode,
        "interaction_mode": interaction_mode,
        "source_lane": telemetry_context.get("source_lane"),
        "source_mode": telemetry_context.get("source_mode") or ("template_direct" if generation_mode == "template_instance_render" else None),
        "uploaded_content_present": bool(uploaded_content),
        "uploaded_length": len(uploaded_content or ""),
        "uploaded_content_compacted": bool(payload.get("uploaded_content_compacted")),
        "uploaded_content_strategy": payload.get("uploaded_content_strategy"),
        "uploaded_heading_count_detected": payload.get("uploaded_heading_count_detected") or 0,
        "uploaded_heading_count_preserved": payload.get("uploaded_heading_count_preserved") or 0,
        "artifact_id": artifact_id,
        "artifact_family_key": artifact_family_key(title),
        "created_category": infer_created_category(prompt, plan, upload_analysis),
        "title": title,
        "description": plan.get("meta", {}).get("description"),
        "template_match": plan.get("meta", {}).get("template_match"),
        "document_format": document_format,
        "wrapper_engine_id": plan.get("wrapper", {}).get("engine"),
        "theme_slug": plan.get("theme"),
        "theme_name": friendly_theme_name(plan.get("theme")),
        "prompt_stack_bundle_version": payload.get("prompt_stack_bundle_version"),
        "active_prompt_modules": payload.get("active_prompt_modules") or [],
        "plan_quality_audit": payload.get("plan_quality_audit"),
        "warnings": warnings,
        "quality_score": quality_score,
        "render_validation_summary": render_validation_summary,
        "iterative_refinement": payload.get("iterative_refinement") or render_validation_summary.get("iterative_refinement"),
        "section_count": len(plan.get("sections", [])),
        "section_titles": [section.get("title") for section in plan.get("sections", []) if section.get("title")],
        "storage": storage,
        "local_path": str(local_path),
        "object_key": object_key,
        "preview_url": preview_url,
        "planner_model": payload.get("model"),
        "planner_repaired": bool(payload.get("repair_applied")),
        "retry_strategy": payload.get("retry_strategy"),
        "usage": usage,
        "llm_calls_count": provider_tracker.total_calls,
        "llm_models_used": provider_tracker.models_used,
        "llm_model_used": provider_tracker.dominant_model,
        "total_input_tokens": provider_tracker.total_input_tokens,
        "total_output_tokens": provider_tracker.total_output_tokens,
        "estimated_cost_usd": resolve_estimated_generation_cost_usd(
            generation_mode=generation_mode,
            provider_tracker=provider_tracker,
            usage=usage,
        ),
        "durations_ms": durations_ms,
        "upload_ingest_decision": (
            "admin_passthrough" if generation_mode == "admin_passthrough_html" else upload_analysis.decision if upload_analysis else "not_applicable"
        ),
        "upload_ingest_category": upload_analysis.category if upload_analysis else None,
        "upload_preservation_level": upload_analysis.preservation_level if upload_analysis else None,
        "upload_policy_profile": upload_analysis.policy_profile if upload_analysis else None,
        "upload_reconstruction_status": (
            "bypassed"
            if generation_mode == "admin_passthrough_html"
            else (
                "succeeded"
                if upload_analysis and upload_analysis.decision in {"safe_rebuild", "guided_reconstruction"}
                else "not_applicable"
            )
        ),
        "upload_rejection_reason": upload_analysis.rejection_reason if upload_analysis else None,
        "upload_findings_counts": upload_analysis.finding_counts() if upload_analysis else {},
        "upload_external_domains": upload_analysis.external_domains if upload_analysis else [],
        "upload_analysis_summary": upload_analysis.summary() if upload_analysis else {},
        "upload_findings": [
            {
                "severity": finding.severity,
                "code": finding.code,
                "detail": finding.detail,
                "evidence": finding.evidence,
            }
            for finding in (upload_analysis.findings if upload_analysis else [])
        ],
        "engines_used": engine_usage,
        "unique_engine_ids": unique_engine_ids,
        "unique_engine_count": len(unique_engine_ids),
        "provider_events": provider_events,
    }


def build_failure_event(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    generation_mode: str,
    interaction_mode: str | None,
    telemetry_context: dict[str, str | None],
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    ctx: Context | None,
    stage: str,
    error: Exception,
    durations_ms: dict[str, int],
    provider_events: list[dict[str, Any]] | None = None,
    upload_analysis: UploadAnalysis | None = None,
) -> dict[str, Any]:
    if isinstance(error, UploadRejectedError):
        upload_analysis = error.analysis
    merged_provider_events = merge_provider_events(provider_events)
    provider_tracker = token_tracker_from_provider_events(merged_provider_events)
    return {
        "event_type": "generation_failed",
        "event_id": str(uuid.uuid4()),
        "timestamp_utc": datetime.now(timezone.utc).isoformat(),
        "request_id": ctx.request_id if ctx is not None else None,
        "client_id": ctx.client_id if ctx is not None else None,
        "identity_mode": request_identity_mode(ctx),
        "source_surface": telemetry_context.get("source_surface") or "mcp",
        "actor_key": telemetry_context.get("actor_key"),
        "user_id": telemetry_context.get("user_id"),
        "prompt_hash": prompt_hash(prompt),
        "prompt_preview": prompt[:240],
        "prompt_length": len(prompt),
        "prompt_token_estimate": estimate_text_token_count(prompt),
        "prompt_category": classify_prompt_category(prompt, template_hint=template_hint),
        "template_hint": template_hint,
        "theme_hint": theme_hint,
        "structured_data_present": bool(structured_data),
        "structured_data_keys": structured_data_keys(structured_data),
        "engine_override_requested": engine_override,
        "section_count_requested": section_count_requested,
        "generation_mode": generation_mode,
        "interaction_mode": interaction_mode,
        "source_lane": telemetry_context.get("source_lane"),
        "source_mode": telemetry_context.get("source_mode") or ("template_direct" if generation_mode == "template_instance_render" else None),
        "uploaded_content_present": bool(uploaded_content),
        "uploaded_length": len(uploaded_content or ""),
        "created_category": upload_analysis.category if upload_analysis else None,
        "upload_ingest_decision": upload_analysis.decision if upload_analysis else "not_applicable",
        "upload_ingest_category": upload_analysis.category if upload_analysis else None,
        "upload_preservation_level": upload_analysis.preservation_level if upload_analysis else None,
        "upload_policy_profile": upload_analysis.policy_profile if upload_analysis else None,
        "upload_reconstruction_status": (
            "rejected"
            if upload_analysis and upload_analysis.decision == "reject"
            else "failed"
            if upload_analysis
            else "not_applicable"
        ),
        "upload_rejection_reason": upload_analysis.rejection_reason if upload_analysis else None,
        "upload_findings_counts": upload_analysis.finding_counts() if upload_analysis else {},
        "upload_external_domains": upload_analysis.external_domains if upload_analysis else [],
        "upload_analysis_summary": upload_analysis.summary() if upload_analysis else {},
        "warnings": [],
        "quality_score": None,
        "render_validation_summary": {},
        "llm_calls_count": provider_tracker.total_calls,
        "llm_models_used": provider_tracker.models_used,
        "llm_model_used": provider_tracker.dominant_model,
        "total_input_tokens": provider_tracker.total_input_tokens,
        "total_output_tokens": provider_tracker.total_output_tokens,
        "estimated_cost_usd": resolve_estimated_generation_cost_usd(
            generation_mode=generation_mode,
            provider_tracker=provider_tracker,
            usage=None,
        ),
        "provider_events": merged_provider_events,
        "upload_findings": [
            {
                "severity": finding.severity,
                "code": finding.code,
                "detail": finding.detail,
                "evidence": finding.evidence,
            }
            for finding in (upload_analysis.findings if upload_analysis else [])
        ],
        "stage": stage,
        "error_type": type(error).__name__,
        "error_message": str(error),
        "durations_ms": durations_ms,
    }


def build_metadata_telemetry_event_from_generation(
    generation_event: dict[str, Any],
    *,
    state: dict[str, Any] | None = None,
) -> dict[str, Any]:
    tracker = token_tracker_from_provider_events(generation_event.get("provider_events") or [])
    warning_shapes = summarize_warning_shapes(generation_event.get("warnings") or [])
    iterative_refinement = generation_event.get("iterative_refinement")
    if not isinstance(iterative_refinement, dict):
        iterative_refinement = {}
    critic_skipped = bool(iterative_refinement.get("critic_skipped"))
    critic_timed_out = bool(iterative_refinement.get("critic_timed_out"))
    regeneration_count, regenerated_sections = collect_regeneration_metadata(state)
    wrapper_engine_id = generation_event.get("wrapper_engine_id")
    wrapper_engine = ENGINE_CATALOG.get(str(wrapper_engine_id or ""), {})
    template_hint = generation_event.get("template_hint")
    template_match_method = generation_event.get("template_match") or ("exact_name" if template_hint else "planner_selected")
    if generation_event.get("generation_mode") == "template_instance_render" and template_hint:
        template_match_method = "exact_name"
    return {
        "event_type": "artifact_generated",
        "event_id": str(uuid.uuid4()),
        "artifact_id": generation_event.get("artifact_id"),
        "timestamp": generation_event.get("timestamp_utc") or now_utc_iso(),
        "trigger_source": detect_trigger_source(
            {
                "source_surface": generation_event.get("source_surface"),
                "actor_key": generation_event.get("actor_key"),
                "source_lane": generation_event.get("source_lane"),
                "source_mode": generation_event.get("source_mode"),
                "user_id": generation_event.get("user_id"),
            },
            str(generation_event.get("generation_mode") or ""),
        ),
        "prompt_token_count": int(generation_event.get("prompt_token_estimate") or 0),
        "prompt_category": generation_event.get("prompt_category") or classify_prompt_category(
            str(generation_event.get("created_category") or ""),
            template_hint=str(template_hint) if template_hint else None,
            plan=state.get("plan") if isinstance(state, dict) and isinstance(state.get("plan"), dict) else None,
        ),
        "section_count": int(generation_event.get("section_count") or 0),
        "has_uploaded_content": bool(generation_event.get("uploaded_content_present")),
        "engine_id": wrapper_engine_id,
        "engine_name": wrapper_engine.get("name") or friendly_wrapper_name(wrapper_engine_id),
        "template_id": template_hint,
        "template_match_method": template_match_method,
        "templates_composed": None,
        "theme_hint": generation_event.get("theme_hint"),
        "theme_resolved": generation_event.get("theme_name") or generation_event.get("theme_slug"),
        "llm_calls_count": tracker.total_calls or int(generation_event.get("llm_calls_count") or 0),
        "llm_model_used": tracker.dominant_model or generation_event.get("llm_model_used") or generation_event.get("planner_model"),
        "total_input_tokens": tracker.total_input_tokens or int(generation_event.get("total_input_tokens") or 0),
        "total_output_tokens": tracker.total_output_tokens or int(generation_event.get("total_output_tokens") or 0),
        "estimated_cost_usd": tracker.estimated_cost_usd if tracker.total_calls else generation_event.get("estimated_cost_usd"),
        "quality_score": generation_event.get("quality_score"),
        "warnings": warning_shapes,
        "warning_types": [warning.get("type") for warning in warning_shapes if warning.get("type")],
        "critic_fix_cycles": int(iterative_refinement.get("passes_run") or 0),
        "critic_skipped": critic_skipped,
        "critic_timed_out": critic_timed_out,
        "artifact_status": "published",
        "render_duration_ms": int(((generation_event.get("durations_ms") or {}).get("total")) or 0),
        "embed_views_24h": 0,
        "regeneration_count": regeneration_count,
        "regenerated_sections": regenerated_sections,
    }


def build_metadata_telemetry_event_from_failure(
    failure_event: dict[str, Any],
) -> dict[str, Any]:
    tracker = token_tracker_from_provider_events(failure_event.get("provider_events") or [])
    return {
        "template_match_method": (
            "exact_name"
            if failure_event.get("generation_mode") == "template_instance_render" and failure_event.get("template_hint")
            else ("exact_name" if failure_event.get("template_hint") else "planner_selected")
        ),
        "event_type": "artifact_failed",
        "event_id": str(uuid.uuid4()),
        "artifact_id": failure_event.get("artifact_id"),
        "timestamp": failure_event.get("timestamp_utc") or now_utc_iso(),
        "trigger_source": detect_trigger_source(
            {
                "source_surface": failure_event.get("source_surface"),
                "actor_key": failure_event.get("actor_key"),
                "source_lane": failure_event.get("source_lane"),
                "source_mode": failure_event.get("source_mode"),
                "user_id": failure_event.get("user_id"),
            },
            str(failure_event.get("generation_mode") or ""),
        ),
        "prompt_token_count": int(failure_event.get("prompt_token_estimate") or 0),
        "prompt_category": failure_event.get("prompt_category") or classify_prompt_category(
            str(failure_event.get("created_category") or ""),
            template_hint=failure_event.get("template_hint"),
        ),
        "section_count": int(failure_event.get("section_count_requested") or 0),
        "has_uploaded_content": bool(failure_event.get("uploaded_content_present")),
        "engine_id": failure_event.get("engine_override_requested"),
        "engine_name": ENGINE_CATALOG.get(str(failure_event.get("engine_override_requested") or ""), {}).get("name"),
        "template_id": failure_event.get("template_hint"),
        "templates_composed": None,
        "theme_hint": failure_event.get("theme_hint"),
        "theme_resolved": None,
        "llm_calls_count": tracker.total_calls or int(failure_event.get("llm_calls_count") or 0),
        "llm_model_used": tracker.dominant_model or failure_event.get("llm_model_used"),
        "total_input_tokens": tracker.total_input_tokens or int(failure_event.get("total_input_tokens") or 0),
        "total_output_tokens": tracker.total_output_tokens or int(failure_event.get("total_output_tokens") or 0),
        "estimated_cost_usd": tracker.estimated_cost_usd if tracker.total_calls else failure_event.get("estimated_cost_usd"),
        "quality_score": failure_event.get("quality_score"),
        "warnings": [],
        "warning_types": [],
        "critic_fix_cycles": 0,
        "critic_skipped": False,
        "critic_timed_out": False,
        "artifact_status": "failed",
        "render_duration_ms": int(((failure_event.get("durations_ms") or {}).get("total")) or 0),
        "embed_views_24h": 0,
        "regeneration_count": 0,
        "regenerated_sections": [],
        "failure_stage": failure_event.get("stage"),
        "failure_type": failure_event.get("error_type"),
    }


def build_regeneration_telemetry_event(
    *,
    artifact_id: str,
    section_index: int,
    telemetry_context: dict[str, Any] | None = None,
) -> dict[str, Any]:
    return {
        "event_type": "artifact_regenerated",
        "event_id": str(uuid.uuid4()),
        "artifact_id": artifact_id,
        "timestamp": now_utc_iso(),
        "trigger_source": detect_trigger_source(telemetry_context, "section_regeneration"),
        "section_index": section_index,
    }


def build_chunked_draft_plan(scaffold_plan: dict[str, Any]) -> dict[str, Any]:
    return {
        "meta": dict(scaffold_plan.get("meta", {})),
        "theme": scaffold_plan.get("theme"),
        "wrapper": dict(scaffold_plan.get("wrapper", {})),
        "sections": [],
    }


def build_chunked_draft_state(
    *,
    source_chunks: list[SourceChunk],
    payload: dict[str, Any] | None,
    status: str = "draft",
) -> dict[str, Any]:
    return {
        "status": status,
        "builder_mode": "chunked_append",
        "source_chunk_count": len(source_chunks),
        "planned_section_titles": [chunk.title for chunk in source_chunks],
        "appended_sections": 0,
        "uploaded_content_compacted": bool(payload and payload.get("uploaded_content_compacted")),
        "uploaded_content_strategy": payload.get("uploaded_content_strategy") if isinstance(payload, dict) else None,
    }


def infer_chunked_draft_wrapper_engine(
    prompt: str,
    template_hint: str | None,
    engine_override: str | None,
) -> str:
    normalized_override = normalize_engine_override(engine_override)
    if normalized_override and normalized_override in VALID_WRAPPERS:
        return normalized_override

    lower = prompt.lower()
    if any(keyword in lower for keyword in ("slide", "slides", "deck", "board", "investor presentation")):
        return "02"
    if any(keyword in lower for keyword in ("walkthrough", "guided tour", "demo", "product tour")):
        return "21"
    if any(keyword in lower for keyword in ("contract", "agreement", "signature", "e-sign", "esign", "nda", "policy acknowledgment", "proposal acceptance")):
        return "25"
    if any(keyword in lower for keyword in ("event", "countdown", "invitation", "invite", "rsvp", "conference", "webinar", "wedding", "gala")):
        return "24"
    if any(keyword in lower for keyword in ("scrollytelling", "scroll story", "parallax", "immersive", "brand story", "showcase", "launch story")):
        return "18"
    if any(keyword in lower for keyword in ("landing page", "homepage", "one pager", "one-pager", "report", "analysis", "brief", "memo", "research", "proposal", "cim", "client report", "board update", "investment memo")):
        return "12"
    if template_hint in {"client_proposal", "research_report", "financial_briefing"}:
        return "12"
    return "12"


def build_chunked_draft_blueprint_sections(source_chunks: list[SourceChunk]) -> list[dict[str, Any]]:
    sections: list[dict[str, Any]] = []
    for index, chunk in enumerate(source_chunks, start=1):
        preferred_title = compact_whitespace(chunk.title) or f"Section {index}"
        sections.append(
            {
                "title": preferred_title,
                "content": {
                    "headline": preferred_title,
                    "body": "",
                },
                "components": [],
            }
        )
    return sections


def build_fast_chunked_scaffold_plan(
    *,
    prompt: str,
    source_chunks: list[SourceChunk],
    template_hint: str | None,
    theme_hint: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    uploaded_content: str | None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    wrapper_engine = infer_chunked_draft_wrapper_engine(prompt, template_hint, engine_override)
    title = infer_source_document_title(prompt, uploaded_content or "") or prompt.strip()[:120] or "IterFact Draft"
    blueprint_sections = build_chunked_draft_blueprint_sections(source_chunks)
    scaffold_plan = {
        "meta": {
            "title": title,
            "description": "Chunk-built draft scaffold for a source-backed IterFact.",
            "document_format": friendly_wrapper_name(wrapper_engine),
            "template_match": template_hint,
        },
        "theme": theme_hint or "obsidian-cyan",
        "wrapper": {"engine": wrapper_engine},
        "sections": blueprint_sections,
    }
    scaffold_plan = apply_primary_engine_constraints(scaffold_plan, engine_override, structured_data)
    payload = {
        "model": None,
        "usage": {},
        "repair_applied": False,
        "retry_strategy": "deterministic-draft-scaffold",
        "uploaded_content_compacted": len(uploaded_content or "") > PLANNER_UPLOADED_CONTENT_INLINE_MAX_CHARS,
        "uploaded_content_strategy": "deterministic_fast_scaffold",
        "_provider_events": [],
        "prompt_stack_bundle_version": PROMPT_STACK_BRIDGE_VERSION,
        "active_prompt_modules": resolve_active_prompt_modules(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode="chunked_draft_scaffold",
        ),
        "plan_quality_audit": {
            "score_penalty": 0,
            "critical": False,
            "should_retry": False,
            "feedback_lines": [],
            "warnings": [],
            "scaffold_mode": "deterministic_fast",
        },
    }
    return scaffold_plan, payload


def combine_payloads(payloads: list[dict[str, Any]]) -> dict[str, Any]:
    valid_payloads = [payload for payload in payloads if isinstance(payload, dict)]
    combined = {
        "model": next((payload.get("model") for payload in valid_payloads if payload.get("model")), None),
        "usage": combine_usage(*valid_payloads) or {},
        "repair_applied": any(bool(payload.get("repair_applied")) for payload in valid_payloads),
        "retry_strategy": "chunked-section-build" if valid_payloads else None,
        "_provider_events": [event for payload in valid_payloads for event in (payload.get("_provider_events") or [])],
        "uploaded_content_compacted": any(bool(payload.get("uploaded_content_compacted")) for payload in valid_payloads),
        "uploaded_content_strategy": next(
            (payload.get("uploaded_content_strategy") for payload in valid_payloads if payload.get("uploaded_content_strategy")),
            None,
        ),
        "uploaded_heading_count_detected": max(
            [int(payload.get("uploaded_heading_count_detected") or 0) for payload in valid_payloads] or [0]
        ),
        "uploaded_heading_count_preserved": max(
            [int(payload.get("uploaded_heading_count_preserved") or 0) for payload in valid_payloads] or [0]
        ),
    }
    return combined


async def create_chunked_artifact_draft_internal(
    *,
    prompt: str,
    source_chunks: list[SourceChunk],
    template_hint: str | None,
    theme_hint: str | None,
    interaction_mode: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    uploaded_content: str | None = None,
    scaffold_plan_override: dict[str, Any] | None = None,
    payload_override: dict[str, Any] | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    if isinstance(scaffold_plan_override, dict):
        scaffold_plan = normalize_plan(copy.deepcopy(scaffold_plan_override))
        payload = dict(payload_override or {})
    else:
        scaffold_plan, payload = build_fast_chunked_scaffold_plan(
            prompt=prompt,
            source_chunks=source_chunks,
            template_hint=template_hint,
            theme_hint=theme_hint,
            structured_data=structured_data,
            engine_override=engine_override,
            uploaded_content=uploaded_content,
        )
    draft_plan = build_chunked_draft_plan(scaffold_plan)
    if theme_hint:
        draft_plan["theme"] = theme_hint
    if is_wrapper_engine(engine_override):
        draft_plan.setdefault("wrapper", {})["engine"] = engine_override
    draft_plan = apply_primary_engine_constraints(
        draft_plan,
        engine_override,
        structured_data,
        create_placeholder_section=False,
    )
    title = draft_plan.get("meta", {}).get("title") or infer_source_document_title(prompt, uploaded_content or "")
    draft_plan.setdefault("meta", {})["title"] = title
    artifact_id = str(uuid.uuid4())
    draft_state = build_chunked_draft_state(source_chunks=source_chunks, payload=payload)
    state = persist_artifact_draft_state(
        artifact_id=artifact_id,
        title=title,
        prompt=prompt,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        generation_mode="chunked_draft",
        structured_data=structured_data,
        engine_override=engine_override,
        section_count_requested=section_count_requested or len(source_chunks) or None,
        plan=draft_plan,
        draft_state=draft_state,
        draft_blueprint=scaffold_plan,
    )
    record_artifact_provider_events(artifact_id, payload.get("_provider_events") if isinstance(payload, dict) else None)
    payload["retry_strategy"] = payload.get("retry_strategy") or "chunked-draft-scaffold"
    return state, payload


async def append_section_to_artifact_draft_internal(
    *,
    artifact_id: str,
    source_chunk: SourceChunk,
    updated_prompt: str | None,
    section_title_hint: str | None,
) -> tuple[dict[str, Any], dict[str, Any]]:
    state = load_artifact_state(artifact_id)
    draft_state = state.get("draft") if isinstance(state.get("draft"), dict) else {}
    if draft_state.get("status") != "draft":
        raise ValueError("This artifact draft is already finalized. Start a new draft before appending more sections.")

    plan = state.get("plan")
    if not isinstance(plan, dict):
        raise ValueError("The saved artifact draft is missing a reusable plan scaffold.")

    engine_override = normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None
    target_index = len(plan.get("sections", []) or []) + 1
    deterministic_section = build_deterministic_section_from_source_chunk(
        chunk=source_chunk,
        engine_override=engine_override,
        target_index=target_index,
        section_title_hint=section_title_hint,
    )
    if deterministic_section is not None:
        section, payload = deterministic_section
    else:
        section, payload = await generate_section_from_source_chunk(
            original_prompt=str(state.get("prompt") or state.get("title") or "Build this IterFact."),
            template_hint=state.get("template_hint"),
            plan=plan,
            draft_blueprint=state.get("draft_blueprint") if isinstance(state.get("draft_blueprint"), dict) else None,
            chunk=source_chunk,
            target_index=target_index,
            updated_prompt=updated_prompt,
            section_title_hint=section_title_hint,
            structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
            engine_override=engine_override,
        )
    plan.setdefault("sections", []).append(section)
    draft_state["appended_sections"] = len(plan.get("sections", []) or [])
    draft_state["last_appended_at"] = datetime.now(timezone.utc).isoformat()
    draft_state["last_appended_title"] = section.get("title") or source_chunk.title
    persist_artifact_draft_state(
        artifact_id=artifact_id,
        title=str(state.get("title") or plan.get("meta", {}).get("title") or "IterFact Draft"),
        prompt=str(state.get("prompt") or ""),
        template_hint=state.get("template_hint"),
        theme_hint=state.get("theme_hint"),
        interaction_mode=state.get("interaction_mode"),
        generation_mode="chunked_draft",
        structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
        engine_override=engine_override,
        section_count_requested=state.get("section_count_requested"),
        plan=plan,
        draft_state=draft_state,
        draft_blueprint=state.get("draft_blueprint") if isinstance(state.get("draft_blueprint"), dict) else None,
    )
    record_artifact_provider_events(artifact_id, payload.get("_provider_events") if isinstance(payload, dict) else None)
    return section, payload


async def finalize_artifact_draft_internal(
    *,
    artifact_id: str,
    payload: dict[str, Any] | None = None,
    skip_critic: bool = False,
) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    draft_state = state.get("draft") if isinstance(state.get("draft"), dict) else {}
    if draft_state.get("status") != "draft":
        raise ValueError("This artifact draft has already been finalized.")

    plan = state.get("plan")
    if not isinstance(plan, dict):
        raise ValueError("The saved artifact draft is missing a reusable plan.")

    validate_plan(plan)
    structured_data = sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None
    prompt_text = str(state.get("prompt") or state.get("title") or "IterFact Document")
    requested_section_count = state.get("section_count_requested") or len(plan.get("sections", []) or [])
    engine_override = normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None
    if engine_override and not plan_honors_engine_override(plan, engine_override):
        raise ValueError(
            f"The chunked draft does not yet honor the requested engine override {engine_override}. "
            "Append a section that uses the requested engine before calling finalize_artifact."
        )
    render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)

    def save_checkpoint(checkpoint: dict[str, Any]) -> None:
        update_artifact_render_checkpoint(artifact_id, checkpoint=checkpoint)

    def set_render_progress(progress: int, message: str) -> None:
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="rendering",
                render_job_patch={
                    "status": "running",
                    "progress": max(0, min(100, int(progress))),
                    "last_message": message,
                },
            )

    title = str(render_checkpoint.get("title") or plan.get("meta", {}).get("title") or state.get("title") or "IterFact Document")
    interaction_mode = str(render_checkpoint.get("interaction_mode") or resolve_interaction_mode(prompt_text, state.get("interaction_mode")))
    html = str(render_checkpoint.get("html") or "")
    warnings = list(render_checkpoint.get("warnings") or [])
    quality_score = int(render_checkpoint.get("quality_score") or 100)
    render_validation_summary = (
        dict(render_checkpoint.get("render_validation_summary"))
        if isinstance(render_checkpoint.get("render_validation_summary"), dict)
        else {}
    )
    payload = dict(render_checkpoint.get("payload") or payload or {})
    if isinstance(render_checkpoint.get("plan"), dict):
        plan = normalize_plan(copy.deepcopy(render_checkpoint["plan"]))

    if not checkpoint_stage_at_least(render_checkpoint, "render_refined"):
        set_render_progress(55, "Rendering, validating, and critiquing the finalized artifact.")
        plan, html, payload, warnings, quality_score, render_validation_summary = await render_plan_with_iterative_refinement(
            prompt=prompt_text,
            template_hint=state.get("template_hint"),
            uploaded_content=None,
            structured_data=structured_data,
            generation_mode="chunked_planned_render",
            plan=plan,
            payload=payload,
            requested_section_count=requested_section_count,
            engine_override=engine_override,
            resume_checkpoint=render_checkpoint if render_checkpoint.get("stage") == "iterative_refinement" else None,
            checkpoint_writer=save_checkpoint,
            progress_callback=set_render_progress,
            skip_critic=skip_critic,
            critic_timeout_seconds=SETTINGS.anthropic_critic_timeout_seconds,
        )
        title = plan.get("meta", {}).get("title", state.get("title") or "IterFact Document")
        interaction_mode = resolve_interaction_mode(str(state.get("prompt") or title), state.get("interaction_mode"))
        critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "render_refined",
                    plan=plan,
                    payload=payload,
                    html=html,
                    warnings=warnings,
                    quality_score=quality_score,
                    render_validation_summary=render_validation_summary,
                    title=title,
                    interaction_mode=interaction_mode,
                    critic_skipped=critic_skipped,
                    critic_timed_out=critic_timed_out,
                ),
            ).get("render_checkpoint")
        )

    if checkpoint_stage_at_least(render_checkpoint, "delivery_validated"):
        html = str(render_checkpoint.get("html") or html)
        warnings = list(render_checkpoint.get("warnings") or warnings)
        quality_score = int(render_checkpoint.get("quality_score") or quality_score)
        render_validation_summary = (
            dict(render_checkpoint.get("render_validation_summary"))
            if isinstance(render_checkpoint.get("render_validation_summary"), dict)
            else render_validation_summary
        )
        payload = dict(render_checkpoint.get("payload") or payload or {})
        if isinstance(render_checkpoint.get("plan"), dict):
            plan = normalize_plan(copy.deepcopy(render_checkpoint["plan"]))
        title = str(render_checkpoint.get("title") or title)
        interaction_mode = str(render_checkpoint.get("interaction_mode") or interaction_mode)
    else:
        LOGGER.info("[publish:%s] Starting delivery validation for chunked artifact.", artifact_id)
        set_render_progress(72, "Applying delivery chrome and validating the final artifact.")
        html, warnings, quality_score, render_validation_summary = validate_delivery_ready_artifact(
            html=html,
            artifact_id=artifact_id,
            artifact_title=title,
            interaction_mode=interaction_mode,
            plan=plan,
            requested_section_count=requested_section_count,
            engine_override=engine_override,
            prompt=str(state.get("prompt") or title),
            template_hint=state.get("template_hint"),
            uploaded_content=None,
            structured_data=structured_data,
            generation_mode="chunked_planned_render",
            payload=payload,
        )
        critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
        enforce_render_validation_or_raise(
            warnings,
            quality_score,
            render_validation_summary,
            allow_low_quality_publish=allow_nonfatal_low_quality_publish(payload, render_validation_summary),
        )
        LOGGER.info("[publish:%s] Delivery validation passed for chunked artifact.", artifact_id)
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "delivery_validated",
                    plan=plan,
                    payload=payload,
                    html=html,
                    warnings=warnings,
                    quality_score=quality_score,
                    render_validation_summary=render_validation_summary,
                    title=title,
                    interaction_mode=interaction_mode,
                    critic_skipped=critic_skipped,
                    critic_timed_out=critic_timed_out,
                ),
            ).get("render_checkpoint")
        )
    critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
    public_score = public_quality_score(quality_score, payload, render_validation_summary)

    current_state = state.get("current") if isinstance(state.get("current"), dict) else {}
    storage = current_state.get("storage") or "local"
    object_key = None
    preview_url = None
    download_url = None
    local_path_text = str(render_checkpoint.get("local_path") or "")
    if checkpoint_stage_at_least(render_checkpoint, "uploaded"):
        local_path = Path(local_path_text) if local_path_text else None
        if local_path is None or not local_path.exists():
            artifact_id, local_path = write_local_output(title, html, artifact_id)
        storage = str(render_checkpoint.get("storage") or storage or "local")
        object_key = render_checkpoint.get("object_key")
        preview_url = render_checkpoint.get("preview_url")
        download_url = render_checkpoint.get("download_url") or preview_url
    else:
        LOGGER.info("[publish:%s] Starting artifact write/upload for chunked artifact.", artifact_id)
        set_render_progress(88, "Publishing the validated artifact.")
        artifact_id, local_path = write_local_output(title, html, artifact_id)
        if SETTINGS.has_r2:
            storage = "r2"
            object_key, preview_url = upload_to_r2(local_path, artifact_id)
            download_url = preview_url
        LOGGER.info("[publish:%s] Artifact write/upload completed for chunked artifact.", artifact_id)
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "uploaded",
                    plan=plan,
                    payload=payload,
                    html=html,
                    warnings=warnings,
                    quality_score=quality_score,
                    render_validation_summary=render_validation_summary,
                    title=title,
                    interaction_mode=interaction_mode,
                    local_path=str(local_path),
                    storage=storage,
                    object_key=object_key,
                    preview_url=preview_url,
                    download_url=download_url,
                    critic_skipped=critic_skipped,
                    critic_timed_out=critic_timed_out,
                ),
            ).get("render_checkpoint")
        )

    finalized_draft_state = dict(draft_state)
    finalized_draft_state["status"] = "published"
    finalized_draft_state["published_at"] = datetime.now(timezone.utc).isoformat()
    finalized_draft_state["appended_sections"] = len(plan.get("sections", []) or [])
    persist_artifact_state(
        artifact_id=artifact_id,
        title=title,
        prompt=str(state.get("prompt") or ""),
        template_hint=state.get("template_hint"),
        theme_hint=state.get("theme_hint"),
        interaction_mode=interaction_mode,
        generation_mode="chunked_planned_render",
        structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
        engine_override=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
        section_count_requested=state.get("section_count_requested"),
        plan=plan,
        local_path=local_path,
        storage=storage,
        object_key=object_key,
        preview_url=preview_url,
        warnings=warnings,
        quality_score=public_score,
        render_validation_summary=render_validation_summary,
        regeneration_context={"builder_mode": "chunked_append"},
        draft_state=finalized_draft_state,
        draft_blueprint=state.get("draft_blueprint") if isinstance(state.get("draft_blueprint"), dict) else None,
        critic_skipped=critic_skipped,
        critic_timed_out=critic_timed_out,
    )
    return {
        "artifact_id": artifact_id,
        "title": title,
        "plan": plan,
        "payload": payload or {"usage": {}, "_provider_events": []},
        "warnings": warnings,
        "quality_score": public_score,
        "critic_skipped": critic_skipped,
        "critic_timed_out": critic_timed_out,
        "render_validation_summary": render_validation_summary,
        "local_path": local_path,
        "storage": storage,
        "object_key": object_key,
        "preview_url": preview_url,
        "download_url": download_url,
        "interaction_mode": interaction_mode,
    }


async def record_chunked_finalize_success(
    *,
    state: dict[str, Any],
    result: dict[str, Any],
    durations_ms: dict[str, int],
    ctx: Context | None = None,
) -> None:
    payload = dict(result["payload"] or {})
    payload["_provider_events"] = merge_provider_events(
        telemetry_provider_events_from_state(state),
        payload.get("_provider_events") or [],
    )
    generation_event = build_generation_event(
        prompt=str(state.get("prompt") or result["title"]),
        template_hint=state.get("template_hint"),
        theme_hint=state.get("theme_hint"),
        uploaded_content=None,
        generation_mode="chunked_planned_render",
        interaction_mode=result.get("interaction_mode"),
        telemetry_context=sanitize_telemetry_context({"source_surface": "mcp", "source_mode": "chunked_finalize"}),
        structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
        engine_override=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
        section_count_requested=state.get("section_count_requested"),
        warnings=result["warnings"],
        quality_score=result["quality_score"],
        render_validation_summary=result["render_validation_summary"],
        ctx=ctx,
        plan=result["plan"],
        payload=payload,
        artifact_id=result["artifact_id"],
        storage=result["storage"],
        local_path=result["local_path"],
        object_key=result["object_key"],
        preview_url=result["preview_url"],
        durations_ms=durations_ms,
        upload_analysis=None,
    )
    append_jsonl(GENERATION_EVENTS_PATH, generation_event)
    latest_state = load_artifact_state(result["artifact_id"])
    emit_telemetry_event(build_metadata_telemetry_event_from_generation(generation_event, state=latest_state))
    with contextlib.suppress(Exception):
        await sync_generation_event_to_supabase(
            generation_event=generation_event,
            provider_events=payload.get("_provider_events") or [],
        )


async def record_chunked_finalize_failure(
    *,
    state: dict[str, Any],
    error: Exception,
    durations_ms: dict[str, int],
    stage: str,
    ctx: Context | None = None,
) -> None:
    provider_events = telemetry_provider_events_from_state(state)
    failure_event = build_failure_event(
        prompt=str(state.get("prompt") or state.get("title") or "Finalize artifact"),
        template_hint=state.get("template_hint"),
        theme_hint=state.get("theme_hint"),
        uploaded_content=None,
        generation_mode="chunked_planned_render",
        interaction_mode=state.get("interaction_mode"),
        telemetry_context=sanitize_telemetry_context({"source_surface": "mcp", "source_mode": "chunked_finalize"}),
        structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
        engine_override=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
        section_count_requested=state.get("section_count_requested"),
        ctx=ctx,
        stage=stage,
        error=error,
        durations_ms=durations_ms,
        provider_events=provider_events,
        upload_analysis=None,
    )
    append_jsonl(FAILURE_EVENTS_PATH, failure_event)
    emit_telemetry_event(build_metadata_telemetry_event_from_failure(failure_event))
    with contextlib.suppress(Exception):
        await sync_failure_event_to_supabase(failure_event)


def run_finalize_artifact_job(artifact_id: str, *, skip_critic: bool = False) -> None:
    run_started = time.perf_counter()
    durations_ms: dict[str, int] = {}
    state: dict[str, Any] | None = None
    current_stage = "background_finalize"
    try:
        state = load_artifact_state(artifact_id)
        render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
        resume_stage = render_checkpoint.get("stage")
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="rendering",
            render_job_patch={
                "status": "running",
                "resume_stage": resume_stage,
                "started_at": datetime.now(timezone.utc).isoformat(),
                "progress": checkpoint_resume_progress(resume_stage),
                "last_message": checkpoint_resume_message(resume_stage),
                "last_error_type": None,
                "last_error_message": None,
                "failed_at": None,
                "finished_at": None,
                "preview_url": None,
                "quality_score": None,
                "warning_count": None,
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": False,
                "critic_timed_out": False,
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        finalize_started = time.perf_counter()
        result = asyncio.run(finalize_artifact_draft_internal(artifact_id=artifact_id, skip_critic=skip_critic))
        durations_ms["assembling"] = int((time.perf_counter() - finalize_started) * 1000)
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} is live.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": bool(result.get("critic_skipped")),
                "critic_timed_out": bool(result.get("critic_timed_out")),
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        latest_state = load_artifact_state(artifact_id)
        asyncio.run(record_chunked_finalize_success(state=latest_state, result=result, durations_ms=durations_ms))
    except Exception as exc:  # noqa: BLE001
        LOGGER.exception("Background finalize failed for artifact %s", artifact_id)
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failure_state = state
        with contextlib.suppress(Exception):
            failure_state = update_artifact_runtime_state(
                artifact_id,
                artifact_status="draft",
                render_job_patch={
                    "status": "failed",
                    "resume_stage": normalize_render_checkpoint_state(
                        failure_state.get("render_checkpoint") if isinstance(failure_state, dict) and isinstance(failure_state.get("render_checkpoint"), dict) else None
                    ).get("stage"),
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": (
                        "Finalize failed, but the last completed render stage was checkpointed. Retry will resume instead of starting over."
                        if normalize_render_checkpoint_state(
                            failure_state.get("render_checkpoint") if isinstance(failure_state, dict) and isinstance(failure_state.get("render_checkpoint"), dict) else None
                        ).get("stage")
                        else "Finalize failed. Review the error and retry."
                    ),
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                    "skip_critic_requested": bool(skip_critic),
                    "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
                },
            )
        if isinstance(failure_state, dict):
            asyncio.run(
                record_chunked_finalize_failure(
                    state=failure_state,
                    error=exc,
                    durations_ms=durations_ms,
                    stage=current_stage,
                )
            )
    finally:
        clear_finalize_thread(artifact_id)


def start_finalize_artifact_job(artifact_id: str, *, skip_critic: bool = False) -> dict[str, Any]:
    existing_thread = get_active_finalize_thread(artifact_id)
    if existing_thread is not None:
        return load_artifact_state(artifact_id)

    state = load_artifact_state(artifact_id)
    render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
    render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
    resume_stage = render_checkpoint.get("stage")
    attempt_count = int(render_job.get("attempt_count") or 0) + 1
    queued_state = update_artifact_runtime_state(
        artifact_id,
        artifact_status="rendering",
        render_job_patch={
            "status": "queued",
            "job_id": str(uuid.uuid4()),
            "attempt_count": attempt_count,
            "resume_stage": resume_stage,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": None,
            "finished_at": None,
            "failed_at": None,
            "progress": 5,
            "last_message": (
                "Finalize requested. Rendering will continue in the background."
                if not resume_stage
                else f"Finalize requested. Retry will resume from the saved {resume_stage.replace('_', ' ')} stage."
            ),
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
            "skip_critic_requested": bool(skip_critic),
            "critic_skipped": False,
            "critic_timed_out": False,
            "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
        },
    )
    thread = threading.Thread(
        target=run_finalize_artifact_job,
        kwargs={"artifact_id": artifact_id, "skip_critic": skip_critic},
        name=f"iterfact-finalize-{artifact_id[:8]}",
        daemon=True,
    )
    register_finalize_thread(artifact_id, thread)
    thread.start()
    return queued_state


def run_chunked_generate_document_job(
    *,
    artifact_id: str,
    source_chunks: list[SourceChunk],
    skip_critic: bool = False,
) -> None:
    run_started = time.perf_counter()
    durations_ms: dict[str, int] = {}
    state: dict[str, Any] | None = None
    payloads: list[dict[str, Any]] = []
    current_stage = "background_chunked_build"
    total_chunks = len(source_chunks)
    try:
        state = load_artifact_state(artifact_id)
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="building",
            render_job_patch={
                "status": "running",
                "mode": "chunked_generate_document",
                "started_at": datetime.now(timezone.utc).isoformat(),
                "progress": 12,
                "last_message": f"Appending source section 1 of {max(total_chunks, 1)}.",
                "last_error_type": None,
                "last_error_message": None,
                "failed_at": None,
                "finished_at": None,
                "preview_url": None,
                "quality_score": None,
                "warning_count": None,
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": False,
                "critic_timed_out": False,
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        append_started = time.perf_counter()
        for chunk_index, chunk in enumerate(source_chunks, start=1):
            progress = 12 + int((chunk_index / max(total_chunks, 1)) * 48)
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="building",
                render_job_patch={
                    "status": "running",
                "mode": "chunked_generate_document",
                "progress": progress,
                "last_message": f"Appending source section {chunk_index} of {total_chunks}.",
                "skip_critic_requested": bool(skip_critic),
            },
        )
            _, section_payload = asyncio.run(
                append_section_to_artifact_draft_internal(
                    artifact_id=artifact_id,
                    source_chunk=chunk,
                    updated_prompt=f"Use this source chunk as section {chunk_index} of {total_chunks}. Preserve the original substance and structure.",
                    section_title_hint=chunk.title,
                )
            )
            payloads.append(section_payload)
        durations_ms["planning"] = int((time.perf_counter() - append_started) * 1000)

        update_artifact_runtime_state(
            artifact_id,
            artifact_status="rendering",
            render_job_patch={
                "status": "running",
                "mode": "chunked_generate_document",
                "progress": 72,
                "last_message": "Rendering, validating, and critiquing the completed artifact.",
                "skip_critic_requested": bool(skip_critic),
            },
        )
        finalize_started = time.perf_counter()
        combined_payload = combine_payloads(payloads)
        result = asyncio.run(finalize_artifact_draft_internal(artifact_id=artifact_id, payload=combined_payload, skip_critic=skip_critic))
        durations_ms["assembling"] = int((time.perf_counter() - finalize_started) * 1000)
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "mode": "chunked_generate_document",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} is live.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": bool(result.get("critic_skipped")),
                "critic_timed_out": bool(result.get("critic_timed_out")),
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        latest_state = load_artifact_state(artifact_id)
        asyncio.run(record_chunked_finalize_success(state=latest_state, result=result, durations_ms=durations_ms))
    except Exception as exc:  # noqa: BLE001
        LOGGER.exception("Background chunked generate_document build failed for artifact %s", artifact_id)
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failure_state = state
        with contextlib.suppress(Exception):
            failure_state = update_artifact_runtime_state(
                artifact_id,
                artifact_status="draft",
                render_job_patch={
                    "status": "failed",
                    "mode": "chunked_generate_document",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": "Background chunked build failed. Review the error and retry.",
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                    "skip_critic_requested": bool(skip_critic),
                    "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
                },
            )
        if isinstance(failure_state, dict):
            asyncio.run(
                record_chunked_finalize_failure(
                    state=failure_state,
                    error=exc,
                    durations_ms=durations_ms,
                    stage=current_stage,
                )
            )


def build_background_freeform_stub_plan(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    engine_override: str | None,
) -> dict[str, Any]:
    wrapper_engine = infer_chunked_draft_wrapper_engine(prompt, template_hint, engine_override)
    title = prompt.strip()[:120] or "IterFact Document"
    return {
        "meta": {
            "title": title,
            "description": "Background planned render queued for a heavy freeform request.",
            "document_format": friendly_wrapper_name(wrapper_engine),
            "template_match": template_hint,
        },
        "theme": theme_hint or "obsidian-cyan",
        "wrapper": {"engine": wrapper_engine},
        "sections": [],
    }


async def record_background_freeform_success(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    interaction_mode: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    result: dict[str, Any],
    durations_ms: dict[str, int],
) -> None:
    generation_event = build_generation_event(
        prompt=prompt,
        template_hint=template_hint,
        theme_hint=theme_hint,
        uploaded_content=uploaded_content,
        generation_mode="planned_render",
        interaction_mode=interaction_mode,
        telemetry_context=sanitize_telemetry_context({"source_surface": "mcp", "source_mode": "background_generate_document"}),
        structured_data=structured_data,
        engine_override=engine_override,
        section_count_requested=section_count_requested,
        warnings=result["warnings"],
        quality_score=result["quality_score"],
        render_validation_summary=result["render_validation_summary"],
        ctx=None,
        plan=result["plan"],
        payload=result["payload"],
        artifact_id=result["artifact_id"],
        storage=result["storage"],
        local_path=result["local_path"],
        object_key=result["object_key"],
        preview_url=result["preview_url"],
        durations_ms=durations_ms,
        upload_analysis=None,
    )
    append_jsonl(GENERATION_EVENTS_PATH, generation_event)
    emit_telemetry_event(build_metadata_telemetry_event_from_generation(generation_event, state=load_artifact_state(result["artifact_id"])))
    with contextlib.suppress(Exception):
        await sync_generation_event_to_supabase(
            generation_event=generation_event,
            provider_events=result["payload"].get("_provider_events") or [],
        )


async def record_background_freeform_failure(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    interaction_mode: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    error: Exception,
    durations_ms: dict[str, int],
    stage: str,
) -> None:
    failure_event = build_failure_event(
        prompt=prompt,
        template_hint=template_hint,
        theme_hint=theme_hint,
        uploaded_content=uploaded_content,
        generation_mode="planned_render",
        interaction_mode=interaction_mode,
        telemetry_context=sanitize_telemetry_context({"source_surface": "mcp", "source_mode": "background_generate_document"}),
        structured_data=structured_data,
        engine_override=engine_override,
        section_count_requested=section_count_requested,
        ctx=None,
        stage=stage,
        error=error,
        durations_ms=durations_ms,
        provider_events=[],
        upload_analysis=None,
    )
    append_jsonl(FAILURE_EVENTS_PATH, failure_event)
    emit_telemetry_event(build_metadata_telemetry_event_from_failure(failure_event))
    with contextlib.suppress(Exception):
        await sync_failure_event_to_supabase(failure_event)


async def background_freeform_generate_document_internal(
    *,
    artifact_id: str,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    requested_engine_override: str | None,
    requested_section_count: int | None,
    primary_engine_constraint: str | None,
    interaction_mode: str | None,
    artifact_plan: dict[str, Any] | None = None,
    durations_ms: dict[str, int] | None = None,
    skip_critic: bool = False,
) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    uploaded_content = normalize_source_text(uploaded_content or str(state.get("uploaded_content") or "")) or None
    render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
    current_render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
    title = str(render_checkpoint.get("title") or state.get("title") or "IterFact Document")
    interaction_mode = str(render_checkpoint.get("interaction_mode") or interaction_mode or resolve_interaction_mode(prompt, state.get("interaction_mode")))
    plan = (
        normalize_plan(copy.deepcopy(render_checkpoint.get("plan")))
        if isinstance(render_checkpoint.get("plan"), dict)
        else normalize_plan(copy.deepcopy(state.get("plan")))
        if isinstance(state.get("plan"), dict)
        else build_background_freeform_stub_plan(
            prompt=prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            engine_override=requested_engine_override,
        )
    )
    payload = dict(render_checkpoint.get("payload") or {})
    html = str(render_checkpoint.get("html") or "")
    warnings = list(render_checkpoint.get("warnings") or [])
    try:
        quality_score = int(render_checkpoint.get("quality_score") or 100)
    except (TypeError, ValueError):
        quality_score = 100
    render_validation_summary = (
        dict(render_checkpoint.get("render_validation_summary"))
        if isinstance(render_checkpoint.get("render_validation_summary"), dict)
        else {}
    )

    durations = durations_ms if durations_ms is not None else {}

    def persist_progress_state(artifact_status: str, plan_to_store: dict[str, Any]) -> None:
        latest_state = load_artifact_state(artifact_id)
        persist_background_generation_state(
            artifact_id=artifact_id,
            title=title,
            prompt=prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            interaction_mode=interaction_mode,
            generation_mode="planned_render",
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=requested_section_count,
            plan=plan_to_store,
            artifact_status=artifact_status,
            render_job=normalize_render_job_state(
                latest_state.get("render_job") if isinstance(latest_state.get("render_job"), dict) else current_render_job
            ),
            uploaded_content=uploaded_content,
        )

    def set_render_progress(artifact_status: str, progress: int, message: str) -> None:
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status=artifact_status,
                render_job_patch={
                    "status": "running",
                    "mode": "freeform_generate_document",
                    "progress": max(0, min(100, int(progress))),
                    "last_message": message,
                },
            )

    if not checkpoint_stage_at_least(render_checkpoint, "planned"):
        if isinstance(artifact_plan, dict):
            plan = normalize_plan(copy.deepcopy(artifact_plan))
            payload = build_client_supplied_plan_payload(
                prompt=prompt,
                template_hint=template_hint,
                uploaded_content=uploaded_content,
                structured_data=structured_data,
                generation_mode="planned_render",
                wrapper_engine=plan.get("wrapper", {}).get("engine") if isinstance(plan.get("wrapper"), dict) else None,
            )
            durations["planning"] = 0
            title = plan.get("meta", {}).get("title", "IterFact Document")
            interaction_mode = resolve_interaction_mode(prompt, interaction_mode)
            persist_progress_state("rendering", plan)
            set_render_progress("rendering", 36, "Claude-side story plan received. Preparing the first rendered draft.")
        else:
            planner_started = time.perf_counter()
            plan, payload = await call_planner(
                prompt=prompt,
                template_hint=template_hint,
                uploaded_content=uploaded_content,
                upload_analysis=None,
                structured_data=structured_data,
                engine_override=requested_engine_override,
                section_count=requested_section_count,
                generation_mode="planned_render",
                progress_callback=lambda progress, message: set_render_progress("planning", progress, message),
            )
            durations["planning"] = int((time.perf_counter() - planner_started) * 1000)
            if theme_hint:
                plan["theme"] = theme_hint
            if is_wrapper_engine(requested_engine_override):
                plan.setdefault("wrapper", {})["engine"] = requested_engine_override
            plan = apply_primary_engine_constraints(plan, primary_engine_constraint, structured_data)
            title = plan.get("meta", {}).get("title", "IterFact Document")
            interaction_mode = resolve_interaction_mode(prompt, interaction_mode)
            persist_progress_state("rendering", plan)
            set_render_progress("rendering", 36, "Planning is complete. Preparing the first rendered draft.")
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "planned",
                    plan=plan,
                    payload=payload,
                    title=title,
                    interaction_mode=interaction_mode,
                ),
            ).get("render_checkpoint")
        )
    else:
        if isinstance(render_checkpoint.get("plan"), dict):
            plan = normalize_plan(copy.deepcopy(render_checkpoint["plan"]))
        title = str(render_checkpoint.get("title") or plan.get("meta", {}).get("title") or title)
        interaction_mode = str(render_checkpoint.get("interaction_mode") or interaction_mode)

    if not checkpoint_stage_at_least(render_checkpoint, "render_refined"):
        set_render_progress("rendering", 55, "Rendering, validating, and critiquing the artifact.")
        render_started = time.perf_counter()
        plan, html, payload, warnings, quality_score, render_validation_summary = await render_plan_with_iterative_refinement(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode="planned_render",
            plan=plan,
            payload=payload,
            requested_section_count=requested_section_count,
            engine_override=requested_engine_override,
            resume_checkpoint=render_checkpoint if render_checkpoint.get("stage") == "iterative_refinement" else None,
            checkpoint_writer=lambda checkpoint: update_artifact_render_checkpoint(artifact_id, checkpoint=checkpoint),
            progress_callback=lambda progress, message: set_render_progress("rendering", progress, message),
            skip_critic=skip_critic,
            critic_timeout_seconds=SETTINGS.anthropic_critic_timeout_seconds,
        )
        durations["assembling"] = int((time.perf_counter() - render_started) * 1000)
        title = plan.get("meta", {}).get("title", title or "IterFact Document")
        interaction_mode = resolve_interaction_mode(prompt, interaction_mode)
        critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
        persist_progress_state("rendering", plan)
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "render_refined",
                    plan=plan,
                    payload=payload,
                    html=html,
                    warnings=warnings,
                    quality_score=quality_score,
                    render_validation_summary=render_validation_summary,
                    title=title,
                    interaction_mode=interaction_mode,
                    critic_skipped=critic_skipped,
                    critic_timed_out=critic_timed_out,
                ),
            ).get("render_checkpoint")
        )
    else:
        if isinstance(render_checkpoint.get("plan"), dict):
            plan = normalize_plan(copy.deepcopy(render_checkpoint["plan"]))
        payload = dict(render_checkpoint.get("payload") or payload or {})
        html = str(render_checkpoint.get("html") or html)
        warnings = list(render_checkpoint.get("warnings") or warnings)
        quality_score = int(render_checkpoint.get("quality_score") or quality_score)
        render_validation_summary = (
            dict(render_checkpoint.get("render_validation_summary"))
            if isinstance(render_checkpoint.get("render_validation_summary"), dict)
            else render_validation_summary
        )
        title = str(render_checkpoint.get("title") or title)
        interaction_mode = str(render_checkpoint.get("interaction_mode") or interaction_mode)

    if not checkpoint_stage_at_least(render_checkpoint, "delivery_validated"):
        LOGGER.info("[publish:%s] Starting delivery validation for freeform artifact.", artifact_id)
        set_render_progress("rendering", 72, "Applying delivery chrome and validating the final artifact.")
        html, warnings, quality_score, render_validation_summary = validate_delivery_ready_artifact(
            html=html,
            artifact_id=artifact_id,
            artifact_title=title,
            interaction_mode=interaction_mode,
            plan=plan,
            requested_section_count=requested_section_count,
            engine_override=requested_engine_override,
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=uploaded_content,
            structured_data=structured_data,
            generation_mode="planned_render",
            payload=payload,
        )
        critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
        enforce_render_validation_or_raise(
            warnings,
            quality_score,
            render_validation_summary,
            allow_low_quality_publish=allow_nonfatal_low_quality_publish(payload, render_validation_summary),
        )
        LOGGER.info("[publish:%s] Delivery validation passed for freeform artifact.", artifact_id)
        persist_progress_state("rendering", plan)
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "delivery_validated",
                    plan=plan,
                    payload=payload,
                    html=html,
                    warnings=warnings,
                    quality_score=quality_score,
                    render_validation_summary=render_validation_summary,
                    title=title,
                    interaction_mode=interaction_mode,
                    critic_skipped=critic_skipped,
                    critic_timed_out=critic_timed_out,
                ),
            ).get("render_checkpoint")
        )
    else:
        if isinstance(render_checkpoint.get("plan"), dict):
            plan = normalize_plan(copy.deepcopy(render_checkpoint["plan"]))
        payload = dict(render_checkpoint.get("payload") or payload or {})
        html = str(render_checkpoint.get("html") or html)
        warnings = list(render_checkpoint.get("warnings") or warnings)
        quality_score = int(render_checkpoint.get("quality_score") or quality_score)
        render_validation_summary = (
            dict(render_checkpoint.get("render_validation_summary"))
            if isinstance(render_checkpoint.get("render_validation_summary"), dict)
            else render_validation_summary
        )
        title = str(render_checkpoint.get("title") or title)
        interaction_mode = str(render_checkpoint.get("interaction_mode") or interaction_mode)
    critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
    public_score = public_quality_score(quality_score, payload, render_validation_summary)

    storage = "local"
    object_key = None
    preview_url = None
    download_url = None
    admin_source_backup_local_path = None
    admin_source_backup_object_key = None
    local_path_text = str(render_checkpoint.get("local_path") or "")
    if checkpoint_stage_at_least(render_checkpoint, "uploaded"):
        local_path = Path(local_path_text) if local_path_text else None
        if local_path is None or not local_path.exists():
            artifact_id, local_path = write_local_output(title, html, artifact_id)
        storage = str(render_checkpoint.get("storage") or "local")
        object_key = render_checkpoint.get("object_key")
        preview_url = render_checkpoint.get("preview_url")
        download_url = render_checkpoint.get("download_url") or preview_url
    else:
        LOGGER.info("[publish:%s] Starting artifact write/upload for freeform artifact.", artifact_id)
        set_render_progress("rendering", 88, "Publishing the validated artifact.")
        artifact_id, local_path = write_local_output(title, html, artifact_id)
        if SETTINGS.has_r2:
            upload_started = time.perf_counter()
            storage = "r2"
            object_key, preview_url = upload_to_r2(local_path, artifact_id)
            download_url = preview_url
            durations["uploading"] = int((time.perf_counter() - upload_started) * 1000)
        LOGGER.info("[publish:%s] Artifact write/upload completed for freeform artifact.", artifact_id)
        render_checkpoint = normalize_render_checkpoint_state(
            update_artifact_render_checkpoint(
                artifact_id,
                checkpoint=build_render_checkpoint(
                    "uploaded",
                    plan=plan,
                    payload=payload,
                    html=html,
                    warnings=warnings,
                    quality_score=quality_score,
                    render_validation_summary=render_validation_summary,
                    title=title,
                    interaction_mode=interaction_mode,
                    local_path=str(local_path),
                    storage=storage,
                    object_key=object_key,
                    preview_url=preview_url,
                    download_url=download_url,
                    critic_skipped=critic_skipped,
                    critic_timed_out=critic_timed_out,
                ),
            ).get("render_checkpoint")
        )

    persist_artifact_state(
        artifact_id=artifact_id,
        title=title,
        prompt=prompt,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        generation_mode="planned_render",
        structured_data=structured_data,
        engine_override=requested_engine_override,
        section_count_requested=requested_section_count,
        plan=plan,
        local_path=local_path,
        storage=storage,
        object_key=object_key,
        preview_url=preview_url,
        warnings=warnings,
        quality_score=public_score,
        render_validation_summary=render_validation_summary,
        critic_skipped=critic_skipped,
        critic_timed_out=critic_timed_out,
        uploaded_content=uploaded_content,
    )
    return {
        "artifact_id": artifact_id,
        "title": title,
        "plan": plan,
        "payload": payload or {},
        "warnings": warnings,
        "quality_score": public_score,
        "critic_skipped": critic_skipped,
        "critic_timed_out": critic_timed_out,
        "render_validation_summary": render_validation_summary,
        "local_path": local_path,
        "storage": storage,
        "object_key": object_key,
        "preview_url": preview_url,
        "download_url": download_url,
    }


def run_background_freeform_generate_document_job(
    *,
    artifact_id: str,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    requested_engine_override: str | None,
    requested_section_count: int | None,
    primary_engine_constraint: str | None,
    interaction_mode: str | None,
    artifact_plan: dict[str, Any] | None = None,
    skip_critic: bool = False,
) -> None:
    run_started = time.perf_counter()
    durations_ms: dict[str, int] = {}
    current_stage = "background_planning"
    try:
        state = load_artifact_state(artifact_id)
        render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
        resume_stage = render_checkpoint.get("stage")
        update_artifact_runtime_state(
            artifact_id,
            artifact_status=(
                "rendering"
                if checkpoint_stage_at_least(render_checkpoint, "planned") or isinstance(artifact_plan, dict)
                else "planning"
            ),
            render_job_patch={
                "status": "running",
                "mode": "freeform_generate_document",
                "resume_stage": resume_stage,
                "started_at": datetime.now(timezone.utc).isoformat(),
                "progress": checkpoint_resume_progress(resume_stage) if resume_stage else (36 if isinstance(artifact_plan, dict) else 18),
                "last_message": (
                    checkpoint_resume_message(resume_stage)
                    if resume_stage
                    else "Claude-side story plan received. Preparing the first rendered draft."
                    if isinstance(artifact_plan, dict)
                    else "Planning the artifact structure in the background."
                ),
                "last_error_type": None,
                "last_error_message": None,
                "failed_at": None,
                "finished_at": None,
                "preview_url": None,
                "quality_score": None,
                "warning_count": None,
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": False,
                "critic_timed_out": False,
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        current_stage = "background_rendering"
        result = asyncio.run(
            background_freeform_generate_document_internal(
                artifact_id=artifact_id,
                prompt=prompt,
                template_hint=template_hint,
                theme_hint=theme_hint,
                uploaded_content=uploaded_content,
                structured_data=structured_data,
                requested_engine_override=requested_engine_override,
                requested_section_count=requested_section_count,
                primary_engine_constraint=primary_engine_constraint,
                interaction_mode=interaction_mode,
                artifact_plan=artifact_plan,
                durations_ms=durations_ms,
                skip_critic=skip_critic,
            )
        )
        title = result["title"]
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "mode": "freeform_generate_document",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{title} is ready.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": bool(result.get("critic_skipped")),
                "critic_timed_out": bool(result.get("critic_timed_out")),
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        asyncio.run(
            record_background_freeform_success(
                prompt=prompt,
                template_hint=template_hint,
                theme_hint=theme_hint,
                uploaded_content=uploaded_content,
                interaction_mode=interaction_mode,
                structured_data=structured_data,
                engine_override=requested_engine_override,
                section_count_requested=requested_section_count,
                result=result,
                durations_ms=durations_ms,
            )
        )
    except Exception as exc:  # noqa: BLE001
        LOGGER.exception("Background freeform generate_document failed for artifact %s", artifact_id)
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failed_resume_stage = None
        with contextlib.suppress(Exception):
            latest_failed_state = load_artifact_state(artifact_id)
            failed_resume_stage = normalize_render_checkpoint_state(
                latest_failed_state.get("render_checkpoint") if isinstance(latest_failed_state.get("render_checkpoint"), dict) else None
            ).get("stage")
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="failed",
                render_job_patch={
                    "status": "failed",
                    "mode": "freeform_generate_document",
                    "resume_stage": failed_resume_stage,
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": (
                        "Background freeform generation failed, but the last completed render stage was checkpointed. Retry will resume instead of starting over."
                        if failed_resume_stage
                        else "Background freeform generation failed. Review the error and retry."
                    ),
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                    "skip_critic_requested": bool(skip_critic),
                    "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
                },
            )
        asyncio.run(
            record_background_freeform_failure(
                prompt=prompt,
                template_hint=template_hint,
                theme_hint=theme_hint,
                uploaded_content=uploaded_content,
                interaction_mode=interaction_mode,
                structured_data=structured_data,
                engine_override=requested_engine_override,
                section_count_requested=requested_section_count,
                error=exc,
                durations_ms=durations_ms,
                stage=current_stage,
            )
        )


def start_background_freeform_resume_job(artifact_id: str, *, skip_critic: bool = False) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
    render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
    resume_stage = render_checkpoint.get("stage")
    effective_skip_critic = bool(skip_critic or render_job.get("skip_critic_requested"))
    attempt_count = int(render_job.get("attempt_count") or 0) + 1
    queued_state = update_artifact_runtime_state(
        artifact_id,
        artifact_status="rendering" if checkpoint_stage_at_least(render_checkpoint, "planned") else "planning",
        render_job_patch={
            "status": "queued",
            "mode": "freeform_generate_document",
            "job_id": str(uuid.uuid4()),
            "attempt_count": attempt_count,
            "resume_stage": resume_stage,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": None,
            "finished_at": None,
            "failed_at": None,
            "progress": checkpoint_resume_progress(resume_stage) if resume_stage else 4,
            "last_message": (
                "Generation requested. Rendering in the background."
                if not resume_stage
                else f"Freeform retry queued. Generation will resume from the saved {resume_stage.replace('_', ' ')} stage."
            ),
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
            "skip_critic_requested": effective_skip_critic,
            "critic_skipped": False,
            "critic_timed_out": False,
            "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
        },
    )
    thread = threading.Thread(
        target=run_background_freeform_generate_document_job,
        kwargs={
            "artifact_id": artifact_id,
            "prompt": str(state.get("prompt") or state.get("title") or "IterFact Document"),
            "template_hint": state.get("template_hint"),
            "theme_hint": state.get("theme_hint"),
            "uploaded_content": str(state.get("uploaded_content") or ""),
            "structured_data": sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
            "requested_engine_override": normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
            "requested_section_count": state.get("section_count_requested"),
            "primary_engine_constraint": normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else infer_primary_engine_from_request(
                str(state.get("prompt") or state.get("title") or ""),
                state.get("template_hint"),
                sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
            ),
            "interaction_mode": state.get("interaction_mode"),
            "artifact_plan": (
                normalize_plan(copy.deepcopy(state.get("plan")))
                if isinstance(state.get("plan"), dict)
                and ((state.get("plan") or {}).get("meta", {}) or {}).get("planning_source") == "client_supplied_plan"
                else None
            ),
            "skip_critic": effective_skip_critic,
        },
        name=f"iterfact-freeform-{artifact_id[:8]}",
        daemon=True,
    )
    thread.start()
    return queued_state


def start_background_freeform_generate_document_job(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str | None,
    structured_data: dict[str, Any] | None,
    requested_engine_override: str | None,
    requested_section_count: int | None,
    primary_engine_constraint: str | None,
    interaction_mode: str | None,
    artifact_plan: dict[str, Any] | None = None,
    skip_critic: bool = False,
) -> dict[str, Any]:
    artifact_id = str(uuid.uuid4())
    initial_plan = (
        normalize_plan(copy.deepcopy(artifact_plan))
        if isinstance(artifact_plan, dict)
        else build_background_freeform_stub_plan(
            prompt=prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            engine_override=requested_engine_override,
        )
    )
    initial_status = "rendering" if isinstance(artifact_plan, dict) else "planning"
    initial_progress = 32 if isinstance(artifact_plan, dict) else 4
    initial_message = (
        "Client-supplied story plan received. Rendering in the background."
        if isinstance(artifact_plan, dict)
        else "Generation requested. Rendering in the background."
    )
    queued_state = persist_background_generation_state(
        artifact_id=artifact_id,
        title=initial_plan.get("meta", {}).get("title") or "IterFact Document",
        prompt=prompt,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        generation_mode="planned_render",
        structured_data=structured_data,
        engine_override=requested_engine_override,
        section_count_requested=requested_section_count,
        plan=initial_plan,
        artifact_status=initial_status,
        uploaded_content=uploaded_content,
        render_job={
            "status": "queued",
            "mode": "freeform_generate_document",
            "job_id": str(uuid.uuid4()),
            "attempt_count": 1,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": None,
            "finished_at": None,
            "failed_at": None,
            "progress": initial_progress,
            "last_message": initial_message,
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
            "skip_critic_requested": bool(skip_critic),
            "critic_skipped": False,
            "critic_timed_out": False,
            "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
        },
    )
    thread = threading.Thread(
        target=run_background_freeform_generate_document_job,
        kwargs={
            "artifact_id": artifact_id,
            "prompt": prompt,
            "template_hint": template_hint,
            "theme_hint": theme_hint,
            "uploaded_content": uploaded_content,
            "structured_data": structured_data,
            "requested_engine_override": requested_engine_override,
            "requested_section_count": requested_section_count,
            "primary_engine_constraint": primary_engine_constraint,
            "interaction_mode": interaction_mode,
            "artifact_plan": initial_plan if isinstance(artifact_plan, dict) else None,
            "skip_critic": skip_critic,
        },
        name=f"iterfact-freeform-{artifact_id[:8]}",
        daemon=True,
    )
    thread.start()
    return queued_state


async def start_chunked_generate_document_job(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str,
    interaction_mode: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    skip_critic: bool = False,
) -> dict[str, Any]:
    source_chunks = build_source_chunks_from_text(uploaded_content)
    if not source_chunks:
        raise ValueError("IterFact could not split the uploaded source into usable document sections.")

    state, payload = await create_chunked_artifact_draft_internal(
        prompt=prompt,
        source_chunks=source_chunks,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        structured_data=structured_data,
        engine_override=engine_override,
        section_count_requested=section_count_requested,
        uploaded_content=uploaded_content,
    )
    queued_state = update_artifact_runtime_state(
        state["artifact_id"],
        artifact_status="building",
        render_job_patch={
            "status": "queued",
            "mode": "chunked_generate_document",
            "job_id": str(uuid.uuid4()),
            "attempt_count": 1,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": None,
            "finished_at": None,
            "failed_at": None,
            "progress": 4,
            "last_message": "Chunked document build queued. Source sections will be appended in the background.",
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
            "skip_critic_requested": bool(skip_critic),
            "critic_skipped": False,
            "critic_timed_out": False,
            "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
        },
    )
    thread = threading.Thread(
        target=run_chunked_generate_document_job,
        kwargs={
            "artifact_id": state["artifact_id"],
            "source_chunks": source_chunks,
            "skip_critic": skip_critic,
        },
        name=f"iterfact-build-{state['artifact_id'][:8]}",
        daemon=True,
    )
    thread.start()
    queued_state["_draft_payload"] = payload
    queued_state["_chunk_count"] = len(source_chunks)
    return queued_state


async def build_large_uploaded_document_with_chunks(
    *,
    prompt: str,
    template_hint: str | None,
    theme_hint: str | None,
    uploaded_content: str,
    interaction_mode: str | None,
    structured_data: dict[str, Any] | None,
    engine_override: str | None,
    section_count_requested: int | None,
    ctx: Context | None,
) -> dict[str, Any]:
    source_chunks = build_source_chunks_from_text(uploaded_content)
    if not source_chunks:
        raise ValueError("IterFact could not split the uploaded source into usable document sections.")

    await report_stage(ctx, 16, "Building a chunked document draft from the uploaded source")
    state, scaffold_payload = await create_chunked_artifact_draft_internal(
        prompt=prompt,
        source_chunks=source_chunks,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        structured_data=structured_data,
        engine_override=engine_override,
        section_count_requested=section_count_requested,
        uploaded_content=uploaded_content,
    )

    payloads = [scaffold_payload]
    total_chunks = len(source_chunks)
    for chunk_index, chunk in enumerate(source_chunks, start=1):
        progress = 18 + int((chunk_index / max(total_chunks, 1)) * 44)
        await report_stage(ctx, progress, f"Appending source section {chunk_index} of {total_chunks}")
        _, section_payload = await append_section_to_artifact_draft_internal(
            artifact_id=state["artifact_id"],
            source_chunk=chunk,
            updated_prompt=f"Use this source chunk as section {chunk_index} of {total_chunks}. Preserve the original substance and structure.",
            section_title_hint=chunk.title,
        )
        payloads.append(section_payload)

    await report_stage(ctx, 70, "Finalizing the chunk-built artifact")
    combined_payload = combine_payloads(payloads)
    final_result = await finalize_artifact_draft_internal(
        artifact_id=state["artifact_id"],
        payload=combined_payload,
    )
    final_result["chunk_count"] = total_chunks
    final_result["draft_state"] = state.get("draft")
    return final_result


async def report_stage(ctx: Context | None, progress: float, message: str) -> None:
    if ctx is None:
        return
    await ctx.info(message)
    await ctx.report_progress(progress, 100, message=message)


async def root(request: Request) -> HTMLResponse:
    base = str(request.base_url).rstrip("/")
    icon_version = "if-stack-20260322-ifmark"
    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>IterFact MCP</title>
  <meta name="description" content="IterFact MCP server for generating interactive documents.">
  <link rel="icon" type="image/png" sizes="16x16" href="{base}/favicon-16.png?v={icon_version}">
  <link rel="icon" type="image/png" sizes="32x32" href="{base}/favicon-32.png?v={icon_version}">
  <link rel="icon" type="image/svg+xml" href="{base}/favicon.svg?v={icon_version}">
  <style>
    :root {{ color-scheme: dark; }}
    * {{ box-sizing: border-box; }}
    body {{ margin: 0; min-height: 100vh; display: grid; place-items: center; background: #0b0f12; color: #f5f7f9; font-family: system-ui, sans-serif; }}
    main {{ width: min(720px, calc(100vw - 32px)); background: #12181d; border: 1px solid rgba(255,255,255,0.08); border-radius: 20px; padding: 32px; }}
    img {{ height: 28px; width: auto; display: block; margin-bottom: 20px; }}
    h1 {{ margin: 0 0 8px; font-size: 1.8rem; }}
    p {{ margin: 0 0 14px; color: #a9b3bb; line-height: 1.6; }}
    code {{ background: #0d1317; padding: 2px 6px; border-radius: 8px; }}
    ul {{ color: #d6dde3; padding-left: 20px; }}
    a {{ color: #66d1b8; text-decoration: none; }}
  </style>
</head>
<body>
  <main>
    <img src="{base}/logo-nav.png" alt="IterFact">
    <h1>IterFact MCP</h1>
    <p>Public MCP server for generating interactive documents through IterFact.</p>
    <ul>
      <li>MCP endpoint: <code>{base}/mcp</code></li>
      <li>Health check: <a href="{base}/healthz">{base}/healthz</a></li>
    </ul>
    <p><strong>Chunked Draft Quickstart</strong></p>
    <ul>
      <li><code>create_artifact_draft</code> -> <code>append_section</code> -> <code>finalize_artifact</code> -> <code>get_artifact_status</code></li>
      <li><code>finalize_artifact</code> returns quickly for long renders, so poll until the artifact is <code>published</code> or <code>failed</code>.</li>
      <li>If your MCP client does not show <code>get_artifact_status</code> yet, call <code>finalize_artifact</code> again with the same <code>artifact_id</code> to check status, or open <code>{base}/v1/artifacts/&lt;artifact_id&gt;/status</code>.</li>
    </ul>
    <p><strong>Feedback Revision Quickstart</strong></p>
    <ul>
      <li>Share the published artifact in <code>iterate</code> mode so viewers can leave targeted notes on the live link.</li>
      <li>Use <code>get_artifact_feedback</code> to read those notes back through the MCP.</li>
      <li>Use <code>apply_artifact_feedback</code> to revise the same <code>artifact_id</code> in place, then poll <code>get_artifact_status</code> until the updated revision is published.</li>
      <li>Fallback HTTP endpoints are available at <code>{base}/v1/artifacts/&lt;artifact_id&gt;/feedback</code> and <code>{base}/v1/artifacts/&lt;artifact_id&gt;/status</code>.</li>
    </ul>
  </main>
</body>
</html>"""
    return HTMLResponse(html)


async def favicon_16(_: Request) -> FileResponse:
    return FileResponse(STATIC_DIR / "favicon-16.png", media_type="image/png")


async def favicon_32(_: Request) -> FileResponse:
    return FileResponse(STATIC_DIR / "favicon-32.png", media_type="image/png")


async def favicon_svg(_: Request) -> FileResponse:
    return FileResponse(STATIC_DIR / "favicon.svg", media_type="image/svg+xml")


async def favicon_ico(_: Request) -> FileResponse:
    return FileResponse(STATIC_DIR / "favicon-32.png", media_type="image/png")


async def logo_nav(_: Request) -> FileResponse:
    return FileResponse(STATIC_DIR / "logo-nav.png", media_type="image/png")


async def healthz(_: Request) -> JSONResponse:
    return JSONResponse(
        {
            "ok": True,
            "vault_path": str(SETTINGS.vault_path),
            "r2_enabled": SETTINGS.has_r2,
            "public_artifact_base_url": SETTINGS.public_artifact_base_url or None,
        }
    )


async def artifact_status_v1(request: Request) -> JSONResponse:
    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        state = load_artifact_state(artifact_id)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    return JSONResponse(build_artifact_status_response(state))


def request_has_internal_bearer(request: Request, *, require_admin: bool = False) -> bool:
    if not (SETTINGS.bearer_token or SETTINGS.admin_bearer_token):
        return True

    auth_header = request.headers.get("authorization", "").strip()
    valid_tokens: list[str] = []
    if require_admin:
        if SETTINGS.admin_bearer_token:
            valid_tokens.append(f"Bearer {SETTINGS.admin_bearer_token}")
        elif SETTINGS.bearer_token:
            valid_tokens.append(f"Bearer {SETTINGS.bearer_token}")
    else:
        if SETTINGS.admin_bearer_token:
            valid_tokens.append(f"Bearer {SETTINGS.admin_bearer_token}")
        if SETTINGS.bearer_token:
            valid_tokens.append(f"Bearer {SETTINGS.bearer_token}")
    return bool(auth_header and auth_header in valid_tokens)


def build_public_artifact_versions_response(payload: dict[str, Any]) -> dict[str, Any]:
    return {
        "artifact_id": payload.get("artifact_id"),
        "title": payload.get("title"),
        "version_count": payload.get("version_count"),
        "show_public_iterations": bool(payload.get("show_public_iterations")),
        "public_iterations_url": payload.get("public_iterations_url"),
        "primary_version": payload.get("primary_version"),
        "latest_version": payload.get("latest_version"),
        "versions": [
            {
                "title": version.get("title"),
                "published_at": version.get("published_at"),
                "superseded_at": version.get("superseded_at"),
                "reason": version.get("reason"),
                "preview_url": version.get("preview_url"),
                "poster_url": version.get("poster_url"),
                "frame_urls": version.get("frame_urls"),
                "is_latest": version.get("is_latest"),
                "is_primary": version.get("is_primary"),
            }
            for version in (payload.get("versions") or [])
            if isinstance(version, dict)
        ],
    }


async def artifact_versions_v1(request: Request) -> JSONResponse:
    if not request_has_internal_bearer(request, require_admin=True):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        state = load_artifact_state(artifact_id)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    return JSONResponse(build_artifact_versions_response(state))


async def artifact_sections_v1(request: Request) -> JSONResponse:
    if not request_has_internal_bearer(request, require_admin=True):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        state = load_artifact_state(artifact_id)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    return JSONResponse(build_artifact_sections_response(state))


async def artifact_versions_public_v1(request: Request) -> JSONResponse:
    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        state = load_artifact_state(artifact_id)
    except FileNotFoundError:
        return JSONResponse(
            {"error": "This artifact is not exposing public iteration history.", "artifact_id": artifact_id},
            status_code=404,
        )

    payload = build_artifact_versions_response(state)
    if not payload.get("show_public_iterations") or int(payload.get("version_count") or 0) < 2:
        return JSONResponse(
            {"error": "This artifact is not exposing public iteration history.", "artifact_id": artifact_id},
            status_code=404,
        )
    return JSONResponse(build_public_artifact_versions_response(payload))


async def artifact_primary_version_v1(request: Request) -> JSONResponse:
    if not request_has_internal_bearer(request, require_admin=True):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        body = await request.json()
    except Exception:
        body = {}
    html_file = compact_whitespace(str((body or {}).get("html_file") or ""))
    if not html_file:
        return JSONResponse({"error": "html_file is required.", "artifact_id": artifact_id}, status_code=400)

    try:
        state = set_artifact_primary_version(artifact_id, html_file)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    except ValueError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=400)
    return JSONResponse(build_artifact_versions_response(state))


async def artifact_public_visibility_v1(request: Request) -> JSONResponse:
    if not request_has_internal_bearer(request, require_admin=True):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        body = await request.json()
    except Exception:
        body = {}
    show_public_iterations = bool((body or {}).get("show_public_iterations"))

    try:
        state = set_artifact_public_iteration_visibility(
            artifact_id,
            show_public_iterations=show_public_iterations,
        )
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    return JSONResponse(build_artifact_versions_response(state))


def build_artifact_feedback_response(state: dict[str, Any], *, include_resolved: bool) -> dict[str, Any]:
    notes = get_feedback_notes_from_state(state, include_resolved=include_resolved)
    return {
        "artifact_id": state.get("artifact_id"),
        "title": state.get("title"),
        "notes": notes,
        "feedback_summary": summarize_feedback_notes(normalize_feedback_notes(state.get("feedback_notes"))),
        "updated_at": state.get("updated_at"),
    }


async def artifact_feedback_v1(request: Request) -> JSONResponse:
    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    if request.method == "GET":
        include_resolved = str(request.query_params.get("include_resolved") or "").strip().lower() in {"1", "true", "yes", "all"}
        try:
            state = load_artifact_state(artifact_id)
        except FileNotFoundError as exc:
            return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
        return JSONResponse(build_artifact_feedback_response(state, include_resolved=include_resolved))

    try:
        body = await request.json()
    except Exception:
        body = {}
    if not isinstance(body, dict):
        return JSONResponse({"error": "Feedback payload must be a JSON object.", "artifact_id": artifact_id}, status_code=400)
    try:
        state, note = append_artifact_feedback_note(artifact_id, body)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    except ValueError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=400)
    response = build_artifact_feedback_response(state, include_resolved=False)
    response["note"] = note
    return JSONResponse(response, status_code=201)


async def artifact_feedback_note_v1(request: Request) -> JSONResponse:
    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    note_id = compact_whitespace(str(request.path_params.get("note_id") or ""))
    if not note_id:
        return JSONResponse({"error": "note_id is required.", "artifact_id": artifact_id}, status_code=400)
    try:
        body = await request.json()
    except Exception:
        body = {}
    status = compact_whitespace(str((body or {}).get("status") or "dismissed")).lower()
    try:
        state = update_feedback_note_statuses(artifact_id, [note_id], status=status)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    return JSONResponse(build_artifact_feedback_response(state, include_resolved=True))


async def artifact_feedback_apply_v1(request: Request) -> JSONResponse:
    if not request_has_internal_bearer(request, require_admin=True):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        body = await request.json()
    except Exception:
        body = {}

    if not isinstance(body, dict):
        return JSONResponse(
            {"error": "Feedback apply payload must be a JSON object.", "artifact_id": artifact_id},
            status_code=400,
        )

    raw_feedback_ids = body.get("feedback_ids")
    feedback_ids = None
    if isinstance(raw_feedback_ids, list):
        feedback_ids = [
            compact_whitespace(str(item))
            for item in raw_feedback_ids
            if compact_whitespace(str(item))
        ]

    additional_instruction = compact_whitespace(str(body.get("additional_instruction") or "")) or None
    wait_for_completion = bool(body.get("wait_for_completion"))

    try:
        result = await apply_artifact_feedback(
            artifact_id=artifact_id,
            feedback_ids=feedback_ids,
            additional_instruction=additional_instruction,
            wait_for_completion=wait_for_completion,
            ctx=None,
        )
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)
    except ValueError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=400)

    return JSONResponse(result)


async def artifact_section_regenerate_v1(request: Request) -> JSONResponse:
    if not request_has_internal_bearer(request, require_admin=True):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    artifact_id = str(request.path_params.get("artifact_id") or "").strip()
    try:
        body = await request.json()
    except Exception:
        body = {}

    if not isinstance(body, dict):
        return JSONResponse(
            {"error": "Section regeneration payload must be a JSON object.", "artifact_id": artifact_id},
            status_code=400,
        )

    raw_section_index = body.get("section_index")
    try:
        section_index = int(raw_section_index)
    except (TypeError, ValueError):
        return JSONResponse(
            {"error": "section_index must be a 1-based integer.", "artifact_id": artifact_id},
            status_code=400,
        )

    updated_prompt = compact_whitespace(str(body.get("updated_prompt") or "")) or None
    wait_for_completion = bool(body.get("wait_for_completion"))

    try:
        state = load_artifact_state(artifact_id)
    except FileNotFoundError as exc:
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=404)

    render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
    if get_active_finalize_thread(artifact_id) is not None or render_job.get("status") in {"queued", "running"}:
        latest_state = load_artifact_state(artifact_id)
        return JSONResponse(
            build_artifact_status_response(
                latest_state,
                notes=["No new section regeneration started because another background artifact job is already active."],
            )
        )

    sections_payload = build_artifact_sections_response(state)
    section_count = int(sections_payload.get("section_count") or 0)
    if section_count <= 0:
        return JSONResponse(
            {"error": "This artifact does not have regenerable plan sections.", "artifact_id": artifact_id},
            status_code=400,
        )
    if section_index < 1 or section_index > section_count:
        return JSONResponse(
            {
                "error": f"section_index must be between 1 and {section_count} for this artifact.",
                "artifact_id": artifact_id,
            },
            status_code=400,
        )

    if not wait_for_completion:
        queued_state = start_regenerate_artifact_section_job(artifact_id, section_index, updated_prompt)
        return JSONResponse(
            build_artifact_status_response(
                queued_state,
                notes=[
                    f"Queued section {section_index} for regeneration and in-place publication.",
                    "Call get_artifact_status to poll for the updated preview URL and render status.",
                ],
            )
        )

    update_artifact_runtime_state(
        artifact_id,
        artifact_status="rendering",
        render_job_patch={
            "status": "running",
            "mode": "section_regeneration",
            "job_id": str(uuid.uuid4()),
            "attempt_count": int(render_job.get("attempt_count") or 0) + 1,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": datetime.now(timezone.utc).isoformat(),
            "finished_at": None,
            "failed_at": None,
            "progress": 42,
            "last_message": f"Regenerating section {section_index} and publishing a revised version.",
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
        },
    )
    try:
        result = await regenerate_artifact_section_internal(
            artifact_id=artifact_id,
            section_index=section_index,
            updated_prompt=updated_prompt,
            ctx=None,
        )
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "mode": "section_regeneration",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} updated section {result['section_index']}.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
            },
        )
        return JSONResponse(result)
    except ValueError as exc:
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="published",
                render_job_patch={
                    "status": "failed",
                    "mode": "section_regeneration",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": "Section regeneration failed. Review the error and retry.",
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                },
            )
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=400)
    except Exception as exc:
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="published",
                render_job_patch={
                    "status": "failed",
                    "mode": "section_regeneration",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": "Section regeneration failed. Review the error and retry.",
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                },
            )
        return JSONResponse({"error": str(exc), "artifact_id": artifact_id}, status_code=500)


async def telemetry_view_v1(request: Request) -> JSONResponse:
    try:
        body = await request.json()
    except Exception:
        body = {}
    if not isinstance(body, dict):
        return JSONResponse({"error": "View telemetry payload must be a JSON object."}, status_code=400)

    event = build_view_event_payload(request=request, body=body)
    append_jsonl(VIEW_EVENTS_PATH, event)
    with contextlib.suppress(Exception):
        await sync_view_event_to_supabase(event)
    engagement_event = build_engagement_event_from_view_event(event)
    append_jsonl(ENGAGEMENT_EVENTS_PATH, engagement_event)
    with contextlib.suppress(Exception):
        await sync_engagement_event_to_supabase(engagement_event)
    return JSONResponse(
        {
            "ok": True,
            "event_id": event["id"],
            "surface": event["surface"],
            "artifact_id": event.get("artifact_id"),
        },
        status_code=202,
    )


async def telemetry_engagement_v1(request: Request) -> JSONResponse:
    try:
        body = await request.json()
    except Exception:
        body = {}
    if not isinstance(body, dict):
        return JSONResponse({"error": "Engagement telemetry payload must be a JSON object."}, status_code=400)

    event = build_engagement_event_payload(request=request, body=body)
    append_jsonl(ENGAGEMENT_EVENTS_PATH, event)
    with contextlib.suppress(Exception):
        await sync_engagement_event_to_supabase(event)
    return JSONResponse(
        {
            "ok": True,
            "event_id": event["id"],
            "event_name": event["event_name"],
            "surface": event["surface"],
            "artifact_id": event.get("artifact_id"),
        },
        status_code=202,
    )


def coerce_template_normalized_payload(
    template_id: str,
    normalized_input: dict[str, Any] | None,
) -> dict[str, Any] | None:
    if not isinstance(normalized_input, dict):
        return None

    if isinstance(normalized_input.get("normalized_data"), dict):
        return normalized_input

    if isinstance(normalized_input.get("connections"), list):
        summary = (
            normalized_input.get("summary")
            if isinstance(normalized_input.get("summary"), dict)
            else {}
        )
        return {
            "normalized_data": normalized_input,
            "summary": summary,
            "warnings": [],
            "parse_confidence": 1.0,
            "input_mode": "json",
        }

    template = None
    with contextlib.suppress(Exception):
        template = get_template_definition(template_id)
    if isinstance(template, dict) and template.get("template_source") == "vault":
        summary = (
            normalized_input.get("summary")
            if isinstance(normalized_input.get("summary"), dict)
            else {}
        )
        return {
            "normalized_data": normalized_input,
            "summary": summary,
            "warnings": [],
            "parse_confidence": 1.0,
            "input_mode": "json",
        }

    return None


async def run_template_instance_generation(
    *,
    template_id: str,
    raw_input: str | None = None,
    normalized_input: dict[str, Any] | None = None,
    compose: list[dict[str, Any]] | None = None,
    input_mode: str | None = None,
    display_name: str | None = None,
    network_label: str | None = None,
    interaction_mode: str | None = None,
    telemetry_context: dict[str, Any] | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    run_started = time.perf_counter()
    current_stage = "initializing"
    durations_ms: dict[str, int] = {}
    warnings: list[dict[str, Any]] = []
    quality_score = 100
    render_validation_summary: dict[str, Any] = {}
    effective_telemetry_context = sanitize_telemetry_context(telemetry_context)
    effective_interaction_mode = resolve_interaction_mode(
        f"template {template_id}", interaction_mode
    )
    normalized_payload = coerce_template_normalized_payload(template_id, normalized_input)
    template = get_template_definition(template_id)
    plan: dict[str, Any] | None = None
    artifact_id: str | None = None
    local_path: Path | None = None
    preview_url = None
    download_url = None
    object_key = None
    storage = "local"

    try:
        if normalized_payload is None:
            current_stage = "normalizing_input"
            await report_stage(ctx, 14, "Normalizing the template input")
            normalize_started = time.perf_counter()
            normalized_payload = normalize_template_instance_input(
                template_id=template_id,
                raw_input=raw_input or "",
                input_mode=input_mode,
            )
            durations_ms["normalizing"] = int(
                (time.perf_counter() - normalize_started) * 1000
            )

        normalized_data = normalized_payload.get("normalized_data")
        if not isinstance(normalized_data, dict):
            raise ValueError("The template normalization payload is missing normalized_data.")

        input_contract_validation = validate_template_input_payload(
            template_id=template_id,
            normalized_data=normalized_data,
            input_mode=input_mode or normalized_payload.get("input_mode"),
        )
        resolved_input_mode = str(
            input_contract_validation.get("input_mode")
            or input_mode
            or normalized_payload.get("input_mode")
            or "json"
        )

        warnings = list(normalized_payload.get("warnings") or [])
        parse_confidence = float(normalized_payload.get("parse_confidence") or 1.0)
        summary = (
            normalized_payload.get("summary")
            if isinstance(normalized_payload.get("summary"), dict)
            else {}
        )

        current_stage = "rendering_template"
        await report_stage(ctx, 46, "Rendering the fixed template with your data")
        render_started = time.perf_counter()
        plan = build_template_plan(
            template_id=template_id,
            normalized_data=normalized_data,
            display_name=display_name,
            network_label=network_label,
            compose=compose,
        )
        html = render_template_instance(
            template_id=template_id,
            normalized_data=normalized_data,
            display_name=display_name,
            network_label=network_label,
            compose=compose,
        )
        title = plan.get("meta", {}).get("title", template["title"])
        artifact_id = str(uuid.uuid4())
        html, render_warnings, quality_score, render_validation_summary = validate_delivery_ready_artifact(
            html=html,
            artifact_id=artifact_id,
            artifact_title=title,
            interaction_mode=effective_interaction_mode,
            plan=plan,
            requested_section_count=len(plan.get("sections", [])),
            engine_override=None,
            prompt=f"Template instance render for {template_id}",
            template_hint=template_id,
            uploaded_content=None,
            structured_data={"template_id": template_id, "normalized_data": normalized_data},
            generation_mode="template_instance_render",
            payload=None,
        )
        enforce_render_validation_or_raise(render_warnings, quality_score, render_validation_summary)
        warnings.extend(render_warnings)
        artifact_id, local_path = write_local_output(title, html, artifact_id)
        durations_ms["assembling"] = int((time.perf_counter() - render_started) * 1000)

        if SETTINGS.has_r2:
            current_stage = "uploading"
            await report_stage(ctx, 86, "Publishing the template artifact")
            upload_started = time.perf_counter()
            storage = "r2"
            object_key, preview_url = upload_to_r2(local_path, artifact_id)
            download_url = preview_url
            durations_ms["uploading"] = int((time.perf_counter() - upload_started) * 1000)

        structured_data = {
            "template_id": template_id,
            "input_mode": resolved_input_mode,
            "display_name": display_name,
            "network_label": network_label,
            "normalized_data": normalized_data,
            "compose": compose or [],
            "input_contract": input_contract_validation.get("input_contract"),
        }

        persist_artifact_state(
            artifact_id=artifact_id,
            title=title,
            prompt=f"Template instance render for {template_id}",
            template_hint=template_id,
            theme_hint=plan.get("theme"),
            interaction_mode=effective_interaction_mode,
            generation_mode="template_instance_render",
            structured_data=structured_data,
            engine_override=None,
            section_count_requested=len(plan.get("sections", [])),
            plan=plan,
            local_path=local_path,
            storage=storage,
            object_key=object_key,
            preview_url=preview_url,
            warnings=warnings,
            quality_score=quality_score,
            render_validation_summary=render_validation_summary,
            regeneration_context={
                "template_id": template_id,
                "template_renderer_key": template["renderer_key"],
                "parse_confidence": parse_confidence,
            },
        )

        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        await report_stage(ctx, 100, f"Done. {title} is ready.")

        generation_prompt = (
            f"Template instance render for {template_id}. "
            f"Fixed structure, updated data. "
            f"Connections: {summary.get('connection_count') or 0}. "
            f"Top industry: {summary.get('top_industry') or 'n/a'}."
        )
        generation_event = build_generation_event(
            prompt=generation_prompt,
            template_hint=template_id,
            theme_hint=plan.get("theme"),
            uploaded_content=None,
            generation_mode="template_instance_render",
            interaction_mode=effective_interaction_mode,
            telemetry_context=effective_telemetry_context,
            structured_data=structured_data,
            engine_override=None,
            section_count_requested=len(plan.get("sections", [])),
            warnings=warnings,
            quality_score=quality_score,
            render_validation_summary=render_validation_summary,
            ctx=ctx,
            plan=plan,
            payload={},
            artifact_id=artifact_id,
            storage=storage,
            local_path=local_path,
            object_key=object_key,
            preview_url=preview_url,
            durations_ms=durations_ms,
            upload_analysis=None,
        )
        append_jsonl(GENERATION_EVENTS_PATH, generation_event)
        emit_telemetry_event(build_metadata_telemetry_event_from_generation(generation_event, state=load_artifact_state(artifact_id)))
        with contextlib.suppress(Exception):
            await sync_generation_event_to_supabase(
                generation_event=generation_event,
                provider_events=[],
            )

        notes = [
            "Rendered from a fixed IterFact template. The structure stayed frozen while the network data updated.",
            "This path skipped the planner and used deterministic template rendering instead.",
        ]
        if parse_confidence < 0.8:
            notes.append(
                "The input parse confidence was lower than ideal. Review the normalization warnings before sharing widely."
            )

        return {
            "artifact_id": artifact_id,
            "template_id": template_id,
            "title": title,
            "description": plan.get("meta", {}).get("description"),
            "summary": build_summary(plan),
            "document_format": "template instance",
            "theme_name": friendly_theme_name(plan.get("theme")),
            "section_count": len(plan.get("sections", [])),
            "section_titles": [
                section.get("title")
                for section in plan.get("sections", [])
                if section.get("title")
            ],
            "interaction_mode": effective_interaction_mode,
            "storage": storage,
            "preview_url": preview_url,
            "download_url": download_url,
            "quality_score": quality_score,
            "warnings": warnings or None,
            "notes": notes,
            "normalization": {
                "input_mode": resolved_input_mode,
                "parse_confidence": parse_confidence,
                "summary": summary,
                "warning_count": len(warnings),
                "input_contract": input_contract_validation.get("input_contract"),
            },
        }
    except Exception as exc:
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failure_event = build_failure_event(
            prompt=f"Template instance render for {template_id}",
            template_hint=template_id,
            theme_hint=plan.get("theme") if isinstance(plan, dict) else None,
            uploaded_content=None,
            generation_mode="template_instance_render",
            interaction_mode=effective_interaction_mode,
            telemetry_context=effective_telemetry_context,
            structured_data=(
                normalized_payload.get("normalized_data")
                if isinstance(normalized_payload, dict)
                else None
            ),
            engine_override=None,
            section_count_requested=(
                len(plan.get("sections", [])) if isinstance(plan, dict) else None
            ),
            ctx=ctx,
            stage=current_stage,
            error=exc,
            durations_ms=durations_ms,
            provider_events=[],
            upload_analysis=None,
        )
        append_jsonl(FAILURE_EVENTS_PATH, failure_event)
        emit_telemetry_event(build_metadata_telemetry_event_from_failure(failure_event))
        with contextlib.suppress(Exception):
            await sync_failure_event_to_supabase(failure_event)
        raise


def select_feedback_notes(
    state: dict[str, Any],
    feedback_ids: list[str] | None = None,
) -> list[dict[str, Any]]:
    available = get_feedback_notes_from_state(state, include_resolved=False)
    if not feedback_ids:
        return available
    selected_ids = {compact_whitespace(note_id) for note_id in (feedback_ids or []) if compact_whitespace(note_id)}
    return [note for note in available if str(note.get("id") or "") in selected_ids]


def describe_feedback_revision_request(feedback_count: int, has_instruction: bool) -> str:
    if feedback_count > 0 and has_instruction:
        return f"{feedback_count} saved note(s) and a prompt"
    if feedback_count > 0:
        return f"{feedback_count} saved note(s)"
    if has_instruction:
        return "a prompt"
    return "iteration input"


def build_feedback_revision_result_note(feedback_count: int, has_instruction: bool) -> str:
    if feedback_count > 0 and has_instruction:
        return (
            f"Applied {feedback_count} feedback note(s) plus an iteration prompt to the existing artifact "
            "without creating a new artifact id."
        )
    if feedback_count > 0:
        return f"Applied {feedback_count} feedback note(s) to the existing artifact without creating a new artifact id."
    if has_instruction:
        return "Applied a prompt-only iteration update to the existing artifact without creating a new artifact id."
    return "Updated the existing artifact in place without creating a new artifact id."


async def apply_artifact_feedback_internal(
    *,
    artifact_id: str,
    feedback_ids: list[str] | None,
    additional_instruction: str | None,
) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    plan = state.get("plan")
    if not isinstance(plan, dict):
        raise ValueError("The saved artifact state is missing a reusable assembly plan.")
    plan = normalize_plan(plan)
    validate_plan(plan)

    selected_feedback = select_feedback_notes(state, feedback_ids)
    has_instruction = bool(compact_whitespace(additional_instruction))
    if not selected_feedback and not has_instruction:
        raise ValueError(
            "Add an iteration prompt or select at least one open feedback note before updating this artifact."
        )

    original_prompt = str(state.get("prompt") or state.get("title") or "Revise this IterFact.")
    template_hint = state.get("template_hint")
    theme_hint = state.get("theme_hint")
    structured_data = sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None
    requested_engine_override = normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None
    interaction_mode = resolve_interaction_mode(original_prompt, state.get("interaction_mode"))

    revised_plan, payload = await revise_plan_from_feedback_with_anthropic(
        original_prompt=original_prompt,
        template_hint=template_hint,
        current_plan=plan,
        selected_feedback=selected_feedback,
        structured_data=structured_data,
        engine_override=requested_engine_override,
        additional_instruction=additional_instruction,
    )
    if is_wrapper_engine(requested_engine_override):
        revised_plan.setdefault("wrapper", {})["engine"] = requested_engine_override
    if theme_hint:
        revised_plan["theme"] = theme_hint
    revised_plan = apply_primary_engine_constraints(revised_plan, requested_engine_override, structured_data)

    revised_plan, html, payload, warnings, quality_score, render_validation_summary = await render_plan_with_iterative_refinement(
        prompt=original_prompt,
        template_hint=template_hint,
        uploaded_content=None,
        structured_data=structured_data,
        generation_mode="feedback_revision",
        plan=revised_plan,
        payload=payload,
        requested_section_count=state.get("section_count_requested"),
        engine_override=requested_engine_override,
        critic_timeout_seconds=SETTINGS.anthropic_critic_timeout_seconds,
    )
    title = revised_plan.get("meta", {}).get("title", state.get("title") or "IterFact Document")
    html, warnings, quality_score, render_validation_summary = validate_delivery_ready_artifact(
        html=html,
        artifact_id=artifact_id,
        artifact_title=title,
        interaction_mode=interaction_mode,
        plan=revised_plan,
        requested_section_count=state.get("section_count_requested") or len(revised_plan.get("sections", []) or []),
        engine_override=requested_engine_override,
        prompt=original_prompt,
        template_hint=template_hint,
        uploaded_content=None,
        structured_data=structured_data,
        generation_mode="feedback_revision",
        payload=payload,
    )
    critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
    enforce_render_validation_or_raise(
        warnings,
        quality_score,
        render_validation_summary,
        allow_low_quality_publish=allow_nonfatal_low_quality_publish(payload, render_validation_summary),
    )
    public_score = public_quality_score(quality_score, payload, render_validation_summary)
    artifact_id, local_path = write_local_output(title, html, artifact_id)

    storage = state.get("current", {}).get("storage") or "local"
    object_key = None
    preview_url = None
    download_url = None
    if SETTINGS.has_r2:
        storage = "r2"
        object_key, preview_url = upload_to_r2(local_path, artifact_id)
        download_url = preview_url

    persist_artifact_state(
        artifact_id=artifact_id,
        title=title,
        prompt=original_prompt,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        generation_mode="feedback_revision",
        structured_data=structured_data,
        engine_override=requested_engine_override,
        section_count_requested=state.get("section_count_requested") or len(revised_plan.get("sections", []) or []),
        plan=revised_plan,
        local_path=local_path,
        storage=storage,
        object_key=object_key,
        preview_url=preview_url,
        warnings=warnings,
        quality_score=public_score,
        render_validation_summary=render_validation_summary,
        regeneration_context={
            "feedback_ids": [note.get("id") for note in selected_feedback if note.get("id")],
            "additional_instruction": additional_instruction,
            "feedback_summary": [note.get("text") for note in selected_feedback[:6] if note.get("text")],
        },
        draft_state=state.get("draft") if isinstance(state.get("draft"), dict) else None,
        draft_blueprint=state.get("draft_blueprint") if isinstance(state.get("draft_blueprint"), dict) else None,
        critic_skipped=critic_skipped,
        critic_timed_out=critic_timed_out,
    )
    update_feedback_note_statuses(
        artifact_id,
        [str(note.get("id")) for note in selected_feedback if note.get("id")],
        status="applied",
    )

    return {
        "artifact_id": artifact_id,
        "title": title,
        "summary": build_summary(revised_plan),
        "preview_url": preview_url,
        "download_url": download_url,
        "quality_score": public_score,
        "critic_skipped": critic_skipped,
        "critic_timed_out": critic_timed_out,
        "warnings": warnings or None,
        "feedback_summary": summarize_feedback_notes(normalize_feedback_notes(load_artifact_state(artifact_id).get("feedback_notes"))),
        "notes": [
            build_feedback_revision_result_note(len(selected_feedback), has_instruction),
            "A new revision was published under the same artifact id and older versions remain in revision history.",
            *build_critic_public_notes(payload, render_validation_summary),
        ],
    }


def run_apply_artifact_feedback_job(
    artifact_id: str,
    feedback_ids: list[str] | None,
    additional_instruction: str | None,
) -> None:
    feedback_count = len(feedback_ids or [])
    has_instruction = bool(compact_whitespace(additional_instruction))
    try:
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="rendering",
            render_job_patch={
                "status": "running",
                "mode": "feedback_revision",
                "job_id": str(uuid.uuid4()),
                "attempt_count": 1,
                "requested_at": datetime.now(timezone.utc).isoformat(),
                "started_at": datetime.now(timezone.utc).isoformat(),
                "finished_at": None,
                "failed_at": None,
                "progress": 58,
                "last_message": f"Applying {describe_feedback_revision_request(feedback_count, has_instruction)} and publishing an updated revision.",
                "last_error_type": None,
                "last_error_message": None,
                "preview_url": None,
                "quality_score": None,
                "warning_count": None,
            },
        )
        result = asyncio.run(
            apply_artifact_feedback_internal(
                artifact_id=artifact_id,
                feedback_ids=feedback_ids,
                additional_instruction=additional_instruction,
            )
        )
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "mode": "feedback_revision",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} was updated from {describe_feedback_revision_request(feedback_count, has_instruction)}.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
                "critic_skipped": bool(result.get("critic_skipped")),
                "critic_timed_out": bool(result.get("critic_timed_out")),
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
    except Exception as exc:  # noqa: BLE001
        LOGGER.exception("Background feedback revision failed for artifact %s", artifact_id)
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="published",
                render_job_patch={
                    "status": "failed",
                    "mode": "feedback_revision",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": "Applying feedback failed. Review the error and retry.",
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                },
            )
    finally:
        clear_finalize_thread(artifact_id)


def start_apply_artifact_feedback_job(
    artifact_id: str,
    feedback_ids: list[str] | None,
    additional_instruction: str | None,
) -> dict[str, Any]:
    existing = get_active_finalize_thread(artifact_id)
    if existing is not None:
        return load_artifact_state(artifact_id)
    feedback_count = len(feedback_ids or [])
    has_instruction = bool(compact_whitespace(additional_instruction))
    queued_state = update_artifact_runtime_state(
        artifact_id,
        artifact_status="rendering",
        render_job_patch={
            "status": "queued",
            "mode": "feedback_revision",
            "job_id": str(uuid.uuid4()),
            "attempt_count": 1,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": None,
            "finished_at": None,
            "failed_at": None,
            "progress": 8,
            "last_message": f"Queued {describe_feedback_revision_request(feedback_count, has_instruction)} for background revision and publication.",
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
        },
    )
    thread = threading.Thread(
        target=run_apply_artifact_feedback_job,
        kwargs={
            "artifact_id": artifact_id,
            "feedback_ids": feedback_ids,
            "additional_instruction": additional_instruction,
        },
        name=f"iterfact-feedback-{artifact_id[:8]}",
        daemon=True,
    )
    register_finalize_thread(artifact_id, thread)
    thread.start()
    return queued_state


def run_regenerate_artifact_section_job(
    artifact_id: str,
    section_index: int,
    updated_prompt: str | None,
) -> None:
    try:
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="rendering",
            render_job_patch={
                "status": "running",
                "mode": "section_regeneration",
                "job_id": str(uuid.uuid4()),
                "attempt_count": 1,
                "requested_at": datetime.now(timezone.utc).isoformat(),
                "started_at": datetime.now(timezone.utc).isoformat(),
                "finished_at": None,
                "failed_at": None,
                "progress": 42,
                "last_message": f"Regenerating section {section_index} and publishing an updated revision.",
                "last_error_type": None,
                "last_error_message": None,
                "preview_url": None,
                "quality_score": None,
                "warning_count": None,
            },
        )
        result = asyncio.run(
            regenerate_artifact_section_internal(
                artifact_id=artifact_id,
                section_index=section_index,
                updated_prompt=updated_prompt,
                ctx=None,
            )
        )
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "mode": "section_regeneration",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} updated section {result['section_index']}.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
            },
        )
    except Exception as exc:  # noqa: BLE001
        LOGGER.exception("Background section regeneration failed for artifact %s", artifact_id)
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="published",
                render_job_patch={
                    "status": "failed",
                    "mode": "section_regeneration",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": "Section regeneration failed. Review the error and retry.",
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                },
            )
    finally:
        clear_finalize_thread(artifact_id)


def start_regenerate_artifact_section_job(
    artifact_id: str,
    section_index: int,
    updated_prompt: str | None,
) -> dict[str, Any]:
    existing = get_active_finalize_thread(artifact_id)
    if existing is not None:
        return load_artifact_state(artifact_id)
    queued_state = update_artifact_runtime_state(
        artifact_id,
        artifact_status="rendering",
        render_job_patch={
            "status": "queued",
            "mode": "section_regeneration",
            "job_id": str(uuid.uuid4()),
            "attempt_count": 1,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": None,
            "finished_at": None,
            "failed_at": None,
            "progress": 8,
            "last_message": f"Queued section {section_index} for regeneration and publication.",
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
        },
    )
    thread = threading.Thread(
        target=run_regenerate_artifact_section_job,
        kwargs={
            "artifact_id": artifact_id,
            "section_index": section_index,
            "updated_prompt": updated_prompt,
        },
        name=f"iterfact-section-{artifact_id[:8]}-{section_index}",
        daemon=True,
    )
    register_finalize_thread(artifact_id, thread)
    thread.start()
    return queued_state


mcp = FastMCP(
    name="IterFact",
    instructions=(
        "Use IterFact to generate and refine interactive documents from natural language. "
        "Use list_engines to discover shells, list_templates to discover fixed template instances, match_template to preview likely template fits, "
        "generate_document to create freeform artifacts, generate_from_template to reuse a frozen template with new data, "
        "Whenever you can confidently shape the story yourself, do that in Claude first: decide the thesis, audience, wrapper, planned sections, and narrative spine on the user side, then pass that structure into generate_document or create_artifact_draft as artifact_plan so IterFact can skip server-side planning work. "
        "Reserve IterFact's own planner for low-confidence cases where you cannot confidently choose the story shape yourself. "
        "When a user asks about templates, asks you to choose a template, or asks you to render from a template, first surface the template's supported_input_modes and preferred_input_mode so they know whether to continue with chat, paste, csv, xlsx, or json. "
        "If a template is strict about input mode, do not pretend freeform chat is acceptable; tell the user the preferred mode and any required columns or sheets first. "
        "For longform or chunk-built documents, prefer this exact workflow: create_artifact_draft, append_section as many times as needed, finalize_artifact, then poll get_artifact_status until artifact_status is published or render_job.status is failed. "
        "If you already know the longform scaffold, pass artifact_plan to create_artifact_draft so IterFact reuses your planned title, wrapper, section list, and narrative spine instead of building a fresh scaffold on the server. "
        "If your MCP client does not expose get_artifact_status yet because of a stale tool manifest, call finalize_artifact again with the same artifact_id to check status; no duplicate render starts while the background job is queued or running. "
        "Use that draft flow instead of generate_document when the source is long, sectioned, or likely to trigger validation and critic passes. "
        "Standard freeform generate_document now queues in the background by default; if generate_document returns artifact_status or render_job, poll get_artifact_status until publication completes, or use the same finalize_artifact fallback when needed. "
        "If you need speed over editorial polish, pass skip_critic=true to generate_document or finalize_artifact so IterFact publishes as soon as delivery validation succeeds. "
        "If the critic is enabled but times out, IterFact now publishes with critic_timed_out=true instead of hanging indefinitely in the critic stage. "
        "If a background freeform generate_document run fails, call finalize_artifact with retry_failed_render=true on the same artifact_id to resume from the last completed checkpoint instead of starting the request over. "
        "Published artifacts can also collect live Iterate notes: use get_artifact_feedback to read saved notes from the artifact link and apply_artifact_feedback to revise the same artifact_id in place instead of starting over. "
        "The planner now expects one coherent narrative spine across the full artifact, and regenerate_section should preserve that story instead of rewriting sections in isolation."
    ),
    website_url="https://iterfact.com",
    icons=[
        Icon(src="https://mcp.iterfact.com/favicon-32.png?v=if-stack-20260322-ifmark", mimeType="image/png", sizes=["32x32"]),
        Icon(src="https://mcp.iterfact.com/favicon.svg?v=if-stack-20260322-ifmark", mimeType="image/svg+xml"),
    ],
    stateless_http=True,
    json_response=True,
    transport_security=TRANSPORT_SECURITY,
)

mcp_http_app = mcp.streamable_http_app()


@mcp.tool(
    name="list_engines",
    description="List available IterFact engines so the caller can choose a stronger wrapper or understand what each engine is best for.",
)
async def list_engines() -> dict[str, Any]:
    engines: list[dict[str, Any]] = []
    for engine in MANIFEST.get("engines", []):
        folder = engine.get("folder", "")
        engine_key = folder[:2] if folder[:2].isdigit() else None
        engine_path = SETTINGS.vault_path / (engine.get("path") or "")
        engines.append(
            {
                "engine_id": engine_key,
                "name": engine.get("name"),
                "description": f"{engine.get('name')} ({engine.get('category')}).",
                "best_for": derive_engine_best_for(engine),
                "supports": derive_engine_supports(engine),
                "category": engine.get("category"),
                "complexity": engine.get("complexity"),
                "compatible_verticals": engine.get("compatible_verticals") or [],
                "core_libs": engine.get("core_libs") or [],
                "can_be_wrapper": bool(engine_key in VALID_WRAPPERS),
                "path": str(engine_path),
                "preview_thumbnail_url": None,
            }
        )

    return {
        "engine_count": len(engines),
        "engines": engines,
    }


@mcp.tool(
    name="list_templates",
    description="List vault-backed IterFact templates, including fixed primary templates and composable panels that keep structure frozen while only data updates. Use the returned supported_input_modes, preferred_input_mode, and input_guidance to tell the user how they can add data before rendering anything.",
)
async def list_templates() -> dict[str, Any]:
    return list_public_templates()


@mcp.tool(
    name="match_template",
    description="Recommend the best matching fixed IterFact templates for a prompt before generating anything. Before you render, tell the user the matched template's supported_input_modes and preferred_input_mode.",
)
async def match_template(
    prompt: str,
    top_k: int | None = 3,
) -> dict[str, Any]:
    return match_public_templates(prompt=prompt, top_k=max(1, min(int(top_k or 3), 10)))


@mcp.tool(
    name="preview_normalized_input",
    description="Normalize raw template input before rendering so the caller can inspect parse confidence, warnings, and sample rows after confirming the template's accepted input modes.",
)
async def preview_normalized_input(
    template_id: str,
    raw_input: str,
    input_mode: str | None = None,
) -> dict[str, Any]:
    return normalize_template_instance_input(
        template_id=template_id,
        raw_input=raw_input,
        input_mode=input_mode,
    )


@mcp.tool(
    name="generate_from_template",
    description="Render a vault-backed IterFact template with new data. The structure stays frozen while the data updates, and primary templates can compose compatible panels through the optional compose payload. Before calling this, surface the template's supported_input_modes and preferred_input_mode to the user.",
)
async def generate_from_template(
    template_id: str,
    raw_input: str | None = None,
    normalized_input: dict[str, Any] | None = None,
    compose: list[dict[str, Any]] | None = None,
    input_mode: str | None = None,
    display_name: str | None = None,
    network_label: str | None = None,
    interaction_mode: str | None = None,
    telemetry_context: dict[str, Any] | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    if not raw_input and not normalized_input:
        raise ValueError("Provide raw_input or normalized_input before rendering a template.")

    return await run_template_instance_generation(
        template_id=template_id,
        raw_input=raw_input,
        normalized_input=normalized_input,
        compose=compose,
        input_mode=input_mode,
        display_name=display_name,
        network_label=network_label,
        interaction_mode=interaction_mode,
        telemetry_context=telemetry_context,
        ctx=ctx,
    )


@mcp.tool(
    name="create_artifact_draft",
    description="Step 1 for longform chunked builds: create a draft scaffold that can be filled section by section before publication. If Claude already knows the story, wrapper, and planned sections, pass artifact_plan to skip server-side scaffold planning.",
)
async def create_artifact_draft(
    prompt: str,
    uploaded_content: str | None = None,
    template_hint: str | None = None,
    theme_hint: str | None = None,
    data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    interaction_mode: str | None = None,
    artifact_plan: dict[str, Any] | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    structured_data = sanitize_structured_data(data)
    requested_engine_override = normalize_engine_override(engine_override)
    requested_section_count = normalize_section_count(section_count)
    normalized_uploaded = normalize_source_text(uploaded_content or "") if uploaded_content else ""
    supplied_plan: dict[str, Any] | None = None
    scaffold_payload: dict[str, Any] | None = None
    if isinstance(artifact_plan, dict):
        supplied_plan = prepare_client_supplied_artifact_plan(
            artifact_plan=artifact_plan,
            prompt=prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=requested_section_count,
            require_sections=False,
        )
        source_chunks = build_source_chunks_from_plan_sections(supplied_plan)
        scaffold_payload = build_client_supplied_plan_payload(
            prompt=prompt,
            template_hint=template_hint,
            uploaded_content=normalized_uploaded or None,
            structured_data=structured_data,
            generation_mode="chunked_draft_scaffold",
            wrapper_engine=supplied_plan.get("wrapper", {}).get("engine") if isinstance(supplied_plan.get("wrapper"), dict) else None,
        )
    else:
        source_chunks = (
            build_source_chunks_from_text(normalized_uploaded)
            if normalized_uploaded
            else build_placeholder_source_chunks(requested_section_count or 5)
        )

    await report_stage(ctx, 18, "Creating a chunked artifact draft")
    state, payload = await create_chunked_artifact_draft_internal(
        prompt=prompt,
        source_chunks=source_chunks,
        template_hint=template_hint,
        theme_hint=theme_hint,
        interaction_mode=interaction_mode,
        structured_data=structured_data,
        engine_override=requested_engine_override,
        section_count_requested=requested_section_count or (None if normalized_uploaded else len(source_chunks)),
        uploaded_content=normalized_uploaded or None,
        scaffold_plan_override=supplied_plan,
        payload_override=scaffold_payload,
    )
    await report_stage(ctx, 100, f"Done. Draft {state.get('title') or 'IterFact Draft'} is ready for section appends.")
    draft = state.get("draft") if isinstance(state.get("draft"), dict) else {}
    return {
        "artifact_id": state["artifact_id"],
        "title": state.get("title"),
        "artifact_status": "draft",
        "wrapper_engine_id": state.get("wrapper_engine_id"),
        "primary_engine_id": normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
        "theme_name": friendly_theme_name(state.get("theme")),
        "planning_source": ((state.get("plan") or {}).get("meta", {}) or {}).get("planning_source") if isinstance(state.get("plan"), dict) else None,
        "planned_section_titles": draft.get("planned_section_titles"),
        "source_chunk_count": draft.get("source_chunk_count"),
        "uploaded_content_compacted": payload.get("uploaded_content_compacted") or False,
        "notes": [
            "This draft is not published yet. Call append_section to add source-backed sections.",
            "Call finalize_artifact when the draft is ready to render and publish.",
            *(
                ["Claude-side story planning was supplied in artifact_plan, so IterFact skipped server-side scaffold planning."]
                if supplied_plan is not None
                else []
            ),
        ],
    }


@mcp.tool(
    name="append_section",
    description="Step 2 for longform chunked builds: append one source-backed section to a chunk-build IterFact draft.",
)
async def append_section(
    artifact_id: str,
    source_text: str,
    section_title_hint: str | None = None,
    updated_prompt: str | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    normalized_source = normalize_source_text(source_text)
    if not normalized_source:
        raise ValueError("source_text is required when appending a section.")

    await report_stage(ctx, 20, "Appending the next draft section")
    state = load_artifact_state(artifact_id)
    if str(state.get("artifact_status") or "") in {"building", "rendering"}:
        raise ValueError("This artifact is currently being processed in the background. Wait for get_artifact_status to finish before appending more sections.")
    next_index = len((state.get("plan") or {}).get("sections", []) or []) + 1 if isinstance(state.get("plan"), dict) else 1
    chunk = SourceChunk(
        index=next_index,
        title=compact_whitespace(section_title_hint) or f"Section {next_index}",
        body=normalized_source,
        heading_level=None,
        source_heading=compact_whitespace(section_title_hint) or None,
    )
    section, payload = await append_section_to_artifact_draft_internal(
        artifact_id=artifact_id,
        source_chunk=chunk,
        updated_prompt=updated_prompt,
        section_title_hint=section_title_hint,
    )
    updated_state = load_artifact_state(artifact_id)
    await report_stage(ctx, 100, f"Done. Added section {len((updated_state.get('plan') or {}).get('sections', []) or [])}.")
    return {
        "artifact_id": artifact_id,
        "artifact_status": "draft",
        "section_index": len((updated_state.get("plan") or {}).get("sections", []) or []),
        "section_title": section.get("title"),
        "section_titles": [item.get("title") for item in (updated_state.get("plan") or {}).get("sections", []) if item.get("title")],
        "repair_applied": bool(payload.get("repair_applied")),
        "notes": [
            "The draft was updated in place under the same artifact id.",
            "Call finalize_artifact when you are ready to render and publish the full document.",
        ],
    }


@mcp.tool(
    name="get_artifact_status",
    description="Step 4 for longform chunked builds: poll whether the artifact is still rendering, published, or failed, and return the latest preview pointers when available.",
)
async def get_artifact_status(artifact_id: str) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    return build_artifact_status_response(state)


@mcp.tool(
    name="get_artifact_feedback",
    description="Read feedback notes saved against an existing IterFact artifact, including notes left from the published Iterate link.",
)
async def get_artifact_feedback(
    artifact_id: str,
    include_resolved: bool = False,
) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    return build_artifact_feedback_response(state, include_resolved=include_resolved)


@mcp.tool(
    name="get_artifact_sections",
    description="List the saved plan sections for an existing IterFact artifact so callers can target a single section for regeneration.",
)
async def get_artifact_sections(artifact_id: str) -> dict[str, Any]:
    state = load_artifact_state(artifact_id)
    return build_artifact_sections_response(state)


@mcp.tool(
    name="apply_artifact_feedback",
    description="Revise an existing published IterFact in place from saved feedback notes, a prompt-only instruction, or both instead of starting over. By default this runs in the background and republishes under the same artifact id.",
)
async def apply_artifact_feedback(
    artifact_id: str,
    feedback_ids: list[str] | None = None,
    additional_instruction: str | None = None,
    wait_for_completion: bool = False,
    ctx: Context | None = None,
) -> dict[str, Any]:
    await report_stage(ctx, 18, "Loading the saved artifact and feedback notes")
    state = load_artifact_state(artifact_id)
    current = state.get("current") if isinstance(state.get("current"), dict) else {}
    if not current.get("preview_url") and str(state.get("artifact_status") or "") != "published":
        raise ValueError("Feedback application is only available after the artifact has been rendered at least once.")

    selected_feedback = select_feedback_notes(state, feedback_ids)
    has_instruction = bool(compact_whitespace(additional_instruction))
    if not selected_feedback and not has_instruction:
        raise ValueError("Add an iteration prompt or select at least one open feedback note before updating this artifact.")

    render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
    if get_active_finalize_thread(artifact_id) is not None or render_job.get("status") in {"queued", "running"}:
        await report_stage(ctx, 100, "A background artifact job is already active.")
        latest_state = load_artifact_state(artifact_id)
        return build_artifact_status_response(
            latest_state,
            notes=["No new feedback revision started because another background artifact job is already active."],
        )

    if not wait_for_completion:
        queued_state = start_apply_artifact_feedback_job(artifact_id, feedback_ids, additional_instruction)
        await report_stage(ctx, 100, "Feedback revision queued. Poll get_artifact_status for publication.")
        return build_artifact_status_response(
            queued_state,
            notes=[
                f"Queued {describe_feedback_revision_request(len(selected_feedback), has_instruction)} for an in-place artifact update.",
                "Call get_artifact_status to poll for the updated preview URL and render status.",
            ],
        )

    update_artifact_runtime_state(
        artifact_id,
        artifact_status="rendering",
        render_job_patch={
            "status": "running",
            "mode": "feedback_revision",
            "job_id": str(uuid.uuid4()),
            "attempt_count": int(render_job.get("attempt_count") or 0) + 1,
            "requested_at": datetime.now(timezone.utc).isoformat(),
            "started_at": datetime.now(timezone.utc).isoformat(),
            "finished_at": None,
            "failed_at": None,
            "progress": 55,
            "last_message": f"Applying {describe_feedback_revision_request(len(selected_feedback), has_instruction)} and publishing a revised version.",
            "last_error_type": None,
            "last_error_message": None,
            "preview_url": None,
            "quality_score": None,
            "warning_count": None,
        },
    )
    await report_stage(ctx, 58, "Applying feedback to the saved artifact plan")
    try:
        result = await apply_artifact_feedback_internal(
            artifact_id=artifact_id,
            feedback_ids=feedback_ids,
            additional_instruction=additional_instruction,
        )
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "mode": "feedback_revision",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} was updated from {describe_feedback_revision_request(len(selected_feedback), has_instruction)}.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
                "critic_skipped": bool(result.get("critic_skipped")),
                "critic_timed_out": bool(result.get("critic_timed_out")),
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        await report_stage(
            ctx,
            100,
            f"Done. Updated {result['title']} from {describe_feedback_revision_request(len(selected_feedback), has_instruction)}.",
        )
        return result
    except Exception as exc:
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="published",
                render_job_patch={
                    "status": "failed",
                    "mode": "feedback_revision",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": "Applying feedback failed. Review the error and retry.",
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                },
            )
        raise


@mcp.tool(
    name="finalize_artifact",
    description="Step 3 for longform chunked builds: kick off rendering and publication for a draft. By default this continues in the background so long renders do not time out; then poll get_artifact_status for completion. If skip_critic=true, IterFact skips the editorial critic and publishes as soon as delivery validation passes. If your client does not expose get_artifact_status yet, calling finalize_artifact again with the same artifact_id safely returns the current status while the background render is queued or running. To resume a failed render from its checkpoint, pass retry_failed_render=true explicitly.",
)
async def finalize_artifact(
    artifact_id: str,
    skip_critic: bool = False,
    retry_failed_render: bool = False,
    wait_for_completion: bool = False,
    ctx: Context | None = None,
) -> dict[str, Any]:
    run_started = time.perf_counter()
    durations_ms: dict[str, int] = {}
    await report_stage(ctx, 18, "Loading the artifact draft")
    state = load_artifact_state(artifact_id)
    current_stage = "finalizing_chunked_draft"
    try:
        draft_state = state.get("draft") if isinstance(state.get("draft"), dict) else {}
        render_job = normalize_render_job_state(state.get("render_job") if isinstance(state.get("render_job"), dict) else None)
        generation_mode_state = str(state.get("generation_mode") or "")
        is_background_freeform = (
            generation_mode_state == "planned_render"
            and render_job.get("mode") == "freeform_generate_document"
            and str(state.get("artifact_status") or "") != "published"
        )
        if str(state.get("artifact_status") or "") == "published" and draft_state.get("status") == "published":
            await report_stage(ctx, 100, f"{state.get('title') or 'Artifact'} is already live.")
            return build_artifact_status_response(
                state,
                notes=["No new finalize run was started because the draft is already published."],
            )

        if get_active_finalize_thread(artifact_id) is not None or render_job.get("status") in {"queued", "running"}:
            await report_stage(ctx, 100, "Finalize is already running in the background.")
            latest_state = load_artifact_state(artifact_id)
            return build_artifact_status_response(
                latest_state,
                notes=["No new finalize run was started because a background render job is already active."],
            )

        if render_job.get("status") == "failed" and not retry_failed_render:
            await report_stage(ctx, 100, "Returning the saved failed status without starting a new retry.")
            latest_state = load_artifact_state(artifact_id)
            return build_artifact_status_response(
                latest_state,
                notes=[
                    "No new retry was started because the last render is already in a failed state.",
                    "Pass retry_failed_render=true to finalize_artifact when you explicitly want to resume from the saved checkpoint.",
                ],
            )

        if is_background_freeform:
            if not wait_for_completion:
                queued_state = start_background_freeform_resume_job(artifact_id, skip_critic=skip_critic)
                await report_stage(ctx, 100, "Freeform generation queued. Rendering will continue in the background.")
                return build_artifact_status_response(
                    queued_state,
                    notes=[
                        "This artifact came from background freeform generate_document, so finalize_artifact is acting as an explicit retry/resume handle.",
                        "Call get_artifact_status to poll for publication and preview URLs.",
                        "If the last run failed after a saved checkpoint, this retry will resume from that stage instead of restarting the full generate_document pipeline.",
                        *(
                            ["Critic is being skipped for this retry because skip_critic=true."]
                            if skip_critic
                            else []
                        ),
                    ],
                )

            render_job_attempt = int(render_job.get("attempt_count") or 0) + 1
            render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
            resume_stage = render_checkpoint.get("stage")
            effective_skip_critic = bool(skip_critic or render_job.get("skip_critic_requested"))
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="rendering" if checkpoint_stage_at_least(render_checkpoint, "planned") else "planning",
                render_job_patch={
                    "status": "running",
                    "mode": "freeform_generate_document",
                    "job_id": str(uuid.uuid4()),
                    "attempt_count": render_job_attempt,
                    "resume_stage": resume_stage,
                    "requested_at": datetime.now(timezone.utc).isoformat(),
                    "started_at": datetime.now(timezone.utc).isoformat(),
                    "finished_at": None,
                    "failed_at": None,
                    "progress": checkpoint_resume_progress(resume_stage) if resume_stage else 18,
                    "last_message": checkpoint_resume_message(resume_stage) if resume_stage else "Planning the artifact structure in the background.",
                    "last_error_type": None,
                    "last_error_message": None,
                    "preview_url": None,
                    "quality_score": None,
                    "warning_count": None,
                    "skip_critic_requested": effective_skip_critic,
                    "critic_skipped": False,
                    "critic_timed_out": False,
                    "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
                },
            )
            await report_stage(ctx, 56, "Resuming the freeform artifact")
            current_stage = "resuming_background_freeform"
            result = await background_freeform_generate_document_internal(
                artifact_id=artifact_id,
                prompt=str(state.get("prompt") or state.get("title") or "IterFact Document"),
                template_hint=state.get("template_hint"),
                theme_hint=state.get("theme_hint"),
                uploaded_content=str(state.get("uploaded_content") or ""),
                structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
                requested_engine_override=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
                requested_section_count=state.get("section_count_requested"),
                primary_engine_constraint=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else infer_primary_engine_from_request(
                    str(state.get("prompt") or state.get("title") or ""),
                    state.get("template_hint"),
                    sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
                ),
                interaction_mode=state.get("interaction_mode"),
                artifact_plan=(
                    normalize_plan(copy.deepcopy(state.get("plan")))
                    if isinstance(state.get("plan"), dict)
                    and ((state.get("plan") or {}).get("meta", {}) or {}).get("planning_source") == "client_supplied_plan"
                    else None
                ),
                durations_ms=durations_ms,
                skip_critic=effective_skip_critic,
            )
            durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="published",
                render_job_patch={
                    "status": "succeeded",
                    "mode": "freeform_generate_document",
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": f"{result['title']} is ready.",
                    "preview_url": result.get("preview_url"),
                    "quality_score": result.get("quality_score"),
                    "warning_count": len(result.get("warnings") or []),
                    "skip_critic_requested": effective_skip_critic,
                    "critic_skipped": bool(result.get("critic_skipped")),
                    "critic_timed_out": bool(result.get("critic_timed_out")),
                    "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
                },
            )
            await report_stage(ctx, 100, f"Done. {result['title']} is live.")
            await record_background_freeform_success(
                prompt=str(state.get("prompt") or state.get("title") or "IterFact Document"),
                template_hint=state.get("template_hint"),
                theme_hint=state.get("theme_hint"),
                uploaded_content=str(state.get("uploaded_content") or ""),
                interaction_mode=state.get("interaction_mode"),
                structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
                engine_override=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
                section_count_requested=state.get("section_count_requested"),
                result=result,
                durations_ms=durations_ms,
            )
            return {
                "artifact_id": result["artifact_id"],
                "title": result["title"],
                "summary": build_summary(result["plan"]),
                "preview_url": result["preview_url"],
                "download_url": result["download_url"],
                "quality_score": result["quality_score"],
                "critic_skipped": result.get("critic_skipped"),
                "critic_timed_out": result.get("critic_timed_out"),
                "warnings": result["warnings"] or None,
                "notes": [
                    "The background freeform generate_document artifact resumed under the same artifact id.",
                    "If a checkpoint was available, the resume continued from the last completed stage instead of restarting the full pipeline.",
                    *build_critic_public_notes(result.get("payload"), result.get("render_validation_summary")),
                ],
            }

        if not wait_for_completion:
            queued_state = start_finalize_artifact_job(artifact_id, skip_critic=skip_critic)
            await report_stage(ctx, 100, "Finalize queued. Rendering will continue in the background.")
            return build_artifact_status_response(
                queued_state,
                notes=[
                    "Call get_artifact_status to poll for publication and preview URLs.",
                    "If your client does not expose get_artifact_status yet, call finalize_artifact again with the same artifact_id to check status without starting a duplicate render.",
                    *(
                        ["Critic is being skipped for this render because skip_critic=true."]
                        if skip_critic
                        else []
                    ),
                ],
            )

        render_job_attempt = int(render_job.get("attempt_count") or 0) + 1
        render_checkpoint = normalize_render_checkpoint_state(state.get("render_checkpoint") if isinstance(state.get("render_checkpoint"), dict) else None)
        resume_stage = render_checkpoint.get("stage")
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="rendering",
            render_job_patch={
                "status": "running",
                "job_id": str(uuid.uuid4()),
                "attempt_count": render_job_attempt,
                "resume_stage": resume_stage,
                "requested_at": datetime.now(timezone.utc).isoformat(),
                "started_at": datetime.now(timezone.utc).isoformat(),
                "finished_at": None,
                "failed_at": None,
                "progress": checkpoint_resume_progress(resume_stage),
                "last_message": checkpoint_resume_message(resume_stage),
                "last_error_type": None,
                "last_error_message": None,
                "preview_url": None,
                "quality_score": None,
                "warning_count": None,
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": False,
                "critic_timed_out": False,
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        await report_stage(ctx, 56, "Rendering the finalized artifact")
        finalize_started = time.perf_counter()
        result = await finalize_artifact_draft_internal(artifact_id=artifact_id, skip_critic=skip_critic)
        durations_ms["assembling"] = int((time.perf_counter() - finalize_started) * 1000)
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        update_artifact_runtime_state(
            artifact_id,
            artifact_status="published",
            render_job_patch={
                "status": "succeeded",
                "finished_at": datetime.now(timezone.utc).isoformat(),
                "progress": 100,
                "last_message": f"{result['title']} is live.",
                "preview_url": result.get("preview_url"),
                "quality_score": result.get("quality_score"),
                "warning_count": len(result.get("warnings") or []),
                "skip_critic_requested": bool(skip_critic),
                "critic_skipped": bool(result.get("critic_skipped")),
                "critic_timed_out": bool(result.get("critic_timed_out")),
                "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
            },
        )
        await report_stage(ctx, 100, f"Done. {result['title']} is live.")

        await record_chunked_finalize_success(state=state, result=result, durations_ms=durations_ms, ctx=ctx)

        return {
            "artifact_id": result["artifact_id"],
            "title": result["title"],
            "summary": build_summary(result["plan"]),
            "preview_url": result["preview_url"],
            "download_url": result["download_url"],
            "quality_score": result["quality_score"],
            "critic_skipped": result.get("critic_skipped"),
            "critic_timed_out": result.get("critic_timed_out"),
            "warnings": result["warnings"] or None,
            "notes": [
                "The chunk-built draft was rendered and published under the same artifact id.",
                "Older rendered revisions remain in the artifact state history.",
                *build_critic_public_notes(result.get("payload"), result.get("render_validation_summary")),
            ],
        }
    except Exception as exc:
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failed_resume_stage = None
        with contextlib.suppress(Exception):
            latest_failed_state = load_artifact_state(artifact_id)
            failed_resume_stage = normalize_render_checkpoint_state(
                latest_failed_state.get("render_checkpoint") if isinstance(latest_failed_state.get("render_checkpoint"), dict) else None
            ).get("stage")
        with contextlib.suppress(Exception):
            update_artifact_runtime_state(
                artifact_id,
                artifact_status="failed" if is_background_freeform else "draft",
                render_job_patch={
                    "status": "failed",
                    "mode": "freeform_generate_document" if is_background_freeform else render_job.get("mode") or "chunked_finalize",
                    "resume_stage": failed_resume_stage,
                    "finished_at": datetime.now(timezone.utc).isoformat(),
                    "failed_at": datetime.now(timezone.utc).isoformat(),
                    "progress": 100,
                    "last_message": (
                        (
                            "Background freeform generation failed, but the last completed render stage was checkpointed. Retry will resume instead of starting over."
                            if is_background_freeform
                            else "Finalize failed, but the last completed render stage was checkpointed. Retry will resume instead of starting over."
                        )
                        if failed_resume_stage
                        else ("Background freeform generation failed. Review the error and retry." if is_background_freeform else "Finalize failed. Review the error and retry.")
                    ),
                    "last_error_type": type(exc).__name__,
                    "last_error_message": str(exc),
                    "skip_critic_requested": bool(skip_critic or render_job.get("skip_critic_requested")),
                    "critic_timeout_seconds": SETTINGS.anthropic_critic_timeout_seconds,
                },
            )
        if is_background_freeform:
            await record_background_freeform_failure(
                prompt=str(state.get("prompt") or state.get("title") or "IterFact Document"),
                template_hint=state.get("template_hint"),
                theme_hint=state.get("theme_hint"),
                uploaded_content=str(state.get("uploaded_content") or ""),
                interaction_mode=state.get("interaction_mode"),
                structured_data=sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None,
                engine_override=normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None,
                section_count_requested=state.get("section_count_requested"),
                error=exc,
                durations_ms=durations_ms,
                stage=current_stage,
            )
        else:
            await record_chunked_finalize_failure(
                state=state,
                error=exc,
                durations_ms=durations_ms,
                stage=current_stage,
                ctx=ctx,
            )
        raise


async def regenerate_artifact_section_internal(
    artifact_id: str,
    section_index: int,
    updated_prompt: str | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    run_started = time.perf_counter()
    current_stage = "loading_artifact"
    durations_ms: dict[str, int] = {}
    provider_events: list[dict[str, Any]] = []
    warnings: list[dict[str, Any]] = []
    quality_score = 100
    render_validation_summary: dict[str, Any] = {}
    state: dict[str, Any] | None = None
    plan: dict[str, Any] | None = None
    sections: list[dict[str, Any]] = []
    normalized_index = 0
    original_prompt = "Improve this IterFact section."
    template_hint: str | None = None
    theme_hint: str | None = None
    structured_data: dict[str, Any] | None = None
    requested_engine_override: str | None = None
    interaction_mode: str | None = None

    try:
        state = load_artifact_state(artifact_id)
        plan = state.get("plan")
        if not isinstance(plan, dict):
            raise ValueError("The saved artifact state is missing a reusable assembly plan.")

        plan = normalize_plan(plan)
        validate_plan(plan)
        sections = plan.get("sections", []) or []
        if not sections:
            raise ValueError("This artifact does not have regenerable plan sections.")

        try:
            normalized_index = int(section_index)
        except (TypeError, ValueError) as exc:
            raise ValueError("section_index must be a 1-based integer.") from exc
        if normalized_index < 1 or normalized_index > len(sections):
            raise ValueError(f"section_index must be between 1 and {len(sections)} for this artifact.")

        original_prompt = str(state.get("prompt") or state.get("title") or "Improve this IterFact section.")
        template_hint = state.get("template_hint")
        theme_hint = state.get("theme_hint")
        structured_data = sanitize_structured_data(state.get("structured_data")) if isinstance(state.get("structured_data"), dict) else None
        requested_engine_override = normalize_engine_override(state.get("engine_override_requested")) if state.get("engine_override_requested") else None
        interaction_mode = resolve_interaction_mode(original_prompt, state.get("interaction_mode"))
        current_section = sections[normalized_index - 1]

        await report_stage(ctx, 10, "Loading the saved artifact state")
        current_stage = "planning_section"
        planner_started = time.perf_counter()
        await report_stage(ctx, 35, "Regenerating the selected section")
        regenerated_section, payload = await regenerate_single_section_with_anthropic(
            original_prompt=original_prompt,
            template_hint=template_hint,
            plan=plan,
            section_index=normalized_index,
            current_section=current_section,
            updated_prompt=updated_prompt,
            structured_data=structured_data,
            engine_override=requested_engine_override,
        )
        provider_events = payload.get("_provider_events") or []
        durations_ms["planning"] = int((time.perf_counter() - planner_started) * 1000)

        plan["sections"][normalized_index - 1] = regenerated_section
        if is_wrapper_engine(requested_engine_override):
            plan.setdefault("wrapper", {})["engine"] = requested_engine_override
        if theme_hint:
            plan["theme"] = theme_hint
        plan = apply_primary_engine_constraints(plan, requested_engine_override, structured_data)

        current_stage = "assembling"
        await report_stage(ctx, 65, "Rendering the updated artifact")
        assemble_started = time.perf_counter()
        html = assemble(plan, str(SETTINGS.vault_path / "engines"))
        title = plan.get("meta", {}).get("title", state.get("title") or "IterFact Document")
        html, warnings, quality_score, render_validation_summary = validate_delivery_ready_artifact(
            html=html,
            artifact_id=artifact_id,
            artifact_title=title,
            interaction_mode=interaction_mode,
            plan=plan,
            requested_section_count=state.get("section_count_requested") or len(sections),
            engine_override=requested_engine_override,
            prompt=original_prompt,
            template_hint=template_hint,
            uploaded_content=None,
            structured_data=structured_data,
            generation_mode="section_regeneration",
            payload=payload,
        )
        enforce_render_validation_or_raise(warnings, quality_score, render_validation_summary)
        artifact_id, local_path = write_local_output(title, html, artifact_id)
        durations_ms["assembling"] = int((time.perf_counter() - assemble_started) * 1000)

        storage = state.get("current", {}).get("storage") or "local"
        object_key = None
        preview_url = None
        download_url = None
        if SETTINGS.has_r2:
            current_stage = "uploading"
            await report_stage(ctx, 88, "Publishing the regenerated artifact")
            upload_started = time.perf_counter()
            storage = "r2"
            object_key, preview_url = upload_to_r2(local_path, artifact_id)
            download_url = preview_url
            durations_ms["uploading"] = int((time.perf_counter() - upload_started) * 1000)

        persist_artifact_state(
            artifact_id=artifact_id,
            title=title,
            prompt=original_prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            interaction_mode=interaction_mode,
            generation_mode="section_regeneration",
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=state.get("section_count_requested") or len(sections),
            plan=plan,
            local_path=local_path,
            storage=storage,
            object_key=object_key,
            preview_url=preview_url,
            warnings=warnings,
            quality_score=quality_score,
            render_validation_summary=render_validation_summary,
            regeneration_context={
                "section_index": normalized_index,
                "section_title": regenerated_section.get("title"),
                "updated_prompt": updated_prompt,
            },
        )

        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        await report_stage(ctx, 100, f"Done. Section {normalized_index} is updated.")

        section_prompt = (
            f"{original_prompt}\n\n[section_regeneration index={normalized_index}] "
            f"{updated_prompt.strip() if updated_prompt else 'Refresh this section while preserving the rest of the artifact.'}"
        )
        generation_event = build_generation_event(
            prompt=section_prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            uploaded_content=None,
            generation_mode="section_regeneration",
            interaction_mode=interaction_mode,
            telemetry_context=sanitize_telemetry_context({"source_surface": "mcp", "source_mode": "section_regeneration"}),
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=(state.get("section_count_requested") if isinstance(state, dict) else None) or len(sections) or None,
            warnings=warnings,
            quality_score=quality_score,
            render_validation_summary=render_validation_summary,
            ctx=ctx,
            plan=plan,
            payload=payload or {},
            artifact_id=artifact_id,
            storage=storage,
            local_path=local_path,
            object_key=object_key,
            preview_url=preview_url,
            durations_ms=durations_ms,
            upload_analysis=None,
        )
        append_jsonl(GENERATION_EVENTS_PATH, generation_event)
        emit_telemetry_event(build_metadata_telemetry_event_from_generation(generation_event, state=load_artifact_state(artifact_id)))
        emit_telemetry_event(
            build_regeneration_telemetry_event(
                artifact_id=artifact_id,
                section_index=normalized_index,
                telemetry_context={"source_surface": "mcp", "source_mode": "section_regeneration"},
            )
        )
        with contextlib.suppress(Exception):
            await sync_generation_event_to_supabase(
                generation_event=generation_event,
                provider_events=provider_events,
            )

        return {
            "artifact_id": artifact_id,
            "title": title,
            "section_index": normalized_index,
            "section_title": regenerated_section.get("title"),
            "summary": build_summary(plan),
            "preview_url": preview_url,
            "download_url": download_url,
            "quality_score": quality_score,
            "warnings": warnings or None,
            "notes": [
                f"Regenerated section {normalized_index} while preserving the rest of the artifact.",
                "A new version of the artifact was written under the same artifact id.",
            ],
        }
    except Exception as exc:
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failure_event = build_failure_event(
            prompt=updated_prompt or original_prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            uploaded_content=None,
            generation_mode="section_regeneration",
            interaction_mode=interaction_mode,
            telemetry_context=sanitize_telemetry_context({"source_surface": "mcp", "source_mode": "section_regeneration"}),
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=(state.get("section_count_requested") if isinstance(state, dict) else None) or len(sections) or None,
            ctx=ctx,
            stage=current_stage,
            error=exc,
            durations_ms=durations_ms,
            provider_events=provider_events,
            upload_analysis=None,
        )
        append_jsonl(FAILURE_EVENTS_PATH, failure_event)
        emit_telemetry_event(build_metadata_telemetry_event_from_failure(failure_event))
        with contextlib.suppress(Exception):
            await sync_failure_event_to_supabase(failure_event)
        raise


@mcp.tool(
    name="regenerate_section",
    description="Regenerate a single section of an existing IterFact artifact while keeping the rest of the document intact.",
)
async def regenerate_section(
    artifact_id: str,
    section_index: int,
    updated_prompt: str | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    return await regenerate_artifact_section_internal(
        artifact_id=artifact_id,
        section_index=section_index,
        updated_prompt=updated_prompt,
        ctx=ctx,
    )


@mcp.tool(
    name="generate_document",
    description="Generate an interactive IterFact document from a prompt, safely import supplied HTML, or route uploaded longform source material into a background chunked build. Standard freeform generation now returns immediately with artifact_id plus background status so MCP clients do not race the 45-second tool timeout. Supports Iterate and Fact presentation modes. If Claude already knows the story, wrapper, and section plan, pass artifact_plan to skip the server-side planner and go straight to render/publish. If skip_critic=true, IterFact skips the editorial critic and publishes as soon as delivery validation passes. Poll get_artifact_status until publication completes. If your client does not expose get_artifact_status yet, use finalize_artifact with the same artifact_id as a safe fallback status check while the background job is active or to resume a failed background freeform run from its last checkpoint.",
)
async def generate_document(
    prompt: str,
    template_hint: str | None = None,
    theme_hint: str | None = None,
    data: dict[str, Any] | None = None,
    engine_override: str | None = None,
    section_count: int | None = None,
    uploaded_content: str | None = None,
    upload_passthrough: bool | None = None,
    interaction_mode: str | None = None,
    skip_critic: bool = False,
    telemetry_context: dict[str, Any] | None = None,
    artifact_plan: dict[str, Any] | None = None,
    ctx: Context | None = None,
) -> dict[str, Any]:
    run_started = time.perf_counter()
    current_stage = "initializing"
    generation_mode = "planned_render"
    effective_interaction_mode = resolve_interaction_mode(prompt, interaction_mode)
    effective_telemetry_context = sanitize_telemetry_context(telemetry_context)
    structured_data: dict[str, Any] | None = None
    requested_engine_override: str | None = None
    requested_section_count: int | None = None
    durations_ms: dict[str, int] = {}
    plan: dict[str, Any] | None = None
    payload: dict[str, Any] | None = None
    provider_events: list[dict[str, Any]] = []
    artifact_id: str | None = None
    local_path: Path | None = None
    storage = "local"
    object_key = None
    preview_url = None
    download_url = None
    upload_analysis: UploadAnalysis | None = None
    warnings: list[dict[str, Any]] = []
    quality_score = 100
    render_validation_summary: dict[str, Any] = {}
    allow_upload_passthrough = bool(upload_passthrough)
    admin_request = request_is_admin(ctx)
    artifact_already_persisted = False
    chunk_count: int | None = None
    primary_engine_constraint: str | None = None
    supplied_plan: dict[str, Any] | None = None

    try:
        structured_data = sanitize_structured_data(data)
        requested_engine_override = normalize_engine_override(engine_override)
        requested_section_count = normalize_section_count(section_count)
        primary_engine_constraint = requested_engine_override or infer_primary_engine_from_request(
            prompt,
            template_hint,
            structured_data,
        )
        if isinstance(artifact_plan, dict):
            supplied_plan = prepare_client_supplied_artifact_plan(
                artifact_plan=artifact_plan,
                prompt=prompt,
                template_hint=template_hint,
                theme_hint=theme_hint,
                structured_data=structured_data,
                engine_override=requested_engine_override,
                section_count_requested=requested_section_count,
                require_sections=True,
            )
            primary_engine_constraint = (
                normalize_engine_id(
                    supplied_plan.get("wrapper", {}).get("engine")
                    if isinstance(supplied_plan.get("wrapper"), dict)
                    else None
                )
                or primary_engine_constraint
            )
        current_stage = "reading_request"
        await report_stage(ctx, 5, "Reading your request")
        if (
            supplied_plan is not None
            and not wants_uploaded_html_ingest(prompt, template_hint, uploaded_content)
        ) or (
            not wants_uploaded_html_ingest(prompt, template_hint, uploaded_content)
            and not should_use_chunked_uploaded_document_builder(prompt, template_hint, uploaded_content)
        ):
            current_stage = "background_queue"
            queued_state = start_background_freeform_generate_document_job(
                prompt=prompt,
                template_hint=template_hint,
                theme_hint=theme_hint,
                uploaded_content=uploaded_content,
                structured_data=structured_data,
                requested_engine_override=requested_engine_override,
                requested_section_count=requested_section_count,
                primary_engine_constraint=primary_engine_constraint,
                interaction_mode=effective_interaction_mode,
                artifact_plan=supplied_plan,
                skip_critic=skip_critic,
            )
            plan = queued_state.get("plan") if isinstance(queued_state.get("plan"), dict) else None
            await report_stage(
                ctx,
                100,
                "Generation queued. Poll get_artifact_status for planning, rendering, and publication."
                if supplied_plan is None
                else "Render queued from the Claude-side story plan. Poll get_artifact_status for rendering and publication.",
            )
            return {
                "artifact_id": queued_state.get("artifact_id"),
                "title": queued_state.get("title"),
                "description": (plan or {}).get("meta", {}).get("description") if isinstance(plan, dict) else None,
                "summary": build_summary(plan) if isinstance(plan, dict) else None,
                "document_format": (plan or {}).get("meta", {}).get("document_format") if isinstance(plan, dict) else friendly_wrapper_name((plan or {}).get("wrapper", {}).get("engine") if isinstance(plan, dict) else None),
                "theme_name": friendly_theme_name((plan or {}).get("theme")) if isinstance(plan, dict) else None,
                "section_count": len((plan or {}).get("sections", [])) if isinstance(plan, dict) else 0,
                "section_titles": [section.get("title") for section in (plan or {}).get("sections", []) if section.get("title")] if isinstance(plan, dict) else [],
                "suggested_next_steps": build_follow_up_suggestions(prompt, plan) if isinstance(plan, dict) else None,
                "interaction_mode": effective_interaction_mode,
                "available_modes": ["iterate", "fact"] if effective_interaction_mode else None,
                "wrapper_engine_id": (plan or {}).get("wrapper", {}).get("engine") if isinstance(plan, dict) else None,
                "storage": None,
                "preview_url": None,
                "download_url": None,
                "ingest_decision": "not_applicable",
                "ingest_category": None,
                "ingest_preservation_level": None,
                "chunk_count": None,
                "artifact_status": queued_state.get("artifact_status"),
                "render_job": queued_state.get("render_job"),
                "planning_source": ((plan or {}).get("meta", {}) or {}).get("planning_source") if isinstance(plan, dict) else None,
                "prompt_stack_bundle_version": PROMPT_STACK_BRIDGE_VERSION,
                "active_prompt_modules": resolve_active_prompt_modules(
                    prompt=prompt,
                    template_hint=template_hint,
                    uploaded_content=None,
                    structured_data=structured_data,
                    generation_mode="planned_render",
                ),
                "iterative_refinement": None,
                "quality_score": None,
                "critic_skipped": False,
                "critic_timed_out": False,
                "warnings": None,
                "notes": [
                    *(
                        [
                            "Claude-side story planning was supplied in artifact_plan, so IterFact skipped the server-side planner and moved straight to render/publish.",
                            "Poll get_artifact_status with this artifact_id until artifact_status is published or render_job.status is failed.",
                        ]
                        if supplied_plan is not None
                        else [
                            "Freeform generate_document now runs asynchronously by default so the MCP client does not hit the 45-second tool timeout.",
                            "Poll get_artifact_status with this artifact_id until artifact_status is published or render_job.status is failed.",
                        ]
                    ),
                    "If your client does not expose get_artifact_status yet, call finalize_artifact with the same artifact_id to check status without starting a duplicate render.",
                    "If the background freeform run later fails after saving a checkpoint, call finalize_artifact with the same artifact_id to resume from that stage instead of starting over.",
                    *(
                        ["Critic is being skipped for this generation because skip_critic=true."]
                        if skip_critic
                        else []
                    ),
                ],
            }
        if wants_uploaded_html_ingest(prompt, template_hint, uploaded_content):
            if allow_upload_passthrough and not admin_request:
                raise PermissionError(
                    "Raw passthrough uploads are restricted to IterFact admin accounts."
                )

            if allow_upload_passthrough:
                generation_mode = "admin_passthrough_html"
                current_stage = "passthrough_upload"
                await report_stage(ctx, 20, "Inspecting the uploaded HTML")
                upload_analysis = analyze_uploaded_html(uploaded_content or "", prompt)
                await report_stage(ctx, 48, "Publishing the original HTML runtime under admin passthrough")
                passthrough_started = time.perf_counter()
                title = extract_html_title(uploaded_content or "") or prompt.strip()[:120] or "IterFact Passthrough Upload"
                artifact_id = str(uuid.uuid4())
                plan = build_passthrough_upload_plan(prompt, upload_analysis, title)
                payload = {"model": None, "usage": {}, "repair_applied": False, "retry_strategy": None}
                warnings = [
                    {
                        "type": "admin_passthrough",
                        "message": "Admin passthrough hosted the original uploaded HTML runtime without IterFact reconstruction.",
                    }
                ]
                quality_score = 100
                render_validation_summary = {
                    "passthrough": True,
                    "warning_count": len(warnings),
                }
                artifact_id, local_path = write_local_output(title, uploaded_content or "", artifact_id)
                admin_source_backup_local_path, admin_source_backup_object_key = persist_admin_source_backup(
                    artifact_id,
                    uploaded_content or "",
                )
                durations_ms["planning"] = 0
                durations_ms["assembling"] = int((time.perf_counter() - passthrough_started) * 1000)
            else:
                upload_analysis = analyze_uploaded_html(uploaded_content or "", prompt)
                current_stage = "upload_policy"
                upload_analysis.decision = "reject"
                upload_analysis.preservation_level = "none"
                upload_analysis.policy_profile = "passthrough_only"
                upload_analysis.rejection_reason = (
                    "IterFact no longer reconstructs uploaded HTML. Use a template or create from scratch in IterFact. "
                    "Admin operators can still host the original HTML with upload_passthrough=true."
                )
                add_upload_finding(
                    upload_analysis.findings,
                    "info",
                    "reconstruction_disabled",
                    "IterFact no longer reconstructs uploaded HTML into managed artifacts.",
                )
                raise UploadRejectedError(upload_analysis.rejection_reason, upload_analysis)
        else:
            if should_use_chunked_uploaded_document_builder(prompt, template_hint, uploaded_content):
                generation_mode = "chunked_planned_render"
                current_stage = "chunked_builder"
                chunk_started = time.perf_counter()
                background_state = await start_chunked_generate_document_job(
                    prompt=prompt,
                    template_hint=template_hint,
                    theme_hint=theme_hint,
                    uploaded_content=uploaded_content or "",
                    interaction_mode=effective_interaction_mode,
                    structured_data=structured_data,
                    engine_override=requested_engine_override,
                    section_count_requested=requested_section_count,
                    skip_critic=skip_critic,
                )
                durations_ms["planning"] = int((time.perf_counter() - chunk_started) * 1000)
                await report_stage(ctx, 100, "Chunked build queued. Poll get_artifact_status for progress and publication.")
                plan = background_state.get("plan") if isinstance(background_state.get("plan"), dict) else None
                payload = background_state.get("_draft_payload") if isinstance(background_state.get("_draft_payload"), dict) else {}
                chunk_count = background_state.get("_chunk_count")
                title = str(background_state.get("title") or "IterFact Draft")
                return {
                    "artifact_id": background_state.get("artifact_id"),
                    "title": title,
                    "description": (plan or {}).get("meta", {}).get("description") if isinstance(plan, dict) else None,
                    "summary": build_summary(plan) if isinstance(plan, dict) else None,
                    "document_format": (plan or {}).get("meta", {}).get("document_format") if isinstance(plan, dict) else friendly_wrapper_name((plan or {}).get("wrapper", {}).get("engine") if isinstance(plan, dict) else None),
                    "theme_name": friendly_theme_name((plan or {}).get("theme")) if isinstance(plan, dict) else None,
                    "section_count": len((plan or {}).get("sections", [])) if isinstance(plan, dict) else 0,
                    "section_titles": [section.get("title") for section in (plan or {}).get("sections", []) if section.get("title")] if isinstance(plan, dict) else [],
                    "interaction_mode": effective_interaction_mode,
                    "wrapper_engine_id": (plan or {}).get("wrapper", {}).get("engine") if isinstance(plan, dict) else None,
                    "storage": None,
                    "preview_url": None,
                    "download_url": None,
                    "ingest_decision": "not_applicable",
                    "ingest_category": None,
                    "ingest_preservation_level": None,
                    "chunk_count": chunk_count,
                    "artifact_status": background_state.get("artifact_status"),
                    "render_job": background_state.get("render_job"),
                    "prompt_stack_bundle_version": payload.get("prompt_stack_bundle_version") if isinstance(payload, dict) else None,
                    "active_prompt_modules": payload.get("active_prompt_modules") if isinstance(payload, dict) else None,
                    "iterative_refinement": None,
                    "quality_score": None,
                    "critic_skipped": False,
                    "critic_timed_out": False,
                    "warnings": None,
                    "notes": [
                        "Large uploaded source was routed into the chunked background builder so the MCP client does not time out while IterFact appends sections, validates, and renders.",
                        "Poll get_artifact_status with this artifact_id until artifact_status is published or render_job.status is failed.",
                        "If your client does not expose get_artifact_status yet, call finalize_artifact with the same artifact_id to check status without starting a duplicate render.",
                        *(
                            ["Critic is being skipped for this build because skip_critic=true."]
                            if skip_critic
                            else []
                        ),
                    ],
                }
            else:
                await report_stage(ctx, 18, "Choosing the best document format")
                await report_stage(ctx, 35, "Planning the document structure. Longer prompts can take around a minute.")

                current_stage = "planning"
                planner_started = time.perf_counter()
                plan, payload = await call_planner(
                    prompt=prompt,
                    template_hint=template_hint,
                    uploaded_content=uploaded_content,
                    upload_analysis=upload_analysis,
                    structured_data=structured_data,
                    engine_override=requested_engine_override,
                    section_count=requested_section_count,
                    generation_mode=generation_mode,
                )
                provider_events = payload.get("_provider_events") or []
                durations_ms["planning"] = int((time.perf_counter() - planner_started) * 1000)

                if theme_hint:
                    plan["theme"] = theme_hint
                if is_wrapper_engine(requested_engine_override):
                    plan.setdefault("wrapper", {})["engine"] = requested_engine_override
                plan = apply_primary_engine_constraints(plan, primary_engine_constraint, structured_data)

                current_stage = "assembling"
                await report_stage(ctx, 62, "Rendering the interactive document")
                assemble_started = time.perf_counter()
                plan, html, payload, warnings, quality_score, render_validation_summary = await render_plan_with_iterative_refinement(
                    prompt=prompt,
                    template_hint=template_hint,
                    uploaded_content=uploaded_content,
                    structured_data=structured_data,
                    generation_mode=generation_mode,
                    plan=plan,
                    payload=payload,
                    requested_section_count=requested_section_count,
                    engine_override=requested_engine_override,
                    skip_critic=skip_critic,
                    critic_timeout_seconds=SETTINGS.anthropic_critic_timeout_seconds,
                )
                title = plan.get("meta", {}).get("title", "IterFact Document")
                artifact_id = str(uuid.uuid4())
                html, warnings, quality_score, render_validation_summary = validate_delivery_ready_artifact(
                    html=html,
                    artifact_id=artifact_id,
                    artifact_title=title,
                    interaction_mode=effective_interaction_mode,
                    plan=plan,
                    requested_section_count=requested_section_count,
                    engine_override=requested_engine_override,
                    prompt=prompt,
                    template_hint=template_hint,
                    uploaded_content=uploaded_content,
                    structured_data=structured_data,
                    generation_mode=generation_mode,
                    payload=payload,
                )
                enforce_render_validation_or_raise(
                    warnings,
                    quality_score,
                    render_validation_summary,
                    allow_low_quality_publish=allow_nonfatal_low_quality_publish(payload, render_validation_summary),
                )
                artifact_id, local_path = write_local_output(title, html, artifact_id)
                durations_ms["assembling"] = int((time.perf_counter() - assemble_started) * 1000)

        critic_skipped, critic_timed_out = critic_outcome_flags(payload, render_validation_summary)
        public_score = public_quality_score(quality_score, payload, render_validation_summary)
        if SETTINGS.has_r2 and not artifact_already_persisted:
            current_stage = "uploading"
            await report_stage(ctx, 88, "Publishing your preview link")
            upload_started = time.perf_counter()
            storage = "r2"
            object_key, preview_url = upload_to_r2(local_path, artifact_id)
            download_url = preview_url
            durations_ms["uploading"] = int((time.perf_counter() - upload_started) * 1000)

        if not artifact_already_persisted:
            persist_artifact_state(
                artifact_id=artifact_id,
                title=title,
                prompt=prompt,
                template_hint=template_hint,
                theme_hint=theme_hint,
                interaction_mode=effective_interaction_mode,
                generation_mode=generation_mode,
                structured_data=structured_data,
                engine_override=requested_engine_override,
                section_count_requested=requested_section_count,
                plan=plan,
                local_path=local_path,
                storage=storage,
                object_key=object_key,
                preview_url=preview_url,
                warnings=warnings,
                quality_score=public_score,
                render_validation_summary=render_validation_summary,
                regeneration_context=(
                    {
                        "admin_source_backup_local_path": str(admin_source_backup_local_path)
                        if admin_source_backup_local_path
                        else None,
                        "admin_source_backup_object_key": admin_source_backup_object_key,
                    }
                    if generation_mode == "admin_passthrough_html"
                    else None
                ),
                critic_skipped=critic_skipped,
                critic_timed_out=critic_timed_out,
                uploaded_content=uploaded_content,
            )

        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        await report_stage(ctx, 100, f"Done. {title} is ready.")

        theme_name = friendly_theme_name(plan.get("theme"))
        section_titles = [section.get("title") for section in plan.get("sections", []) if section.get("title")]
        generation_event = build_generation_event(
            prompt=prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            uploaded_content=uploaded_content,
            generation_mode=generation_mode,
            interaction_mode=effective_interaction_mode,
            telemetry_context=effective_telemetry_context,
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=requested_section_count,
            warnings=warnings,
            quality_score=public_score,
            render_validation_summary=render_validation_summary,
            ctx=ctx,
            plan=plan,
            payload=payload or {},
            artifact_id=artifact_id,
            storage=storage,
            local_path=local_path,
            object_key=object_key,
            preview_url=preview_url,
            durations_ms=durations_ms,
            upload_analysis=upload_analysis,
        )
        append_jsonl(GENERATION_EVENTS_PATH, generation_event)
        emit_telemetry_event(build_metadata_telemetry_event_from_generation(generation_event, state=load_artifact_state(artifact_id)))
        with contextlib.suppress(Exception):
            await sync_generation_event_to_supabase(
                generation_event=generation_event,
                provider_events=provider_events,
            )

        notes: list[str] = []
        if generation_mode == "admin_passthrough_html":
            notes.append("Admin passthrough hosted the original uploaded HTML runtime without reconstruction or delivery-shell rewrite. IterFact only injected the lightweight ifx.state runtime at publish time.")
        elif generation_mode == "safe_rebuild_html":
            notes.append("Safely rebuilt the uploaded HTML by reconstructing only the visible structure IterFact could preserve confidently.")
        elif generation_mode == "guided_reconstruction":
            notes.append("Reconstructed the uploaded HTML into a new IterFact instead of hosting the original runtime directly.")
        if effective_interaction_mode == "iterate":
            notes.append("Opened in Iterate mode. Click any visible region in the artifact to leave a targeted note, then switch to Fact when you want a cleaner view.")
        elif effective_interaction_mode == "fact":
            notes.append("Opened in Fact mode. The iteration layer is hidden by default, and viewers can switch back to Iterate inside the artifact if needed.")
        if upload_analysis and upload_analysis.findings:
            notes.append(
                f"Upload ingest path: {('admin passthrough' if generation_mode == 'admin_passthrough_html' else upload_analysis.decision.replace('_', ' '))} ({upload_analysis.category.replace('_', ' ')})."
            )
        if requested_engine_override:
            if is_wrapper_engine(requested_engine_override):
                notes.append(f"Wrapper override: engine {requested_engine_override}.")
            else:
                notes.append(f"Primary component override: engine {requested_engine_override}.")
        if structured_data:
            notes.append("Used exact structured data supplied with the request.")
        if payload and payload.get("retry_strategy") == "quality-critic":
            notes.append("The planner was run through one additional prompt-stack critic pass before publishing because the first valid plan looked weak or misaligned.")
        refinement_summary = (payload or {}).get("iterative_refinement") if isinstance(payload, dict) else None
        if isinstance(refinement_summary, dict) and refinement_summary.get("applied"):
            notes.append(
                f"Ran an automated generate-evaluate-fix loop across {refinement_summary.get('passes_run') or 0} pass(es) before publishing."
            )
        if payload and payload.get("uploaded_content_compacted"):
            strategy = payload.get("uploaded_content_strategy") or "compacted_summary"
            heading_count = payload.get("uploaded_heading_count_preserved") or payload.get("uploaded_heading_count_detected") or 0
            notes.append(
                f"Large uploaded source was compacted for planning ({strategy}); preserved {heading_count} detected headings in the planner context."
            )
        if generation_mode == "chunked_planned_render" and chunk_count:
            notes.append(
                f"Built through the chunked document builder across {chunk_count} source section(s) before publishing the final artifact."
            )
        notes.extend(build_critic_public_notes(payload, render_validation_summary))

        return {
            "artifact_id": artifact_id,
            "title": title,
            "description": plan.get("meta", {}).get("description"),
            "summary": build_summary(plan),
            "document_format": plan.get("meta", {}).get("document_format")
            or friendly_wrapper_name(plan.get("wrapper", {}).get("engine")),
            "theme_name": theme_name,
            "section_count": len(plan.get("sections", [])),
            "section_titles": section_titles,
            "suggested_next_steps": build_follow_up_suggestions(prompt, plan),
            "interaction_mode": None if generation_mode == "admin_passthrough_html" else effective_interaction_mode,
            "available_modes": None if generation_mode == "admin_passthrough_html" else (["iterate", "fact"] if effective_interaction_mode else None),
            "wrapper_engine_id": plan.get("wrapper", {}).get("engine"),
            "storage": storage,
            "preview_url": preview_url,
            "download_url": download_url,
            "ingest_decision": "admin_passthrough" if generation_mode == "admin_passthrough_html" else upload_analysis.decision if upload_analysis else "not_applicable",
            "ingest_category": upload_analysis.category if upload_analysis else None,
            "ingest_preservation_level": upload_analysis.preservation_level if upload_analysis else None,
            "chunk_count": chunk_count,
            "prompt_stack_bundle_version": payload.get("prompt_stack_bundle_version") if payload else None,
            "active_prompt_modules": payload.get("active_prompt_modules") if payload else None,
            "iterative_refinement": payload.get("iterative_refinement") if payload else None,
            "quality_score": public_score,
            "critic_skipped": critic_skipped,
            "critic_timed_out": critic_timed_out,
            "warnings": warnings or None,
            "notes": notes or None,
        }
    except Exception as exc:
        durations_ms["total"] = int((time.perf_counter() - run_started) * 1000)
        failure_event = build_failure_event(
            prompt=prompt,
            template_hint=template_hint,
            theme_hint=theme_hint,
            uploaded_content=uploaded_content,
            generation_mode=generation_mode,
            interaction_mode=effective_interaction_mode,
            telemetry_context=effective_telemetry_context,
            structured_data=structured_data,
            engine_override=requested_engine_override,
            section_count_requested=requested_section_count,
            ctx=ctx,
            stage=current_stage,
            error=exc,
            durations_ms=durations_ms,
            provider_events=provider_events,
            upload_analysis=upload_analysis,
        )
        append_jsonl(
            FAILURE_EVENTS_PATH,
            failure_event,
        )
        emit_telemetry_event(build_metadata_telemetry_event_from_failure(failure_event))
        with contextlib.suppress(Exception):
            await sync_failure_event_to_supabase(failure_event)
        raise


async def list_templates_v1(_: Request) -> JSONResponse:
    return JSONResponse(list_public_templates())


async def telemetry_summary_v1(request: Request) -> JSONResponse:
    days_raw = request.query_params.get("days")
    with contextlib.suppress(Exception):
        if days_raw is not None:
            return JSONResponse(build_telemetry_summary(days=int(days_raw)))
    return JSONResponse(build_telemetry_summary())


async def telemetry_cost_audit_v1(request: Request) -> JSONResponse:
    if not SETTINGS.has_supabase_telemetry:
        return JSONResponse(
            {"error": "Supabase telemetry is not configured for cost audit."},
            status_code=503,
        )

    artifact_id = compact_whitespace(str(request.query_params.get("artifact_id") or "")) or None
    with contextlib.suppress(Exception):
        days = int(request.query_params.get("days") or 30)
    if "days" not in locals():
        days = 30
    with contextlib.suppress(Exception):
        limit = int(request.query_params.get("limit") or 25)
    if "limit" not in locals():
        limit = 25

    try:
        payload = await build_supabase_cost_audit(
            days=days,
            artifact_id=artifact_id,
            limit=limit,
        )
    except Exception as exc:
        LOGGER.exception("Failed to build telemetry cost audit")
        return JSONResponse({"error": str(exc)}, status_code=500)
    return JSONResponse(payload)


async def normalize_template_input_v1(request: Request) -> JSONResponse:
    template_id = str(request.path_params.get("template_id") or "").strip()
    body = await request.json()
    raw_input = str(body.get("raw_input") or body.get("rawInput") or "").strip()
    input_mode = body.get("input_mode") or body.get("inputMode")
    try:
        payload = normalize_template_instance_input(
            template_id=template_id,
            raw_input=raw_input,
            input_mode=str(input_mode).strip() if input_mode is not None else None,
        )
    except ValueError as exc:
        return JSONResponse({"error": str(exc), "template_id": template_id}, status_code=400)
    return JSONResponse(payload)


async def match_template_v1(request: Request) -> JSONResponse:
    body = await request.json()
    prompt = str(body.get("prompt") or "").strip()
    top_k = body.get("top_k") or body.get("topK") or 3
    try:
        payload = match_public_templates(prompt=prompt, top_k=max(1, min(int(top_k or 3), 10)))
    except ValueError as exc:
        return JSONResponse({"error": str(exc)}, status_code=400)
    return JSONResponse(payload)


async def generate_from_template_v1(request: Request) -> JSONResponse:
    template_id = str(request.path_params.get("template_id") or "").strip()
    body = await request.json()
    try:
        result = await run_template_instance_generation(
            template_id=template_id,
            raw_input=body.get("raw_input") or body.get("rawInput"),
            normalized_input=body.get("normalized_input") or body.get("normalizedInput"),
            compose=body.get("compose"),
            input_mode=body.get("input_mode") or body.get("inputMode"),
            display_name=body.get("display_name") or body.get("displayName"),
            network_label=body.get("network_label") or body.get("networkLabel"),
            interaction_mode=body.get("interaction_mode") or body.get("interactionMode"),
            telemetry_context=body.get("telemetry_context") or body.get("telemetryContext"),
            ctx=None,
        )
    except ValueError as exc:
        return JSONResponse({"error": str(exc), "template_id": template_id}, status_code=400)
    return JSONResponse(result)


middleware: list[Middleware] = []
middleware.append(
    Middleware(
        CORSMiddleware,
        allow_origins=MCP_CORS_ORIGINS,
        allow_origin_regex=r"http://(127\.0\.0\.1|localhost|\[::1\])(:\d+)?",
        allow_methods=["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
        allow_headers=[
            "accept",
            "authorization",
            "content-type",
            LAST_EVENT_ID_HEADER,
            MCP_PROTOCOL_VERSION_HEADER,
            MCP_SESSION_ID_HEADER,
        ],
        expose_headers=[
            MCP_SESSION_ID_HEADER,
        ],
        max_age=86400,
    )
)
if SETTINGS.bearer_token or SETTINGS.admin_bearer_token:
    middleware.append(
        Middleware(
            BearerAuthMiddleware,
            token=SETTINGS.bearer_token,
            admin_token=SETTINGS.admin_bearer_token,
        )
    )


@contextlib.asynccontextmanager
async def lifespan(_: Starlette):
    async with mcp.session_manager.run():
        yield


app = Starlette(
    routes=[
        Route("/", root),
        Route("/healthz", healthz),
        Route("/v1/artifacts/{artifact_id:str}/status", artifact_status_v1, methods=["GET"]),
        Route("/v1/artifacts/{artifact_id:str}/versions", artifact_versions_v1, methods=["GET"]),
        Route("/v1/artifacts/{artifact_id:str}/sections", artifact_sections_v1, methods=["GET"]),
        Route("/v1/artifacts/{artifact_id:str}/sections/regenerate", artifact_section_regenerate_v1, methods=["POST"]),
        Route("/v1/artifacts/{artifact_id:str}/versions/public", artifact_versions_public_v1, methods=["GET"]),
        Route("/v1/artifacts/{artifact_id:str}/versions/primary", artifact_primary_version_v1, methods=["POST"]),
        Route("/v1/artifacts/{artifact_id:str}/versions/visibility", artifact_public_visibility_v1, methods=["POST"]),
        Route("/v1/artifacts/{artifact_id:str}/feedback", artifact_feedback_v1, methods=["GET", "POST"]),
        Route("/v1/artifacts/{artifact_id:str}/feedback/apply", artifact_feedback_apply_v1, methods=["POST"]),
        Route("/v1/artifacts/{artifact_id:str}/feedback/{note_id:str}", artifact_feedback_note_v1, methods=["PATCH"]),
        Route("/favicon.ico", favicon_ico),
        Route("/favicon-16.png", favicon_16),
        Route("/favicon-32.png", favicon_32),
        Route("/favicon.svg", favicon_svg),
        Route("/logo-nav.png", logo_nav),
        Route("/v1/telemetry/summary", telemetry_summary_v1, methods=["GET"]),
        Route("/v1/telemetry/cost-audit", telemetry_cost_audit_v1, methods=["GET"]),
        Route("/v1/telemetry/view", telemetry_view_v1, methods=["POST", "OPTIONS"]),
        Route("/v1/telemetry/engagement", telemetry_engagement_v1, methods=["POST", "OPTIONS"]),
        Route("/v1/templates", list_templates_v1, methods=["GET"]),
        Route("/v1/templates/match", match_template_v1, methods=["POST"]),
        Route("/v1/templates/{template_id:str}/normalize-input", normalize_template_input_v1, methods=["POST"]),
        Route("/v1/templates/{template_id:str}/generate", generate_from_template_v1, methods=["POST"]),
        Mount("/", app=mcp_http_app),
    ],
    middleware=middleware,
    lifespan=lifespan,
)


if __name__ == "__main__":
    uvicorn.run(app, host=SETTINGS.host, port=SETTINGS.port)
