'use client';

/**
 * Shared visual primitives for the Executive dashboard, ported from the design
 * handoff (lib/ui.jsx) into typed React. Colors come from the `.noc` CSS
 * variables (theme.css); motion is gated on useReducedMotion(). Charts are
 * hand-rolled SVG to match the prototypes pixel-for-pixel.
 */
import { useEffect, useId, useMemo, useRef, useState } from 'react';
import { motion } from 'framer-motion';
import type { StatusLevel } from '@/lib/executive/types';
import { useReducedMotion } from './useReducedMotion';

export const STATUS_FG: Record<StatusLevel, string> = {
    healthy: 'var(--ok-fg)',
    warning: 'var(--warn-fg)',
    critical: 'var(--crit-fg)',
};
export const STATUS_RAW: Record<StatusLevel, string> = {
    healthy: 'var(--ok)',
    warning: 'var(--warn)',
    critical: 'var(--crit)',
};
export const STATUS_LABEL: Record<StatusLevel, string> = {
    healthy: 'Healthy',
    warning: 'Degraded',
    critical: 'Critical',
};

const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);

/* ---------------- CountUp ---------------- */
export function CountUp({
    value,
    decimals = 0,
    duration = 1400,
    prefix = '',
    suffix = '',
    delay = 0,
    className = '',
}: {
    value: number;
    decimals?: number;
    duration?: number;
    prefix?: string;
    suffix?: string;
    delay?: number;
    className?: string;
}) {
    const reduced = useReducedMotion();
    const [val, setVal] = useState(reduced ? value : 0);
    const raf = useRef(0);

    useEffect(() => {
        if (reduced) {
            // Reduced motion: snap to the final value (no count-up animation).
            // eslint-disable-next-line react-hooks/set-state-in-effect
            setVal(value);
            return;
        }
        let start: number | null = null;
        const tick = (ts: number) => {
            if (start === null) start = ts;
            const p = Math.min(1, (ts - start) / duration);
            setVal(value * easeOutCubic(p));
            if (p < 1) raf.current = requestAnimationFrame(tick);
        };
        const id = window.setTimeout(() => {
            raf.current = requestAnimationFrame(tick);
        }, delay);
        return () => {
            window.clearTimeout(id);
            cancelAnimationFrame(raf.current);
        };
    }, [value, duration, delay, reduced]);

    const formatted = Number(val).toLocaleString('en-US', {
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals,
    });
    return (
        <span className={`tnum ${className}`}>
            {prefix}
            {formatted}
            {suffix}
        </span>
    );
}

/* ---------------- FadeUp (staggered entrance) ---------------- */
export function FadeUp({
    children,
    index = 0,
    className = '',
    style,
}: {
    children: React.ReactNode;
    index?: number;
    className?: string;
    style?: React.CSSProperties;
}) {
    const reduced = useReducedMotion();
    if (reduced) {
        return (
            <div className={className} style={style}>
                {children}
            </div>
        );
    }
    return (
        <motion.div
            className={className}
            style={style}
            initial={{ opacity: 0, y: 16 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.45, delay: index * 0.05, ease: [0.16, 1, 0.3, 1] }}
        >
            {children}
        </motion.div>
    );
}

/* ---------------- Slash (magenta accent) ---------------- */
export function Slash({ style }: { style?: React.CSSProperties }) {
    return <span className="slash" style={style} aria-hidden />;
}

/* ---------------- LiveDot ---------------- */
export function LiveDot({ color = 'var(--ok)', size = 10 }: { color?: string; size?: number }) {
    const reduced = useReducedMotion();
    return (
        <span style={{ position: 'relative', width: size, height: size, display: 'inline-block', flex: 'none' }}>
            {!reduced && (
                <span
                    style={{
                        position: 'absolute',
                        inset: 0,
                        borderRadius: '50%',
                        border: `1.5px solid ${color}`,
                        animation: 'nocRing 2.4s ease-out infinite',
                    }}
                />
            )}
            <span
                style={{
                    position: 'absolute',
                    inset: 0,
                    borderRadius: '50%',
                    background: color,
                    boxShadow: `0 0 10px ${color}`,
                    animation: reduced ? 'none' : 'nocPulse 2.4s ease-in-out infinite',
                }}
            />
        </span>
    );
}

/* ---------------- StatusDot ---------------- */
export function StatusDot({ status, size = 8 }: { status: StatusLevel; size?: number }) {
    return (
        <span
            style={{
                width: size,
                height: size,
                borderRadius: '50%',
                background: STATUS_RAW[status],
                display: 'inline-block',
                flex: 'none',
                boxShadow: `0 0 8px ${STATUS_RAW[status]}`,
            }}
        />
    );
}

/* ---------------- StatusPill ---------------- */
export function StatusPill({ status, label }: { status: StatusLevel; label?: string }) {
    return (
        <span
            className="pill tnum"
            style={{
                display: 'inline-flex',
                alignItems: 'center',
                gap: 6,
                padding: '4px 10px',
                fontFamily: 'var(--font-mono)',
                fontSize: 11,
                fontWeight: 600,
                letterSpacing: '0.04em',
                textTransform: 'uppercase',
                color: STATUS_FG[status],
                background: `color-mix(in srgb, ${STATUS_RAW[status]} 14%, transparent)`,
                border: `1px solid color-mix(in srgb, ${STATUS_RAW[status]} 35%, transparent)`,
            }}
        >
            <StatusDot status={status} size={7} />
            {label ?? STATUS_LABEL[status]}
        </span>
    );
}

/* ---------------- Delta (goodness-colored trend) ---------------- */
export function Delta({
    value,
    suffix = '%',
    goodWhenUp = true,
}: {
    value: number;
    suffix?: string;
    goodWhenUp?: boolean;
}) {
    const up = value >= 0;
    const good = goodWhenUp ? up : !up;
    const color = good ? 'var(--ok-fg)' : 'var(--crit-fg)';
    return (
        <span
            className="tnum"
            style={{
                display: 'inline-flex',
                alignItems: 'center',
                gap: 5,
                fontFamily: 'var(--font-mono)',
                fontSize: 12,
                fontWeight: 600,
                color,
                letterSpacing: '0.02em',
            }}
        >
            <span style={{ fontSize: 9 }}>{up ? '▲' : '▼'}</span>
            {Math.abs(value)}
            {suffix}
        </span>
    );
}

/* ---------------- Sparkline (draws in) ---------------- */
export function Sparkline({
    data,
    w = 120,
    h = 36,
    color = 'var(--accent)',
    strokeWidth = 2,
    fill = false,
    delay = 0,
}: {
    data: number[];
    w?: number;
    h?: number;
    color?: string;
    strokeWidth?: number;
    fill?: boolean;
    delay?: number;
}) {
    const reduced = useReducedMotion();
    const ref = useRef<SVGPathElement>(null);
    const gid = 'spk-' + useId().replace(/:/g, ''); // useId is render-safe; strip ':' for valid SVG url(#id)
    const { d, area } = useMemo(() => {
        const min = Math.min(...data);
        const max = Math.max(...data);
        const span = max - min || 1;
        const pts = data.map((v, i) => [(i / (data.length - 1)) * w, h - ((v - min) / span) * (h - 4) - 2]);
        const path = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
        return { d: path, area: path + ` L ${w} ${h} L 0 ${h} Z` };
    }, [data, w, h]);

    useEffect(() => {
        const path = ref.current;
        if (!path) return;
        if (reduced) {
            path.style.strokeDashoffset = '0';
            return;
        }
        const total = path.getTotalLength();
        path.style.strokeDasharray = String(total);
        path.style.strokeDashoffset = String(total);
        path.style.animation = `nocDraw 1.2s var(--ease) ${delay}ms forwards`;
    }, [d, delay, reduced]);

    return (
        <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ display: 'block', overflow: 'visible' }}>
            {fill && (
                <>
                    <defs>
                        <linearGradient id={gid} x1="0" y1="0" x2="0" y2="1">
                            <stop offset="0" stopColor={color} stopOpacity="0.28" />
                            <stop offset="1" stopColor={color} stopOpacity="0" />
                        </linearGradient>
                    </defs>
                    <path d={area} fill={`url(#${gid})`} />
                </>
            )}
            <path ref={ref} d={d} fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" />
        </svg>
    );
}

/* ---------------- LineChart (area + draw-in) ---------------- */
export interface LineSeries {
    key: string;
    color: string;
    data: number[];
    dashed?: boolean;
    fill?: boolean;
}
export function LineChart({
    series,
    w = 700,
    h = 240,
    pad = 8,
    grid = 4,
    yMin,
    yMax,
    dots = true,
}: {
    series: LineSeries[];
    w?: number;
    h?: number;
    pad?: number;
    grid?: number;
    yMin?: number;
    yMax?: number;
    dots?: boolean;
}) {
    const all = series.flatMap((s) => s.data);
    const min = yMin != null ? yMin : Math.min(...all);
    const max = yMax != null ? yMax : Math.max(...all);
    const span = max - min || 1;
    const n = series[0].data.length;
    const X = (i: number) => pad + (i / (n - 1)) * (w - pad * 2);
    const Y = (v: number) => pad + (1 - (v - min) / span) * (h - pad * 2);

    return (
        <svg width="100%" height="100%" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ display: 'block', overflow: 'visible' }}>
            <g stroke="var(--grid-line)" strokeWidth="1">
                {Array.from({ length: grid + 1 }).map((_, i) => {
                    const y = pad + (i / grid) * (h - pad * 2);
                    return <line key={i} x1={pad} y1={y} x2={w - pad} y2={y} />;
                })}
            </g>
            {series.map((s, si) => {
                const pts = s.data.map((v, i) => [X(i), Y(v)] as [number, number]);
                const d = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
                return <Series key={s.key} d={d} pts={pts} h={h} color={s.color} dashed={s.dashed} fill={s.fill} delay={si * 160} dots={dots} />;
            })}
        </svg>
    );
}
function Series({
    d,
    pts,
    h,
    color,
    dashed,
    fill,
    delay,
    dots,
}: {
    d: string;
    pts: [number, number][];
    h: number;
    color: string;
    dashed?: boolean;
    fill?: boolean;
    delay: number;
    dots: boolean;
}) {
    const reduced = useReducedMotion();
    const ref = useRef<SVGPathElement>(null);
    const gid = 'ln-' + useId().replace(/:/g, '');
    useEffect(() => {
        const path = ref.current;
        if (!path) return;
        if (reduced) {
            path.style.strokeDashoffset = '0';
            return;
        }
        const total = path.getTotalLength();
        path.style.strokeDasharray = String(total);
        path.style.strokeDashoffset = String(total);
        path.style.animation = `nocDraw 1.6s ease-out ${delay}ms forwards`;
    }, [d, delay, reduced]);
    const last = pts[pts.length - 1];
    return (
        <g>
            {fill && (
                <>
                    <defs>
                        <linearGradient id={gid} x1="0" y1="0" x2="0" y2="1">
                            <stop offset="0" stopColor={color} stopOpacity="0.22" />
                            <stop offset="1" stopColor={color} stopOpacity="0" />
                        </linearGradient>
                    </defs>
                    <path
                        d={d + ` L ${last[0]} ${h} L ${pts[0][0]} ${h} Z`}
                        fill={`url(#${gid})`}
                        opacity={reduced ? 1 : 0}
                        style={{ animation: reduced ? 'none' : `nocFadeUp 0.8s ease-out ${delay + 600}ms forwards` }}
                    />
                </>
            )}
            <path ref={ref} d={d} fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" strokeDasharray={dashed ? '5 5' : undefined} />
            {dots && <circle cx={last[0]} cy={last[1]} r="4" fill={color} />}
        </g>
    );
}

/* ---------------- BarChart (grow-in) ---------------- */
export function BarChart({
    values,
    labels,
    colors,
    h = 220,
    max,
}: {
    values: number[];
    labels: string[];
    colors: string[];
    h?: number | string;
    max?: number;
}) {
    const reduced = useReducedMotion();
    const top = max ?? Math.max(...values, 1);
    return (
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 10, height: h, width: '100%' }}>
            {values.map((v, i) => {
                const pct = Math.max(2, (v / top) * 100);
                return (
                    <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, height: '100%' }}>
                        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', alignItems: 'center', width: '100%' }}>
                            <span className="tnum" style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-2)', marginBottom: 4 }}>
                                {v}
                            </span>
                            <motion.div
                                style={{ width: '100%', maxWidth: 34, background: colors[i], borderRadius: 0 }}
                                initial={reduced ? false : { height: 0 }}
                                animate={{ height: `${pct}%` }}
                                transition={{ duration: 0.5, delay: i * 0.055, ease: [0.16, 1, 0.3, 1] }}
                            />
                        </div>
                        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg-3)' }}>{labels[i]}</span>
                    </div>
                );
            })}
        </div>
    );
}

/* ---------------- ResponseMeter (animated horizontal bar) ---------------- */
export function ResponseMeter({ ms, status, maxMs = 1000 }: { ms: number; status: StatusLevel; maxMs?: number }) {
    const reduced = useReducedMotion();
    const pct = Math.min(100, (ms / maxMs) * 100);
    return (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 120 }}>
            <div style={{ position: 'relative', flex: 1, height: 6, background: 'var(--hair)', borderRadius: 0, overflow: 'hidden' }}>
                <motion.div
                    style={{ height: '100%', background: STATUS_RAW[status] }}
                    initial={reduced ? false : { width: 0 }}
                    animate={{ width: `${pct}%` }}
                    transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
                />
            </div>
            <span className="tnum" style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-2)', minWidth: 48, textAlign: 'right' }}>
                {ms > 0 ? `${ms}ms` : '—'}
            </span>
        </div>
    );
}

/* ---------------- Gauge (270° arc) ---------------- */
export function Gauge({
    value,
    min = 0,
    max = 100,
    size = 220,
    stroke = 14,
    color = 'gradient',
    track = 'var(--hair)',
    children,
}: {
    value: number;
    min?: number;
    max?: number;
    size?: number;
    stroke?: number;
    color?: string;
    track?: string;
    children?: React.ReactNode;
}) {
    const reduced = useReducedMotion();
    const ref = useRef<SVGPathElement>(null);
    const r = (size - stroke) / 2;
    const cx = size / 2;
    const cy = size / 2;
    const startAngle = 135;
    const sweep = 270;
    const frac = Math.max(0, Math.min(1, (value - min) / (max - min)));
    const circ = 2 * Math.PI * r;
    const arcLen = (sweep / 360) * circ;
    const gid = 'g-' + useId().replace(/:/g, '');
    const polar = (ang: number): [number, number] => [
        cx + r * Math.cos((ang * Math.PI) / 180),
        cy + r * Math.sin((ang * Math.PI) / 180),
    ];
    const a0 = polar(startAngle);
    const a1 = polar(startAngle + sweep);
    const largeArc = sweep > 180 ? 1 : 0;
    const trackPath = `M ${a0[0]} ${a0[1]} A ${r} ${r} 0 ${largeArc} 1 ${a1[0]} ${a1[1]}`;

    useEffect(() => {
        const p = ref.current;
        if (!p) return;
        const target = arcLen * frac;
        if (reduced) {
            p.style.strokeDasharray = `${target} ${circ}`;
            return;
        }
        p.style.strokeDasharray = `0 ${circ}`;
        requestAnimationFrame(() => {
            p.style.transition = 'stroke-dasharray 1.8s cubic-bezier(0.16,1,0.3,1)';
            p.style.strokeDasharray = `${target} ${circ}`;
        });
    }, [frac, arcLen, circ, reduced]);

    return (
        <div style={{ position: 'relative', width: size, height: size }}>
            <svg width={size} height={size} style={{ overflow: 'visible' }}>
                <defs>
                    <linearGradient id={gid} x1="0" y1="0" x2="1" y2="1">
                        <stop offset="0" stopColor="var(--cyan)" />
                        <stop offset="1" stopColor="var(--violet)" />
                    </linearGradient>
                </defs>
                <path d={trackPath} fill="none" stroke={track} strokeWidth={stroke} strokeLinecap="round" />
                <path
                    ref={ref}
                    d={trackPath}
                    fill="none"
                    stroke={color === 'gradient' ? `url(#${gid})` : color}
                    strokeWidth={stroke}
                    strokeLinecap="round"
                    style={{ filter: 'drop-shadow(0 0 8px rgba(34,211,238,0.35))' }}
                />
            </svg>
            <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
                {children}
            </div>
        </div>
    );
}

/* ---------------- IconMark (brand glyph: open square + magenta slash) ---------------- */
export function IconMark({ size = 28, stroke = 'currentColor' }: { size?: number; stroke?: string }) {
    return (
        <svg width={size} height={size} viewBox="0 0 100 100" fill="none" style={{ display: 'block', flex: 'none' }}>
            <path d="M8 8 H56 M8 8 V92 H92 V44" stroke={stroke} strokeWidth="7" strokeLinecap="square" />
            <path d="M30 86 L74 14" stroke="var(--magenta)" strokeWidth="11" strokeLinecap="round" />
        </svg>
    );
}
