/**
 * Bridge between the CSS token layer and the bits of the UI that are painted
 * from JavaScript — Chart.js datasets and Leaflet markers.
 *
 * Those two can't use var(), so before this they carried their own hardcoded
 * hexes. That is how the chart palette drifted off-brand (a stray purple and
 * Flat UI's stock blue) and why charts would have ignored the dark theme
 * entirely. Reading the computed custom property keeps one source of truth.
 */

'use client';

import { useEffect, useState } from 'react';

export type ThemeToken =
  | '--brand'
  | '--brand-strong'
  | '--brand-bg'
  | '--status-above'
  | '--status-above-bg'
  | '--status-below'
  | '--status-below-bg'
  | '--text-primary'
  | '--text-secondary'
  | '--text-muted'
  | '--text-inverse'
  | '--bg-card'
  | '--bg-secondary'
  | '--border'
  | '--border-strong'
  | '--surface-inverse'
  | '--chart-1'
  | '--chart-2'
  | '--chart-3'
  | '--chart-4'
  | '--chart-5'
  | '--chart-6'
  | '--chart-grid'
  | '--chart-reference'
  | '--phase-primary'
  | '--phase-secondary-text'
  | '--medal-gold'
  | '--medal-silver'
  | '--medal-bronze';

/** Values used before hydration and in any non-DOM context (SSR, tests). */
const FALLBACK: Record<string, string> = {
  '--brand': '#0F766E',
  '--brand-strong': '#0C5F58',
  '--brand-bg': 'rgba(15, 118, 110, 0.10)',
  '--status-above': '#36743F',
  '--status-above-bg': 'rgba(54, 116, 63, 0.12)',
  '--status-below': '#A9481F',
  '--status-below-bg': 'rgba(249, 115, 96, 0.15)',
  '--text-primary': '#1C2731',
  '--text-secondary': '#4A5560',
  '--text-muted': '#5F6A75',
  '--text-inverse': '#FFFFFF',
  '--bg-card': '#FFFFFF',
  '--bg-secondary': '#F5EFE6',
  '--border': '#E5E7EB',
  '--border-strong': '#D3D7DD',
  '--surface-inverse': '#0F766E',
  '--chart-1': '#0F766E',
  '--chart-2': '#36743F',
  '--chart-3': '#806200',
  '--chart-4': '#A9481F',
  '--chart-5': '#2F6F8F',
  '--chart-6': '#5F4FA8',
  '--chart-grid': '#E5E7EB',
  '--chart-reference': '#5F6A75',
  '--phase-primary': '#0F766E',
  '--phase-secondary-text': '#2A6180',
  '--medal-gold': '#806200',
  '--medal-silver': '#6B7580',
  '--medal-bronze': '#8A5A2B',
};

/** Read one token. Safe to call during SSR — returns the light-theme value. */
export function token(name: ThemeToken): string {
  if (typeof window === 'undefined') return FALLBACK[name];
  const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
  return value || FALLBACK[name];
}

/**
 * Same as `token`, but the component re-renders when the OS theme flips so
 * canvas-painted colours don't get stranded in the previous theme.
 */
export function useThemeTokens<T extends readonly ThemeToken[]>(
  ...names: T
): { [K in keyof T]: string } {
  const read = () => names.map((n) => token(n)) as { [K in keyof T]: string };
  const [values, setValues] = useState(read);

  useEffect(() => {
    // Only commit when something actually changed. In the light theme the
    // computed values equal the fallbacks, so this is a no-op — which matters
    // because a state update here re-renders the chart and makes Chart.js
    // resize a canvas that may not be laid out yet.
    const sync = () => setValues((prev) => (sameValues(prev, read()) ? prev : read()));
    sync();
    const mq = window.matchMedia('(prefers-color-scheme: dark)');
    mq.addEventListener('change', sync);
    return () => mq.removeEventListener('change', sync);
    // `names` is a fixed-length literal tuple at every call site.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return values;
}

function sameValues(a: readonly string[], b: readonly string[]): boolean {
  return a.length === b.length && a.every((v, i) => v === b[i]);
}

const SERIES = [
  '--series-1', '--series-2', '--series-3', '--series-4',
  '--series-5', '--series-6', '--series-7', '--series-8',
] as const;

const SERIES_FALLBACK = [
  '#0F766E', '#36743F', '#806200', '#A9481F',
  '#2F6F8F', '#5F4FA8', '#0E7A86', '#8A4A6B',
];

/**
 * Resolved series colours for Chart.js datasets, in the same index order as
 * CHART_COLORS — so a school keeps its colour between the canvas and the DOM
 * swatch beside it. Re-reads when the OS theme flips.
 */
export function useSeriesColors(): string[] {
  const read = () =>
    typeof window === 'undefined'
      ? SERIES_FALLBACK
      : SERIES.map((n, i) => {
          const v = getComputedStyle(document.documentElement).getPropertyValue(n).trim();
          return v || SERIES_FALLBACK[i];
        });

  const [colors, setColors] = useState(read);

  useEffect(() => {
    // See useThemeTokens: skip the commit when nothing changed, so mounting a
    // chart doesn't cost an extra render.
    const sync = () => setColors((prev) => (sameValues(prev, read()) ? prev : read()));
    sync();
    const mq = window.matchMedia('(prefers-color-scheme: dark)');
    mq.addEventListener('change', sync);
    return () => mq.removeEventListener('change', sync);
  }, []);

  return colors;
}

/** rgba() built from a token's hex, for fills that need their own alpha. */
export function alpha(name: ThemeToken, a: number): string {
  const hex = token(name);
  const m = /^#([0-9a-f]{6})$/i.exec(hex);
  if (!m) return hex;
  const n = parseInt(m[1], 16);
  return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
}
