<script lang="ts">
  import type { PricePoint } from './lists.svelte';

  let {
    history,
    target = null,
    currency = 'EUR'
  }: { history: PricePoint[]; target?: number | null; currency?: string | null } = $props();

  // Geometry — a wide, short celestial band. viewBox units; scales to container.
  const W = 600;
  const H = 180;
  const PAD = { t: 16, r: 14, b: 24, l: 46 };
  const innerW = W - PAD.l - PAD.r;
  const innerH = H - PAD.t - PAD.b;

  // Chronological, oldest → newest, so the line reads left → right.
  const points = $derived(
    [...history].sort((a, b) => +new Date(a.fetched_at) - +new Date(b.fetched_at))
  );

  const cur = $derived(currency ?? 'EUR');

  const stats = $derived.by(() => {
    if (points.length === 0) return null;
    const prices = points.map((p) => p.price);
    let min = Math.min(...prices);
    let max = Math.max(...prices);
    // Fold the target into the vertical range so its line is always visible.
    if (target != null) {
      min = Math.min(min, target);
      max = Math.max(max, target);
    }
    if (min === max) {
      // Flat history — give it breathing room so it doesn't sit on an edge.
      const pad = Math.max(1, Math.abs(min) * 0.1);
      min -= pad;
      max += pad;
    }
    const ts = points.map((p) => +new Date(p.fetched_at));
    const tMin = Math.min(...ts);
    const tMax = Math.max(...ts);
    return { min, max, tMin, tMax };
  });

  function x(t: number): number {
    if (!stats) return PAD.l;
    const span = stats.tMax - stats.tMin || 1;
    return PAD.l + ((t - stats.tMin) / span) * innerW;
  }
  function y(price: number): number {
    if (!stats) return PAD.t;
    const span = stats.max - stats.min || 1;
    return PAD.t + (1 - (price - stats.min) / span) * innerH;
  }

  // Plotted coordinates for each price check.
  const coords = $derived(
    points.map((p) => ({ ...p, cx: x(+new Date(p.fetched_at)), cy: y(p.price) }))
  );

  const linePath = $derived(
    coords.length
      ? coords
          .map((c, i) => `${i === 0 ? 'M' : 'L'} ${c.cx.toFixed(1)} ${c.cy.toFixed(1)}`)
          .join(' ')
      : ''
  );
  // Area = the line, then down to the baseline and back — fills under the curve.
  const areaPath = $derived(
    coords.length
      ? `${linePath} L ${coords[coords.length - 1].cx.toFixed(1)} ${(H - PAD.b).toFixed(1)} ` +
          `L ${coords[0].cx.toFixed(1)} ${(H - PAD.b).toFixed(1)} Z`
      : ''
  );

  // Extremes — gild the lowest (the prophecy), mark the highest in rose.
  const lowIdx = $derived(
    coords.length ? coords.reduce((lo, c, i) => (c.price < coords[lo].price ? i : lo), 0) : -1
  );
  const highIdx = $derived(
    coords.length ? coords.reduce((hi, c, i) => (c.price > coords[hi].price ? i : hi), 0) : -1
  );

  // Out-of-stock spans — shade the time the item couldn't be bought.
  // Each maximal run of in_stock===false becomes a band, extended halfway
  // to its in-stock neighbours so single checks still read as a region.
  const oosBands = $derived.by(() => {
    if (!coords.length) return [];
    const bands: { x: number; w: number }[] = [];
    let start: number | null = null;
    for (let i = 0; i <= coords.length; i++) {
      const oos = i < coords.length && coords[i].in_stock === false;
      if (oos && start === null) start = i;
      if (!oos && start !== null) {
        const end = i - 1;
        const left = start > 0 ? (coords[start - 1].cx + coords[start].cx) / 2 : coords[start].cx;
        const right =
          end < coords.length - 1 ? (coords[end].cx + coords[end + 1].cx) / 2 : coords[end].cx;
        bands.push({ x: left, w: Math.max(right - left, 3) });
        start = null;
      }
    }
    return bands;
  });
  const anyOos = $derived(coords.some((c) => c.in_stock === false));

  const targetY = $derived(target != null && stats ? y(target) : null);
  const latest = $derived(coords.length ? coords[coords.length - 1] : null);
  const onSale = $derived(latest != null && target != null && latest.price <= target);

  // Three horizontal gridlines (min / mid / max) for a sense of scale.
  const gridLines = $derived.by(() => {
    if (!stats) return [];
    return [0, 0.5, 1].map((f) => {
      const v = stats.min + (stats.max - stats.min) * f;
      return { v, gy: y(v) };
    });
  });

  // Hover — snap to the nearest point in time.
  let hover = $state<number | null>(null);
  let svgEl: SVGSVGElement | null = $state(null);

  function onMove(e: PointerEvent) {
    if (!svgEl || coords.length === 0) return;
    const rect = svgEl.getBoundingClientRect();
    const px = ((e.clientX - rect.left) / rect.width) * W;
    let best = 0;
    let bestD = Infinity;
    for (let i = 0; i < coords.length; i++) {
      const d = Math.abs(coords[i].cx - px);
      if (d < bestD) {
        bestD = d;
        best = i;
      }
    }
    hover = best;
  }

  function fmtMoney(v: number) {
    return `${cur} ${v.toFixed(2)}`;
  }
  function fmtDay(s: string) {
    return new Date(s).toLocaleDateString('en-GB', { day: '2-digit', month: 'short' });
  }
  function fmtFull(s: string) {
    return new Date(s).toLocaleDateString('en-GB', {
      day: '2-digit',
      month: 'short',
      hour: '2-digit',
      minute: '2-digit'
    });
  }

  const active = $derived(hover != null ? coords[hover] : null);
  // Keep the tooltip from spilling off either edge.
  const tipX = $derived(active ? Math.min(Math.max(active.cx, PAD.l + 56), W - PAD.r - 56) : 0);
  const uid = 'pc-' + Math.random().toString(36).slice(2, 8);
</script>

{#if coords.length === 0}
  <p class="text-xs text-mute">no price checks yet — hit refresh to start tracking.</p>
{:else}
  <figure class="m-0">
    <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
    <svg
      bind:this={svgEl}
      viewBox="0 0 {W} {H}"
      class="block w-full select-none overflow-visible"
      role="img"
      aria-label="price history chart"
      onpointermove={onMove}
      onpointerleave={() => (hover = null)}
    >
      <defs>
        <linearGradient id="{uid}-area" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="var(--color-iris)" stop-opacity="0.35" />
          <stop offset="60%" stop-color="var(--color-iris)" stop-opacity="0.06" />
          <stop offset="100%" stop-color="var(--color-iris)" stop-opacity="0" />
        </linearGradient>
        <linearGradient id="{uid}-line" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="var(--color-holo)" />
          <stop offset="50%" stop-color="var(--color-iris)" />
          <stop offset="100%" stop-color="var(--color-rose)" />
        </linearGradient>
        <filter id="{uid}-glow" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3.5" result="b" />
          <feMerge>
            <feMergeNode in="b" />
            <feMergeNode in="SourceGraphic" />
          </feMerge>
        </filter>
      </defs>

      <!-- Out-of-stock spans — rose shade behind everything -->
      {#each oosBands as b (b.x)}
        <rect
          x={b.x}
          y={PAD.t}
          width={b.w}
          height={innerH}
          fill="var(--color-rose)"
          opacity="0.1"
        />
      {/each}

      <!-- Gridlines + price ticks -->
      {#each gridLines as g (g.v)}
        <line
          x1={PAD.l}
          y1={g.gy}
          x2={W - PAD.r}
          y2={g.gy}
          stroke="var(--color-smoke)"
          stroke-width="1"
          stroke-dasharray="2 5"
          opacity="0.6"
        />
        <text x={PAD.l - 8} y={g.gy + 3.5} text-anchor="end" class="fill-mute" font-size="10">
          {g.v.toFixed(0)}
        </text>
      {/each}

      <!-- Target line — the price you've been praying for -->
      {#if targetY != null}
        <line
          x1={PAD.l}
          y1={targetY}
          x2={W - PAD.r}
          y2={targetY}
          stroke="var(--color-mint)"
          stroke-width="1.25"
          stroke-dasharray="6 4"
          opacity="0.8"
        />
        <text x={W - PAD.r} y={targetY - 5} text-anchor="end" class="fill-mint" font-size="10">
          target {target?.toFixed(0)}
        </text>
      {/if}

      <!-- Area under the curve -->
      <path d={areaPath} fill="url(#{uid}-area)" />

      <!-- The price line itself, haloed -->
      <path
        d={linePath}
        fill="none"
        stroke="url(#{uid}-line)"
        stroke-width="2.25"
        stroke-linecap="round"
        stroke-linejoin="round"
        filter="url(#{uid}-glow)"
      />

      <!-- Per-check dots; sold-out checks ringed in rose -->
      {#each coords as c, i (i)}
        <circle
          cx={c.cx}
          cy={c.cy}
          r={i === lowIdx || i === highIdx ? 4 : 2.5}
          fill={i === lowIdx
            ? 'var(--color-gold)'
            : i === highIdx
              ? 'var(--color-rose)'
              : 'var(--color-iris)'}
          stroke={c.in_stock === false ? 'var(--color-rose)' : 'var(--color-void)'}
          stroke-width="1.5"
        />
      {/each}

      <!-- Hover crosshair + emphasised point -->
      {#if active}
        <line
          x1={active.cx}
          y1={PAD.t}
          x2={active.cx}
          y2={H - PAD.b}
          stroke="var(--color-ink)"
          stroke-width="1"
          opacity="0.25"
        />
        <circle
          cx={active.cx}
          cy={active.cy}
          r="5.5"
          fill="none"
          stroke="var(--color-ink)"
          stroke-width="1.5"
        />
        <g transform="translate({tipX}, {Math.max(active.cy - 16, PAD.t + 8)})">
          <rect
            x="-54"
            y="-26"
            width="108"
            height="34"
            rx="6"
            fill="var(--color-ash)"
            stroke="var(--color-smoke)"
          />
          <text
            x="0"
            y="-12"
            text-anchor="middle"
            class="fill-ink"
            font-size="11"
            font-weight="700"
          >
            {fmtMoney(active.price)}
          </text>
          <text x="0" y="2" text-anchor="middle" class="fill-mute" font-size="9">
            {fmtFull(active.fetched_at)}
          </text>
        </g>
      {/if}

      <!-- X axis endpoints -->
      <text x={PAD.l} y={H - 6} text-anchor="start" class="fill-mute" font-size="10">
        {fmtDay(points[0].fetched_at)}
      </text>
      {#if points.length > 1}
        <text x={W - PAD.r} y={H - 6} text-anchor="end" class="fill-mute" font-size="10">
          {fmtDay(points[points.length - 1].fetched_at)}
        </text>
      {/if}
    </svg>

    <figcaption class="mt-2 flex flex-wrap items-center justify-between gap-2 text-xs">
      <span class="flex items-center gap-3 text-mute">
        <span class="flex items-center gap-1"
          ><span class="inline-block size-2 rounded-full" style="background:var(--color-gold)"
          ></span>low {fmtMoney(coords[lowIdx].price)}</span
        >
        <span class="flex items-center gap-1"
          ><span class="inline-block size-2 rounded-full" style="background:var(--color-rose)"
          ></span>high {fmtMoney(coords[highIdx].price)}</span
        >
        {#if anyOos}
          <span class="flex items-center gap-1"
            ><span
              class="inline-block h-2 w-3 rounded-sm"
              style="background:var(--color-rose);opacity:0.35"
            ></span>out of stock</span
          >
        {/if}
      </span>
      {#if latest}
        <span class:text-mint={onSale} class:text-ink={!onSale}>
          now {fmtMoney(latest.price)}{onSale ? ' ✦' : ''}
        </span>
      {/if}
    </figcaption>
  </figure>
{/if}
