const DARK_TEXT = "#111827";
const LIGHT_TEXT = "#FFFFFF";

type ContrastOptions = {
  dark?: string;
  light?: string;
  minRatio?: number;
};

function expandShortHex(value: string) {
  if (value.length !== 3) return value;
  return value
    .split("")
    .map((char) => `${char}${char}`)
    .join("");
}

export function normalizeHexColor(value: string | null | undefined, fallback = "#4F46E5") {
  const candidate = (value || "").trim().replace(/^#/, "");
  const expanded = expandShortHex(candidate);
  if (!/^[0-9a-fA-F]{6}$/.test(expanded)) {
    return normalizeHexColor(fallback, "#4F46E5");
  }
  return `#${expanded.toUpperCase()}`;
}

function hexToRgb(hex: string) {
  const normalized = normalizeHexColor(hex);
  const h = normalized.replace("#", "");
  return {
    r: Number.parseInt(h.slice(0, 2), 16),
    g: Number.parseInt(h.slice(2, 4), 16),
    b: Number.parseInt(h.slice(4, 6), 16),
  };
}

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

export function getRelativeLuminance(color: string) {
  const rgb = hexToRgb(color);
  const r = srgbToLinear(rgb.r);
  const g = srgbToLinear(rgb.g);
  const b = srgbToLinear(rgb.b);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

export function getContrastRatio(background: string, foreground: string) {
  const L1 = getRelativeLuminance(background);
  const L2 = getRelativeLuminance(foreground);
  const lighter = Math.max(L1, L2);
  const darker = Math.min(L1, L2);
  return (lighter + 0.05) / (darker + 0.05);
}

export function getReadableTextColor(background: string, options: ContrastOptions = {}) {
  const dark = normalizeHexColor(options.dark || DARK_TEXT);
  const light = normalizeHexColor(options.light || LIGHT_TEXT);
  const base = normalizeHexColor(background);
  const contrastWithDark = getContrastRatio(base, dark);
  const contrastWithLight = getContrastRatio(base, light);
  return contrastWithDark >= contrastWithLight ? dark : light;
}

function rgbToHsl(hex: string) {
  const { r, g, b } = hexToRgb(hex);
  const rr = r / 255;
  const gg = g / 255;
  const bb = b / 255;
  const max = Math.max(rr, gg, bb);
  const min = Math.min(rr, gg, bb);
  const delta = max - min;
  const l = (max + min) / 2;

  if (delta === 0) return { h: 0, s: 0, l: l * 100 };

  const s = delta / (1 - Math.abs(2 * l - 1));
  let h: number;
  if (max === rr) h = ((gg - bb) / delta) % 6;
  else if (max === gg) h = (bb - rr) / delta + 2;
  else h = (rr - gg) / delta + 4;
  h *= 60;
  if (h < 0) h += 360;

  return { h, s: s * 100, l: l * 100 };
}

function hslToHex(h: number, s: number, l: number) {
  const sat = clamp(s, 0, 100) / 100;
  const lig = clamp(l, 0, 100) / 100;
  const c = (1 - Math.abs(2 * lig - 1)) * sat;
  const hh = (((h % 360) + 360) % 360) / 60;
  const x = c * (1 - Math.abs((hh % 2) - 1));

  let rgb: [number, number, number];
  if (hh < 1) rgb = [c, x, 0];
  else if (hh < 2) rgb = [x, c, 0];
  else if (hh < 3) rgb = [0, c, x];
  else if (hh < 4) rgb = [0, x, c];
  else if (hh < 5) rgb = [x, 0, c];
  else rgb = [c, 0, x];

  const m = lig - c / 2;
  const toHex = (value: number) =>
    Math.round(clamp((value + m) * 255, 0, 255))
      .toString(16)
      .padStart(2, "0");

  return `#${toHex(rgb[0])}${toHex(rgb[1])}${toHex(rgb[2])}`.toUpperCase();
}

/**
 * A cor da marca ajustada para ser LIDA sobre um fundo, mantendo o matiz.
 *
 * Uma loja com cor clara (um azul bebe, um pastel) usa essa cor tanto em botao
 * quanto em texto e icone. Sobre fundo branco a cor original chega a razao de
 * contraste 1.4 e o elemento simplesmente some da tela, mesmo estando la.
 *
 * Trocar por preto resolveria a leitura e apagaria a identidade da loja. Aqui
 * so a luminosidade muda: escurece contra fundo claro, clareia contra fundo
 * escuro, e para no primeiro tom que atinge a razao pedida, ficando o mais
 * proximo possivel da cor escolhida pelo lojista.
 */
export function getAccessibleAccent(
  color: string,
  background = "#FFFFFF",
  options: ContrastOptions = {}
) {
  const minRatio = options.minRatio ?? 4.5;
  const base = normalizeHexColor(color);
  const safeBackground = normalizeHexColor(background);
  if (getContrastRatio(safeBackground, base) >= minRatio) return base;

  const { h, s, l } = rgbToHsl(base);
  const backgroundIsLight = getRelativeLuminance(safeBackground) > 0.4;

  /* O contraste e monotonico na luminosidade, entao busca binaria acha o tom
     mais proximo do original que ainda passa. 12 passos deixam o erro em L
     abaixo de 0,03% - invisivel a olho nu. */
  let low = backgroundIsLight ? 0 : l;
  let high = backgroundIsLight ? l : 100;
  let best = hslToHex(h, s, backgroundIsLight ? 0 : 100);

  for (let step = 0; step < 12; step += 1) {
    const mid = (low + high) / 2;
    const candidate = hslToHex(h, s, mid);
    if (getContrastRatio(safeBackground, candidate) >= minRatio) {
      best = candidate;
      if (backgroundIsLight) low = mid;
      else high = mid;
    } else if (backgroundIsLight) {
      high = mid;
    } else {
      low = mid;
    }
  }

  return best;
}

export function ensureColorContrast(
  foreground: string,
  background: string,
  options: ContrastOptions = {}
) {
  const minRatio = options.minRatio ?? 4.5;
  const safeForeground = normalizeHexColor(foreground);
  const safeBackground = normalizeHexColor(background);
  const currentRatio = getContrastRatio(safeBackground, safeForeground);
  if (currentRatio >= minRatio) return safeForeground;

  /* Cor com matiz proprio vira uma versao mais escura (ou mais clara) dela
     mesma. Cair direto para preto, como antes, deixava a vitrine legivel mas
     sem nenhum traço da marca. Branco, preto e cinza nao tem matiz para
     preservar e continuam no caminho antigo. */
  const { s, l } = rgbToHsl(safeForeground);
  if (s > 8 && l > 4 && l < 96) {
    return getAccessibleAccent(safeForeground, safeBackground, { minRatio });
  }

  return getReadableTextColor(safeBackground, options);
}

export function withAlpha(color: string, alpha: number) {
  const safe = clamp(alpha, 0, 1);
  const { r, g, b } = hexToRgb(color);
  return `rgba(${r}, ${g}, ${b}, ${safe})`;
}

function clamp(value: number, min: number, max: number) {
  return Math.min(Math.max(value, min), max);
}

