/**
 * Como um adicional é cobrado, e quanto vale a linha.
 *
 * Um produto vendido por m² tem duas quantidades: as peças e a área. O
 * adicional de uma opção (laminação, espessura, instalação) precisa dizer em
 * qual das duas ele se multiplica, ou se entra uma vez só no pedido:
 *
 *   measure  por m² (por metro no produto por metro; no produto por unidade
 *            é a própria peça)
 *   piece    por peça
 *   order    uma vez, seja qual for a quantidade
 *
 * O campo `price_unit` guarda isso na opção. Opção antiga, sem o campo, segue
 * a regra que sempre valeu: `price_applies_per_quantity` falso é uma vez;
 * verdadeiro é por medida. Assim nenhum preço muda sozinho.
 *
 * Este arquivo é igual no admin e na loja de propósito (apps/admin e
 * apps/public): loja, carrinho, checkout, orçamento e PDV fazem a mesma conta.
 */
import { resolveTierPrice, type ProductPriceTier } from "./price-tiers.ts";

export type UnidadeDoAdicional = "measure" | "piece" | "order";

export const UNIDADES_DO_ADICIONAL: UnidadeDoAdicional[] = ["measure", "piece", "order"];

export type OpcaoComPreco = {
  price?: number | string | null;
  price_unit?: string | null;
  price_applies_per_quantity?: boolean | null;
};

/** Produtos em que "por medida" é diferente de "por peça". */
export function vendidoPorMedida(unitType: string | null | undefined): boolean {
  return unitType === "square_meter" || unitType === "meter" || unitType === "linear_meter";
}

/** O nome da medida em que o preço é dado: m², m ou unidade. */
export function nomeDaMedida(unitType: string | null | undefined): string {
  if (unitType === "square_meter") return "m²";
  if (unitType === "meter" || unitType === "linear_meter") return "m";
  return "unidade";
}

export function unidadeDoAdicional(
  opcao: OpcaoComPreco | null | undefined,
  unitType: string | null | undefined
): UnidadeDoAdicional {
  const declarada = String(opcao?.price_unit ?? "").trim().toLowerCase();
  if (declarada === "order" || declarada === "piece") return declarada;
  if (declarada === "measure") return vendidoPorMedida(unitType) ? "measure" : "piece";
  if (opcao?.price_applies_per_quantity === false) return "order";
  return vendidoPorMedida(unitType) ? "measure" : "piece";
}

/** O `price_applies_per_quantity` que acompanha a unidade, para o código antigo. */
export function multiplicaNaQuantidade(unidade: UnidadeDoAdicional): boolean {
  return unidade !== "order";
}

export type SomaDosAdicionais = { porMedida: number; porPeca: number; umaVez: number };

export const SEM_ADICIONAIS: SomaDosAdicionais = { porMedida: 0, porPeca: 0, umaVez: 0 };

export function somarAdicionais(
  opcoes: ReadonlyArray<OpcaoComPreco | null | undefined> | null | undefined,
  unitType: string | null | undefined
): SomaDosAdicionais {
  const soma: SomaDosAdicionais = { porMedida: 0, porPeca: 0, umaVez: 0 };
  for (const opcao of opcoes ?? []) {
    if (!opcao || typeof opcao !== "object") continue;
    const preco = Number(opcao.price) || 0;
    if (!preco) continue;
    const unidade = unidadeDoAdicional(opcao, unitType);
    if (unidade === "order") soma.umaVez += preco;
    else if (unidade === "piece") soma.porPeca += preco;
    else soma.porMedida += preco;
  }
  return soma;
}

export function juntarAdicionais(...somas: Array<SomaDosAdicionais | null | undefined>): SomaDosAdicionais {
  const total: SomaDosAdicionais = { porMedida: 0, porPeca: 0, umaVez: 0 };
  for (const soma of somas) {
    if (!soma) continue;
    total.porMedida += Number(soma.porMedida) || 0;
    total.porPeca += Number(soma.porPeca) || 0;
    total.umaVez += Number(soma.umaVez) || 0;
  }
  return total;
}

export type ContaDaLinha = {
  /** O preço do produto por medida (do m², do metro ou da unidade). */
  precoBase: number;
  faixas?: ProductPriceTier[] | null;
  /** Quantas peças. */
  pecas: number;
  /** Peças × área (ou × metros); no produto por unidade, as próprias peças. */
  quantidadeEfetiva: number;
  adicionais?: SomaDosAdicionais | null;
  /** "Preço apenas pela variação": o preço base não entra. */
  soVariacao?: boolean | null;
  sobConsulta?: boolean | null;
  /** O valor mínimo do item (ver valorMinimoDoItem); vazio = sem mínimo. */
  valorMinimo?: number | null;
};

export type ResultadoDaLinha = {
  /** A faixa que pegou, se alguma. */
  faixa: ProductPriceTier | null;
  /** O preço da faixa (ou o base), por medida, sem adicionais. */
  precoDaFaixa: number;
  /** Preço por medida com os adicionais por medida: o "R$ 58,00 / m²". */
  precoPorMedidaSemRateio: number;
  /** O preço por medida que o carrinho e o pedido guardam: a linha inteira
      dividida pela quantidade efetiva, com peça e pedido rateados. */
  precoPorMedida: number;
  /** O que sai uma peça, com tudo que é dela. */
  precoPorPeca: number;
  total: number;
  /** O mínimo que foi cobrado nesta linha; null quando a conta passou dele. */
  minimoAplicado: number | null;
  /** O que a linha daria sem o mínimo (igual ao total quando não aplicou). */
  totalSemMinimo: number;
};

/**
 * A conta inteira de uma linha:
 *   (faixa + adicionais por medida) × quantidade efetiva
 *   + adicionais por peça × peças
 *   + adicionais uma vez.
 */
export function precoDaLinha(conta: ContaDaLinha): ResultadoDaLinha {
  const pecas = Number(conta.pecas) > 0 ? Number(conta.pecas) : 1;
  const efetiva = Number(conta.quantidadeEfetiva) > 0 ? Number(conta.quantidadeEfetiva) : pecas;
  const adicionais = conta.adicionais ?? SEM_ADICIONAIS;
  if (conta.sobConsulta) {
    return {
      faixa: null, precoDaFaixa: 0, precoPorMedidaSemRateio: 0, precoPorMedida: 0, precoPorPeca: 0, total: 0,
      minimoAplicado: null, totalSemMinimo: 0,
    };
  }
  const { unitPrice, tier } = resolveTierPrice(Number(conta.precoBase) || 0, efetiva, conta.faixas ?? []);
  const base = conta.soVariacao ? 0 : unitPrice;
  const porMedida = base + adicionais.porMedida;
  const automatico = Math.max(0, porMedida * efetiva + adicionais.porPeca * pecas + adicionais.umaVez);
  // O mínimo do item vem por último, depois de faixa e adicionais: a linha
  // inteira (todas as peças juntas) é comparada sozinha com ele, em centavos.
  const minimo = Number(conta.valorMinimo) > 0 ? centavosDoMinimo(Number(conta.valorMinimo)) : 0;
  const aplicou = minimo > 0 && centavosDoMinimo(automatico) < minimo;
  const total = aplicou ? minimo : automatico;
  return {
    faixa: tier,
    precoDaFaixa: unitPrice,
    precoPorMedidaSemRateio: Math.max(0, porMedida),
    precoPorMedida: total / efetiva,
    precoPorPeca: total / pecas,
    total,
    minimoAplicado: aplicou ? minimo : null,
    totalSemMinimo: automatico,
  };
}

function centavosDoMinimo(valor: number): number {
  return Math.round((Number(valor) || 0) * 100) / 100;
}

/* ---------------------------------------------------------------------------
 * Valor mínimo do item (migration 194)
 *
 * Gráfica cobra um mínimo por item em lona, adesivo, chapa: cada linha (a
 * tiragem inteira daquela linha) é comparada sozinha com o mínimo. Juntar
 * itens diferentes não ajuda a chegar nele. Só vale em produto por m² ou por
 * metro, e o produto escolhe onde vale: orçamentos, loja e PDV.
 * ------------------------------------------------------------------------- */

export type CanalDoMinimo = "orcamentos" | "loja" | "pdv";

export type ProdutoComMinimo = {
  unit_type?: string | null;
  price_on_request?: boolean | null;
  valor_minimo_item?: number | string | null;
  valor_minimo_orcamentos?: boolean | null;
  valor_minimo_loja?: boolean | null;
  valor_minimo_pdv?: boolean | null;
};

/** As colunas do produto que a conta do mínimo lê. */
export const COLUNAS_DO_VALOR_MINIMO = "valor_minimo_item,valor_minimo_orcamentos,valor_minimo_loja,valor_minimo_pdv";

/**
 * O mínimo que vale para este produto neste canal, ou null. Chave sem valor
 * (produto antigo, leitura sem a coluna) conta como ligada: o padrão é valer
 * nos três quando há valor.
 */
export function valorMinimoDoItem(
  produto: ProdutoComMinimo | null | undefined,
  canal: CanalDoMinimo
): number | null {
  if (!produto || produto.price_on_request) return null;
  if (!vendidoPorMedida(produto.unit_type)) return null;
  const valor = Number(produto.valor_minimo_item);
  if (!Number.isFinite(valor) || valor <= 0) return null;
  const chave =
    canal === "orcamentos"
      ? produto.valor_minimo_orcamentos
      : canal === "loja"
        ? produto.valor_minimo_loja
        : produto.valor_minimo_pdv;
  if (chave === false) return null;
  return centavosDoMinimo(valor);
}

function reais(valor: number): string {
  return (Number(valor) || 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
}

/** "Valor mínimo aplicado: R$ 70,00 (o tamanho escolhido daria R$ 30,00)". */
export function textoDoMinimoAplicado(
  minimo: number | string | null | undefined,
  semMinimo: number | string | null | undefined
): string {
  const valor = Number(minimo) || 0;
  const daria = Number(semMinimo);
  if (Number.isFinite(daria) && daria >= 0 && semMinimo != null && semMinimo !== "") {
    return `Valor mínimo aplicado: ${reais(valor)} (o tamanho escolhido daria ${reais(daria)})`;
  }
  return `Valor mínimo aplicado: ${reais(valor)}`;
}

/** A etiqueta que o cliente vê na linha que pegou o mínimo (sem o valor de antes). */
export const ROTULO_DO_MINIMO = "Valor mínimo do item";

/** O texto da explicação (a "i" da etiqueta), para o cliente da loja. */
export function explicacaoDoMinimo(minimo: number | string | null | undefined): string {
  const valor = reais(Number(minimo) || 0);
  return `Cada item deste produto tem valor mínimo de ${valor}. Se as medidas e a quantidade escolhidas somarem menos que isso, o item sai por ${valor}. Acima disso, você paga o valor normal do tamanho escolhido.`;
}

/** "Valor mínimo deste item: R$ 70,00", para mostrar antes da conta. */
export function textoDoMinimoDoItem(minimo: number | string | null | undefined): string {
  return `Valor mínimo deste item: ${reais(Number(minimo) || 0)}`;
}

/**
 * Lê o mínimo gravado numa linha (quote_items, order_items, item do carrinho
 * ou raw_item do pedido da loja). Null quando a linha não pegou o mínimo.
 */
export function minimoDaLinhaGravada(
  item: { valor_minimo_aplicado?: number | string | null; valor_sem_minimo?: number | string | null } | null | undefined
): { minimo: number; semMinimo: number | null } | null {
  const minimo = Number(item?.valor_minimo_aplicado);
  if (!item || item.valor_minimo_aplicado == null || !Number.isFinite(minimo) || minimo <= 0) return null;
  const semMinimo = item.valor_sem_minimo == null || item.valor_sem_minimo === "" ? null : Number(item.valor_sem_minimo);
  return { minimo, semMinimo: Number.isFinite(semMinimo as number) ? (semMinimo as number) : null };
}

/** "/m²", "por peça", "no pedido"; no produto por unidade, "por unidade". */
export function rotuloDaUnidade(unidade: UnidadeDoAdicional, unitType: string | null | undefined): string {
  if (unidade === "order") return "no pedido";
  if (unidade === "piece") return vendidoPorMedida(unitType) ? "por peça" : "por unidade";
  return vendidoPorMedida(unitType) ? `/${nomeDaMedida(unitType)}` : "por unidade";
}

/**
 * O que vai depois do valor na vitrine: "/m²", " por peça", " no pedido".
 * No produto por unidade o adicional por unidade continua sem sufixo, como
 * sempre foi; só o "no pedido" ganha aviso, porque muda a conta.
 */
export function sufixoDoAdicional(
  opcao: OpcaoComPreco | null | undefined,
  unitType: string | null | undefined
): string {
  const unidade = unidadeDoAdicional(opcao, unitType);
  if (unidade === "order") return " no pedido";
  if (!vendidoPorMedida(unitType)) return "";
  return unidade === "piece" ? " por peça" : `/${nomeDaMedida(unitType)}`;
}
