<script lang="ts">
  import { onMount } from 'svelte';
  import * as Plot from '@observablehq/plot';
  import type { ActivitySummary } from '../lib/types';

  export let activities: ActivitySummary[] = [];

  type Metric      = 'distance' | 'elevation' | 'time' | 'vam';
  type Granularity = 'week' | 'month';

  let metric:      Metric      = 'distance';
  let granularity: Granularity = 'week';

  const METRIC_LABEL: Record<Metric, string> = {
    distance:  'Distance (km)',
    elevation: 'Elevation gain (m)',
    time:      'Moving time (h)',
    vam:       'Avg climbing VAM (m/h)',
  };
  const METRIC_FMT: Record<Metric, (v: number) => string> = {
    distance:  v => `${Math.round(v)} km`,
    elevation: v => `${Math.round(v)} m`,
    time:      v => `${v.toFixed(1)} h`,
    vam:       v => `${Math.round(v)} m/h`,
  };

  // Cool→warm ramp for past years; current year is always blue-400
  // t=0 → oldest past year (muted purple), t=1 → most recent past year (warm coral)
  // Stops: [t, hue, sat%, light%]
  const _RAMP: [number, number, number, number][] = [
    [0.00, 265, 38, 52],  // muted purple
    [0.18, 230, 45, 55],  // slate-blue
    [0.36, 185, 52, 50],  // teal
    [0.54, 145, 48, 47],  // green
    [0.70, 50,  72, 52],  // amber-yellow
    [0.84, 25,  80, 55],  // orange
    [1.00, 5,   70, 57],  // warm coral-red
  ];

  function _rampColor(t: number): string {
    let i = 0;
    while (i < _RAMP.length - 2 && t > _RAMP[i + 1][0]) i++;
    const [t0, h0, s0, l0] = _RAMP[i];
    const [t1, h1, s1, l1] = _RAMP[i + 1];
    const f = (t - t0) / (t1 - t0);
    return `hsl(${Math.round(h0 + (h1 - h0) * f)},${Math.round(s0 + (s1 - s0) * f)}%,${Math.round(l0 + (l1 - l0) * f)}%)`;
  }

  function dayOfYear(d: Date): number {
    return Math.floor((d.getTime() - new Date(d.getFullYear(), 0, 0).getTime()) / 86400000);
  }
  function weekOfYear(d: Date): number {
    return Math.min(52, Math.ceil(dayOfYear(d) / 7));
  }

  const _now         = new Date();
  const _currentYear = _now.getFullYear();

  // Minimum climbing time per activity to count in the VAM chart (10 min).
  const VAM_MIN_CLIMB_S = 600;
  // Climbing time range for full confidence opacity (10 min → 1 h).
  const VAM_OPACITY_MIN_S = 600;
  const VAM_OPACITY_MAX_S = 3600;

  function vamOpacity(climbTime: number | undefined): number {
    if (!climbTime) return 0.25;
    const t = Math.min(1, Math.max(0, (climbTime - VAM_OPACITY_MIN_S) / (VAM_OPACITY_MAX_S - VAM_OPACITY_MIN_S)));
    return 0.25 + t * 0.75;
  }

  function buildData(acts: ActivitySummary[], m: Metric, g: Granularity) {
    const curPeriod = g === 'week' ? weekOfYear(_now) : _now.getMonth() + 1;
    const byYear          = new Map<number, Map<number, number>>();
    const byYearCnt       = new Map<number, Map<number, number>>();
    const byYearClimbTime = new Map<number, Map<number, number>>();

    for (const act of acts) {
      if (!act.started_at) continue;
      if (m === 'vam') {
        if (act.climbing_vam_mh == null) continue;
        if ((act.climbing_time_s ?? 0) < VAM_MIN_CLIMB_S) continue;
      }
      const d   = new Date(act.started_at);
      const yr  = d.getFullYear();
      const per = g === 'week' ? weekOfYear(d) : d.getMonth() + 1;
      const val = m === 'distance'  ? (act.distance_m      ?? 0) / 1000
                : m === 'elevation' ? (act.elevation_gain_m ?? 0)
                : m === 'vam'       ? (act.climbing_vam_mh  ?? 0)
                :                    (act.moving_time_s     ?? 0) / 3600;
      if (!byYear.has(yr))    byYear.set(yr, new Map());
      if (!byYearCnt.has(yr)) byYearCnt.set(yr, new Map());
      const ym  = byYear.get(yr)!;
      const ymc = byYearCnt.get(yr)!;
      ym.set(per,  (ym.get(per)  ?? 0) + val);
      ymc.set(per, (ymc.get(per) ?? 0) + 1);
      if (m === 'vam') {
        if (!byYearClimbTime.has(yr)) byYearClimbTime.set(yr, new Map());
        const yct = byYearClimbTime.get(yr)!;
        yct.set(per, (yct.get(per) ?? 0) + (act.climbing_time_s ?? 0));
      }
    }

    // VAM: convert sums to averages
    if (m === 'vam') {
      for (const [yr, ym] of byYear) {
        const ymc = byYearCnt.get(yr)!;
        for (const [per, sum] of ym) {
          ym.set(per, sum / (ymc.get(per) ?? 1));
        }
      }
    }

    const years  = [...byYear.keys()].sort();
    const maxPer = g === 'week' ? 52 : 12;
    const rows: { year: string; period: number; value: number; climbTime?: number }[] = [];

    for (const yr of years) {
      const pm    = byYear.get(yr)!;
      const ct    = byYearClimbTime.get(yr);
      const limit = yr === _currentYear ? curPeriod : maxPer;
      for (let p = 1; p <= limit; p++) {
        const row: { year: string; period: number; value: number; climbTime?: number } =
          { year: String(yr), period: p, value: pm.get(p) ?? 0 };
        if (ct?.has(p)) row.climbTime = ct.get(p);
        rows.push(row);
      }
    }

    const colorDomain = years.map(String);
    const pastYears   = years.filter(y => y !== _currentYear);
    const colorRange  = years.map(y => {
      if (y === _currentYear) return '#60a5fa';
      if (y < 2000) return '#71717a'; // undated bucket (year 0 / "0000")
      const i = pastYears.indexOf(y);
      const t = pastYears.length <= 1 ? 0.5 : i / (pastYears.length - 1);
      return _rampColor(t);
    });

    return { rows, colorDomain, colorRange };
  }

  $: ({ rows, colorDomain, colorRange } = buildData(activities, metric, granularity));

  let chartEl:    HTMLElement;
  let chartCumEl: HTMLElement;

  function renderChartInto(
    el:          HTMLElement,
    rows:        { year: string; period: number; value: number }[],
    colorDomain: string[],
    colorRange:  string[],
    m:           Metric,
    g:           Granularity,
    cumulative:  boolean,
  ) {
    if (!el) return;
    el.innerHTML = '';
    if (!rows.length) return;

    // For the cumulative chart, convert per-period rows to running sums.
    // rows are ordered: for each year (sorted asc), periods 1..limit in order.
    let displayRows = rows;
    if (cumulative) {
      const acc = new Map<string, number>();
      displayRows = rows.map(r => {
        const prev = acc.get(r.year) ?? 0;
        const cum  = prev + r.value;
        acc.set(r.year, cum);
        return { ...r, value: cum };
      });
    }

    const maxPer  = g === 'week' ? 52 : 12;
    const xLabel  = g === 'week' ? 'Week' : 'Month';
    const axColor = document.documentElement.getAttribute('data-theme') === 'light' ? '#52525b' : '#a1a1aa';
    const fmt     = METRIC_FMT[m];
    const curYear = String(_currentYear);
    const pastRows = displayRows.filter(r => r.year !== curYear);
    const curRows  = displayRows.filter(r => r.year === curYear);
    const yLabel   = cumulative ? `Cumulative ${METRIC_LABEL[m]}` : METRIC_LABEL[m];

    const MONTH_LABELS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];

    const chart = Plot.plot({
      width:        el.clientWidth || 700,
      height:       320,
      marginLeft:   60,
      marginBottom: 40,
      style: { background: 'transparent', color: axColor },
      x: {
        label:      xLabel,
        domain:     [1, maxPer],
        grid:       true,
        tickFormat: g === 'month'
          ? (d: number) => MONTH_LABELS[d - 1]
          : (d: number) => String(d),
      },
      y: { label: yLabel, grid: true, zero: true },
      color: { domain: colorDomain, range: colorRange, legend: !cumulative },
      marks: [
        ...(m === 'vam' ? (() => {
          // VAM: dots only, no lines — opacity encodes total climbing time in period.
          const vamRows = [...pastRows, ...curRows].filter((r: any) => r.value > 0);
          return vamRows.length ? [
            Plot.dot(vamRows, {
              x: 'period', y: 'value', fill: 'year',
              r: 5,
              fillOpacity: (d: any) => vamOpacity(d.climbTime),
              tip: true,
              title: (d: any) => {
                const mins = d.climbTime ? `${Math.round(d.climbTime / 60)} min climbing` : '';
                return `${d.year}  ·  ${xLabel} ${d.period}\n${fmt(d.value)}${mins ? '\n' + mins : ''}`;
              },
            }),
          ] : [];
        })() : [
          ...(pastRows.length ? [
            Plot.line(pastRows, {
              x: 'period', y: 'value', stroke: 'year',
              strokeWidth: 1.5, curve: 'monotone-x',
            }),
            Plot.dot(pastRows, {
              x: 'period', y: 'value', fill: 'year', r: 2, fillOpacity: 0,
              tip: true,
              title: (d: any) => `${d.year}  ·  ${xLabel} ${d.period}\n${fmt(d.value)}`,
            }),
          ] : []),
          ...(curRows.length ? [
            Plot.line(curRows, {
              x: 'period', y: 'value', stroke: 'year',
              strokeWidth: 2.5, curve: 'monotone-x',
            }),
            Plot.dot(curRows, {
              x: 'period', y: 'value', fill: 'year', r: 2, fillOpacity: 0,
              tip: true,
              title: (d: any) => `${d.year}  ·  ${xLabel} ${d.period}\n${fmt(d.value)}`,
            }),
          ] : []),
        ]),
      ],
    });
    el.appendChild(chart);
  }

  function renderBoth(
    rows:        { year: string; period: number; value: number }[],
    colorDomain: string[],
    colorRange:  string[],
    m:           Metric,
    g:           Granularity,
  ) {
    renderChartInto(chartEl,    rows, colorDomain, colorRange, m, g, false);
    renderChartInto(chartCumEl, rows, colorDomain, colorRange, m, g, true);
  }

  $: renderBoth(rows, colorDomain, colorRange, metric, granularity);

  // Keep current values for resize / theme callbacks
  let _r = rows, _cd = colorDomain, _cr = colorRange, _m = metric, _g = granularity;
  $: _r = rows; $: _cd = colorDomain; $: _cr = colorRange; $: _m = metric; $: _g = granularity;

  onMount(() => {
    const redraw = () => renderBoth(_r, _cd, _cr, _m, _g);
    const ro = new ResizeObserver(redraw);
    ro.observe(chartEl);
    const mo = new MutationObserver(redraw);
    mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
    return () => { ro.disconnect(); mo.disconnect(); };
  });
</script>

<style>
  :global(.plot-tip text) { fill: #18181b !important; }
  .controls { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1.25rem; align-items: center; }
  .pill-group { display: flex; gap: 0.375rem; }
  .pill {
    padding: 0.2rem 0.65rem;
    border-radius: 9999px;
    font-size: 0.78rem;
    font-weight: 500;
    border: 1px solid #3f3f46;
    color: #71717a;
    background: transparent;
    cursor: pointer;
    transition: all 0.12s;
  }
  .pill.active { background: #1d4ed822; border-color: #60a5fa; color: #60a5fa; }
  .pill:hover:not(.active) { border-color: #a1a1aa; color: #d4d4d8; }
  .section-label {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #52525b;
    margin: 2rem 0 0.5rem;
  }
</style>

<div class="controls">
  <div class="pill-group">
    <button class="pill" class:active={metric === 'distance'}  on:click={() => metric = 'distance'}>Distance</button>
    <button class="pill" class:active={metric === 'elevation'} on:click={() => metric = 'elevation'}>Elevation</button>
    <button class="pill" class:active={metric === 'time'}      on:click={() => metric = 'time'}>Time</button>
    <button class="pill" class:active={metric === 'vam'}       on:click={() => metric = 'vam'}>Climbing VAM</button>
  </div>
  <div class="pill-group">
    <button class="pill" class:active={granularity === 'week'}  on:click={() => granularity = 'week'}>Weekly</button>
    <button class="pill" class:active={granularity === 'month'} on:click={() => granularity = 'month'}>Monthly</button>
  </div>
</div>

<div bind:this={chartEl} class="w-full min-h-[320px]"></div>

{#if metric !== 'vam'}
  <p class="section-label">Cumulative</p>
  <div bind:this={chartCumEl} class="w-full min-h-[320px]"></div>
{/if}

{#if !rows.length}
  <p class="text-zinc-500 text-sm mt-4">No activity data to display.</p>
{/if}
