// Tema por restaurante: convierte el color primario (hex) en variables CSS
// que la tienda y la vista previa aplican en vivo. Tailwind usa
// rgb(var(--marca-rgb) / <alpha>) para que funcione tambien con opacidades.
//
// Los tonos NO se derivan multiplicando el RGB (eso ignora que el ojo no percibe
// igual el brillo de un amarillo que el de un morado): se convierten a OKLCH,
// que separa luminosidad de matiz, y se ajusta solo la luminosidad. Asi:
//   - un negocio con amarillo (#F59E0B) obtiene un texto legible en vez de uno
//     que se pierde contra el blanco,
//   - un negocio con morado casi negro (#1a0146) obtiene un tinte con matiz real
//     en vez de un gris sucio, y un texto que se distingue del negro.
// Ademas se calcula --marca-contraste-rgb (blanco o negro) para el texto que va
// ENCIMA del color de marca, porque con marcas claras el blanco es ilegible.

export const COLOR_MARCA_DEFECTO = '#F97316';

// Contraste minimo texto/fondo (WCAG 2.1 AA para texto normal).
const CONTRASTE_MINIMO = 4.5;

type RGB = [number, number, number];
type OKLCH = { L: number; C: number; H: number };

function parseHex(hex: string): RGB | null {
  const h = hex.replace('#', '').trim();
  const n = h.length === 3 ? h.split('').map((c) => c + c).join('') : h;
  if (!/^[0-9a-fA-F]{6}$/.test(n)) return null;
  return [parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16)];
}

function canal(rgb: RGB): string {
  return rgb.map((v) => Math.max(0, Math.min(255, Math.round(v)))).join(' ');
}

// --- sRGB <-> OKLab/OKLCH (Bjorn Ottosson) ---

function aLineal(c: number): number {
  const v = c / 255;
  return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}

function aGamma(v: number): number {
  const c = v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
  return c * 255;
}

function rgbAOklch([r, g, b]: RGB): OKLCH {
  const lr = aLineal(r);
  const lg = aLineal(g);
  const lb = aLineal(b);
  const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
  const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
  const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
  const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
  const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
  const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
  return { L, C: Math.sqrt(a * a + bb * bb), H: Math.atan2(bb, a) };
}

function oklchARgb({ L, C, H }: OKLCH): RGB {
  const a = C * Math.cos(H);
  const b = C * Math.sin(H);
  const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3);
  const m = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3);
  const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3);
  return [
    aGamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
    aGamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
    aGamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
  ];
}

// Al mover la luminosidad, un croma alto puede caerse fuera de sRGB y el recorte
// de canal ensucia el matiz. Se baja el croma hasta que el color entre entero.
function aRgbEnGamut(c: OKLCH): RGB {
  let croma = c.C;
  for (let i = 0; i < 24; i++) {
    const rgb = oklchARgb({ ...c, C: croma });
    if (rgb.every((v) => v >= -0.5 && v <= 255.5)) return rgb.map((v) => Math.max(0, Math.min(255, v))) as RGB;
    croma *= 0.9;
  }
  return oklchARgb({ ...c, C: 0 }).map((v) => Math.max(0, Math.min(255, v))) as RGB;
}

// --- contraste WCAG ---

function luminancia([r, g, b]: RGB): number {
  return 0.2126 * aLineal(r) + 0.7152 * aLineal(g) + 0.0722 * aLineal(b);
}

export function contraste(a: RGB, b: RGB): number {
  const la = luminancia(a);
  const lb = luminancia(b);
  return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
}

const BLANCO: RGB = [255, 255, 255];
const NEGRO: RGB = [23, 23, 23];

// Baja la luminosidad del color hasta que contraste lo suficiente contra el fondo.
// La luminosidad y el contraste contra blanco son monotonos, asi que basta bajar
// de a poco desde el punto de partida.
function oscurecerHastaContrastar(base: OKLCH, fondo: RGB, minimo: number): RGB {
  let L = base.L;
  for (let i = 0; i < 60; i++) {
    const rgb = aRgbEnGamut({ ...base, L });
    if (contraste(rgb, fondo) >= minimo || L <= 0.06) return rgb;
    L -= 0.015;
  }
  return aRgbEnGamut({ ...base, L: 0.06 });
}

export function variablesTema(color: string | null | undefined): Record<string, string> {
  const rgb = parseHex(color ?? '') ?? (parseHex(COLOR_MARCA_DEFECTO) as RGB);
  const base = rgbAOklch(rgb);

  // Tinte de fondo (chips, circulos de icono): luminosidad alta fija y croma
  // reducido pero NO anulado, para que conserve el matiz de la marca.
  const claro = aRgbEnGamut({ L: 0.965, C: Math.min(base.C * 0.4, 0.05), H: base.H });

  // Texto y acentos de la marca sobre fondo claro.
  // El punto de partida se acota: una marca casi negra se ACLARA para que se
  // lea como su color y no como negro; una marca clara se oscurece.
  // El contraste se exige contra el TINTE, no contra el blanco, porque el texto
  // de marca tambien se pinta encima del tinte (circulos de icono, chips) y ese
  // es el fondo mas oscuro de los dos: si cumple ahi, cumple sobre blanco.
  const partida: OKLCH = { ...base, L: Math.max(0.32, Math.min(base.L, 0.62)) };
  const oscuro = oscurecerHastaContrastar(partida, claro, CONTRASTE_MINIMO);

  // Texto que va ENCIMA del color de marca: el que mejor contraste de los dos.
  const contrasteRgb = contraste(rgb, BLANCO) >= contraste(rgb, NEGRO) ? BLANCO : NEGRO;

  return {
    '--marca-rgb': canal(rgb),
    '--marca-oscuro-rgb': canal(oscuro),
    '--marca-claro-rgb': canal(claro),
    '--marca-contraste-rgb': canal(contrasteRgb),
  };
}
