// Fully generated categorical chart colors — no fixed palette, so any number
// of series (tens, hundreds, ~thousands) is covered with nothing to maintain.

function hslToHex(h: number, s: number, l: number): string {
  const c = (1 - Math.abs(2 * l - 1)) * s;
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
  const m = l - c / 2;
  let r = 0;
  let g = 0;
  let b = 0;
  if (h < 60) [r, g, b] = [c, x, 0];
  else if (h < 120) [r, g, b] = [x, c, 0];
  else if (h < 180) [r, g, b] = [0, c, x];
  else if (h < 240) [r, g, b] = [0, x, c];
  else if (h < 300) [r, g, b] = [x, 0, c];
  else [r, g, b] = [c, 0, x];
  const to = (v: number) =>
    Math.round((v + m) * 255)
      .toString(16)
      .padStart(2, "0");
  return `#${to(r)}${to(g)}${to(b)}`;
}

// Golden-angle hue rotation spreads successive hues maximally; saturation and
// lightness cycle so colors landing on a near hue still differ in shade.
export function colorByIndex(i: number): string {
  const hue = (i * 137.508) % 360;
  const sat = 0.55 + 0.2 * ((i % 3) / 2);
  const light = 0.45 + 0.18 * ((i % 4) / 3);
  return hslToHex(hue, sat, light);
}

// Distinct color per key by position. Deterministic for a given key order.
export function buildColorScale(keys: Iterable<string>): Map<string, string> {
  const map = new Map<string, string>();
  let i = 0;
  for (const key of keys) {
    if (!map.has(key)) map.set(key, colorByIndex(i++));
  }
  return map;
}
