import { test } from "node:test";
import assert from "node:assert/strict";
import {
  avisosDaFicha,
  calcularFichaDoItem,
  explicarConsumo,
  formatarDuracao,
  corteNaBobina,
  naturezaDoInsumo,
  notasDaQuantidade,
  planoValido,
  quantidadeLegivel,
  rendimentoDeduzido,
  unidadeDiscreta,
  unidadeLegivel,
  minutosDoPedido,
  separacaoDoPedido,
  variacoesDoItem,
  type InsumoDaReceita,
  type ProcessoDaReceita,
} from "./ficha-de-producao.ts";

/**
 * O banner de lona 3×2, quatro peças, é o caso que a ficha antiga errava:
 * imprimia "1 m² de lona" e "4 min de impressão" para um pedido de 24 m².
 */
const LONA: InsumoDaReceita = { nome: "Lona 440g", unidade: "m²", consumo: 1, base: "por_m2", perdaPct: 5 };
const TORSAL: InsumoDaReceita = { nome: "Torsal", unidade: "m", consumo: 1.5, base: "por_m_largura", perdaPct: 10 };
const PONTEIRA: InsumoDaReceita = { nome: "Ponteira", unidade: "un", consumo: 4, base: "por_peca" };
const BAINHA: InsumoDaReceita = { nome: "Ilhós", unidade: "un", consumo: 2, base: "por_perimetro" };
const IMPRESSAO: ProcessoDaReceita = {
  nome: "Impressão", tipo: "maquina", recurso: "Impressora ecosolvente", minutos: 4, base: "por_m2", minutosDeSetup: 10,
};
const ACABAMENTO: ProcessoDaReceita = {
  nome: "Acabamento", tipo: "pessoa", recurso: "Acabador", minutos: 10, base: "por_peca",
};

const BANNER = {
  quantidade: 4,
  larguraM: 3,
  alturaM: 2,
  insumos: [LONA, TORSAL, PONTEIRA, BAINHA],
  processos: [IMPRESSAO, ACABAMENTO],
};

test("banner 3x2 x4: cada insumo na sua base, com a perda por cima", () => {
  const f = calcularFichaDoItem(BANNER);
  assert.equal(f.areaTotal, 24);

  const [lona, torsal, ponteira, ilhos] = f.insumos;
  assert.equal(lona.medidaTotal, 24);
  assert.equal(lona.quantidadeLiquida, 24);
  assert.equal(lona.quantidade, 25.2);

  /* 3 m de largura × 4 peças = 12 m; 1,5 m por metro = 18 m; +10% = 19,8 m. */
  assert.equal(torsal.medidaTotal, 12);
  assert.equal(torsal.quantidade, 19.8);

  assert.equal(ponteira.medidaTotal, 4);
  assert.equal(ponteira.quantidade, 16);

  /* Contorno 2×(3+2) = 10 m por peça, 40 m no pedido, 2 por metro = 80. */
  assert.equal(ilhos.medidaTotal, 40);
  assert.equal(ilhos.quantidade, 80);
});

test("banner 3x2 x4: tempo por processo e total do item", () => {
  const f = calcularFichaDoItem(BANNER);
  const [impressao, acabamento] = f.processos;

  assert.equal(impressao.minutosDeProducao, 96);
  assert.equal(impressao.minutosDeSetup, 10);
  assert.equal(impressao.minutosTotais, 106);

  assert.equal(acabamento.minutosDeProducao, 40);
  assert.equal(acabamento.minutosTotais, 40);

  assert.equal(f.minutosTotais, 146);
});

test("caneca: o que depende de medida fica marcado, o por peça segue", () => {
  const f = calcularFichaDoItem({
    quantidade: 10,
    insumos: [
      { nome: "Caneca", unidade: "un", consumo: 1, base: "por_peca", perdaPct: 2 },
      { nome: "Lona por engano", unidade: "m²", consumo: 1, base: "por_m2" },
    ],
    processos: [{ nome: "Prensa", tipo: "maquina", recurso: "Prensa de caneca", minutos: 4, base: "por_peca", minutosDeSetup: 5 }],
  });
  const [caneca, lona] = f.insumos;
  /* Peça inteira: separa 10, a perda de 2% fica no custo, não na prateleira. */
  assert.equal(caneca.quantidade, 10);
  assert.equal(caneca.quantidadeComPerda, 10.2);
  assert.equal(caneca.discreta, true);
  assert.equal(lona.semMedida, true);
  assert.equal(lona.quantidade, 0);
  assert.equal(f.processos[0].minutosTotais, 45);
});

test("peça inteira: sobe para o inteiro por peça; contínua: exata com perda", () => {
  const f = calcularFichaDoItem({
    quantidade: 3, larguraM: 1, alturaM: 0.7,
    insumos: [
      { nome: "Ilhós", unidade: "un", consumo: 2, base: "por_perimetro" },
      { nome: "Caneca", unidade: "un", consumo: 1, base: "por_peca", perdaPct: 2 },
      { nome: "Lona", unidade: "m2", consumo: 1, base: "por_m2", perdaPct: 5 },
      { nome: "Tinta", unidade: "ml", consumo: 12, base: "por_m2" },
    ],
    processos: [],
  });
  const [ilhos, caneca, lona, tinta] = f.insumos;
  /* 3,4 m de contorno × 2 = 6,8 por banner → 7 por banner → 21 (não 20,4 → 20). */
  assert.equal(ilhos.natureza, "peca");
  assert.equal(ilhos.quantidadeLiquida, 20.4);
  assert.equal(ilhos.quantidade, 21);
  /* Peça inteira: 3 canecas, a perda de 2% fica no custo. */
  assert.equal(caneca.quantidade, 3);
  assert.equal(caneca.quantidadeComPerda, 3.06);
  assert.deepEqual(notasDaQuantidade(caneca), ["2% de perda só no custo"]);
  /* Contínuas seguem exatas, com a perda. */
  assert.equal(lona.natureza, "continuo");
  assert.equal(lona.quantidade, 2.205);
  assert.equal(tinta.quantidade, 25.2);
  assert.equal(unidadeDiscreta("Folha"), true);
  assert.equal(unidadeDiscreta("kg"), false);
});

test("rendimento: teto de peças ÷ N, com o N gravado ou deduzido do consumo", () => {
  const dez = calcularFichaDoItem({
    quantidade: 10,
    insumos: [
      { nome: "Papel sublimático", unidade: "folha", consumo: 0.3333, base: "por_peca", perdaPct: 2, rendimento: 3 },
      { nome: "Papel foto", unidade: "un", consumo: 0.17, base: "por_peca" },
      { nome: "Elástico", unidade: "un", consumo: 0.22, base: "por_peca" },
    ],
    processos: [],
  });
  const [sub, foto, elastico] = dez.insumos;
  /* 10 canecas a 3 por folha: 4 folhas, sobra ⅔. A regra do "mais próximo" daria 3. */
  assert.equal(sub.natureza, "rendimento");
  assert.equal(sub.rendimento, 3);
  assert.equal(sub.quantidade, 4);
  assert.equal(sub.sobra, 0.6667);
  assert.equal(sub.rendimentoEstimado, false);
  assert.deepEqual(notasDaQuantidade(sub), ["3 por folha", "sobra 0,667 folha", "2% de perda só no custo"]);
  /* 0,17 por peça é "6 por folha" escondido: 10 fotos → 2 folhas, com aviso. */
  assert.equal(foto.natureza, "rendimento");
  assert.equal(foto.rendimento, 6);
  assert.equal(foto.rendimentoEstimado, true);
  assert.equal(foto.quantidade, 2);
  /* 0,22 não é 1/N de nada: é metro disfarçado de "un", segue contínuo. */
  assert.equal(elastico.natureza, "continuo");
  assert.equal(elastico.quantidade, 2.2);
  assert.match(avisosDaFicha(dez, {}).join(" "), /Papel foto/);

  /* Seis cartões a 6 por folha gravado como 0,17: uma folha, não duas. */
  const seis = calcularFichaDoItem({
    quantidade: 6,
    insumos: [{ nome: "Papel foto", unidade: "un", consumo: 0.17, base: "por_peca" }],
    processos: [],
  });
  assert.equal(seis.insumos[0].quantidade, 1);

  assert.equal(rendimentoDeduzido(0.5), 2);
  assert.equal(rendimentoDeduzido(0.03), 33);
  assert.equal(rendimentoDeduzido(0.22), null);
  assert.equal(rendimentoDeduzido(1), null);
});

test("o plano de rodagem vence o cadastro: N desta vez, e a peça sozinha na bobina vira corte", () => {
  const plano = planoValido({
    insumos: { "mat-papel": { rendimento: "2" }, "mat-lona": { rodagem: "sozinho" }, lixo: { rodagem: "x" } },
  });
  assert.deepEqual(plano, { insumos: { "mat-papel": { rendimento: 2 }, "mat-lona": { rodagem: "sozinho" } } });
  assert.deepEqual(planoValido("nada"), {});

  const f = calcularFichaDoItem({
    quantidade: 3, larguraM: 1, alturaM: 0.7,
    insumos: [
      { nome: "Papel", unidade: "folha", consumo: 0.3333, base: "por_peca", rendimento: 3, materialId: "mat-papel" },
      { nome: "Lona", unidade: "m2", consumo: 1, base: "por_m2", perdaPct: 5, larguraBobinaM: 1.6, materialId: "mat-lona" },
    ],
    processos: [],
    plano,
  });
  const [papel, lona] = f.insumos;
  /* Cadastro diz 3 por folha; nesta rodagem couberam 2: 3 canecas → 2 folhas. */
  assert.equal(papel.rendimento, 2);
  assert.equal(papel.quantidade, 2);
  assert.equal(papel.ajustavel, "rendimento");
  /* Bobina de 1,60: a peça 1,00 × 0,70 girada cabe 2 por faixa → 2 faixas × 1,00 m = 2 m → 3,2 m². */
  assert.equal(lona.rodagem, "sozinho");
  assert.deepEqual(lona.corte, { larguraBobinaM: 1.6, comprimentoM: 2, porFaixa: 2, faixas: 2, girada: true });
  assert.equal(lona.quantidade, 3.2);
  assert.equal(lona.ajustavel, "bobina");
  assert.match(notasDaQuantidade(lona)[0], /corte de 2 m na bobina de 1,6 m/);

  /* Sem plano, a lona roda casada: área mais perda. */
  const casada = calcularFichaDoItem({
    quantidade: 3, larguraM: 1, alturaM: 0.7,
    insumos: [{ nome: "Lona", unidade: "m2", consumo: 1, base: "por_m2", perdaPct: 5, larguraBobinaM: 1.6, materialId: "mat-lona" }],
    processos: [],
  });
  assert.equal(casada.insumos[0].rodagem, "casado");
  assert.equal(casada.insumos[0].corte, null);
  assert.equal(casada.insumos[0].quantidade, 2.205);

  /* Peça maior que a bobina nos dois sentidos: sem corte. */
  assert.equal(corteNaBobina(1.6, 2, 3, 1), null);
});

test("serviço modelado como insumo: fica na ficha, não entra na separação", () => {
  const f = calcularFichaDoItem({
    quantidade: 4,
    insumos: [
      { nome: "Corte Cricut", unidade: "un", consumo: 0.03, base: "por_peca", servico: true },
      { nome: "Caneca", unidade: "un", consumo: 1, base: "por_peca" },
    ],
    processos: [],
  });
  assert.equal(f.insumos[0].natureza, "servico");
  assert.deepEqual(notasDaQuantidade(f.insumos[0]), ["serviço · nada a separar"]);
  assert.deepEqual(separacaoDoPedido([f]).map((l) => l.nome), ["Caneca"]);
  assert.deepEqual(naturezaDoInsumo({ unidade: "un", base: "por_peca", consumo: 1, formato: "caixa" }).natureza, "peca");
});

test("unidade legível: litro pequeno vira ml, quilo vira g, m² minúsculo vira cm²", () => {
  assert.deepEqual(quantidadeLegivel(0.017, "l"), { valor: 17, unidade: "ml" });
  assert.deepEqual(quantidadeLegivel(0.01, "kg"), { valor: 10, unidade: "g" });
  assert.deepEqual(quantidadeLegivel(0.0033, "m2"), { valor: 33, unidade: "cm²" });
  assert.deepEqual(quantidadeLegivel(1.47, "m2"), { valor: 1.47, unidade: "m²" });
  assert.deepEqual(quantidadeLegivel(2, "l"), { valor: 2, unidade: "l" });
});

test("linha sem consumo e processo sem minuto nem preparo somem da ficha", () => {
  const f = calcularFichaDoItem({
    quantidade: 1,
    insumos: [{ nome: "Nada", unidade: "un", consumo: 0, base: "por_peca" }],
    processos: [{ nome: "Vazio", tipo: null, recurso: null, minutos: 0, base: "por_peca" }],
  });
  assert.equal(f.insumos.length, 0);
  assert.equal(f.processos.length, 0);
  assert.equal(f.minutosTotais, 0);
});

test("a origem da opção acompanha a linha", () => {
  const f = calcularFichaDoItem({
    quantidade: 3,
    insumos: [{ nome: "Pingente", unidade: "un", consumo: 1, base: "por_peca", origem: { tipo: "opcao", opcao: "Com pingente" } }],
    processos: [],
  });
  assert.deepEqual(f.insumos[0].origem, { tipo: "opcao", opcao: "Com pingente" });
  assert.equal(f.insumos[0].quantidade, 3);
});

test("separação soma o mesmo insumo entre itens, sem se importar com caixa", () => {
  const a = calcularFichaDoItem({ quantidade: 1, larguraM: 1, alturaM: 1, insumos: [LONA], processos: [] });
  const b = calcularFichaDoItem({
    quantidade: 2, larguraM: 2, alturaM: 1,
    insumos: [{ ...LONA, nome: "lona 440G" }, PONTEIRA],
    processos: [],
  });
  const sep = separacaoDoPedido([a, b]);
  assert.deepEqual(sep, [
    { nome: "Lona 440g", unidade: "m²", quantidade: 5.25, itens: 2, natureza: "continuo", fracaoSomada: 0 },
    { nome: "Ponteira", unidade: "un", quantidade: 8, itens: 1, natureza: "peca", fracaoSomada: 0 },
  ]);
});

test("separação com rendimento soma as frações dos itens antes de subir para o inteiro", () => {
  const PAPEL: InsumoDaReceita = { nome: "Papel sublimático", unidade: "folha", consumo: 0.5, base: "por_peca", rendimento: 2 };
  const a = calcularFichaDoItem({ quantidade: 1, insumos: [PAPEL], processos: [] });
  const b = calcularFichaDoItem({ quantidade: 1, insumos: [PAPEL], processos: [] });
  /* Cada item sozinho pede 1 folha; juntos, meia + meia = 1 folha, não 2. */
  assert.equal(a.insumos[0].quantidade, 1);
  const [linha] = separacaoDoPedido([a, b]);
  assert.equal(linha.fracaoSomada, 1);
  assert.equal(linha.quantidade, 1);
  assert.equal(linha.itens, 2);
  const c = calcularFichaDoItem({ quantidade: 3, insumos: [PAPEL], processos: [] });
  assert.equal(separacaoDoPedido([a, b, c])[0].quantidade, 3);
});

test("separação ignora linhas sem medida, que têm quantidade zero", () => {
  const f = calcularFichaDoItem({ quantidade: 5, insumos: [LONA], processos: [] });
  assert.equal(f.insumos[0].semMedida, true);
  assert.deepEqual(separacaoDoPedido([f]), []);
});

test("minutos do pedido somam os itens", () => {
  const banner = calcularFichaDoItem(BANNER);
  const caneca = calcularFichaDoItem({
    quantidade: 10, insumos: [],
    processos: [{ nome: "Prensa", tipo: "maquina", recurso: null, minutos: 4, base: "por_peca", minutosDeSetup: 5 }],
  });
  assert.equal(minutosDoPedido([banner, caneca]), 191);
});

test("duração legível, arredondada ao minuto", () => {
  assert.equal(formatarDuracao(0), "0 min");
  assert.equal(formatarDuracao(45), "45 min");
  assert.equal(formatarDuracao(146), "2 h 26 min");
  assert.equal(formatarDuracao(120), "2 h");
  assert.equal(formatarDuracao(59.6), "1 h");
  assert.equal(formatarDuracao(-3), "0 min");
});

test("variações: lê o formato do orçamento e ignora o resto", () => {
  const lidas = variacoesDoItem({
    options: [
      { label: "PRETA", price: 0, option_id: "9541887c-7ddc-4578-b5d7-ae2a010dd52a", attribute_name: "COR" },
      { label: "  Com pingente ", option_id: "", attribute_name: "" },
      { label: "", attribute_name: "Vazio" },
      "lixo",
      null,
    ],
    inventory_scope: "product",
  });
  assert.deepEqual(lidas, [
    { atributo: "COR", valor: "PRETA", optionId: "9541887c-7ddc-4578-b5d7-ae2a010dd52a" },
    { atributo: "Opção", valor: "Com pingente", optionId: null },
  ]);
  assert.deepEqual(variacoesDoItem(null), []);
  assert.deepEqual(variacoesDoItem({ options: "x" }), []);
  assert.deepEqual(variacoesDoItem([1, 2]), []);
});

test("explicar consumo: português de bancada, com a medida do pedido", () => {
  const f = calcularFichaDoItem(BANNER);
  const [lona, torsal, ponteira, ilhos] = f.insumos;
  assert.equal(explicarConsumo(lona), "a área da peça · 24 m²");
  assert.equal(explicarConsumo(torsal), "1,5 m a cada m de largura · 12 m de largura");
  assert.equal(explicarConsumo(ponteira), "4 un por peça · 4 peças");
  assert.equal(explicarConsumo(ilhos), "2 un a cada m de contorno · 40 m de contorno");

  const tinta = calcularFichaDoItem({
    quantidade: 1, larguraM: 2, alturaM: 3,
    insumos: [{ nome: "Tinta", unidade: "l", consumo: 0.012, base: "por_m2" }], processos: [],
  }).insumos[0];
  assert.equal(explicarConsumo(tinta), "0,012 l a cada m² · 6 m²");

  const caneca = calcularFichaDoItem({
    quantidade: 1, insumos: [{ nome: "Caneca", unidade: "un", consumo: 1, base: "por_peca" }, LONA], processos: [],
  });
  assert.equal(explicarConsumo(caneca.insumos[0]), "1 un por peça · 1 peça");
  assert.equal(explicarConsumo(caneca.insumos[1]), "pela área da peça · sem medida");
  assert.equal(unidadeLegivel("m2"), "m²");
  assert.equal(unidadeLegivel("folha"), "folha");
});

test("avisos: ficha por peça em item por m², e linha sem medida", () => {
  /* Adesivo de 5×1,5 com ficha antiga: tudo por peça. */
  const antiga = calcularFichaDoItem({
    quantidade: 1, larguraM: 5, alturaM: 1.5,
    insumos: [{ nome: "Vinil", unidade: "m²", consumo: 1, base: "por_peca" }],
    processos: [{ nome: "Impressão", tipo: "maquina", recurso: null, minutos: 10, base: "por_peca" }],
  });
  assert.equal(avisosDaFicha(antiga, { unitType: "square_meter" }).length, 1);
  assert.match(avisosDaFicha(antiga, { unitType: "square_meter" })[0], /por peça/);

  /* A mesma ficha num produto por unidade não tem nada de errado. */
  assert.deepEqual(avisosDaFicha(antiga, { unitType: "unit" }), []);

  /* Banner certo, com base: nenhum aviso. */
  assert.deepEqual(avisosDaFicha(calcularFichaDoItem(BANNER), { unitType: "square_meter" }), []);

  /* Caneca com linha por m²: falta medida. */
  const caneca = calcularFichaDoItem({ quantidade: 2, insumos: [LONA, PONTEIRA], processos: [] });
  const avisos = avisosDaFicha(caneca, { unitType: "unit" });
  assert.equal(avisos.length, 1);
  assert.match(avisos[0], /sem largura e altura/);

  /* Sem linha nenhuma, sem aviso: o vazio já se explica sozinho. */
  assert.deepEqual(avisosDaFicha(calcularFichaDoItem({ quantidade: 1, insumos: [], processos: [] }), { unitType: "square_meter" }), []);
});
