import type { Theme } from "./theme";

const rgb = (hex: string) => [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255);
const linear = (v: number) => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
const encoded = (v: number) => v <= .0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - .055;
const cache = new Map<string, string>();

/** A hue-preserving OKLab ramp; equal progress steps have equal lightness steps. */
export function habitProgressColor(base: string, intensity: number, theme: Theme): string {
  if (!/^#[0-9a-f]{6}$/i.test(base)) base = "#58765b";
  const t = Math.max(0, Math.min(1, intensity));
  const key = `${base}:${t.toFixed(5)}:${theme}`;
  const cached = cache.get(key);
  if (cached) return cached;
  const [r, g, b] = rgb(base).map(linear) as [number, number, number];
  const l = Math.cbrt(.4122214708*r + .5363325363*g + .0514459929*b);
  const m = Math.cbrt(.2119034982*r + .6806995451*g + .1073969566*b);
  const s = Math.cbrt(.0883024619*r + .2817188376*g + .6299787005*b);
  const a = 1.9779984951*l - 2.428592205*m + .4505937099*s;
  const bb = .0259040371*l + .7827717662*m - .808675766*s;
  const chroma = Math.hypot(a, bb);
  const L = theme === "dark" ? .43 + .37*t : .86 - .38*t;
  let C = Math.min(chroma, .16) * (.5 + .5*t);
  let channels: number[] = [];
  // Reduce chroma instead of clipping RGB, preserving hue at the gamut boundary.
  for (let attempt = 0; attempt < 40; attempt++, C *= .9) {
    const A = chroma > 1e-6 ? a*C/chroma : 0;
    const B = chroma > 1e-6 ? bb*C/chroma : 0;
    const ll = (L + .3963377774*A + .2158037573*B) ** 3;
    const mm = (L - .1055613458*A - .0638541728*B) ** 3;
    const ss = (L - .0894841775*A - 1.291485548*B) ** 3;
    channels = [4.0767416621*ll - 3.3077115913*mm + .2309699292*ss, -1.2684380046*ll + 2.6097574011*mm - .3413193965*ss, -.0041960863*ll - .7034186147*mm + 1.707614701*ss];
    if (channels.every(v => v >= 0 && v <= 1)) break;
  }
  const result = "#" + channels.map(v => Math.round(Math.max(0, Math.min(1, encoded(v)))*255).toString(16).padStart(2, "0")).join("");
  if (cache.size > 4096) cache.clear();
  cache.set(key, result);
  return result;
}

/** Recover the server's existing step intensity without changing scoring or quantization. */
export function themedHabitShade(shade: string, base: string, theme: Theme): string {
  if (!/^#[0-9a-f]{6}$/i.test(shade) || !/^#[0-9a-f]{6}$/i.test(base)) return habitProgressColor(base, 1, theme);
  const color = rgb(base), sample = rgb(shade);
  const anchor = .2126*color[0]! + .7152*color[1]! + .0722*color[2]! > .75 ? 0 : 1;
  const distance = color.reduce((sum, v) => sum + (v-anchor)**2, 0);
  const intensity = distance ? color.reduce((sum, v, i) => sum + (sample[i]!-anchor)*(v-anchor), 0)/distance : 1;
  return habitProgressColor(base, Math.max(0, (intensity-.2)/.8), theme);
}
