"""HTML generator for the history viewer.

Generates a self-contained HTML file with inline CSS following
the Optix Design System colors and typography.
"""

import html
import json
from pathlib import Path
from typing import Any

from tools.view_history.utils import format_timestamp, normalize_affected_files


class HistoryHtmlGenerator:
    """Generates self-contained HTML for viewing audit history."""

    TEMPLATE_DIR = Path(__file__).parent / "templates"
    STATIC_DIR = Path(__file__).parent / "static"

    SEVERITY_COLORS = {
        "critical": "#ef4444",
        "high": "#f97316",
        "medium": "#eab308",
        "low": "#6b7280",
        "info": "#3b82f6",
    }

    SEVERITY_BG = {
        "critical": "rgba(239, 68, 68, 0.15)",
        "high": "rgba(249, 115, 22, 0.15)",
        "medium": "rgba(234, 179, 8, 0.15)",
        "low": "rgba(107, 114, 128, 0.15)",
        "info": "rgba(59, 130, 246, 0.15)",
    }

    LENS_COLORS = {
        "security": "#ef4444",
        "a11y": "#3b82f6",
        "devops": "#22d3ee",
        "principal": "#a855f7",
    }

    LENS_ICONS = {
        "security": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>',
        "a11y": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="4" r="2"/><path d="M12 8v9m-4-4 4 4 4-4m-10-1h12"/></svg>',
        "devops": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m9 10 2 2-2 2m5-4 2 2-2 2"/></svg>',
        "principal": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18"/><path d="M18 17V9m-5 8V5m-5 12v-3"/></svg>',
    }

    CONFIDENCE_COLORS = {
        "exploring": "#6b7280",
        "low": "#f97316",
        "medium": "#eab308",
        "high": "#22c55e",
        "very_high": "#3dd68c",
        "certain": "#10b981",
    }

    ICONS = {
        "findings": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg>',
        "files": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>',
        "steps": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
        "duration": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
        "check": '<svg class="icon icon-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
        "clipboard": '<svg class="icon icon-xl" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>',
        "file": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>',
        "folder": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>',
        "map-pin": '<svg class="icon icon-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>',
        "shield": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>',
        "alert-triangle": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
        "chevron-down": '<svg class="icon expand-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',
        "download": '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>',
    }

    def __init__(self, project_name: str, audits: list[dict[str, Any]]) -> None:
        self.project_name = project_name
        self.audits = audits

    def _read_template(self, name: str) -> str:
        """Read a template file."""
        return (self.TEMPLATE_DIR / name).read_text()

    def _read_static(self, path: str) -> str:
        """Read a static file (CSS or JS)."""
        return (self.STATIC_DIR / path).read_text()

    def generate(self) -> str:
        """Generate complete self-contained HTML."""
        template = self._read_template("history.html")
        css = self._read_static("css/history.css")
        js = self._read_static("js/history.js")

        return (
            template.replace("{{PROJECT_NAME}}", html.escape(self.project_name))
            .replace("{{INLINE_CSS}}", css)
            .replace("{{INLINE_JS}}", js)
            .replace("{{SIDEBAR}}", self._generate_sidebar())
            .replace("{{HEADER}}", self._generate_header())
            .replace("{{AUDIT_LIST}}", self._generate_audit_list())
            .replace("{{COMPARISON_OVERLAY}}", self._generate_comparison_overlay())
            .replace("{{AUDIT_DATA}}", json.dumps(self._get_audit_data()))
        )

    def _get_audit_data(self) -> dict[str, Any]:
        """Get audit data for JavaScript."""
        audit_data = {}
        for i, audit in enumerate(self.audits):
            dir_name = audit.get("dir_name", f"audit-{i}")
            audit_data[dir_name] = {
                "dir_name": dir_name,
                "lens": audit.get("lens", "unknown"),
                "timestamp": audit.get("timestamp", ""),
                "confidence": audit.get("confidence", "exploring"),
                "total_findings": audit.get("summary", {}).get("total_findings", 0)
                or audit.get("total_findings", 0),
                "by_severity": audit.get("summary", {}).get("by_severity", {})
                or audit.get("by_severity", {}),
                "findings": audit.get("findings", []),
            }
        return audit_data

    def _generate_header(self) -> str:
        audit_count = len(self.audits)
        total_findings = sum(
            a.get("summary", {}).get("total_findings", 0) or a.get("total_findings", 0)
            for a in self.audits
        )

        export_buttons = f"""<div class="export-buttons">
            <button class="export-btn" onclick="exportJSON()" title="Export as JSON">
                {self.ICONS['download']} JSON
            </button>
            <button class="export-btn" onclick="exportCSV()" title="Export as CSV">
                {self.ICONS['download']} CSV
            </button>
            <button class="export-btn" onclick="exportMarkdown()" title="Export as Markdown">
                {self.ICONS['download']} MD
            </button>
        </div>"""

        return f"""<header class="header">
    <div class="header-content">
        <div class="logo">
            <div class="logo-icon">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="10"></circle>
                    <circle cx="12" cy="12" r="4"></circle>
                    <line x1="12" y1="2" x2="12" y2="6"></line>
                    <line x1="12" y1="18" x2="12" y2="22"></line>
                    <line x1="2" y1="12" x2="6" y2="12"></line>
                    <line x1="18" y1="12" x2="22" y2="12"></line>
                </svg>
            </div>
            <div>
                <div class="logo-text">Optix Audit History</div>
                <div class="project-name">{html.escape(self.project_name)}</div>
            </div>
        </div>
        <div class="stats">
            <div class="stat">
                <div class="stat-value">{audit_count}</div>
                <div class="stat-label">Audits</div>
            </div>
            <div class="stat">
                <div class="stat-value">{total_findings}</div>
                <div class="stat-label">Total Findings</div>
            </div>
            {export_buttons}
        </div>
    </div>
</header>"""

    def _generate_audit_list(self) -> str:
        if not self.audits:
            return self._generate_empty_state()

        cards = [
            self._generate_audit_card(i, audit) for i, audit in enumerate(self.audits)
        ]
        return f'<div class="audit-list">{"".join(cards)}</div>'

    def _generate_empty_state(self) -> str:
        return f"""<div class="empty-state">
    {self.ICONS['clipboard']}
    <div class="empty-state-title">No Audit History</div>
    <div class="empty-state-text">
        Run an audit (security_audit, a11y_audit, principal_audit, or devops_audit)
        to see results here.
    </div>
</div>"""

    def _generate_audit_card(self, index: int, audit: dict[str, Any]) -> str:
        dir_name = audit.get("dir_name", f"audit-{index}")
        lens = audit.get("lens", "unknown")
        timestamp = audit.get("timestamp", "")
        confidence = audit.get("confidence", "exploring")

        summary = audit.get("summary", {})
        total_findings = summary.get("total_findings", 0) or audit.get(
            "total_findings", 0
        )
        files_examined = summary.get("files_examined", 0) or audit.get(
            "files_examined", 0
        )
        steps_completed = summary.get("steps_completed", 0)
        duration_seconds = summary.get("duration_seconds")
        by_severity = summary.get("by_severity", {}) or audit.get("by_severity", {})

        lens_color = self.LENS_COLORS.get(lens, "#6b7c72")
        confidence_color = self.CONFIDENCE_COLORS.get(confidence, "#6b7280")
        formatted_time = format_timestamp(timestamp)
        duration_str = self._format_duration(duration_seconds)

        severity_pills = self._generate_severity_pills(by_severity)
        findings_tab = self._generate_findings_tab(audit)
        steps_tab = self._generate_steps_tab(audit)
        files_tab = self._generate_files_tab(audit)
        meta_tab = self._generate_meta_tab(audit)

        duration_html = (
            f'<span>{self.ICONS["duration"]}{duration_str}</span>' if duration_str else ""
        )

        return f"""<div class="audit-card" data-id="{html.escape(dir_name)}">
    <div class="audit-header" onclick="toggleCard(this.parentElement)">
        <div class="audit-meta">
            <div class="audit-top-row">
                <span class="lens-badge" style="background: {lens_color}20; color: {lens_color};">
                    {html.escape(lens)}
                </span>
                <span class="confidence-badge" style="background: {confidence_color}20; color: {confidence_color};">
                    {html.escape(confidence)}
                </span>
                <span class="timestamp">{html.escape(formatted_time)}</span>
            </div>
            <div class="audit-stats">
                <span>{self.ICONS['findings']}{total_findings} findings</span>
                <span>{self.ICONS['files']}{files_examined} files</span>
                <span>{self.ICONS['steps']}{steps_completed} steps</span>
                {duration_html}
            </div>
        </div>
        <div class="severity-pills">
            {severity_pills}
            {self.ICONS['chevron-down']}
        </div>
    </div>
    <div class="audit-detail">
        <div class="detail-tabs">
            <div class="detail-tab active" onclick="switchTab(this, 'findings-{index}')">Findings</div>
            <div class="detail-tab" onclick="switchTab(this, 'steps-{index}')">Steps</div>
            <div class="detail-tab" onclick="switchTab(this, 'files-{index}')">Files</div>
            <div class="detail-tab" onclick="switchTab(this, 'meta-{index}')">Metadata</div>
        </div>
        <div id="findings-{index}" class="tab-content active">
            {findings_tab}
        </div>
        <div id="steps-{index}" class="tab-content">
            {steps_tab}
        </div>
        <div id="files-{index}" class="tab-content">
            {files_tab}
        </div>
        <div id="meta-{index}" class="tab-content">
            {meta_tab}
        </div>
    </div>
</div>"""

    def _generate_severity_pills(self, by_severity: dict[str, int]) -> str:
        pills = []
        for severity in ["critical", "high", "medium", "low"]:
            count = by_severity.get(severity, 0)
            if count > 0:
                color = self.SEVERITY_COLORS.get(severity, "#6b7280")
                bg = self.SEVERITY_BG.get(severity, "rgba(107, 114, 128, 0.15)")
                pills.append(
                    f'<span class="severity-pill" style="background: {bg}; color: {color};">'
                    f"{count} {severity}</span>"
                )
        return (
            "".join(pills)
            if pills
            else '<span class="severity-pill" style="background: rgba(61, 214, 140, 0.15); color: #3dd68c;">Clean</span>'
        )

    def _generate_findings_tab(self, audit: dict[str, Any]) -> str:
        findings = audit.get("findings", [])
        if not findings:
            return f"""<div class="no-findings">
    {self.ICONS['check']}
    <div>No findings recorded</div>
</div>"""

        items = [self._generate_finding_item(f) for f in findings]
        return f"""<div class="section-title">Findings ({len(findings)})</div>
<div class="findings-list">
    {"".join(items)}
</div>"""

    def _generate_finding_item(self, finding: dict[str, Any]) -> str:
        severity = finding.get("severity", "info")
        category = finding.get("category", "Unknown")
        description = finding.get("description", "")
        remediation = finding.get("remediation", "")
        affected_files = finding.get("affected_files", [])

        color = self.SEVERITY_COLORS.get(severity, "#6b7280")
        bg = self.SEVERITY_BG.get(severity, "rgba(107, 114, 128, 0.15)")

        file_paths = normalize_affected_files(affected_files)
        files_html = self._generate_files_list(file_paths)

        remediation_html = ""
        if remediation:
            remediation_html = f"""<div class="finding-remediation">
    <div class="finding-remediation-title">Remediation</div>
    <div class="finding-remediation-text">{html.escape(remediation)}</div>
</div>"""

        return f"""<div class="finding-item" style="border-left-color: {color};">
    <div class="finding-header">
        <span class="finding-category">{html.escape(category)}</span>
        <span class="finding-severity" style="background: {bg}; color: {color};">
            {html.escape(severity)}
        </span>
    </div>
    <div class="finding-description">{html.escape(description)}</div>
    {remediation_html}
    <div class="finding-files">
        {self.ICONS['folder']}
        <div>
            <div>Affected files:</div>
            {files_html}
        </div>
    </div>
</div>"""

    def _generate_files_list(self, files: list[str]) -> str:
        if not files:
            return "<span>No files specified</span>"
        items = [f"<li>{html.escape(f)}</li>" for f in files]
        return f'<ul class="finding-files-list">{"".join(items)}</ul>'

    def _generate_steps_tab(self, audit: dict[str, Any]) -> str:
        steps = audit.get("step_history", [])
        if not steps:
            return f"""<div class="no-findings">
    {self.ICONS['map-pin']}
    <div>No step history recorded</div>
</div>"""

        items = [self._generate_step_item(s) for s in steps]
        return f"""<div class="section-title">Audit Steps ({len(steps)})</div>
<div class="steps-timeline">
    {"".join(items)}
</div>"""

    def _generate_step_item(self, step: dict[str, Any]) -> str:
        step_number = step.get("step_number", 0)
        name = step.get("name", "Unknown step")
        files_examined = step.get("files_examined", [])
        timestamp = step.get("timestamp", "")

        files_count = len(files_examined) if isinstance(files_examined, list) else 0
        time_str = format_timestamp(timestamp, "time")

        files_preview = ""
        if files_examined and isinstance(files_examined, list):
            preview_files = files_examined[:3]
            preview = ", ".join(preview_files)
            if len(files_examined) > 3:
                preview += f" +{len(files_examined) - 3} more"
            files_preview = (
                f'<div class="step-files">Files: {html.escape(preview)}</div>'
            )

        return f"""<div class="step-item">
    <div class="step-header">
        <span class="step-number">Step {step_number}</span>
        <span class="step-timestamp">{html.escape(time_str)}</span>
    </div>
    <div class="step-name">{html.escape(name)}</div>
    <div class="step-files">
        <span class="step-files-count">{files_count}</span> files examined
    </div>
    {files_preview}
</div>"""

    def _generate_files_tab(self, audit: dict[str, Any]) -> str:
        files = audit.get("files_checked", [])
        if not files:
            return f"""<div class="no-findings">
    {self.ICONS['folder']}
    <div>No files recorded</div>
</div>"""

        items = [
            f'<div class="file-item">{self.ICONS["file"]}<span>{html.escape(f)}</span></div>'
            for f in files
        ]
        return f"""<div class="section-title">Files Examined ({len(files)})</div>
<div class="files-grid">
    {"".join(items)}
</div>"""

    def _generate_meta_tab(self, audit: dict[str, Any]) -> str:
        continuation_id = audit.get("continuation_id", "N/A")
        version = audit.get("version", "N/A")
        project = audit.get("project", {})
        project_name = project.get("name", "N/A") if project else "N/A"
        project_root = project.get("root_path", "N/A") if project else "N/A"
        confidence = audit.get("confidence", "N/A")
        timestamp = audit.get("timestamp", "N/A")

        summary = audit.get("summary", {})
        duration = summary.get("duration_seconds")
        duration_str = self._format_duration(duration) if duration else "N/A"

        return f"""<div class="section-title">Audit Metadata</div>
<div class="meta-grid">
    <div class="meta-item">
        <div class="meta-label">Session ID</div>
        <div class="meta-value mono">{html.escape(str(continuation_id))}</div>
    </div>
    <div class="meta-item">
        <div class="meta-label">Version</div>
        <div class="meta-value">{html.escape(str(version))}</div>
    </div>
    <div class="meta-item">
        <div class="meta-label">Project Name</div>
        <div class="meta-value">{html.escape(str(project_name))}</div>
    </div>
    <div class="meta-item">
        <div class="meta-label">Project Root</div>
        <div class="meta-value mono">{html.escape(str(project_root))}</div>
    </div>
    <div class="meta-item">
        <div class="meta-label">Confidence</div>
        <div class="meta-value">{html.escape(str(confidence))}</div>
    </div>
    <div class="meta-item">
        <div class="meta-label">Duration</div>
        <div class="meta-value">{html.escape(duration_str)}</div>
    </div>
    <div class="meta-item">
        <div class="meta-label">Timestamp</div>
        <div class="meta-value mono">{html.escape(str(timestamp))}</div>
    </div>
</div>"""

    def _format_duration(self, seconds: int | None) -> str:
        if seconds is None:
            return ""
        if seconds < 60:
            return f"{seconds}s"
        minutes = seconds // 60
        remaining_seconds = seconds % 60
        if minutes < 60:
            return f"{minutes}m {remaining_seconds}s"
        hours = minutes // 60
        remaining_minutes = minutes % 60
        return f"{hours}h {remaining_minutes}m"

    def _group_audits_by_lens(self) -> dict[str, list[dict[str, Any]]]:
        """Group audits by lens type."""
        groups: dict[str, list[dict[str, Any]]] = {}
        for i, audit in enumerate(self.audits):
            lens = audit.get("lens", "unknown")
            if lens not in groups:
                groups[lens] = []
            audit_with_index = {**audit, "_index": i}
            groups[lens].append(audit_with_index)
        return groups

    def _generate_sidebar(self) -> str:
        """Generate the sidebar with lens groups and timeline."""
        lens_groups = self._group_audits_by_lens()
        groups_html = []

        for lens in ["security", "a11y", "devops", "principal"]:
            if lens not in lens_groups:
                continue
            audits = lens_groups[lens]
            lens_color = self.LENS_COLORS.get(lens, "#6b7c72")
            lens_icon = self.LENS_ICONS.get(lens, self.ICONS["shield"])

            timeline_items = []
            for audit in audits:
                dir_name = audit.get("dir_name", "")
                timestamp = audit.get("timestamp", "")
                summary = audit.get("summary", {})
                total_findings = summary.get("total_findings", 0) or audit.get(
                    "total_findings", 0
                )
                by_severity = summary.get("by_severity", {}) or audit.get(
                    "by_severity", {}
                )

                date_display = format_timestamp(timestamp, "short")
                severity_dots = self._generate_severity_dots(by_severity)

                timeline_items.append(
                    f"""<div class="timeline-item" data-id="{html.escape(dir_name)}" data-lens="{html.escape(lens)}" onclick="selectAudit(this)">
                    <input type="checkbox" class="timeline-checkbox" onclick="event.stopPropagation(); toggleCompareSelection(this)" data-id="{html.escape(dir_name)}" data-lens="{html.escape(lens)}">
                    <div class="timeline-date">{html.escape(date_display)}</div>
                    <div class="timeline-stats">
                        <span class="timeline-finding-count">{total_findings} findings</span>
                        <div class="timeline-severity-dots">{severity_dots}</div>
                    </div>
                </div>"""
                )

            groups_html.append(
                f"""<div class="lens-group" data-lens="{html.escape(lens)}">
                <div class="lens-group-header" onclick="toggleLensGroup(this.parentElement)" style="color: {lens_color};">
                    <span class="lens-group-icon">{lens_icon}</span>
                    <span class="lens-group-name">{html.escape(lens)}</span>
                    <span class="lens-group-count">{len(audits)}</span>
                    {self.ICONS['chevron-down'].replace('class="icon expand-chevron"', 'class="icon lens-group-chevron"')}
                </div>
                <div class="timeline">
                    {"".join(timeline_items)}
                </div>
            </div>"""
            )

        for lens, audits in lens_groups.items():
            if lens in ["security", "a11y", "devops", "principal"]:
                continue
            lens_color = "#6b7c72"
            lens_icon = self.ICONS["shield"]

            timeline_items = []
            for audit in audits:
                dir_name = audit.get("dir_name", "")
                timestamp = audit.get("timestamp", "")
                summary = audit.get("summary", {})
                total_findings = summary.get("total_findings", 0) or audit.get(
                    "total_findings", 0
                )
                by_severity = summary.get("by_severity", {}) or audit.get(
                    "by_severity", {}
                )

                date_display = format_timestamp(timestamp, "short")
                severity_dots = self._generate_severity_dots(by_severity)

                timeline_items.append(
                    f"""<div class="timeline-item" data-id="{html.escape(dir_name)}" data-lens="{html.escape(lens)}" onclick="selectAudit(this)">
                    <input type="checkbox" class="timeline-checkbox" onclick="event.stopPropagation(); toggleCompareSelection(this)" data-id="{html.escape(dir_name)}" data-lens="{html.escape(lens)}">
                    <div class="timeline-date">{html.escape(date_display)}</div>
                    <div class="timeline-stats">
                        <span class="timeline-finding-count">{total_findings} findings</span>
                        <div class="timeline-severity-dots">{severity_dots}</div>
                    </div>
                </div>"""
                )

            groups_html.append(
                f"""<div class="lens-group" data-lens="{html.escape(lens)}">
                <div class="lens-group-header" onclick="toggleLensGroup(this.parentElement)" style="color: {lens_color};">
                    <span class="lens-group-icon">{lens_icon}</span>
                    <span class="lens-group-name">{html.escape(lens)}</span>
                    <span class="lens-group-count">{len(audits)}</span>
                    {self.ICONS['chevron-down'].replace('class="icon expand-chevron"', 'class="icon lens-group-chevron"')}
                </div>
                <div class="timeline">
                    {"".join(timeline_items)}
                </div>
            </div>"""
            )

        return f"""<aside class="sidebar">
    <div class="sidebar-header">
        <div class="sidebar-logo">
            <div class="sidebar-logo-icon">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="12" cy="12" r="10"></circle>
                    <circle cx="12" cy="12" r="4"></circle>
                    <line x1="12" y1="2" x2="12" y2="6"></line>
                    <line x1="12" y1="18" x2="12" y2="22"></line>
                    <line x1="2" y1="12" x2="6" y2="12"></line>
                    <line x1="18" y1="12" x2="22" y2="12"></line>
                </svg>
            </div>
            <span class="sidebar-logo-text">Optix</span>
        </div>
    </div>
    <div class="compare-controls" id="compare-controls">
        <div class="compare-info">
            <span class="compare-count"><strong id="compare-count">0</strong> selected</span>
            <button class="btn btn-secondary" onclick="cancelCompare()">Cancel</button>
        </div>
        <div class="compare-buttons">
            <button class="btn btn-primary" id="compare-btn" disabled onclick="runComparison()">Compare</button>
        </div>
    </div>
    <div class="lens-groups">
        {"".join(groups_html)}
    </div>
</aside>"""

    def _generate_severity_dots(self, by_severity: dict[str, int]) -> str:
        """Generate mini severity dots for sidebar timeline."""
        dots = []
        for severity in ["critical", "high", "medium", "low"]:
            count = by_severity.get(severity, 0)
            if count > 0:
                color = self.SEVERITY_COLORS.get(severity, "#6b7280")
                for _ in range(min(count, 3)):
                    dots.append(
                        f'<span class="severity-dot" style="background: {color};"></span>'
                    )
        return "".join(dots)

    def _generate_comparison_overlay(self) -> str:
        """Generate the comparison overlay HTML."""
        close_icon = '<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>'
        arrow_icon = '<svg class="icon icon-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14m-7-7 7 7-7 7"/></svg>'
        check_icon = self.ICONS["check"]

        return f"""<div class="comparison-overlay" id="comparison-overlay">
    <div class="comparison-header">
        <span class="comparison-title">Audit Comparison</span>
        <button class="comparison-close" onclick="closeComparison()">{close_icon}</button>
    </div>
    <div class="comparison-content">
        <div class="comparison-cards">
            <div class="comparison-card">
                <div class="comparison-card-label">Before</div>
                <div class="comparison-card-date" id="comparison-before-date">-</div>
                <div class="comparison-card-findings" id="comparison-before-findings">-</div>
            </div>
            <div class="comparison-arrow">{arrow_icon}</div>
            <div class="comparison-card">
                <div class="comparison-card-label">After</div>
                <div class="comparison-card-date" id="comparison-after-date">-</div>
                <div class="comparison-card-findings" id="comparison-after-findings">-</div>
            </div>
        </div>

        <div class="comparison-trend" id="comparison-trend">
            <div class="trend-indicator" id="trend-indicator">→</div>
            <div class="trend-label" id="trend-label">STABLE</div>
            <div class="trend-percentage" id="trend-percentage"></div>
        </div>

        <div class="severity-delta-grid" id="severity-delta-grid"></div>

        <div class="breakdown-section">
            <div class="breakdown-tabs">
                <div class="breakdown-tab active" data-tab="resolved" onclick="showBreakdownTab('resolved')">
                    Resolved<span class="breakdown-tab-count" id="count-resolved">0</span>
                </div>
                <div class="breakdown-tab" data-tab="new" onclick="showBreakdownTab('new')">
                    New<span class="breakdown-tab-count" id="count-new">0</span>
                </div>
                <div class="breakdown-tab" data-tab="persisting" onclick="showBreakdownTab('persisting')">
                    Persisting<span class="breakdown-tab-count" id="count-persisting">0</span>
                </div>
                <div class="breakdown-tab" data-tab="improved" onclick="showBreakdownTab('improved')">
                    Improved<span class="breakdown-tab-count" id="count-improved">0</span>
                </div>
                <div class="breakdown-tab" data-tab="worsened" onclick="showBreakdownTab('worsened')">
                    Worsened<span class="breakdown-tab-count" id="count-worsened">0</span>
                </div>
            </div>
            <div class="breakdown-content">
                <div class="breakdown-panel active" id="panel-resolved">
                    <div class="breakdown-empty">
                        <div class="breakdown-empty-icon">{check_icon}</div>
                        <div>No resolved findings</div>
                    </div>
                </div>
                <div class="breakdown-panel" id="panel-new"></div>
                <div class="breakdown-panel" id="panel-persisting"></div>
                <div class="breakdown-panel" id="panel-improved"></div>
                <div class="breakdown-panel" id="panel-worsened"></div>
            </div>
        </div>
    </div>
</div>"""
