"""Procedural SVG visuals derived from case data.

Zero external image-gen, zero curated assets — every visual is generated
server-side from the case_id (deterministic per case) and severity. Cyber-HUD
aesthetic, theme-coordinated with cockpit.css.
"""

from __future__ import annotations

import hashlib
import math
from typing import Optional

from psyc.models import Case, Severity


_SEV_ACCENT = {
    Severity.CRITICAL: "#f87171",
    Severity.HIGH:     "#fbbf24",
    Severity.MEDIUM:   "#1ec8ff",
    Severity.LOW:      "#7d8597",
}
_DEFAULT_ACCENT = "#1ec8ff"


def _seed(case_id: str) -> int:
    """Stable 32-bit int seed from a string — deterministic per case."""
    return int.from_bytes(hashlib.sha256(case_id.encode()).digest()[:4], "big")


def _prng(seed: int):
    """Mulberry32 — small fast deterministic PRNG. Yields floats in [0,1)."""
    state = [seed & 0xFFFFFFFF]
    def nxt():
        state[0] = (state[0] + 0x6D2B79F5) & 0xFFFFFFFF
        t = state[0]
        t = ((t ^ (t >> 15)) * (t | 1)) & 0xFFFFFFFF
        t ^= (t + ((t ^ (t >> 7)) * (t | 61))) & 0xFFFFFFFF
        return ((t ^ (t >> 14)) & 0xFFFFFFFF) / 4294967296.0
    return nxt


def _accent(case: Optional[Case]) -> str:
    if case and case.classification.severity:
        return _SEV_ACCENT.get(case.classification.severity, _DEFAULT_ACCENT)
    return _DEFAULT_ACCENT


# ---------- hero SVG (featured card) -----------------------------------

def case_hero_svg(case: Case, width: int = 880, height: int = 220) -> str:
    """Wide SVG for the featured-case hero. Particle constellation + severity glow."""
    seed = _seed(case.case_id)
    rng = _prng(seed)
    accent = _accent(case)
    sid = f"h{seed:x}"

    parts = []
    parts.append(
        f'<defs>'
        f'<linearGradient id="bg-{sid}" x1="0" y1="0" x2="1" y2="1">'
        f'<stop offset="0%" stop-color="#0f1115"/><stop offset="100%" stop-color="#1c2230"/>'
        f'</linearGradient>'
        f'<radialGradient id="glow-{sid}" cx="78%" cy="50%" r="60%">'
        f'<stop offset="0%" stop-color="{accent}" stop-opacity="0.42"/>'
        f'<stop offset="55%" stop-color="{accent}" stop-opacity="0.08"/>'
        f'<stop offset="100%" stop-color="{accent}" stop-opacity="0"/>'
        f'</radialGradient>'
        f'<radialGradient id="grain-{sid}" cx="20%" cy="35%" r="60%">'
        f'<stop offset="0%" stop-color="#1ec8ff" stop-opacity="0.10"/>'
        f'<stop offset="100%" stop-color="#1ec8ff" stop-opacity="0"/>'
        f'</radialGradient>'
        f'</defs>'
    )
    parts.append(f'<rect width="{width}" height="{height}" fill="url(#bg-{sid})"/>')
    parts.append(f'<rect width="{width}" height="{height}" fill="url(#grain-{sid})"/>')
    parts.append(f'<rect width="{width}" height="{height}" fill="url(#glow-{sid})"/>')

    # Faint scan grid
    for x in range(0, width, 44):
        parts.append(f'<line x1="{x}" y1="0" x2="{x}" y2="{height}" stroke="{accent}" stroke-opacity="0.04"/>')
    for y in range(0, height, 44):
        parts.append(f'<line x1="0" y1="{y}" x2="{width}" y2="{y}" stroke="{accent}" stroke-opacity="0.04"/>')

    # Particle field — each particle gets a CSS animation-delay so it blinks
    # exactly when the sweep column passes over its x-position. Sweep cycle is
    # 12s left-to-right; arrival time at x = 12 * (px*100 + 20) / 140 sec.
    n = 42
    pts = []
    for _ in range(n):
        x = rng() * width
        y = rng() * height
        s = 1.2 + rng() * 3
        op = 0.3 + rng() * 0.55
        # negative delay so the cycle is already at the right phase when the
        # page loads (otherwise every particle would flash in unison at t=0).
        delay = -round(12.0 * ((x / width) * 100 + 20) / 140, 2)
        pts.append((x, y, s, op, delay))

    # Connect close particles
    for i in range(n):
        ax, ay, *_ = pts[i]
        for j in range(i + 1, n):
            bx, by, *_ = pts[j]
            d = math.hypot(bx - ax, by - ay)
            if d < 90:
                op = (1 - d / 90) * 0.20
                parts.append(
                    f'<line x1="{ax:.1f}" y1="{ay:.1f}" x2="{bx:.1f}" y2="{by:.1f}" '
                    f'stroke="{accent}" stroke-width="0.6" stroke-opacity="{op:.2f}"/>'
                )

    for x, y, s, op, d in pts:
        style = f'animation-delay: {d}s;'
        if rng() < 0.7:
            parts.append(
                f'<circle class="hero-particle" cx="{x:.1f}" cy="{y:.1f}" r="{s:.1f}" '
                f'fill="{accent}" fill-opacity="{op:.2f}" style="{style}"/>'
            )
        else:
            parts.append(
                f'<rect class="hero-particle" x="{x - s:.1f}" y="{y - s:.1f}" '
                f'width="{s * 2:.1f}" height="{s * 2:.1f}" fill="{accent}" fill-opacity="{op:.2f}" '
                f'transform="rotate(45 {x:.1f} {y:.1f})" style="{style}"/>'
            )

    # HUD corner brackets
    for cx, cy, dx, dy in [
        (12, 12, 1, 1), (width - 12, 12, -1, 1),
        (12, height - 12, 1, -1), (width - 12, height - 12, -1, -1),
    ]:
        parts.append(
            f'<polyline points="{cx + dx*18},{cy} {cx},{cy} {cx},{cy + dy*18}" '
            f'fill="none" stroke="{accent}" stroke-width="1.6" stroke-opacity="0.55"/>'
        )

    # Ornamental case id, very faint
    parts.append(
        f'<text x="{width - 14}" y="{height - 12}" text-anchor="end" '
        f'fill="{accent}" fill-opacity="0.22" font-family="ui-monospace,Menlo,monospace" font-size="11">'
        f'{case.case_id}</text>'
    )

    return (
        f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}" '
        f'preserveAspectRatio="xMidYMid slice" class="case-hero-svg" aria-hidden="true">'
        + "".join(parts) + "</svg>"
    )


# ---------- glyph SVG (news list items) ---------------------------------

def case_glyph_svg(case: Case, size: int = 36) -> str:
    """A small identicon-like SVG glyph for a case — symmetric, severity-colored."""
    seed = _seed(case.case_id)
    rng = _prng(seed)
    accent = _accent(case)
    grid = 5
    cell = size / grid
    parts = [f'<rect width="{size}" height="{size}" fill="#1c2230" rx="7"/>']
    pad = 3
    # mirror-symmetric pattern
    for cy in range(grid):
        for cx in range((grid + 1) // 2):
            if rng() < 0.55:
                op = 0.45 + rng() * 0.45
                x = cx * cell + pad / 2
                y = cy * cell + pad / 2
                w = cell - pad
                h = cell - pad
                parts.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{w:.1f}" height="{h:.1f}" '
                             f'fill="{accent}" fill-opacity="{op:.2f}" rx="1"/>')
                # mirror
                mx = (grid - 1 - cx) * cell + pad / 2
                if cx != grid - 1 - cx:
                    parts.append(f'<rect x="{mx:.1f}" y="{y:.1f}" width="{w:.1f}" height="{h:.1f}" '
                                 f'fill="{accent}" fill-opacity="{op:.2f}" rx="1"/>')
    return (
        f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {size} {size}" '
        f'width="{size}" height="{size}" class="case-glyph-svg" aria-hidden="true">'
        + "".join(parts) + "</svg>"
    )
