/*
  A cara de cada loja no catálogo: as cores, a logo e as fontes.

  Nada aqui é escolhido à mão por loja. A cor da marca é a que o lojista
  escolheu na personalização; se ele nunca escolheu (ou ficou a do sistema),
  ela sai da própria logo. A segunda cor, a "tinta", é o escuro da logo quando
  ele tem cor (o marinho da Aurora), senão a marca bem escura — nunca preto
  chapado. Todo par de cor e texto passa pela conta de contraste da vitrine
  (`contraste.ts`, gêmeo do app da loja).

  Regras do protótipo aprovado em 19/09/2026; mudar aqui muda o catálogo de
  todas as lojas de uma vez.
*/

import { getAccessibleAccent, getContrastRatio, getReadableTextColor, normalizeHexColor } from "./contraste.ts";

/* ------------------------------------------------------------------ cor */

export function hexParaRgb(hex: string): [number, number, number] {
  const h = normalizeHexColor(hex).slice(1);
  return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];
}

export function rgbParaHex(r: number, g: number, b: number) {
  return `#${[r, g, b]
    .map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0"))
    .join("")
    .toUpperCase()}`;
}

export function misturar(a: string, b: string, quantoDeA: number) {
  const [r1, g1, b1] = hexParaRgb(a);
  const [r2, g2, b2] = hexParaRgb(b);
  return rgbParaHex(r1 * quantoDeA + r2 * (1 - quantoDeA), g1 * quantoDeA + g2 * (1 - quantoDeA), b1 * quantoDeA + b2 * (1 - quantoDeA));
}

export function hsl(hex: string) {
  const [r, g, b] = hexParaRgb(hex).map((v) => v / 255);
  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  const d = max - min;
  const l = (max + min) / 2;
  if (!d) return { h: 0, s: 0, l };
  const s = d / (1 - Math.abs(2 * l - 1));
  let h = max === r ? ((g - b) / d) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
  h *= 60;
  if (h < 0) h += 360;
  return { h, s, l };
}

export function deHsl(h: number, s: number, l: number) {
  const c = (1 - Math.abs(2 * l - 1)) * s;
  const hh = h / 60;
  const x = c * (1 - Math.abs((hh % 2) - 1));
  const [r, g, b] = hh < 1 ? [c, x, 0] : hh < 2 ? [x, c, 0] : hh < 3 ? [0, c, x] : hh < 4 ? [0, x, c] : hh < 5 ? [x, 0, c] : [c, 0, x];
  const m = l - c / 2;
  return rgbParaHex((r + m) * 255, (g + m) * 255, (b + m) * 255);
}

/* ----------------------------------------------------------------- logo */

export type TomDaLogo = "clara" | "escura" | "media" | "chapada";

export type AnaliseDaLogo = {
  transparente: boolean;
  tom: TomDaLogo;
  /* A cor média dos pixels visíveis. */
  media: string;
  /* A cor mais usada que tem cor de verdade (nem cinza, nem quase preta). */
  forte: string | null;
  /* A cor escura mais usada que ainda tem cor (um marinho, um vinho). */
  escura: string | null;
};

/**
 * O que a logo é: clara (branca), escura, ou chapada (tem fundo próprio,
 * como um JPG). E as cores que ela usa. Recebe os pixels RGBA já reduzidos
 * (96 × 96 no máximo): quem baixa e reduz é o servidor.
 */
export function analisarPixelsDaLogo(pixels: Uint8Array | Buffer, largura: number, altura: number): AnaliseDaLogo {
  let transparentes = 0;
  let opacos = 0;
  let somaLuz = 0;
  let r0 = 0;
  let g0 = 0;
  let b0 = 0;
  const baldes = new Map<string, { n: number; r: number; g: number; b: number }>();
  for (let i = 0; i + 3 < pixels.length; i += 4) {
    const r = pixels[i];
    const g = pixels[i + 1];
    const b = pixels[i + 2];
    const a = pixels[i + 3];
    if (a < 40) {
      transparentes += 1;
      continue;
    }
    if (a < 200) continue;
    opacos += 1;
    somaLuz += (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
    r0 += r;
    g0 += g;
    b0 += b;
    const chave = `${r >> 4}-${g >> 4}-${b >> 4}`;
    const balde = baldes.get(chave) || { n: 0, r: 0, g: 0, b: 0 };
    balde.n += 1;
    balde.r += r;
    balde.g += g;
    balde.b += b;
    baldes.set(chave, balde);
  }
  const total = Math.max(1, largura * altura);
  const transparente = transparentes / total > 0.08;
  const media = opacos ? rgbParaHex(r0 / opacos, g0 / opacos, b0 / opacos) : "#000000";
  const cores = [...baldes.values()]
    .map((balde) => ({ n: balde.n, hex: rgbParaHex(balde.r / balde.n, balde.g / balde.n, balde.b / balde.n) }))
    .sort((a, b) => b.n - a.n);
  const forte =
    cores.find((cor) => {
      const c = hsl(cor.hex);
      return c.s > 0.45 && c.l > 0.25 && c.l < 0.75;
    })?.hex ?? null;
  const escura =
    cores.find((cor) => {
      const c = hsl(cor.hex);
      return c.l < 0.22 && c.s > 0.12;
    })?.hex ?? null;
  const luzMedia = opacos ? somaLuz / opacos : 0;
  const tom: TomDaLogo = !transparente ? "chapada" : luzMedia > 0.72 ? "clara" : luzMedia < 0.4 ? "escura" : "media";
  return { transparente, tom, media, forte, escura };
}

export type CoresDoCatalogo = {
  marca: string;
  sobreMarca: string;
  tintaNaMarca: string;
  tinta: string;
  sobreTinta: string;
  marcaNaTinta: string;
  papel2: string;
  linha: string;
  suave: string;
  preco: string;
};

/* A cor que o sistema põe quando o lojista não escolheu nenhuma. */
export const COR_DO_SISTEMA = "#4F46E5";

export function coresDaMarca(corEscolhida: string | null | undefined, logo: AnaliseDaLogo | null): CoresDoCatalogo {
  const escolhida = normalizeHexColor(corEscolhida, COR_DO_SISTEMA);
  /* Sem cor escolhida (ou com a do sistema), a cor sai da logo. */
  const marca = !corEscolhida || escolhida === COR_DO_SISTEMA ? logo?.forte || escolhida : escolhida;
  const { h, s } = hsl(marca);
  /* A "tinta": o escuro da própria logo quando ele tem cor (um marinho),
     senão a cor da marca bem escura. Nunca preto chapado. */
  const tinta = logo?.escura && hsl(logo.escura).l < 0.22 ? logo.escura : deHsl(h, Math.min(s, 0.55), 0.12);
  const sobreMarca = getReadableTextColor(marca, { dark: tinta, light: "#FFFFFF" });
  const tintaNaMarca = getContrastRatio(marca, tinta) >= 4.5 ? tinta : sobreMarca;
  const suave = getAccessibleAccent(misturar(tinta, "#FFFFFF", 0.62), "#FFFFFF", { minRatio: 4.6 });
  return {
    marca,
    sobreMarca,
    tintaNaMarca,
    tinta,
    sobreTinta: "#FFFFFF",
    marcaNaTinta: getAccessibleAccent(marca, tinta, { minRatio: 4.5 }),
    papel2: misturar(marca, "#FFFFFF", 0.08),
    linha: misturar(tinta, "#FFFFFF", 0.12),
    suave,
    preco: getAccessibleAccent(marca, "#FFFFFF", { minRatio: 3 }),
  };
}

export type VarianteDaLogo = { url: string; placa: "branca" | "tinta" | null } | null;

export type LogosDoCatalogo = {
  /* Sobre a cor da marca (capa). */
  naMarca: VarianteDaLogo;
  /* Sobre o papel branco (cabeçalho das páginas). */
  noClaro: VarianteDaLogo;
  /* O que entra no lugar da foto que falta. */
  placeholder: { url: string | null; modo: "silhueta" | "imagem" | "inicial" };
};

/* Onde a logo aparece e se precisa de "placa" por trás para não sumir. */
export function logosDaLoja(url: string | null, logo: AnaliseDaLogo | null, cores: CoresDoCatalogo): LogosDoCatalogo {
  if (!url || !logo) return { naMarca: null, noClaro: null, placeholder: { url: null, modo: "inicial" } };
  if (logo.tom === "chapada") {
    return { naMarca: { url, placa: "branca" }, noClaro: { url, placa: null }, placeholder: { url, modo: "imagem" } };
  }
  const contrasteNaMarca = getContrastRatio(logo.media, cores.marca);
  if (logo.tom === "clara") {
    return {
      naMarca: { url, placa: contrasteNaMarca >= 2 ? null : "tinta" },
      noClaro: { url, placa: "tinta" },
      placeholder: { url, modo: "silhueta" },
    };
  }
  return {
    naMarca: { url, placa: contrasteNaMarca >= 2.2 ? null : "branca" },
    noClaro: { url, placa: null },
    placeholder: { url, modo: "silhueta" },
  };
}

/* --------------------------------------------------------------- fontes */

/*
  A chave gravada em `catalog_font_heading`/`catalog_font_body` (a mesma do
  seletor da personalização, em `lib/catalog-fonts.ts`) vira o nome da família
  no Google Fonts. A regra é capitalizar cada pedaço; estas são as que têm
  sigla no nome, e o Google recusa a família com a caixa errada.
*/
const NOMES_COM_SIGLA: Record<string, string> = {
  "dm-sans": "DM Sans",
  "dm-serif-display": "DM Serif Display",
  "eb-garamond": "EB Garamond",
  "ibm-plex-mono": "IBM Plex Mono",
  "jetbrains-mono": "JetBrains Mono",
};

export const FONTE_DO_TITULO_PADRAO = "Archivo";
export const FONTE_DO_TEXTO_PADRAO = "Plus Jakarta Sans";

export function familiaDaFonte(chave: string | null | undefined): string | null {
  const limpa = String(chave ?? "").trim().toLowerCase();
  if (!limpa || !/^[a-z0-9_-]+$/.test(limpa)) return null;
  const normal = limpa.replace(/_/g, "-");
  if (NOMES_COM_SIGLA[normal]) return NOMES_COM_SIGLA[normal];
  return normal
    .split("-")
    .filter(Boolean)
    .map((parte) => parte.charAt(0).toUpperCase() + parte.slice(1))
    .join(" ");
}

/* O endereço da folha de estilo de uma família, só com os pesos pedidos. */
export function folhaDoGoogle(familia: string, pesos: string | null) {
  const nome = encodeURIComponent(familia).replace(/%20/g, "+");
  return `https://fonts.googleapis.com/css2?family=${nome}${pesos ? `:wght@${pesos}` : ""}&display=swap`;
}

/* Os pesos que o desenho usa: títulos em 600–800, texto em 400–700. */
export const PESOS_DO_TITULO = "600;700;800";
export const PESOS_DO_TEXTO = "400;500;600;700";

export function pilhaDaFonte(familia: string, reserva: string) {
  return `"${familia}", "${reserva}", system-ui, sans-serif`;
}
