"""
Reporting models and console/HTML/JSON reporters.

Rich console output with full colour — far better than competitors'
plain-text logging.
"""

from __future__ import annotations

import json
import time
from dataclasses import asdict, dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

try:
    from rich.console import Console
    from rich.table import Table
    from rich import print as rprint
    _RICH = True
except ImportError:
    _RICH = False

_console = Console(highlight=False) if _RICH else None


@dataclass
class HealingEvent:
    """Record of a single healing attempt."""

    selector: str
    description: str
    url: str
    healed_selector: str = ""
    stage: str = ""            # ORIGINAL | HEURISTIC | DOM_FUZZY | AI_DOM | AI_VISUAL | CACHE | NONE
    success: bool = False
    confidence: float = 0.0
    latency_ms: float = 0.0
    tokens_used: int = 0
    provider: str = ""
    model: str = ""
    test_name: str = ""
    timestamp: float = field(default_factory=time.time)
    error: str = ""


@dataclass
class SessionReport:
    """Aggregate report for a test session."""

    session_id: str = ""
    start_time: float = field(default_factory=time.time)
    end_time: float = 0.0
    events: List[HealingEvent] = field(default_factory=list)

    @property
    def total(self) -> int:
        return len(self.events)

    @property
    def healed(self) -> int:
        return sum(1 for e in self.events if e.success and e.stage != "ORIGINAL")

    @property
    def from_cache(self) -> int:
        return sum(1 for e in self.events if e.stage == "CACHE")

    @property
    def failed(self) -> int:
        return sum(1 for e in self.events if not e.success)

    @property
    def total_tokens(self) -> int:
        return sum(e.tokens_used for e in self.events)

    @property
    def avg_latency_ms(self) -> float:
        lats = [e.latency_ms for e in self.events if e.latency_ms > 0]
        return sum(lats) / len(lats) if lats else 0.0

    def to_dict(self) -> dict:
        d = asdict(self)
        d["summary"] = {
            "total": self.total,
            "healed": self.healed,
            "from_cache": self.from_cache,
            "failed": self.failed,
            "total_tokens": self.total_tokens,
            "avg_latency_ms": round(self.avg_latency_ms, 1),
        }
        return d


class ConsoleReporter:
    """Rich coloured console output for healing events."""

    STAGE_COLORS = {
        "ORIGINAL":   "green",
        "CACHE":      "cyan",
        "HEURISTIC":  "blue",
        "DOM_FUZZY":  "magenta",
        "AI_DOM":     "yellow",
        "AI_VISUAL":  "bright_yellow",
        "NONE":       "red",
    }

    def log_event(self, event: HealingEvent, enabled: bool = True) -> None:
        if not enabled:
            return
        if _RICH and _console:
            self._log_rich(event)
        else:
            self._log_plain(event)

    def _log_rich(self, event: HealingEvent) -> None:
        assert _console
        stage = event.stage
        color = self.STAGE_COLORS.get(stage, "white")
        status = "[bold green]HEALED[/]" if event.success else "[bold red]FAILED[/]"
        symbol = "✓" if event.success else "✗"

        if stage == "ORIGINAL":
            _console.print(
                f"  [dim]{symbol}[/dim] [dim]ORIGINAL[/dim] [{color}]{event.selector}[/{color}]"
            )
        elif event.success:
            tokens_str = f" [{event.tokens_used}t]" if event.tokens_used else ""
            _console.print(
                f"  [bold green]{symbol}[/bold green] {status} "
                f"[{color}]{stage}[/{color}]"
                f"  [dim]{event.selector}[/dim] → [bold]{event.healed_selector}[/bold]"
                f"  [dim]{event.latency_ms:.0f}ms{tokens_str}[/dim]"
                f"  [dim]conf={event.confidence:.0%}[/dim]"
            )
        else:
            _console.print(
                f"  [bold red]{symbol}[/bold red] {status} "
                f"[dim]{event.selector}[/dim]"
                + (f"  [red]{event.error[:80]}[/red]" if event.error else "")
            )

    def _log_plain(self, event: HealingEvent) -> None:
        status = "SUCCESS" if event.success else "FAILED"
        print(
            f"  [{status}] [{event.stage}] {event.latency_ms:.0f}ms "
            f"{event.selector} -> {event.healed_selector}"
        )

    def print_summary(self, report: SessionReport, enabled: bool = True) -> None:
        if not enabled:
            return
        if _RICH and _console:
            self._summary_rich(report)
        else:
            self._summary_plain(report)

    def _summary_rich(self, report: SessionReport) -> None:
        assert _console
        table = Table(title="playwright-healer Session Summary", show_header=True)
        table.add_column("Metric", style="bold")
        table.add_column("Value")
        table.add_row("Total lookups", str(report.total))
        table.add_row("Healed (non-trivial)", str(report.healed))
        table.add_row("From cache", str(report.from_cache))
        table.add_row("Failed", str(report.failed) if report.failed == 0 else f"[red]{report.failed}[/red]")
        table.add_row("AI tokens used", str(report.total_tokens))
        table.add_row("Avg latency", f"{report.avg_latency_ms:.0f} ms")
        _console.print(table)

    def _summary_plain(self, report: SessionReport) -> None:
        print(f"\nplaywright-healer: {report.healed}/{report.total} healed "
              f"({report.from_cache} cached, {report.failed} failed)")


class HTMLReporter:
    """Generate a self-contained HTML report."""

    def generate(self, report: SessionReport, output_dir: str) -> str:
        out = Path(output_dir)
        out.mkdir(parents=True, exist_ok=True)
        ts = datetime.fromtimestamp(report.start_time).strftime("%Y%m%d_%H%M%S")
        filename = out / f"playwright_healer_{ts}.html"

        rows = ""
        for idx, e in enumerate(report.events):
            stage_class = e.stage.lower().replace("_", "-")
            status_class = "success" if e.success else ("failed" if not e.success else "original")

            # Token usage indicator
            if e.tokens_used and e.tokens_used > 0:
                token_cell = (
                    f'<span class="token-badge ai">'
                    f'<span class="token-dot ai-dot"></span>'
                    f'{e.tokens_used} tokens</span>'
                )
            else:
                token_cell = (
                    f'<span class="token-badge free">'
                    f'<span class="token-dot free-dot"></span>'
                    f'FREE</span>'
                )

            # Confidence bar
            conf_pct = int(e.confidence * 100)
            conf_color = "#22c55e" if conf_pct >= 80 else "#f59e0b" if conf_pct >= 50 else "#f87171"
            conf_bar = (
                f'<div class="conf-wrap">'
                f'<div class="conf-bar" style="width:{conf_pct}%;background:{conf_color}"></div>'
                f'<span class="conf-label">{conf_pct}%</span>'
                f'</div>'
            ) if e.success else '<span class="dim-text">—</span>'

            # Healed selector cell
            if e.healed_selector and e.healed_selector != e.selector:
                healed_cell = f'<code class="selector healed">{_esc(e.healed_selector)}</code>'
            elif e.stage == "ORIGINAL":
                healed_cell = '<span class="dim-text">no change</span>'
            elif not e.success:
                healed_cell = '<span class="err-text">not found</span>'
            else:
                healed_cell = f'<code class="selector">{_esc(e.healed_selector)}</code>'

            # Provider + model detail
            provider_cell = ""
            if e.provider:
                provider_cell = f'<span class="provider-name">{_esc(e.provider)}</span>'
                if e.model:
                    provider_cell += f'<br><span class="model-name">{_esc(e.model)}</span>'
            else:
                provider_cell = '<span class="dim-text">—</span>'

            # Error detail
            error_row = ""
            if e.error:
                error_row = (
                    f'<tr class="detail-row" id="detail-{idx}">'
                    f'<td colspan="9"><div class="error-detail">'
                    f'<span class="err-label">ERROR</span> {_esc(e.error)}'
                    f'</div></td></tr>'
                )

            # Description + URL tooltip row (expandable)
            desc_parts = []
            if e.description and e.description != e.selector:
                desc_parts.append(f'<span class="desc-text">{_esc(e.description)}</span>')
            if e.url:
                desc_parts.append(f'<a class="url-link" href="{_esc(e.url)}" target="_blank">{_esc(e.url[:60])}{"…" if len(e.url) > 60 else ""}</a>')
            ts_str = datetime.fromtimestamp(e.timestamp).strftime("%H:%M:%S")

            rows += f"""
            <tr class="event-row {status_class}" data-stage="{e.stage}" data-status="{'success' if e.success else 'failed'}">
              <td><span class="test-name">{_esc(e.test_name) if e.test_name else '<span class="dim-text">—</span>'}</span>
                  <br><span class="ts-label">{ts_str}</span></td>
              <td><code class="selector orig">{_esc(e.selector)}</code>
                  {'<br>' + ' '.join(desc_parts) if desc_parts else ''}</td>
              <td>{healed_cell}</td>
              <td><span class="badge {stage_class}">{e.stage}</span></td>
              <td>{conf_bar}</td>
              <td><span class="latency">{e.latency_ms:.0f}<span class="unit">ms</span></span></td>
              <td>{token_cell}</td>
              <td>{provider_cell}</td>
            </tr>{error_row}"""

        # Stage breakdown counts
        from collections import Counter
        stage_counts = Counter(e.stage for e in report.events)
        stage_breakdown = ""
        stage_order = ["ORIGINAL", "CACHE", "HEURISTIC", "DOM_FUZZY", "AI_DOM", "AI_VISUAL", "NONE"]
        for s in stage_order:
            c = stage_counts.get(s, 0)
            if c:
                sc = s.lower().replace("_", "-")
                stage_breakdown += (
                    f'<div class="sb-item">'
                    f'<span class="badge {sc}">{s}</span>'
                    f'<span class="sb-count">{c}</span>'
                    f'</div>'
                )

        ai_cost_note = ""
        if report.total_tokens:
            ai_events = [e for e in report.events if e.tokens_used]
            providers_used = ", ".join(sorted({e.provider for e in ai_events if e.provider}))
            ai_cost_note = (
                f'<div class="ai-note">'
                f'<span class="ai-note-icon">🤖</span>'
                f'AI was used for <strong>{len(ai_events)}</strong> heal(s) — '
                f'<strong>{report.total_tokens}</strong> total tokens consumed'
                f'{(" via " + providers_used) if providers_used else ""}.'
                f'</div>'
            )
        else:
            ai_cost_note = (
                '<div class="ai-note free-note">'
                '<span class="ai-note-icon">✨</span>'
                'All heals were <strong>FREE</strong> — heuristic or cache, zero AI tokens used.'
                '</div>'
            )

        html = _HTML_TEMPLATE.format(
            ts=datetime.fromtimestamp(report.start_time).strftime("%Y-%m-%d %H:%M:%S"),
            session_id=report.session_id,
            total=report.total,
            healed=report.healed,
            cached=report.from_cache,
            failed=report.failed,
            tokens=report.total_tokens,
            avg_lat=f"{report.avg_latency_ms:.0f}",
            rows=rows,
            stage_breakdown=stage_breakdown,
            ai_cost_note=ai_cost_note,
        )
        filename.write_text(html, encoding="utf-8")
        return str(filename)


class JSONReporter:
    """Generate a machine-readable JSON report."""

    def generate(self, report: SessionReport, output_dir: str) -> str:
        out = Path(output_dir)
        out.mkdir(parents=True, exist_ok=True)
        ts = datetime.fromtimestamp(report.start_time).strftime("%Y%m%d_%H%M%S")
        filename = out / f"playwright_healer_{ts}.json"
        filename.write_text(
            json.dumps(report.to_dict(), indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
        return str(filename)


def _esc(s: str) -> str:
    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")


_HTML_TEMPLATE = """\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>playwright-healer \xb7 Session Report</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
  *, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}
  :root {{
    --bg:         #080808;
    --surface:    #111111;
    --surface2:   #181818;
    --surface3:   #202020;
    --border:     #2a2a2a;
    --border2:    #333333;
    --text:       #e8e8e8;
    --text-dim:   #555555;
    --text-muted: #888888;
    --accent:     #7c3aed;
    --accent-lt:  #a78bfa;
    --green:      #22c55e;
    --green-dim:  #14532d;
    --cyan:       #06b6d4;
    --cyan-dim:   #0c4a6e;
    --amber:      #f59e0b;
    --amber-dim:  #451a03;
    --red:        #f87171;
    --red-dim:    #450a0a;
    --blue:       #60a5fa;
    --blue-dim:   #1e3a5f;
    --purple:     #c084fc;
    --purple-dim: #3b0764;
    --radius:     10px;
    --radius-sm:  6px;
    --shadow:     0 4px 24px rgba(0,0,0,.6);
  }}
  body {{
    font-family: 'Manrope', -apple-system, BlinkMacSystemFont, sans-serif;
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    padding: 0 0 60px;
    font-size: 14px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
  }}
  /* ── Header ── */
  .header {{
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding: 24px 48px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
  }}
  .logo {{ display: flex; align-items: center; gap: 12px; }}
  .logo-icon {{
    width: 34px; height: 34px;
    background: linear-gradient(135deg, var(--accent), #4f46e5);
    border-radius: 8px;
    display: flex; align-items: center; justify-content: center;
    font-size: 16px;
  }}
  .logo-text {{ font-size: 17px; font-weight: 800; letter-spacing: -.3px; }}
  .logo-text span {{ color: var(--accent-lt); }}
  .header-meta {{ text-align: right; color: var(--text-muted); font-size: 12px; font-weight: 500; }}
  .header-meta strong {{ color: var(--text); display: block; font-size: 13px; margin-bottom: 2px; }}
  /* ── Layout ── */
  .container {{ padding: 36px 48px; max-width: 1600px; margin: 0 auto; }}
  .section-label {{
    font-size: 11px; font-weight: 700; letter-spacing: .1em;
    text-transform: uppercase; color: var(--text-muted); margin-bottom: 14px;
  }}
  /* ── Stats ── */
  .stats {{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: 12px;
    margin-bottom: 32px;
  }}
  .stat {{
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 18px 20px;
    position: relative;
    overflow: hidden;
  }}
  .stat::before {{
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--stat-color, var(--accent));
  }}
  .stat-value {{
    font-size: 26px; font-weight: 800;
    color: var(--stat-color, var(--accent));
    line-height: 1; margin-bottom: 6px; letter-spacing: -1px;
  }}
  .stat-label {{ font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }}
  /* ── AI note ── */
  .ai-note {{
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--amber);
    border-radius: var(--radius);
    padding: 12px 18px;
    margin-bottom: 28px;
    font-size: 13px; font-weight: 500;
    color: var(--text-muted);
    display: flex; align-items: center; gap: 10px;
  }}
  .ai-note.free-note {{ border-left-color: var(--green); }}
  .ai-note strong {{ color: var(--text); }}
  .ai-note-icon {{ font-size: 15px; flex-shrink: 0; }}
  /* ── Stage breakdown ── */
  .stage-breakdown {{ display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 28px; align-items: center; }}
  .sb-item {{
    display: flex; align-items: center; gap: 6px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 20px; padding: 4px 10px 4px 6px;
  }}
  .sb-count {{ font-size: 12px; font-weight: 700; color: var(--text-muted); }}
  /* ── Filters ── */
  .filters {{ display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 18px; align-items: center; }}
  .filter-label {{ font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; margin-right: 4px; }}
  .filter-btn {{
    background: var(--surface); border: 1px solid var(--border);
    color: var(--text-muted); font-family: 'Manrope', sans-serif;
    font-size: 12px; font-weight: 600; padding: 4px 12px;
    border-radius: 20px; cursor: pointer; transition: all .15s;
  }}
  .filter-btn:hover {{ border-color: var(--accent); color: var(--text); }}
  .filter-btn.active {{ background: var(--accent); border-color: var(--accent); color: #fff; }}
  /* ── Table ── */
  .table-wrap {{
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
  }}
  table {{ width: 100%; border-collapse: collapse; }}
  thead tr {{ background: var(--surface2); border-bottom: 1px solid var(--border2); }}
  th {{
    padding: 12px 16px; text-align: left;
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .07em; color: var(--text-muted); white-space: nowrap;
  }}
  .event-row td {{ padding: 11px 16px; border-bottom: 1px solid var(--border); vertical-align: top; }}
  .event-row:last-child td {{ border-bottom: none; }}
  .event-row:hover td {{ background: var(--surface2); }}
  /* ── Selectors ── */
  code.selector {{
    font-family: 'JetBrains Mono','Fira Code','Cascadia Code',monospace;
    font-size: 12px; background: var(--surface3); border: 1px solid var(--border);
    border-radius: 4px; padding: 2px 7px; color: var(--text);
    display: inline-block; max-width: 260px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle;
  }}
  code.selector.orig {{ color: var(--text-muted); }}
  code.selector.healed {{ color: var(--green); border-color: var(--green-dim); background: rgba(34,197,94,.06); }}
  /* ── Test meta ── */
  .test-name {{ font-size: 13px; font-weight: 600; color: var(--text); display: block; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
  .ts-label {{ font-size: 11px; color: var(--text-muted); font-weight: 400; margin-top: 2px; }}
  /* ── Badges ── */
  .badge {{
    display: inline-flex; align-items: center;
    padding: 3px 8px; border-radius: 5px;
    font-size: 11px; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; white-space: nowrap;
  }}
  .badge.original  {{ background: rgba(34,197,94,.12);   color: var(--green);  border: 1px solid var(--green-dim); }}
  .badge.cache     {{ background: rgba(6,182,212,.12);   color: var(--cyan);   border: 1px solid var(--cyan-dim); }}
  .badge.heuristic {{ background: rgba(96,165,250,.12);  color: var(--blue);   border: 1px solid var(--blue-dim); }}
  .badge.dom-fuzzy {{ background: rgba(192,132,252,.12); color: var(--purple); border: 1px solid var(--purple-dim); }}
  .badge.ai-dom    {{ background: rgba(245,158,11,.12);  color: var(--amber);  border: 1px solid var(--amber-dim); }}
  .badge.ai-visual {{ background: rgba(245,158,11,.15);  color: var(--amber);  border: 1px solid var(--amber-dim); }}
  .badge.none      {{ background: rgba(248,113,113,.12); color: var(--red);    border: 1px solid var(--red-dim); }}
  /* ── Confidence bar ── */
  .conf-wrap {{ display: flex; align-items: center; gap: 7px; min-width: 80px; }}
  .conf-bar {{ height: 5px; border-radius: 3px; flex: 1; max-width: 55px; min-width: 4px; }}
  .conf-label {{ font-size: 12px; font-weight: 700; color: var(--text-muted); min-width: 30px; }}
  /* ── Latency ── */
  .latency {{ font-weight: 700; font-size: 13px; color: var(--text); }}
  .latency .unit {{ font-size: 10px; font-weight: 500; color: var(--text-muted); margin-left: 1px; }}
  /* ── Token badge ── */
  .token-badge {{
    display: inline-flex; align-items: center; gap: 5px;
    padding: 3px 8px; border-radius: 5px;
    font-size: 11px; font-weight: 700; white-space: nowrap;
  }}
  .token-badge.free {{ background: rgba(34,197,94,.10);  color: var(--green); border: 1px solid var(--green-dim); }}
  .token-badge.ai   {{ background: rgba(245,158,11,.10); color: var(--amber); border: 1px solid var(--amber-dim); }}
  .token-dot {{ width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }}
  .free-dot {{ background: var(--green); }}
  .ai-dot   {{ background: var(--amber); }}
  /* ── Provider ── */
  .provider-name {{ font-size: 12px; font-weight: 700; color: var(--accent-lt); }}
  .model-name {{ font-size: 11px; color: var(--text-muted); font-weight: 400; }}
  /* ── Description & URL ── */
  .desc-text {{ font-size: 11px; color: var(--text-muted); font-style: italic; display: block; margin-top: 3px; }}
  .url-link {{ font-size: 10px; color: var(--text-dim); text-decoration: none; display: block; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }}
  .url-link:hover {{ color: var(--accent-lt); }}
  /* ── Error row ── */
  .detail-row td {{ padding: 0 16px 10px; border-bottom: 1px solid var(--border); }}
  .error-detail {{
    background: rgba(248,113,113,.06); border: 1px solid var(--red-dim);
    border-radius: var(--radius-sm); padding: 10px 14px;
    font-size: 12px; font-family: 'JetBrains Mono',monospace;
    color: var(--red); line-height: 1.5;
  }}
  .err-label {{ font-weight: 800; margin-right: 8px; font-size: 11px; letter-spacing: .05em; }}
  .err-text {{ color: var(--red); font-size: 12px; }}
  .dim-text {{ color: var(--text-dim); font-size: 12px; }}
  /* ── Footer ── */
  .footer {{ margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--border); text-align: center; font-size: 12px; color: var(--text-muted); font-weight: 500; }}
  .footer a {{ color: var(--accent-lt); text-decoration: none; }}
  /* ── Empty state ── */
  .empty {{ padding: 48px; text-align: center; color: var(--text-muted); font-size: 14px; }}
  /* ── Filter hidden ── */
  .hidden-row {{ display: none; }}
</style>
</head>
<body>

<div class="header">
  <div class="logo">
    <div class="logo-icon">\u26a1</div>
    <div class="logo-text">playwright-<span>healer</span></div>
  </div>
  <div class="header-meta">
    <strong>{ts}</strong>
    Session&nbsp;{session_id}
  </div>
</div>

<div class="container">

  <div class="section-label">Session Summary</div>
  <div class="stats">
    <div class="stat" style="--stat-color:var(--text-muted)">
      <div class="stat-value">{total}</div><div class="stat-label">Total Lookups</div>
    </div>
    <div class="stat" style="--stat-color:var(--green)">
      <div class="stat-value">{healed}</div><div class="stat-label">Healed</div>
    </div>
    <div class="stat" style="--stat-color:var(--cyan)">
      <div class="stat-value">{cached}</div><div class="stat-label">From Cache</div>
    </div>
    <div class="stat" style="--stat-color:var(--red)">
      <div class="stat-value">{failed}</div><div class="stat-label">Failed</div>
    </div>
    <div class="stat" style="--stat-color:var(--amber)">
      <div class="stat-value">{tokens}</div><div class="stat-label">AI Tokens</div>
    </div>
    <div class="stat" style="--stat-color:var(--accent-lt)">
      <div class="stat-value">{avg_lat}<span style="font-size:13px;font-weight:500;color:var(--text-muted)">ms</span></div>
      <div class="stat-label">Avg Latency</div>
    </div>
  </div>

  {ai_cost_note}

  <div class="section-label">Heal Stages</div>
  <div class="stage-breakdown">{stage_breakdown}</div>

  <div class="filters">
    <span class="filter-label">Filter</span>
    <button class="filter-btn active" onclick="setFilter('all',this)">All</button>
    <button class="filter-btn" onclick="setFilter('success',this)">Healed</button>
    <button class="filter-btn" onclick="setFilter('failed',this)">Failed</button>
    <button class="filter-btn" onclick="setStageFilter('ORIGINAL',this)">Original</button>
    <button class="filter-btn" onclick="setStageFilter('CACHE',this)">Cache</button>
    <button class="filter-btn" onclick="setStageFilter('HEURISTIC',this)">Heuristic</button>
    <button class="filter-btn" onclick="setStageFilter('DOM_FUZZY',this)">DOM Fuzzy</button>
    <button class="filter-btn" onclick="setStageFilter('AI_DOM',this)">AI DOM</button>
    <button class="filter-btn" onclick="setStageFilter('AI_VISUAL',this)">AI Visual</button>
  </div>

  <div class="section-label">Healing Events</div>
  <div class="table-wrap">
    <table>
      <thead>
        <tr>
          <th>Test&nbsp;/&nbsp;Time</th>
          <th>Original Selector</th>
          <th>Healed Selector</th>
          <th>Stage</th>
          <th>Confidence</th>
          <th>Latency</th>
          <th>Tokens</th>
          <th>Provider&nbsp;/&nbsp;Model</th>
        </tr>
      </thead>
      <tbody id="events-body">
        {rows}
        <tr id="empty-row" style="display:none">
          <td colspan="8" class="empty">No events match the current filter.</td>
        </tr>
      </tbody>
    </table>
  </div>

  <div class="footer">
    Generated by <a href="https://pypi.org/project/playwright-healer/" target="_blank">playwright-healer</a>
    &nbsp;\xb7&nbsp;
    <a href="https://github.com/playwright-healer/playwright-healer" target="_blank">GitHub</a>
  </div>
</div>

<script>
  var _cf = 'all', _cs = null;
  function setFilter(s,b){{ _cf=s; _cs=null; apply(); activate(b); }}
  function setStageFilter(s,b){{ _cs=s; _cf='all'; apply(); activate(b); }}
  function activate(b){{ document.querySelectorAll('.filter-btn').forEach(function(x){{x.classList.remove('active');}});b.classList.add('active'); }}
  function apply(){{
    var rows=document.querySelectorAll('#events-body .event-row'), v=0;
    rows.forEach(function(r){{
      var show=true;
      if(_cs){{ show=r.dataset.stage===_cs; }}
      else if(_cf!=='all'){{ show=r.dataset.status===_cf; }}
      r.classList.toggle('hidden-row',!show);
      if(show) v++;
      var n=r.nextElementSibling;
      if(n&&n.classList.contains('detail-row')) n.classList.toggle('hidden-row',!show);
    }});
    document.getElementById('empty-row').style.display=v===0?'':'none';
  }}
</script>
</body>
</html>"""
