<script lang="ts">
    // A small, dependency-free line+area sparkline for admin trend series.
    // Null values render as gaps (a bucket with no data), so a missing point
    // never draws a misleading line to zero.
    type Point = { t: string; value: number | null };

    let {
        points,
        label,
        format = (v: number) => v.toFixed(0),
        height = 72,
        accent = 'var(--primary, #2563eb)'
    }: {
        points: Point[];
        label: string;
        format?: (v: number) => string;
        height?: number;
        accent?: string;
    } = $props();

    // Internal coordinate system; the SVG scales to its container width.
    const W = 600;
    const PAD = 6;

    const values = $derived(
        points.map((p) => p.value).filter((v): v is number => v != null)
    );
    const hasData = $derived(values.length > 0);
    const max = $derived(hasData ? Math.max(...values) : 0);
    const min = $derived(hasData ? Math.min(...values) : 0);
    const latest = $derived.by(() => {
        for (let i = points.length - 1; i >= 0; i--) {
            if (points[i].value != null) return points[i].value as number;
        }
        return null;
    });

    function x(i: number): number {
        if (points.length <= 1) return W / 2;
        return PAD + (i / (points.length - 1)) * (W - 2 * PAD);
    }
    function y(v: number): number {
        const range = max > min ? max - min : 1;
        const norm = max > min ? (v - min) / range : 0.5;
        const inner = height - 2 * PAD;
        return PAD + (1 - norm) * inner;
    }

    // Break the series into runs of consecutive non-null points so gaps stay
    // gaps. Each run becomes one line path and one area path.
    type Run = { i: number; v: number }[];
    const runs = $derived.by<Run[]>(() => {
        const out: Run[] = [];
        let cur: Run = [];
        points.forEach((p, i) => {
            if (p.value == null) {
                if (cur.length) out.push(cur);
                cur = [];
            } else {
                cur.push({ i, v: p.value });
            }
        });
        if (cur.length) out.push(cur);
        return out;
    });

    function linePath(run: Run): string {
        return run.map((pt, k) => `${k === 0 ? 'M' : 'L'} ${x(pt.i).toFixed(1)} ${y(pt.v).toFixed(1)}`).join(' ');
    }
    function areaPath(run: Run): string {
        if (run.length === 0) return '';
        const base = height - PAD;
        const top = run.map((pt) => `L ${x(pt.i).toFixed(1)} ${y(pt.v).toFixed(1)}`).join(' ');
        const x0 = x(run[0].i).toFixed(1);
        const xn = x(run[run.length - 1].i).toFixed(1);
        return `M ${x0} ${base} ${top} L ${xn} ${base} Z`;
    }

    function shortTime(iso: string): string {
        const d = new Date(iso);
        return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric' });
    }
</script>

<figure class="trend" data-testid="trend-chart">
    <figcaption>
        <span class="lbl">{label}</span>
        {#if hasData}
            <span class="muted"
                >peak {format(max)} · latest {latest != null ? format(latest) : '—'}</span
            >
        {/if}
    </figcaption>
    {#if !hasData}
        <div class="empty muted" style={`height:${height}px`} data-testid="trend-empty">
            No data in this window
        </div>
    {:else}
        <svg
            viewBox={`0 0 ${W} ${height}`}
            preserveAspectRatio="none"
            role="img"
            aria-label={`${label}: ${values.length} points, latest ${latest != null ? format(latest) : 'none'}, peak ${format(max)}`}
        >
            {#each runs as run, ri (ri)}
                <path class="area" d={areaPath(run)} fill={accent} />
                <path class="line" d={linePath(run)} stroke={accent} />
                {#if run.length === 1}
                    <circle cx={x(run[0].i)} cy={y(run[0].v)} r="3" fill={accent} />
                {/if}
            {/each}
        </svg>
        <div class="xaxis muted">
            <span>{shortTime(points[0].t)}</span>
            <span>{shortTime(points[points.length - 1].t)}</span>
        </div>
    {/if}
</figure>

<style>
    .trend {
        margin: 0 0 var(--space-3) 0;
    }
    figcaption {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: var(--space-2);
        font-size: var(--font-sm);
        margin-bottom: 2px;
    }
    .lbl {
        font-weight: var(--weight-medium);
    }
    svg {
        width: 100%;
        display: block;
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
    }
    .area {
        opacity: 0.12;
        stroke: none;
    }
    .line {
        fill: none;
        stroke-width: 1.5;
        vector-effect: non-scaling-stroke;
    }
    .xaxis {
        display: flex;
        justify-content: space-between;
        font-size: var(--font-xs);
        margin-top: 2px;
    }
    .empty {
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px dashed var(--border);
        border-radius: var(--radius-sm);
        font-size: var(--font-sm);
    }
    .muted {
        color: var(--text-muted);
    }
</style>
