/**
 * O custo de um produto a partir do que ele consome.
 *
 * O caso que este arquivo precisa aguentar é o banner de lona: lona vendida por
 * m² numa certa gramatura, tinta que se gasta por m² impresso, torsal e madeira
 * que correm ao longo da largura, bainha que dá a volta inteira, ponteira por
 * unidade, tempo de máquina para imprimir e tempo de pessoa para acabar.
 *
 * O levantamento do ramo — comunicação visual, gráfica rápida, sublimação,
 * têxtil, brindes — mostrou que são centenas de insumos e acabamentos, mas
 * **todos consomem de apenas cinco jeitos**, mais o que acontece uma vez por
 * pedido:
 *
 *   por peça       — ponteira, blank de caneca, caixinha
 *   por m²         — lona, vinil, tinta, laminação
 *   pela largura   — torsal, madeira, tubete
 *   pela altura    — acabamento lateral
 *   pelo perímetro — bainha, ilhós a cada tanto, fita de borda
 *   por pedido     — arte, acerto de máquina, deslocamento
 *
 * É o que impede a vastidão do ramo de virar código: acabamento novo é uma
 * linha de dado com uma dessas bases, nunca uma regra nova aqui.
 *
 * Disso sai a estrutura que este módulo devolve. O custo não é um número: são
 * **naturezas** que reagem cada uma de um jeito quando o tamanho ou a
 * quantidade mudam.
 *
 *     custo = (porM2·L·A + porLargura·L + porAltura·A + porPerimetro·2(L+A)
 *              + porPeca) × peças + porPedido
 *
 * É por isso que "quanto custa o m²" não tem uma resposta só — e também por
 * que dois banners de mesma área custam diferente: o de 4×1,5 leva o dobro de
 * torsal do de 2×3, com os mesmos 6 m² de lona. Guardar as naturezas separadas
 * é o que permite dizer isso na tela em vez de esconder tudo numa média.
 */

export type BaseDeConsumo =
  | "por_peca"
  | "por_m2"
  | "por_m_largura"
  | "por_m_altura"
  | "por_perimetro";

export const ROTULO_DA_BASE: Record<BaseDeConsumo, string> = {
  por_peca: "por peça",
  por_m2: "por m²",
  por_m_largura: "acompanha a largura",
  por_m_altura: "acompanha a altura",
  por_perimetro: "a volta toda (contorno)",
};

export type LinhaDeMaterial = {
  id: string;
  nome: string;
  unidade: string;
  /* Quanto custa uma unidade do insumo. */
  custoUnitario: number;
  /* Quanto se consome, na base escolhida. */
  consumo: number;
  base: BaseDeConsumo;
  /* Refile, recorte, o que se perde no processo. Em percentual. */
  perdaPct?: number;
  /* De onde a linha veio (o cadastro de insumos). A conta não usa; o
     salvamento da ficha, sim. */
  origemId?: string;
  /* Item da biblioteca que originou a linha, quando veio de lá. */
  librarySlug?: string;
  /* Aproveitamento: quantas peças saem de uma unidade do insumo (folha,
     chapa). Quando presente, `consumo` = 1/aproveitamento — o campo existe
     para a tela perguntar do jeito que a gráfica pensa. */
  aproveitamento?: number;
  /* Insumo comprado em bobina: a largura dela. A conta não usa (a perda de
     rodagem já chega em perdaPct); a tela usa para calcular essa perda. */
  larguraBobina?: number;
  /* Como a peça roda na bobina: "casado" (junto com outros trabalhos — a
     perda é a típica da receita) ou "sozinho" (a peça ocupa a largura
     inteira — a perda sai da geometria). A conta não usa; a tela decide a
     perda a partir daqui. Ausente = casado, o caso comum. */
  rodagem?: "casado" | "sozinho";
  /* A perda típica de rodagem casada (da receita), guardada para o toggle
     voltar a ela ao sair de "sozinho". */
  perdaCasada?: number;
  /* A pessoa mexeu na perda com a própria mão: o recálculo automático para de
     sobrescrever — quem roda casado sabe mais que a geometria. */
  perdaEditada?: boolean;
};

export type LinhaDeProcesso = {
  id: string;
  nome: string;
  /* Máquina ou pessoa: muda só o rótulo, a conta é a mesma. */
  tipo: "maquina" | "pessoa";
  /* O que custa uma hora deste recurso. */
  custoHora: number;
  /* Minutos gastos, na base escolhida. */
  minutos: number;
  base: BaseDeConsumo;
  /* Preparação, que acontece uma vez por pedido e não por peça. É o que
     encarece a peça única e barateia a tiragem grande. */
  minutosDeSetup?: number;
  origemId?: string;
  librarySlug?: string;
};

export type EntradaDaComposicao = {
  materiais: LinhaDeMaterial[];
  processos: LinhaDeProcesso[];
  /* Quantas peças serão feitas. */
  quantidade: number;
  /* Só quando o produto é medido: metros. */
  larguraM?: number | null;
  alturaM?: number | null;
};

export type ItemDoCusto = {
  nome: string;
  tipo: "material" | "maquina" | "pessoa";
  detalhe: string;
  custo: number;
};

/**
 * As velocidades do custo.
 *
 * Cada taxa vale por uma unidade da sua medida — um m², um metro de largura,
 * uma peça — e é justamente a diferença entre elas que faz o preço por m² não
 * ser constante.
 */
export type NaturezasDoCusto = {
  /* Acompanha a área: lona, tinta, tempo de impressão. R$ por m². */
  porM2: number;
  /* Corre ao longo da largura: torsal, madeira. R$ por metro. */
  porLargura: number;
  /* Corre ao longo da altura. R$ por metro. */
  porAltura: number;
  /* Dá a volta inteira: bainha, fita de borda. R$ por metro de contorno. */
  porPerimetro: number;
  /* Fixo em cada peça, independe do tamanho: ponteira, blank, acabamento. */
  porPeca: number;
  /* Uma vez por pedido, façam-se 1 ou 200: arte, acerto da máquina. */
  porPedido: number;
};

export type CustoDaComposicao = {
  custoTotal: number;
  custoPorPeca: number;
  /* Só faz sentido quando há área. É o número que vai para o produto medido. */
  custoPorM2: number;
  areaUnitaria: number;
  areaTotal: number;
  itens: ItemDoCusto[];
  naturezas: NaturezasDoCusto;
  /* O setup separado, porque é o número que explica por que 1 peça sai cara. */
  custoDeSetup: number;
};

const duasCasas = (n: number) => Math.round(n * 100) / 100;

/* Casas decimais so quando existem. "1,000 m2/m2" se le como mil em portugues,
   e um numero que confunde na hora de conferir custo nao serve de nada. */
const enxuto = (n: number) => {
  const arredondado = Math.round(n * 1000) / 1000;
  return String(arredondado).replace(".", ",");
};
const numero = (v: unknown) => {
  const n = Number(v);
  return Number.isFinite(n) && n > 0 ? n : 0;
};

export function areaDaPeca(larguraM?: number | null, alturaM?: number | null): number {
  return duasCasas(numero(larguraM) * numero(alturaM));
}

/**
 * Quanto vale **uma unidade da base** numa peça deste tamanho.
 *
 * É o coração do modelo: por m² vale a área, pela largura vale a largura, pelo
 * perímetro vale a volta inteira. Zero quando a peça não tem a dimensão — uma
 * caneca não tem largura em metros, e inventar uma seria pior que ignorar.
 */
export function medidaDaBase(base: BaseDeConsumo, larguraM: number, alturaM: number): number {
  const largura = numero(larguraM);
  const altura = numero(alturaM);
  switch (base) {
    case "por_peca":
      return 1;
    case "por_m2":
      return largura * altura;
    case "por_m_largura":
      return largura;
    case "por_m_altura":
      return altura;
    case "por_perimetro":
      return largura > 0 && altura > 0 ? 2 * (largura + altura) : 0;
  }
}

/**
 * O custo de um pedido de um certo tamanho, direto das naturezas.
 *
 * Recebe largura e altura — e não a área — porque desde que existem bases
 * lineares a área não determina mais o custo: 4×1,5 e 2×3 têm os mesmos 6 m² e
 * torsais bem diferentes.
 */
export function custoNoTamanho(
  n: NaturezasDoCusto,
  larguraM: number,
  alturaM: number,
  quantidade = 1
): number {
  const pecas = numero(quantidade);
  const porPeca =
    n.porM2 * medidaDaBase("por_m2", larguraM, alturaM) +
    n.porLargura * medidaDaBase("por_m_largura", larguraM, alturaM) +
    n.porAltura * medidaDaBase("por_m_altura", larguraM, alturaM) +
    n.porPerimetro * medidaDaBase("por_perimetro", larguraM, alturaM) +
    n.porPeca;
  return duasCasas(porPeca * pecas + n.porPedido);
}

/**
 * Quanto sai o m² num tamanho específico.
 *
 * Cai conforme o tamanho sobe, porque o que é por peça e por pedido se dilui
 * numa área maior. Essa curva é a informação que faltava para precificar
 * comunicação visual sem perder dinheiro na peça pequena.
 */
export function custoPorM2NoTamanho(
  n: NaturezasDoCusto,
  larguraM: number,
  alturaM: number,
  quantidade = 1
): number {
  const area = numero(larguraM) * numero(alturaM);
  const pecas = numero(quantidade);
  if (area <= 0 || pecas <= 0) return 0;
  return duasCasas(custoNoTamanho(n, larguraM, alturaM, pecas) / (area * pecas));
}

/**
 * Soma o que o produto consome, item a item.
 *
 * Devolve a lista detalhada junto com o total porque a tela precisa mostrar de
 * onde veio cada centavo. Um custo total sozinho é um número que ninguém
 * confere — e custo que ninguém confere é custo em que ninguém confia.
 */
export function calcularCustoDaComposicao(entrada: EntradaDaComposicao): CustoDaComposicao {
  const pecas = Math.max(0, numero(entrada.quantidade));
  const largura = numero(entrada.larguraM);
  const altura = numero(entrada.alturaM);
  const areaUnitaria = areaDaPeca(largura, altura);
  const areaTotal = duasCasas(areaUnitaria * pecas);

  const itens: ItemDoCusto[] = [];
  const bruto: NaturezasDoCusto = {
    porM2: 0, porLargura: 0, porAltura: 0, porPerimetro: 0, porPeca: 0, porPedido: 0,
  };

  const acumula = (base: BaseDeConsumo, taxa: number) => {
    if (base === "por_m2") bruto.porM2 += taxa;
    else if (base === "por_m_largura") bruto.porLargura += taxa;
    else if (base === "por_m_altura") bruto.porAltura += taxa;
    else if (base === "por_perimetro") bruto.porPerimetro += taxa;
    else bruto.porPeca += taxa;
  };

  /* Como a medida aparece no extrato de cada linha: o nome da base e a unidade
     do total consumido. */
  const nomeDaMedida: Record<Exclude<BaseDeConsumo, "por_peca">, [string, string]> = {
    por_m2: ["m²", "m²"],
    por_m_largura: ["m de largura", "m"],
    por_m_altura: ["m de altura", "m"],
    por_perimetro: ["m de contorno", "m"],
  };

  for (const m of entrada.materiais) {
    const custoDoInsumo = numero(m.custoUnitario);
    const consumo = numero(m.consumo);
    if (custoDoInsumo <= 0 || consumo <= 0) continue;

    const medidaUnitaria = medidaDaBase(m.base, largura, altura);
    /* Caneca, camiseta, chaveiro: não têm medida, e o que depende dela não se
       aplica. Ignorar é a resposta certa; multiplicar por 1 seria inventar uma
       dimensão que não existe. */
    if (m.base !== "por_peca" && medidaUnitaria <= 0) continue;

    const comPerda = consumo * (1 + numero(m.perdaPct) / 100);
    /* A taxa é o custo de uma unidade da base. Independe do tamanho do pedido
       — é o que a torna reaproveitável na tabela de tamanhos. */
    const taxa = comPerda * custoDoInsumo;
    acumula(m.base, taxa);

    const medidaTotal = duasCasas(medidaUnitaria * pecas);
    const custo = duasCasas(taxa * medidaTotal);
    if (custo <= 0) continue;

    itens.push({
      nome: m.nome,
      tipo: "material",
      detalhe:
        m.base === "por_peca"
          ? `${enxuto(comPerda)} ${m.unidade} × ${pecas} ${pecas === 1 ? "peça" : "peças"}`
          : `${enxuto(comPerda)} ${m.unidade} por ${nomeDaMedida[m.base][0]} × ${enxuto(medidaTotal)} ${nomeDaMedida[m.base][1]}`,
      custo,
    });
  }

  for (const p of entrada.processos) {
    const custoHora = numero(p.custoHora);
    if (custoHora <= 0) continue;

    const medidaUnitaria = medidaDaBase(p.base, largura, altura);
    if (p.base !== "por_peca" && medidaUnitaria <= 0) continue;

    const porMinuto = custoHora / 60;
    const taxa = numero(p.minutos) * porMinuto;
    const minutosDeSetup = numero(p.minutosDeSetup);
    const custoDoSetup = minutosDeSetup * porMinuto;

    acumula(p.base, taxa);
    /* O setup entra uma vez por pedido, não por peça: é o que faz a peça única
       sair cara e a tiragem grande diluir. */
    bruto.porPedido += custoDoSetup;

    const medidaTotal = duasCasas(medidaUnitaria * pecas);
    const minutosDeProducao = numero(p.minutos) * medidaTotal;
    const custo = duasCasas(taxa * medidaTotal + custoDoSetup);
    if (custo <= 0) continue;

    itens.push({
      nome: p.nome,
      tipo: p.tipo,
      detalhe:
        minutosDeSetup > 0
          ? `${enxuto(minutosDeProducao)} min + ${enxuto(minutosDeSetup)} min de preparo`
          : `${enxuto(minutosDeProducao)} min`,
      custo,
    });
  }

  /* As naturezas são arredondadas **antes** de virarem total, e não depois.
     Assim o que a tela mostra multiplica exatamente: se lá está escrito R$ 23,48
     por peça, dez peças dão R$ 234,80, e não R$ 234,78 com o centavo perdido no
     caminho. O preço de manter a promessa é abrir mão da terceira casa; o preço
     de quebrá-la é alguém conferir na calculadora e não bater. */
  const naturezas: NaturezasDoCusto = {
    porM2: duasCasas(bruto.porM2),
    porLargura: duasCasas(bruto.porLargura),
    porAltura: duasCasas(bruto.porAltura),
    porPerimetro: duasCasas(bruto.porPerimetro),
    porPeca: duasCasas(bruto.porPeca),
    porPedido: duasCasas(bruto.porPedido),
  };
  const custoTotal = custoNoTamanho(naturezas, largura, altura, pecas);

  return {
    custoTotal,
    /* Por peça, e não por m²: é assim que a pessoa pensa o produto, mesmo
       quando cobra por metro. */
    custoPorPeca: pecas > 0 ? duasCasas(custoTotal / pecas) : 0,
    custoPorM2: custoPorM2NoTamanho(naturezas, largura, altura, pecas),
    areaUnitaria,
    areaTotal,
    itens,
    naturezas,
    custoDeSetup: naturezas.porPedido,
  };
}
