/*
  Estoque v2 no assistente — as contas puras (sem banco): ler a combinação
  que a pessoa escreveu ("Tamanho: G, Cor: Preta", "G / Preta"), achar a
  célula da grade de um item e montar a escolha de um produto no formato do
  pedido (selected_options), do mesmo jeito que o banco compara nomes
  (estoque_v2_norm: sem acento, sem maiúscula, sem ":" no fim).

  Testado em ../estoque-v2.test.ts (node --test).
*/

const COM_ACENTO = "áàâãäéèêëíìîïóòôõöúùûüçñ";
const SEM_ACENTO = "aaaaaeeeeiiiiooooouuuucn";

/** O mesmo estoque_v2_norm do banco. Vazio = "". */
export function normalizar(texto: string | null | undefined): string {
  let saida = "";
  for (const letra of String(texto ?? "").trim().toLowerCase()) {
    const i = COM_ACENTO.indexOf(letra);
    saida += i >= 0 ? SEM_ACENTO[i] : letra;
  }
  return saida.replace(/\s+/g, " ").replace(/[\s:]+$/, "");
}

export type Par = { atributo: string | null; valor: string };

/**
 * "Tamanho: G, Cor: Preta" → [{Tamanho,G},{Cor,Preta}]; "G / Preta" ou
 * "G, Preta" → só os valores. Vazio = [].
 */
export function lerCombinacao(texto: string | null | undefined): Par[] {
  const bruto = String(texto ?? "").trim();
  if (!bruto) return [];
  return bruto
    .split(/\s*[,;]\s*|\s+\/\s+|\s*\/\s*/)
    .map((parte) => parte.trim())
    .filter(Boolean)
    .map((parte) => {
      const m = parte.match(/^([^:=]+?)\s*[:=]\s*(.+)$/);
      return m ? { atributo: m[1].trim(), valor: m[2].trim() } : { atributo: null, valor: parte };
    });
}

export type AtributoSimples = { name?: string | null; options?: unknown };

function rotulosDoAtributo(atributo: AtributoSimples): Array<{ id: string | null; label: string }> {
  const opcoes = Array.isArray(atributo.options) ? (atributo.options as unknown[]) : [];
  return opcoes
    .map((opcao) => {
      if (typeof opcao === "string") return { id: null, label: opcao };
      if (opcao && typeof opcao === "object") {
        const o = opcao as { id?: unknown; label?: unknown };
        return { id: typeof o.id === "string" ? o.id : null, label: String(o.label ?? "") };
      }
      return null;
    })
    .filter((o): o is { id: string | null; label: string } => Boolean(o && o.label));
}

export type EscolhaMontada =
  | { ok: true; selected: { options: Array<{ option_id: string | null; attribute_name: string; label: string }> } | null; texto: string }
  | { ok: false; erro: string };

/**
 * A escolha de um produto no formato do pedido, a partir do texto. Cada
 * valor tem de existir nas opções do produto; valor sem atributo vale quando
 * só um atributo tem aquela opção.
 */
export function escolhaDoProduto(pares: Par[], atributos: AtributoSimples[]): EscolhaMontada {
  const options: Array<{ option_id: string | null; attribute_name: string; label: string }> = [];
  for (const par of pares) {
    const candidatos = atributos.filter((atributo) =>
      par.atributo ? normalizar(atributo.name) === normalizar(par.atributo) : true
    );
    if (par.atributo && !candidatos.length) {
      return {
        ok: false,
        erro: `O produto não tem o atributo "${par.atributo}". Atributos: ${atributos.map((a) => a.name).filter(Boolean).join(", ") || "nenhum"}.`,
      };
    }
    const achados = candidatos
      .map((atributo) => ({ atributo, opcao: rotulosDoAtributo(atributo).find((o) => normalizar(o.label) === normalizar(par.valor)) }))
      .filter((a) => a.opcao);
    if (!achados.length) {
      const onde = par.atributo ? candidatos[0] : null;
      const possiveis = onde ? rotulosDoAtributo(onde).map((o) => o.label).join(", ") : "";
      return {
        ok: false,
        erro: `"${par.valor}" não é uma opção${par.atributo ? ` de ${par.atributo}` : " do produto"}.${possiveis ? ` Opções: ${possiveis}.` : ""}`,
      };
    }
    if (achados.length > 1) {
      return { ok: false, erro: `"${par.valor}" existe em mais de um atributo (${achados.map((a) => a.atributo.name).join(", ")}): diga qual, como "Cor: ${par.valor}".` };
    }
    const { atributo, opcao } = achados[0];
    options.push({ option_id: opcao!.id, attribute_name: String(atributo.name || ""), label: opcao!.label });
  }
  return {
    ok: true,
    selected: options.length ? { options } : null,
    texto: options.map((o) => `${o.attribute_name}: ${o.label}`).join(", "),
  };
}

export type EixoDaGrade = { nome: string; valores: string[] };

export function gradeValida(grade: unknown): EixoDaGrade[] {
  if (!Array.isArray(grade)) return [];
  return grade
    .filter((eixo) => eixo && typeof eixo === "object")
    .map((eixo) => ({
      nome: String((eixo as { nome?: unknown }).nome ?? "").trim(),
      valores: Array.isArray((eixo as { valores?: unknown }).valores)
        ? ((eixo as { valores: unknown[] }).valores.map((v) => String(v ?? "").trim()).filter(Boolean))
        : [],
    }))
    .filter((eixo) => eixo.nome && eixo.valores.length)
    .slice(0, 2);
}

export type CelulaSimples = { id: string; valor1: string | null; valor2: string | null; ativo?: boolean | null };

/**
 * A célula de um item pela combinação escrita. Item sem grade = a célula
 * única (a combinação é ignorada). Com grade, cada eixo precisa de um valor.
 */
export function acharCelula(
  pares: Par[],
  celulas: CelulaSimples[],
  gradeBruta: unknown,
  nomeDoItem = "o item"
): { ok: true; celula: CelulaSimples } | { ok: false; erro: string } {
  const grade = gradeValida(gradeBruta);
  const ativas = celulas.filter((c) => c.ativo !== false);
  if (!grade.length) {
    const unica = ativas.find((c) => !c.valor1 && !c.valor2) || ativas[0];
    return unica ? { ok: true, celula: unica } : { ok: false, erro: `"${nomeDoItem}" ainda não tem saldo aberto no estoque novo.` };
  }
  const valores: Array<string | null> = grade.map(() => null);
  for (const par of pares) {
    let eixo = -1;
    if (par.atributo) eixo = grade.findIndex((e) => normalizar(e.nome) === normalizar(par.atributo));
    if (eixo < 0) eixo = grade.findIndex((e, i) => valores[i] === null && e.valores.some((v) => normalizar(v) === normalizar(par.valor)));
    if (eixo < 0) {
      return {
        ok: false,
        erro: `"${par.valor}" não está na grade de "${nomeDoItem}" (${grade.map((e) => `${e.nome}: ${e.valores.join(", ")}`).join(" · ")}).`,
      };
    }
    const valor = grade[eixo].valores.find((v) => normalizar(v) === normalizar(par.valor));
    if (!valor) return { ok: false, erro: `"${par.valor}" não é um valor de ${grade[eixo].nome} em "${nomeDoItem}" (${grade[eixo].valores.join(", ")}).` };
    valores[eixo] = valor;
  }
  const faltam = grade.filter((_, i) => valores[i] === null).map((e) => e.nome.toLowerCase());
  if (faltam.length) {
    return { ok: false, erro: `"${nomeDoItem}" tem grade: diga ${faltam.join(" e ")} (ex.: "${grade.map((e) => `${e.nome}: ${e.valores[0]}`).join(", ")}").` };
  }
  const celula = ativas.find(
    (c) => normalizar(c.valor1) === normalizar(valores[0]) && normalizar(c.valor2) === normalizar(valores[1] ?? null)
  );
  return celula ? { ok: true, celula } : { ok: false, erro: `Não achei a célula ${valores.filter(Boolean).join(" / ")} de "${nomeDoItem}".` };
}

/** "Camiseta lisa (G / Preta)"; sem grade, só o nome. */
export function rotuloDaCelula(nome: string, valor1: string | null | undefined, valor2: string | null | undefined): string {
  const valores = [valor1, valor2].map((v) => String(v ?? "").trim()).filter(Boolean);
  return valores.length ? `${nome} (${valores.join(" / ")})` : nome;
}

/** "Tamanho (P, M, G) × Cor (Branca, Preta)" ou null. */
export function textoDaGrade(gradeBruta: unknown): string | null {
  const grade = gradeValida(gradeBruta);
  if (!grade.length) return null;
  return grade.map((e) => `${e.nome} (${e.valores.join(", ")})`).join(" × ");
}

/** Saldo no mínimo ou abaixo (mínimo da célula, senão o do item). */
export function estaBaixo(saldo: number, minimoDaCelula: number | null | undefined, minimoDoItem: number | null | undefined): boolean {
  const minimo = minimoDaCelula ?? minimoDoItem;
  return minimo !== null && minimo !== undefined && Number.isFinite(Number(minimo)) && saldo <= Number(minimo);
}
