/*
  Como a vitrine desenha as variações.

  Até aqui todo atributo virava a mesma fileira de pílulas — cor, tamanho,
  acabamento e o lixo que veio de importação (ncm_8, id_fornecedor, cofins),
  tudo igual e tudo na cara do cliente. Numa caneta com quatro atributos isso
  são trinta e poucos quadrados antes de o botão Comprar acender.

  Aqui mora só a **decisão**: que tipo de coisa é este atributo e como ele
  merece ser mostrado. Nenhum desenho, nenhum React — assim a mesma regra
  serve a loja, o admin e o que vier depois, sem duas verdades.

  A escolha do lojista (`display_style`) sempre vence; sem ela o conteúdo
  decide. É o que faz os 1.511 produtos que já existem melhorarem sozinhos,
  sem ninguém voltar em 4 mil atributos para reconfigurar.
*/

export type EstiloDaVariacao = "cores" | "miniaturas" | "botoes" | "lista" | "dropdown";

/* Quantas opções cabem em botões antes de virar uma lista com busca. Acima
   disso a fileira vira parede — e existe um produto com 86 opções num
   atributo só. */
const LIMITE_DE_BOTOES = 8;

export type OpcaoDaVitrine = {
  id: string | null;
  label: string;
  preco: number;
  precoPorPeca: boolean;
  /* Em que o adicional se multiplica (measure, piece, order); nulo na opção antiga. */
  unidadeDoPreco?: string | null;
  estoque: number | null;
  obrigatoria: boolean;
  posicao: number;
  imageId: string | null;
  /* A cor que o lojista escolheu à mão para esta opção; vence o dicionário. */
  swatch: string | null;
};

export type AtributoDaVitrine = {
  id: string;
  /* O nome como o cliente lê: sem o dois-pontos que veio da importação. */
  nome: string;
  nomeOriginal: string;
  multipla: boolean;
  obrigatorio: boolean;
  posicao: number;
  opcoes: OpcaoDaVitrine[];
  estilo: EstiloDaVariacao;
  /* Uma opção só e sem preço não é escolha: é informação. Vira uma linha de
     texto em vez de um botão que só pode ser clicado de um jeito. */
  informativo: boolean;
};

type OpcaoBruta =
  | string
  | {
      id?: string | null;
      label?: string | null;
      price?: number | null;
      price_applies_per_quantity?: boolean | null;
      price_unit?: string | null;
      stock?: number | null;
      is_required?: boolean | null;
      position?: number | null;
      image_id?: string | null;
      swatch?: string | null;
    };

type AtributoBruto = {
  id?: string | null;
  name?: string | null;
  options?: OpcaoBruta[] | null;
  allow_multiple?: boolean | null;
  is_required?: boolean | null;
  position?: number | null;
  display_style?: string | null;
  show_in_store?: boolean | null;
};

function texto(valor: unknown) {
  return String(valor ?? "").trim();
}

export function semAcento(valor: string) {
  return valor
    .normalize("NFD")
    .replace(/\p{Diacritic}/gu, "")
    .toLowerCase()
    .trim();
}

/*
  O nome do atributo como o cliente merece ler.

  A importação do sistema antigo deixou "Tamanho:" e "Cor:" com dois-pontos
  grudados — 155 atributos assim no banco. Ninguém vai voltar lá para tirar,
  então a vitrine tira na hora de mostrar.
*/
export function nomeLimpo(nome: string) {
  return texto(nome).replace(/[:\-–—]+\s*$/, "").trim();
}

/* ------------------------------------------------------------------ *
 *  Cores
 * ------------------------------------------------------------------ */

/*
  O dicionário existe porque os rótulos já são bons: "preto" aparece 229
  vezes, "azul" 161, "branco" 159 — nomes de cor em português, escritos por
  gente. Casar isso com um hex é o que transforma 455 atributos de cor em
  bolinha sem pedir nada a ninguém.

  Só nomes inequívocos de cor entram aqui. "Limão", "Uva" e "Salmão" ficam
  de fora de propósito: num atributo "Sabor" eles viravam bolinha, e uma
  bolinha roxa não diz nada sobre sabor de uva.
*/
const CORES_BASE: Record<string, string> = {
  preto: "#101014",
  branco: "#ffffff",
  cinza: "#8b8f98",
  chumbo: "#4a4e57",
  grafite: "#3a3d44",
  azul: "#1f5fd0",
  vermelho: "#d92c2c",
  verde: "#1f9d55",
  amarelo: "#f5c518",
  laranja: "#f1701f",
  rosa: "#ef5da8",
  roxo: "#7b3fe4",
  lilas: "#b98cf0",
  violeta: "#8b5cf6",
  marrom: "#7a4a24",
  bege: "#e4d3b4",
  creme: "#f6ecd6",
  nude: "#e0bfa8",
  vinho: "#6d1f33",
  bordo: "#5f1d2c",
  turquesa: "#1fbfb8",
  ciano: "#18b6d8",
  magenta: "#d9219e",
  coral: "#f4705c",
  pessego: "#f9b48a",
  terracota: "#c96a4a",
  mostarda: "#d9a520",
  caramelo: "#b5762e",
  chocolate: "#5b3a24",
  cafe: "#4a3226",
  areia: "#ddc9a3",
  offwhite: "#f4f1ea",
  gelo: "#eef3f7",
  perola: "#f0eae0",
  marfim: "#f7f0de",
  oliva: "#7d8b3a",
  musgo: "#5c6b3a",
  petroleo: "#1d4a52",
  jeans: "#4a6b96",
  indigo: "#3a4a8c",
  celeste: "#8fc9ef",
  salmon: "#f79b84",
};

/* Modificadores que andam colados numa cor base: "azul bebê", "verde
   escuro", "rosa pink". Multiplicam o dicionário sem inchar a lista. */
const MODIFICADORES: Record<string, { clarear?: number; escurecer?: number; saturar?: number }> = {
  claro: { clarear: 0.34 },
  clara: { clarear: 0.34 },
  bebe: { clarear: 0.52 },
  baby: { clarear: 0.52 },
  pastel: { clarear: 0.44 },
  suave: { clarear: 0.3 },
  escuro: { escurecer: 0.34 },
  escura: { escurecer: 0.34 },
  marinho: { escurecer: 0.48 },
  petroleo: { escurecer: 0.4 },
  royal: { saturar: 0.25 },
  forte: { saturar: 0.3 },
  neon: { saturar: 0.55, clarear: 0.08 },
  pink: { saturar: 0.4 },
  vivo: { saturar: 0.3 },
  militar: { escurecer: 0.25, saturar: -0.35 },
  fluorescente: { saturar: 0.55, clarear: 0.08 },
};

/* Nomes que não são uma cor chapada: brilham, misturam ou não têm cor. */
const ESPECIAIS: Record<string, CorDaOpcao> = {
  transparente: { tipo: "xadrez", fundo: "#ffffff" },
  cristal: { tipo: "xadrez", fundo: "#ffffff" },
  incolor: { tipo: "xadrez", fundo: "#ffffff" },
  dourado: { tipo: "brilho", fundo: "#d4af37", brilho: ["#f7e7a1", "#c9992c"] },
  ouro: { tipo: "brilho", fundo: "#d4af37", brilho: ["#f7e7a1", "#c9992c"] },
  prata: { tipo: "brilho", fundo: "#c0c4c9", brilho: ["#f2f4f6", "#9aa0a6"] },
  prateado: { tipo: "brilho", fundo: "#c0c4c9", brilho: ["#f2f4f6", "#9aa0a6"] },
  cromado: { tipo: "brilho", fundo: "#c9ced4", brilho: ["#ffffff", "#8f969e"] },
  bronze: { tipo: "brilho", fundo: "#a9722e", brilho: ["#d7a86a", "#7d4f1c"] },
  cobre: { tipo: "brilho", fundo: "#b06a3b", brilho: ["#e0a075", "#7f4522"] },
  metalizado: { tipo: "brilho", fundo: "#b6bcc4", brilho: ["#eef1f4", "#868d96"] },
  holografico: { tipo: "arcoiris", fundo: "#c9b8f0" },
  furtacor: { tipo: "arcoiris", fundo: "#c9b8f0" },
  colorido: { tipo: "arcoiris", fundo: "#c9b8f0" },
  multicolor: { tipo: "arcoiris", fundo: "#c9b8f0" },
  sortido: { tipo: "arcoiris", fundo: "#c9b8f0" },
  variadas: { tipo: "arcoiris", fundo: "#c9b8f0" },
  natural: { tipo: "chapada", fundo: "#e2d5bd" },
  madeira: { tipo: "chapada", fundo: "#b5814a" },
};

export type CorDaOpcao = {
  tipo: "chapada" | "brilho" | "xadrez" | "arcoiris";
  fundo: string;
  /* Do claro ao escuro, para o metálico não parecer tinta fosca. */
  brilho?: [string, string];
};

function hexParaRgb(hex: string) {
  const limpo = hex.replace("#", "");
  const cheio = limpo.length === 3 ? limpo.split("").map((c) => c + c).join("") : limpo;
  const n = Number.parseInt(cheio, 16);
  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}

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

function aplicarModificador(hex: string, mod: { clarear?: number; escurecer?: number; saturar?: number }) {
  let { r, g, b } = hexParaRgb(hex);
  if (mod.saturar) {
    const media = (r + g + b) / 3;
    const f = 1 + mod.saturar;
    r = media + (r - media) * f;
    g = media + (g - media) * f;
    b = media + (b - media) * f;
  }
  if (mod.clarear) {
    r += (255 - r) * mod.clarear;
    g += (255 - g) * mod.clarear;
    b += (255 - b) * mod.clarear;
  }
  if (mod.escurecer) {
    r *= 1 - mod.escurecer;
    g *= 1 - mod.escurecer;
    b *= 1 - mod.escurecer;
  }
  return rgbParaHex(r, g, b);
}

/*
  O gênero não muda a cor: "preta" e "preto" são a mesma tinta.

  A palavra escrita vem sempre primeiro — senão "laranja" virava "laranjo" e
  "cinza" virava "cinzo", e as duas saíam sem cor nenhuma na vitrine.
*/
function variantes(palavra: string) {
  if (palavra.length > 4 && palavra.endsWith("a")) return [palavra, `${palavra.slice(0, -1)}o`];
  return [palavra];
}

function acharNoDicionario<T>(palavra: string, dicionario: Record<string, T>): T | null {
  for (const forma of variantes(palavra)) {
    if (dicionario[forma]) return dicionario[forma];
  }
  return null;
}

/**
 * A cor de um rótulo — ou nada, quando o texto não fala de cor.
 *
 * Lê da direita para a esquerda o que a gente escreve: a cor base primeiro
 * ("azul"), depois o que a modifica ("bebê", "escuro", "royal").
 */
export function corDoRotulo(rotulo: string): CorDaOpcao | null {
  const limpo = semAcento(rotulo).replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
  if (!limpo) return null;

  const palavras = limpo.split(" ");
  const especialDireta = ESPECIAIS[limpo.replace(/\s/g, "")] || ESPECIAIS[limpo];
  if (especialDireta) return especialDireta;

  /* Guarda o hex da cor base, não o nome — a primeira que casar. */
  let base: string | null = null;
  const mods: Array<{ clarear?: number; escurecer?: number; saturar?: number }> = [];
  let especial: CorDaOpcao | null = null;

  for (const palavra of palavras) {
    const comoEspecial = acharNoDicionario(palavra, ESPECIAIS);
    if (comoEspecial) {
      especial = comoEspecial;
      continue;
    }
    const comoCor = acharNoDicionario(palavra, CORES_BASE);
    if (comoCor) {
      /* A primeira cor manda: em "azul royal" quem pinta é o azul. */
      if (!base) base = comoCor;
      continue;
    }
    const comoModificador = acharNoDicionario(palavra, MODIFICADORES);
    if (comoModificador) mods.push(comoModificador);
  }

  if (!base) return especial;

  let hex = base;
  for (const mod of mods) hex = aplicarModificador(hex, mod);

  /* "Dourado escuro", "prata claro": o brilho manda, o tom acompanha. */
  if (especial && especial.tipo !== "chapada") {
    return { ...especial, fundo: hex };
  }
  return { tipo: "chapada", fundo: hex };
}

/* Branco, gelo, marfim somem no fundo da vitrine: precisam de anel. */
export function corPrecisaDeAnel(cor: CorDaOpcao) {
  if (cor.tipo !== "chapada") return false;
  const { r, g, b } = hexParaRgb(cor.fundo);
  const luz = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
  return luz > 0.82;
}

/* ------------------------------------------------------------------ *
 *  Normalização e decisão
 * ------------------------------------------------------------------ */

function normalizarOpcao(bruta: OpcaoBruta, indice: number): OpcaoDaVitrine {
  if (typeof bruta === "string") {
    return {
      id: null,
      label: bruta,
      preco: 0,
      precoPorPeca: true,
      unidadeDoPreco: null,
      estoque: null,
      obrigatoria: false,
      posicao: indice,
      imageId: null,
      swatch: null,
    };
  }
  const swatch = texto(bruta.swatch);
  return {
    id: texto(bruta.id) || null,
    label: texto(bruta.label),
    preco: Number(bruta.price) || 0,
    precoPorPeca: bruta.price_applies_per_quantity !== false,
    unidadeDoPreco: texto(bruta.price_unit) || null,
    estoque: Number.isFinite(Number(bruta.stock)) ? Number(bruta.stock) : null,
    obrigatoria: bruta.is_required === true,
    posicao: typeof bruta.position === "number" ? bruta.position : indice,
    imageId: texto(bruta.image_id) || null,
    swatch: /^#[0-9a-f]{3,8}$/i.test(swatch) ? swatch : null,
  };
}

const ESTILOS: EstiloDaVariacao[] = ["cores", "miniaturas", "botoes", "lista", "dropdown"];

/* O nome fala de cor? É o que separa "Cor: Verde" de "Sabor: Uva". */
function nomeFalaDeCor(nome: string) {
  return /\b(cor|cores|colour|color|tom|tonalidade|tonalidades)\b/.test(semAcento(nome));
}

/**
 * O estilo de um atributo: o que o lojista mandou, ou o que o conteúdo pede.
 */
export function estiloDoAtributo(
  atributo: Pick<AtributoBruto, "name" | "display_style">,
  opcoes: OpcaoDaVitrine[]
): EstiloDaVariacao {
  const escolhido = texto(atributo.display_style) as EstiloDaVariacao;
  if (ESTILOS.includes(escolhido)) return escolhido;

  /* Cor pintada à mão ganha na hora: o lojista já disse o que queria. */
  if (opcoes.some((opcao) => opcao.swatch)) return "cores";

  const nome = texto(atributo.name);
  if (nomeFalaDeCor(nome)) {
    const comCor = opcoes.filter((opcao) => Boolean(corDoRotulo(opcao.label))).length;
    if (opcoes.length > 0 && comCor / opcoes.length >= 0.6) return "cores";
  }

  /* A foto diz mais que o nome: estampa, modelo, arte. */
  const comImagem = opcoes.filter((opcao) => opcao.imageId).length;
  if (opcoes.length > 0 && comImagem / opcoes.length >= 0.6) return "miniaturas";

  if (opcoes.length > LIMITE_DE_BOTOES) return "lista";
  return "botoes";
}

/**
 * As variações de um produto, prontas para a vitrine.
 *
 * Devolve separado o que é escolha (`escolhas`) e o que é só informação
 * (`informativos`) — uma opção só, sem preço, não merece um botão. O que
 * está escondido pelo lojista, ou sem opção nenhuma, não volta.
 */
export function variacoesDaVitrine(atributos: AtributoBruto[] | null | undefined) {
  const lista = Array.isArray(atributos) ? atributos : [];
  const escolhas: AtributoDaVitrine[] = [];
  const informativos: AtributoDaVitrine[] = [];

  for (const bruto of lista) {
    if (bruto?.show_in_store === false) continue;

    const opcoes = (Array.isArray(bruto?.options) ? bruto.options : [])
      .map(normalizarOpcao)
      .filter((opcao) => opcao.label)
      .sort((a, b) => a.posicao - b.posicao);
    if (!opcoes.length) continue;

    const nomeOriginal = texto(bruto?.name);
    /*
      Uma opção só e sem preço não é escolha — é o que o produto é. Vira
      linha de texto. Com preço, ou marcada como obrigatória, continua
      botão: aí clicar muda o que o cliente paga.
    */
    const informativo = opcoes.length === 1 && opcoes[0].preco <= 0 && !opcoes[0].obrigatoria;

    const atributo: AtributoDaVitrine = {
      id: texto(bruto?.id) || nomeOriginal,
      nome: nomeLimpo(nomeOriginal),
      nomeOriginal,
      multipla: bruto?.allow_multiple === true,
      obrigatorio: bruto?.is_required === true,
      posicao: typeof bruto?.position === "number" ? bruto.position : escolhas.length + informativos.length,
      opcoes,
      estilo: estiloDoAtributo(bruto, opcoes),
      informativo,
    };

    (informativo ? informativos : escolhas).push(atributo);
  }

  const porPosicao = (a: AtributoDaVitrine, b: AtributoDaVitrine) => a.posicao - b.posicao;
  return { escolhas: escolhas.sort(porPosicao), informativos: informativos.sort(porPosicao) };
}

/*
  Os campos que a importação transformou em variação.

  Vieram de planilha de ERP — `ncm_8`, `id_fornecedor`, `cofins`, `peso_liq` —
  e hoje aparecem na vitrine como quadradinho de escolher. São 148 produtos
  com cada um. A vitrine não esconde sozinha: quem decide é o lojista, na
  chave "Mostrar na loja". Esta lista só serve para o admin **sugerir**.
*/
const TECNICOS = new Set([
  "ncm",
  "ncm_8",
  "cest",
  "cfop",
  "cofins",
  "pis",
  "icms",
  "ipi",
  "id_fornecedor",
  "fornecedor",
  "marca_25",
  "origem_0_a_8",
  "tipo_class_fiscal",
  "tipo_produto_servico",
  "produto_variado_sim_nao",
  "valor_custo",
  "custo",
  "peso",
  "peso_liq",
  "peso_bruto",
  "estoque_minimo",
  "estoque_maximo",
  "unidade",
  "gtin",
  "ean",
  "codigo_barras",
]);

/** Cheira a campo de planilha em vez de escolha do cliente? */
export function pareceCampoTecnico(nome: string, quantasOpcoes: number) {
  const limpo = semAcento(nome).replace(/\s+/g, "_");
  if (TECNICOS.has(limpo)) return true;
  /* Uma opção só, e um nome que ninguém escreveria numa vitrine. */
  return quantasOpcoes <= 1 && /_/.test(limpo) && /\d/.test(limpo);
}
