<script lang="ts">
  /**
   * A live streaming chart backed by a decimating `History` (NFR-3). The data
   * arrays are typed-array views that never grow, so this stays cheap for a
   * two-hour ride.
   */
  import { onMount } from 'svelte';
  import uPlot from 'uplot';
  import { axis, observeSize } from '../lib/uplot';
  import type { History } from '../lib/history';

  interface SeriesSpec {
    stroke: string;
    width?: number;
    fill?: string;
    dash?: number[];
  }

  interface Props {
    history: History;
    series: SeriesSpec[];
    revision: number;
    /** Fixed y range, or null to autoscale. */
    range?: [number, number] | null;
    zeroLine?: boolean;
  }

  let { history, series, revision, range = null, zeroLine = false }: Props = $props();

  let host = $state<HTMLDivElement | null>(null);
  let plot: uPlot | null = null;
  let dispose: (() => void) | null = null;

  onMount(() => {
    if (!host) return;
    const opts: uPlot.Options = {
      width: host.clientWidth || 400,
      height: host.clientHeight || 140,
      padding: [8, 6, 0, 0],
      cursor: { show: false },
      legend: { show: false },
      scales: {
        x: { time: false },
        y: range ? { range: () => range as [number, number] } : {},
      },
      axes: [
        axis({
          values: (_u, splits) => splits.map((v) => formatClock(v)),
        }),
        axis({ side: 3, size: 42, values: (_u, splits) => splits.map((v) => v.toFixed(0)) }),
      ],
      series: [
        {},
        ...series.map((s) => ({
          stroke: s.stroke,
          width: s.width ?? 1.5,
          fill: s.fill,
          dash: s.dash,
          points: { show: false },
        })),
      ],
      hooks: zeroLine
        ? {
            draw: [
              (u: uPlot) => {
                const y = u.valToPos(0, 'y', true);
                if (!Number.isFinite(y)) return;
                u.ctx.save();
                u.ctx.strokeStyle = '#243040';
                u.ctx.lineWidth = 1;
                u.ctx.beginPath();
                u.ctx.moveTo(u.bbox.left, y);
                u.ctx.lineTo(u.bbox.left + u.bbox.width, y);
                u.ctx.stroke();
                u.ctx.restore();
              },
            ],
          }
        : {},
    };
    plot = new uPlot(opts, seed() as unknown as uPlot.AlignedData, host);
    dispose = observeSize(host, (w, h) => plot?.setSize({ width: w, height: h }));
    return () => {
      dispose?.();
      plot?.destroy();
    };
  });

  /** uPlot dislikes zero-length series; seed with a single flat point. */
  function seed(): (Float64Array | number[])[] {
    if (history.length > 1) return history.view();
    return [[0], ...series.map(() => [0])];
  }

  function formatClock(v: number): string {
    const m = Math.floor(v / 60);
    return m >= 60 ? `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}` : `${m}m`;
  }

  $effect(() => {
    revision;
    if (plot && history.length > 1) {
      plot.setData(history.view() as unknown as uPlot.AlignedData, true);
    }
  });
</script>

<div class="stream" bind:this={host}></div>

<style>
  .stream {
    width: 100%;
    height: 100%;
    min-height: 0;
  }
</style>
