# SPDX-License-Identifier: MIT
"""Render a deterministic SVG from the sanitized Langfuse evidence."""

from __future__ import annotations

import argparse
from html import escape
import json
from pathlib import Path
from typing import Dict, List

from run_integration import assert_shape


def scenario_map(payload: Dict[str, object], name: str) -> Dict[str, object]:
    scenarios = payload["scenarios"]
    if not isinstance(scenarios, list):
        raise ValueError("evidence has no scenario list")
    for scenario in scenarios:
        if isinstance(scenario, dict) and scenario.get("scenario") == name:
            return scenario
    raise ValueError(f"missing scenario: {name}")


def observation_map(scenario: Dict[str, object]) -> Dict[str, Dict[str, object]]:
    observations = scenario["observations"]
    if not isinstance(observations, list):
        raise ValueError("scenario has no observations")
    assert_shape(str(scenario["scenario"]), observations)
    return {
        str(item["metadata"]["logical_observation_id"]): item
        for item in observations
    }


def card(
    x: int,
    y: int,
    width: int,
    title: str,
    detail: str,
    color: str,
) -> str:
    return f"""
    <rect x="{x}" y="{y}" width="{width}" height="60" rx="12"
      fill="#151518" stroke="{color}" stroke-width="2"/>
    <circle cx="{x + 24}" cy="{y + 30}" r="7" fill="{color}"/>
    <text x="{x + 44}" y="{y + 26}" class="card-title">{escape(title)}</text>
    <text x="{x + 44}" y="{y + 45}" class="card-detail">{escape(detail)}</text>
    """


def render(payload: Dict[str, object]) -> str:
    retry = observation_map(scenario_map(payload, "retry"))
    terminal = observation_map(scenario_map(payload, "terminal"))
    versions = payload["versions"]
    unavailable = payload["unavailableExporter"]
    if not isinstance(versions, dict) or not isinstance(unavailable, dict):
        raise ValueError("evidence manifest is incomplete")

    rows = [
        ("retrieval-1", "retrieve-account-policy", "retrieval"),
        ("plan-1", "draft-plan", "recorded generation"),
        ("subscription-1", "get-subscription · attempt 1", "timeout retained"),
        ("subscription-2", "get-subscription · attempt 2", "success retained"),
        ("refund-1", "get-refund-window", "tool success"),
        ("answer-1", "compose-answer", "recorded generation"),
    ]
    palette = {
        "ERROR": "#ef6a6a",
        "WARNING": "#e2a832",
        "DEFAULT": "#5ad1a4",
    }

    cards: List[str] = []
    connectors: List[str] = []
    root_y = 190
    child_x = 230
    child_width = 600
    for index, (logical_id, title, note) in enumerate(rows):
        item = retry[logical_id]
        y = 320 + index * 76
        level = str(item["level"])
        color = palette[level]
        detail = f"{item['type']} · {level} · {note}"
        cards.append(card(child_x, y, child_width, title, detail, color))
        center_y = y + 30
        connectors.append(
            f'<path d="M 170 280 L 170 {center_y} L {child_x} {center_y}" '
            'fill="none" stroke="#4c4c55" stroke-width="2"/>'
        )

    terminal_refund = terminal["refund-1"]
    terminal_root = terminal["task-1"]
    sibling_levels = [
        terminal[logical_id]["level"]
        for logical_id in ("retrieval-1", "plan-1", "answer-1")
    ]
    if any(level == "ERROR" for level in sibling_levels):
        raise AssertionError("terminal scenario contains a failed sibling")

    return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="900"
  viewBox="0 0 1440 900" role="img"
  aria-labelledby="title description">
  <title id="title">Observed Langfuse agent trace shape</title>
  <desc id="description">One task with separate retry attempts, isolated errors,
  source-side redaction, and telemetry failure isolation.</desc>
  <style>
    text {{ font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }}
    .eyebrow {{ fill: #e2a832; font-size: 16px; font-weight: 700; letter-spacing: 1.8px; }}
    .title {{ fill: #f5f5f3; font-size: 38px; font-weight: 750; }}
    .subtitle {{ fill: #aaaab2; font-size: 17px; }}
    .panel-title {{ fill: #f5f5f3; font-size: 20px; font-weight: 700; }}
    .card-title {{ fill: #f5f5f3; font-size: 17px; font-weight: 650; }}
    .card-detail {{ fill: #9d9da7; font-size: 13px; }}
    .fact-title {{ fill: #f5f5f3; font-size: 16px; font-weight: 650; }}
    .fact-copy {{ fill: #aaaab2; font-size: 14px; }}
    .footer {{ fill: #777781; font-size: 13px; }}
  </style>
  <rect width="1440" height="900" fill="#0a0a0b"/>
  <text x="70" y="58" class="eyebrow">FIRST-PARTY TRACE EVIDENCE</text>
  <text x="70" y="106" class="title">One task. Separate tool attempts.</text>
  <text x="70" y="137" class="subtitle">Observed with Langfuse Python
    {escape(str(versions['langfusePython']))} and self-hosted server
    {escape(str(versions['langfuseServer']))}</text>

  <rect x="70" y="160" width="810" height="670" rx="20" fill="#101012"
    stroke="#26262b"/>
  <text x="110" y="202" class="panel-title">Retry scenario</text>
  <rect x="110" y="220" width="720" height="60" rx="12" fill="#151518"
    stroke="#5ad1a4" stroke-width="2"/>
  <circle cx="134" cy="250" r="7" fill="#5ad1a4"/>
  <text x="154" y="246" class="card-title">answer-support-question</text>
  <text x="154" y="265" class="card-detail">SPAN · DEFAULT · single root observation</text>
  {''.join(connectors)}
  {''.join(cards)}

  <rect x="910" y="160" width="460" height="670" rx="20" fill="#101012"
    stroke="#26262b"/>
  <text x="950" y="202" class="panel-title">What the run established</text>

  <rect x="950" y="228" width="380" height="92" rx="14" fill="#151518"/>
  <text x="974" y="258" class="fact-title">Retry history survives</text>
  <text x="974" y="283" class="fact-copy">Attempt 1 stays ERROR. Attempt 2 stays</text>
  <text x="974" y="304" class="fact-copy">DEFAULT. Neither overwrites the other.</text>

  <rect x="950" y="336" width="380" height="116" rx="14" fill="#151518"/>
  <text x="974" y="366" class="fact-title">A terminal error stays local</text>
  <circle cx="982" cy="394" r="6" fill="#ef6a6a"/>
  <text x="998" y="399" class="fact-copy">refund tool · {escape(str(terminal_refund['level']))}</text>
  <circle cx="982" cy="423" r="6" fill="#e2a832"/>
  <text x="998" y="428" class="fact-copy">task root · {escape(str(terminal_root['level']))}</text>

  <rect x="950" y="468" width="380" height="116" rx="14" fill="#151518"/>
  <text x="974" y="498" class="fact-title">Payloads are minimized</text>
  <text x="974" y="523" class="fact-copy">Input and output fields are absent.</text>
  <text x="974" y="544" class="fact-copy">Synthetic email and account IDs are</text>
  <text x="974" y="565" class="fact-copy">redacted before the exporter sees them.</text>

  <rect x="950" y="600" width="380" height="116" rx="14" fill="#151518"/>
  <text x="974" y="630" class="fact-title">Telemetry cannot own the result</text>
  <text x="974" y="655" class="fact-copy">Exporter endpoint: {escape(str(unavailable['endpoint']))}</text>
  <text x="974" y="676" class="fact-copy">Application result preserved:</text>
  <text x="1168" y="676" class="fact-copy" fill="#5ad1a4">YES</text>
  <text x="974" y="697" class="fact-copy">The failure path ran in a separate process.</text>

  <text x="950" y="766" class="footer">Deterministic fixture · 26 Aug 2026</text>
  <text x="950" y="789" class="footer">No prompts, credentials, or raw user data.</text>
  <text x="70" y="870" class="footer">FutureCraft · source:
    sanitized-trace-shapes-2026-08-26.json</text>
</svg>
"""


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    return parser.parse_args()


if __name__ == "__main__":
    arguments = parse_args()
    evidence = json.loads(arguments.input.read_text(encoding="utf-8"))
    arguments.output.write_text(render(evidence), encoding="utf-8")
