/* Os modelos de condições comerciais da empresa.

   Antes existia um padrão só, guardado em `profiles.quote_default_payment_terms`:
   depois de salvo não dava para apagar nem ter um segundo. Só que uma gráfica
   trabalha com mais de uma condição ("à vista no Pix", "50% de entrada",
   "faturado em 30 dias") e escolhe pelo cliente.

   Agora a empresa guarda uma lista em `profiles.quote_payment_terms_templates`,
   e um dos modelos pode ser o padrão — o ponto de partida de todo orçamento
   novo. Sem padrão também é resposta: o orçamento novo começa à vista no Pix.

   `quote_default_payment_terms` continua existindo como ESPELHO do modelo
   padrão, porque é dele que as telas e as funções antigas leem. Quem manda é
   a lista; o espelho é gravado junto, sempre pela mesma função daqui.

   Este arquivo não desenha nada nem fala com o banco: é só a regra, para
   poder ser testada sozinha. */

import { erroDasCondicoes, normalizarCondicoes, type CondicoesComerciais } from "./condicoes-comerciais.ts";

export type ModeloDeCondicoes = {
  id: string;
  nome: string;
  condicoes: CondicoesComerciais;
  /** O ponto de partida dos orçamentos novos. No máximo um da lista. */
  padrao: boolean;
};

export const LIMITE_DE_MODELOS = 12;
export const LIMITE_DO_NOME = 60;

/** Nome do modelo que nasce do padrão antigo, de quem já tinha um salvo. */
export const NOME_DO_PADRAO_ANTIGO = "Padrão da empresa";

function texto(valor: unknown) {
  return typeof valor === "string" ? valor.replace(/\s+/g, " ").trim() : "";
}

/** Dois nomes são o mesmo quando só mudam maiúsculas e espaços. */
function mesmoNome(a: string, b: string) {
  return a.trim().toLocaleLowerCase("pt-BR") === b.trim().toLocaleLowerCase("pt-BR");
}

/**
 * Lê a lista guardada no perfil.
 *
 * `padraoAntigo` é o `quote_default_payment_terms` de quem salvou um padrão
 * antes desta tela existir: sem lista nenhuma, ele vira o primeiro modelo, já
 * marcado como padrão. Ninguém perde o que tinha, e não precisou converter o
 * banco.
 */
export function lerModelos(bruto: unknown, padraoAntigo?: unknown): ModeloDeCondicoes[] {
  const lidos: ModeloDeCondicoes[] = [];
  const vistos = new Set<string>();

  if (Array.isArray(bruto)) {
    for (const item of bruto) {
      if (!item || typeof item !== "object" || Array.isArray(item)) continue;
      const linha = item as Record<string, unknown>;
      const condicoes = normalizarCondicoes(linha.condicoes);
      const id = texto(linha.id);
      const nome = texto(linha.nome).slice(0, LIMITE_DO_NOME);
      if (!condicoes || !id || !nome || vistos.has(id)) continue;
      vistos.add(id);
      lidos.push({ id, nome, condicoes, padrao: linha.padrao === true });
      if (lidos.length >= LIMITE_DE_MODELOS) break;
    }
  }

  if (!lidos.length) {
    const antigo = normalizarCondicoes(padraoAntigo);
    if (antigo) return [{ id: "padrao", nome: NOME_DO_PADRAO_ANTIGO, condicoes: antigo, padrao: true }];
    return [];
  }

  /* Dois padrões ao mesmo tempo é lista corrompida: vale o primeiro. */
  let jaTemPadrao = false;
  return lidos.map((modelo) => {
    const padrao = modelo.padrao && !jaTemPadrao;
    if (padrao) jaTemPadrao = true;
    return { ...modelo, padrao };
  });
}

/** As condições do modelo padrão: é o que vai para o espelho no perfil. */
export function condicoesDoPadrao(modelos: ModeloDeCondicoes[]): CondicoesComerciais | null {
  return modelos.find((modelo) => modelo.padrao)?.condicoes ?? null;
}

export type AcaoNosModelos =
  | { acao: "criar"; id: string; nome: string; condicoes: unknown; padrao?: boolean }
  | { acao: "renomear"; id: string; nome: string }
  | { acao: "substituir"; id: string; condicoes: unknown }
  | { acao: "excluir"; id: string }
  /** `id` nulo tira o padrão: a empresa passa a não ter nenhum. */
  | { acao: "padrao"; id: string | null };

/* `erro` e `modelos` sempre presentes (um deles nulo): é o que deixa o
   `if (!resultado.modelos)` estreitar o tipo sem ginástica. */
export type ResultadoDaAcao = { modelos: ModeloDeCondicoes[]; erro: null } | { modelos: null; erro: string };

const recusar = (erro: string): ResultadoDaAcao => ({ modelos: null, erro });
const aceitar = (modelos: ModeloDeCondicoes[]): ResultadoDaAcao => ({ modelos, erro: null });

function erroDoNome(nome: string, modelos: ModeloDeCondicoes[], idAtual?: string): string | null {
  if (!nome) return "Dê um nome ao modelo.";
  if (modelos.some((modelo) => modelo.id !== idAtual && mesmoNome(modelo.nome, nome))) {
    return "Já existe um modelo com esse nome.";
  }
  return null;
}

function condicoesValidas(bruto: unknown): { condicoes: CondicoesComerciais; erro: null } | { condicoes: null; erro: string } {
  const condicoes = normalizarCondicoes(bruto);
  if (!condicoes) return { condicoes: null, erro: "Informe as condições." };
  /* Total zero: aqui não há orçamento, então só as regras que não dependem
     do valor (uma forma de pagamento escolhida, entrada em reais > 0). */
  const erro = erroDasCondicoes(condicoes, 0);
  return erro ? { condicoes: null, erro } : { condicoes, erro: null };
}

/** Só um padrão na lista; marcar um tira a marca dos outros. */
function comPadrao(modelos: ModeloDeCondicoes[], id: string | null): ModeloDeCondicoes[] {
  return modelos.map((modelo) => ({ ...modelo, padrao: modelo.id === id }));
}

/**
 * Aplica uma mudança na lista e devolve a lista nova, ou a mensagem que a
 * tela mostra. Função pura: quem grava é a rota.
 */
export function aplicarNosModelos(modelos: ModeloDeCondicoes[], acao: AcaoNosModelos): ResultadoDaAcao {
  if (acao.acao === "criar") {
    if (modelos.length >= LIMITE_DE_MODELOS) {
      return recusar(`A empresa pode ter até ${LIMITE_DE_MODELOS} modelos. Apague um para criar outro.`);
    }
    const nome = texto(acao.nome).slice(0, LIMITE_DO_NOME);
    const problema = erroDoNome(nome, modelos);
    if (problema) return recusar(problema);
    const lidas = condicoesValidas(acao.condicoes);
    if (!lidas.condicoes) return recusar(lidas.erro);
    const novo: ModeloDeCondicoes = { id: acao.id, nome, condicoes: lidas.condicoes, padrao: false };
    /* O primeiro modelo da empresa já nasce padrão: quem salva o primeiro
       quer que ele seja o ponto de partida. */
    const lista = [...modelos, novo];
    return aceitar(acao.padrao === true || modelos.length === 0 ? comPadrao(lista, novo.id) : lista);
  }

  if (acao.acao === "renomear") {
    const nome = texto(acao.nome).slice(0, LIMITE_DO_NOME);
    const problema = erroDoNome(nome, modelos, acao.id);
    if (problema) return recusar(problema);
    if (!modelos.some((modelo) => modelo.id === acao.id)) return recusar("Modelo não encontrado.");
    return aceitar(modelos.map((modelo) => (modelo.id === acao.id ? { ...modelo, nome } : modelo)));
  }

  if (acao.acao === "substituir") {
    if (!modelos.some((modelo) => modelo.id === acao.id)) return recusar("Modelo não encontrado.");
    const lidas = condicoesValidas(acao.condicoes);
    const condicoes = lidas.condicoes;
    if (!condicoes) return recusar(lidas.erro);
    return aceitar(modelos.map((modelo) => (modelo.id === acao.id ? { ...modelo, condicoes } : modelo)));
  }

  if (acao.acao === "excluir") {
    if (!modelos.some((modelo) => modelo.id === acao.id)) return recusar("Modelo não encontrado.");
    /* Apagar o padrão deixa a empresa SEM padrão, de propósito: promover
       outro sozinho é decidir pela pessoa o que ela acabou de desfazer. */
    return aceitar(modelos.filter((modelo) => modelo.id !== acao.id));
  }

  if (acao.id !== null && !modelos.some((modelo) => modelo.id === acao.id)) return recusar("Modelo não encontrado.");
  return aceitar(comPadrao(modelos, acao.id));
}
