/*
  As regras da loja, e o que cada visitante pode ver e fazer.

  A aba "Regras da loja" do painel grava em `profiles` como a loja vende:
  se mostra preco (para todos, para ninguem ou so para quem entrou), como o
  cliente fecha (carrinho, pedido de orcamento, WhatsApp ou so vitrine),
  quem pode entrar, pedido minimo, estoque visivel e se a loja esta aberta.

  Tudo passa por uma funcao so, `resolverVisao`, que junta as regras com
  quem esta olhando (visitante, cliente aprovado, cliente pendente ou a
  propria equipe) e devolve decisoes prontas: mostra preco?, qual texto no
  lugar dele?, pode comprar?, que botao?, para onde o botao leva?

  Ela e calculada a cada requisicao, no servidor, e vai para os componentes
  pelo contexto (components/VisaoDaLojaContext.tsx). Nunca entra na
  lembranca da loja: a lembranca e a mesma para todos os visitantes, e esta
  decisao depende de quem esta olhando.

  Quando o visitante nao pode ver preco, os produtos passam por `semPrecos`
  ANTES de ir para um componente de navegador: preco, faixas, acrescimos das
  opcoes, servicos e combos viram nulos. O que nao vai, nao vaza.

  Toda regra nasce com o valor que reproduz o comportamento de hoje: loja
  que nao mexeu na aba nova nao muda em nada.
*/

export type ModoDePreco = "public" | "hidden" | "clients";
export type ModoDeCompra = "cart" | "quote" | "whatsapp" | "showcase";
export type ModoDeAcesso = "public" | "clients";
export type ModoDeCadastro = "open" | "approval" | "closed";
export type EstoqueVisivel = "exact" | "low_only" | "never";
export type ModoEsgotado = "show" | "hide" | "quote";
export type SituacaoDaLoja = "open" | "paused" | "closed";
export type StatusDoCliente = "approved" | "pending" | "rejected";

export type RegrasDaLoja = {
  modoDePreco: ModoDePreco;
  textoPrecoOculto: string;
  textoPrecoLogin: string;
  avisoDePreco: string | null;
  modoDeCompra: ModoDeCompra;
  rotuloDoBotao: string | null;
  rotuloDeFechar: string | null;
  abrirWhatsappNoOrcamento: boolean;
  modoDeAcesso: ModoDeAcesso;
  modoDeCadastro: ModoDeCadastro;
  exigirCnpj: boolean;
  mensagemDeLogin: string | null;
  exigirLoginParaPedir: boolean;
  pedidoMinimoValor: number | null;
  pedidoMinimoItens: number | null;
  estoqueVisivel: EstoqueVisivel;
  limiteEstoqueBaixo: number;
  esgotado: ModoEsgotado;
  avaliacoes: boolean;
  mostrarSku: boolean;
  mostrarPrazo: boolean;
  situacao: SituacaoDaLoja;
  mensagemDaSituacao: string | null;
  voltaEm: string | null;
  /* Os modelos de WhatsApp > Mensagens > Catalogo Publico (profiles.whatsapp_templates).
     Nulo = o texto de sempre. */
  mensagensDoWhatsapp: {
    produto: string | null;
    cabecalhoDoPedido: string | null;
    rodapeDoPedido: string | null;
  };
};

/** Troca os {{marcadores}} de um modelo de mensagem pelos valores. */
export function preencherModelo(modelo: string, valores: Record<string, string>) {
  return Object.entries(valores).reduce(
    (texto, [chave, valor]) => texto.split(`{{${chave}}}`).join(valor),
    modelo
  );
}

export const TEXTO_PADRAO_PRECO_OCULTO = "Sob consulta";
export const TEXTO_PADRAO_PRECO_LOGIN = "Entre para ver o preço";
export const TEXTO_PADRAO_EM_ANALISE = "Cadastro em análise";
export const TEXTO_PADRAO_RECUSADO = "Cadastro não aprovado";

const ROTULO_PADRAO_DO_BOTAO: Record<ModoDeCompra, string | null> = {
  cart: null,
  quote: "Pedir orçamento",
  whatsapp: "Pedir pelo WhatsApp",
  showcase: null,
};

const ROTULO_PADRAO_DE_FECHAR: Record<ModoDeCompra, string | null> = {
  cart: null,
  quote: "Enviar pedido de orçamento",
  whatsapp: null,
  showcase: null,
};

type Perfil = Record<string, unknown> | null | undefined;

function texto(valor: unknown): string | null {
  const limpo = String(valor ?? "").trim();
  return limpo ? limpo : null;
}

function entre<T extends string>(valor: unknown, aceitos: readonly T[], padrao: T): T {
  const limpo = String(valor ?? "").trim();
  return (aceitos as readonly string[]).includes(limpo) ? (limpo as T) : padrao;
}

function numeroOuNulo(valor: unknown): number | null {
  if (valor === null || valor === undefined || valor === "") return null;
  const numero = Number(valor);
  return Number.isFinite(numero) && numero > 0 ? numero : null;
}

/** As regras gravadas no perfil, com os padroes de hoje no lugar do que falta. */
export function regrasDaLoja(perfil: Perfil): RegrasDaLoja {
  const p = (perfil || {}) as Record<string, unknown>;
  const limite = Number(p.catalog_low_stock_threshold);
  const modelos = modelosDoWhatsapp(p.whatsapp_templates);
  return {
    modoDePreco: entre(p.catalog_price_mode, ["public", "hidden", "clients"] as const, "public"),
    textoPrecoOculto: texto(p.catalog_price_hidden_text) || TEXTO_PADRAO_PRECO_OCULTO,
    textoPrecoLogin: texto(p.catalog_price_login_text) || TEXTO_PADRAO_PRECO_LOGIN,
    avisoDePreco: texto(p.catalog_price_notice),
    modoDeCompra: entre(p.catalog_purchase_mode, ["cart", "quote", "whatsapp", "showcase"] as const, "cart"),
    rotuloDoBotao: texto(p.catalog_cta_label),
    rotuloDeFechar: texto(p.catalog_checkout_label),
    abrirWhatsappNoOrcamento: p.catalog_quote_open_whatsapp !== false,
    modoDeAcesso: entre(p.catalog_access_mode, ["public", "clients"] as const, "public"),
    modoDeCadastro: entre(p.catalog_signup_mode, ["open", "approval", "closed"] as const, "open"),
    exigirCnpj: p.catalog_signup_require_cnpj === true,
    mensagemDeLogin: texto(p.catalog_login_message),
    exigirLoginParaPedir: p.catalog_require_login_to_order === true,
    pedidoMinimoValor: numeroOuNulo(p.catalog_min_order_total),
    pedidoMinimoItens: numeroOuNulo(p.catalog_min_order_items),
    estoqueVisivel: entre(p.catalog_stock_display, ["exact", "low_only", "never"] as const, "exact"),
    limiteEstoqueBaixo: Number.isFinite(limite) && limite >= 0 ? limite : 5,
    esgotado: entre(p.catalog_sold_out_mode, ["show", "hide", "quote"] as const, "show"),
    avaliacoes: p.catalog_reviews_enabled !== false,
    mostrarSku: p.catalog_show_sku === true,
    mostrarPrazo: p.catalog_show_production_time !== false,
    situacao: entre(p.catalog_status, ["open", "paused", "closed"] as const, "open"),
    mensagemDaSituacao: texto(p.catalog_status_message),
    voltaEm: texto(p.catalog_status_until),
    mensagensDoWhatsapp: {
      produto: texto(modelos.product_inquiry),
      cabecalhoDoPedido: texto(modelos.cart_checkout_header),
      rodapeDoPedido: texto(modelos.cart_checkout_footer),
    },
  };
}

function modelosDoWhatsapp(valor: unknown): Record<string, unknown> {
  if (valor && typeof valor === "object") return valor as Record<string, unknown>;
  if (typeof valor === "string") {
    try {
      const lido = JSON.parse(valor);
      return lido && typeof lido === "object" ? (lido as Record<string, unknown>) : {};
    } catch {
      return {};
    }
  }
  return {};
}

export type ClienteDaVisao = {
  id: string;
  name: string;
  status: StatusDoCliente;
} | null;

export type MotivoDoPrecoOculto = "nenhum" | "loja" | "login" | "aprovacao" | "recusa";

export type VisaoDoVisitante = {
  regras: RegrasDaLoja;
  /** O navegador da equipe da loja (cookie do painel): ve preco sempre. */
  equipe: boolean;
  cliente: ClienteDaVisao;
  /** A decisao principal: este visitante ve os precos? */
  mostrarPreco: boolean;
  motivoPrecoOculto: MotivoDoPrecoOculto;
  /** O que aparece onde ficaria o preco (tambem vale para "sob consulta" por produto). */
  textoNoLugarDoPreco: string;
  /** O botao do produto deve levar ao login (preco so para quem entrou). */
  precisaEntrar: boolean;
  /** Cliente entrou, mas a loja ainda nao aprovou o cadastro. */
  aguardandoAprovacao: boolean;
  /** Cliente logado cujo cadastro a loja recusou: navega, mas sem preco nem compra. */
  cadastroRecusado: boolean;
  /** Ha um botao de compra ativo (qualquer modo que nao seja so vitrine). */
  podeComprar: boolean;
  /** O modo de compra que vale para este visitante (carrinho sem preco vira orcamento). */
  modoDeCompra: ModoDeCompra;
  /** Texto do botao do produto; nulo = o componente usa o texto de sempre. */
  rotuloDoBotao: string | null;
  /** Texto do botao de fechar o carrinho; nulo = o de sempre. */
  rotuloDeFechar: string | null;
  tituloDoCarrinho: string;
  lojaPausada: boolean;
  lojaFechada: boolean;
  /** A loja inteira exige login e este visitante nao entrou: a pagina redireciona. */
  exigeLoginNaLoja: boolean;
  /** Fechar pedido exige cadastro (regra da loja ou modo de preco). */
  exigeLoginParaFechar: boolean;
  avisoDePreco: string | null;
  /** Por que nao da para comprar agora, quando nao da. */
  motivoDoBloqueio: string | null;
};

/** O que todo mundo ve numa loja que nunca mexeu nas regras: o comportamento de hoje. */
export function visaoPadrao(): VisaoDoVisitante {
  return resolverVisao(null, null, false);
}

export function resolverVisao(perfil: Perfil, cliente: ClienteDaVisao, equipe: boolean): VisaoDoVisitante {
  const regras = regrasDaLoja(perfil);
  const aprovado = Boolean(cliente && cliente.status === "approved");
  const pendente = Boolean(cliente && cliente.status === "pending");
  const recusado = Boolean(cliente && cliente.status === "rejected");
  const lojaSoParaClientes = regras.modoDePreco === "clients" || regras.modoDeAcesso === "clients";

  let mostrarPreco = true;
  let motivoPrecoOculto: MotivoDoPrecoOculto = "nenhum";
  if (!equipe) {
    if (regras.modoDePreco === "hidden") {
      mostrarPreco = false;
      motivoPrecoOculto = "loja";
    } else if (regras.modoDePreco === "clients" && !aprovado) {
      mostrarPreco = false;
      motivoPrecoOculto = recusado ? "recusa" : pendente ? "aprovacao" : "login";
    } else if (regras.modoDeAcesso === "clients" && (pendente || recusado)) {
      /* Loja so para clientes: quem ainda nao foi aprovado (ou foi recusado)
         entra, mas sem preco, mesmo com o preco publico para os aprovados. */
      mostrarPreco = false;
      motivoPrecoOculto = recusado ? "recusa" : "aprovacao";
    }
  }

  const lojaFechada = regras.situacao === "closed" && !equipe;
  const lojaPausada = regras.situacao === "paused" && !equipe;
  const exigeLoginNaLoja = regras.modoDeAcesso === "clients" && !equipe && !cliente;
  const aguardandoAprovacao = pendente && lojaSoParaClientes;
  const cadastroRecusado = recusado && lojaSoParaClientes;
  const precisaEntrar = motivoPrecoOculto === "login";

  /* Sem preco na tela, o carrinho nao tem como somar nem cobrar: o
     fechamento vira pedido de orcamento, e a loja precifica no servidor. */
  const modoDeCompra: ModoDeCompra =
    !mostrarPreco && motivoPrecoOculto === "loja" && regras.modoDeCompra === "cart" ? "quote" : regras.modoDeCompra;

  let motivoDoBloqueio: string | null = null;
  if (lojaFechada) motivoDoBloqueio = regras.mensagemDaSituacao || "A loja está fechada no momento.";
  else if (lojaPausada) motivoDoBloqueio = regras.mensagemDaSituacao || "A loja está em pausa. Voltamos em breve.";
  else if (aguardandoAprovacao) motivoDoBloqueio = "Seu cadastro está em análise pela loja.";
  else if (cadastroRecusado) motivoDoBloqueio = "Seu cadastro não foi aprovado pela loja. Fale com a loja para liberar o acesso.";
  else if (exigeLoginNaLoja) motivoDoBloqueio = "Entre na sua conta para ver a loja.";
  else if (precisaEntrar) motivoDoBloqueio = regras.textoPrecoLogin;

  /* Quem nem pode entrar na loja (so para clientes, sem sessao) nao compra:
     as paginas redirecionam para o login e as rotas recusam o pedido. */
  const podeComprar =
    !lojaFechada &&
    !lojaPausada &&
    !aguardandoAprovacao &&
    !cadastroRecusado &&
    !exigeLoginNaLoja &&
    !precisaEntrar &&
    modoDeCompra !== "showcase";

  const textoNoLugarDoPreco =
    motivoPrecoOculto === "login"
      ? regras.textoPrecoLogin
      : motivoPrecoOculto === "aprovacao"
        ? TEXTO_PADRAO_EM_ANALISE
        : motivoPrecoOculto === "recusa"
          ? TEXTO_PADRAO_RECUSADO
          : regras.textoPrecoOculto;

  let rotuloDoBotao: string | null = regras.rotuloDoBotao || ROTULO_PADRAO_DO_BOTAO[modoDeCompra];
  if (precisaEntrar) rotuloDoBotao = regras.textoPrecoLogin;
  else if (aguardandoAprovacao) rotuloDoBotao = TEXTO_PADRAO_EM_ANALISE;
  else if (cadastroRecusado) rotuloDoBotao = TEXTO_PADRAO_RECUSADO;
  else if (lojaPausada) rotuloDoBotao = "Loja em pausa";
  else if (lojaFechada) rotuloDoBotao = "Loja fechada";

  const rotuloDeFechar = regras.rotuloDeFechar || ROTULO_PADRAO_DE_FECHAR[modoDeCompra];
  const tituloDoCarrinho = modoDeCompra === "quote" ? "Lista de orçamento" : "Seu Carrinho";

  /* Preco so para quem entrou ja exige o login para comprar; o lojista pode
     exigir cadastro para fechar pedido mesmo com preco publico. */
  const exigeLoginParaFechar = regras.exigirLoginParaPedir || regras.modoDePreco === "clients";

  return {
    regras,
    equipe,
    cliente,
    mostrarPreco,
    motivoPrecoOculto,
    textoNoLugarDoPreco,
    precisaEntrar,
    aguardandoAprovacao,
    cadastroRecusado,
    podeComprar,
    modoDeCompra,
    rotuloDoBotao,
    rotuloDeFechar,
    tituloDoCarrinho,
    lojaPausada,
    lojaFechada,
    exigeLoginNaLoja,
    exigeLoginParaFechar,
    avisoDePreco: regras.avisoDePreco,
    motivoDoBloqueio,
  };
}

/* ------------------------------------------------------------------------ */
/* Tirar os precos do que vai ao navegador                                   */
/* ------------------------------------------------------------------------ */

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Linha = any;

/**
 * O produto sem nenhum preco: preco zero e "sob consulta" ligado (os
 * templates ja sabem desenhar isso), faixas vazias, acrescimos das opcoes
 * e das variacoes zerados. `preco_oculto` marca que foi a regra da loja, e
 * nao o cadastro, que escondeu.
 */
export function semPrecos(produto: Linha): Linha {
  if (!produto || typeof produto !== "object") return produto;
  const atributos = Array.isArray(produto.product_attributes)
    ? produto.product_attributes.map((atributo: Linha) =>
        atributo && typeof atributo === "object"
          ? {
              ...atributo,
              options: Array.isArray(atributo.options)
                ? atributo.options.map((opcao: Linha) =>
                    opcao && typeof opcao === "object" ? { ...opcao, price: 0 } : opcao
                  )
                : atributo.options,
            }
          : atributo
      )
    : produto.product_attributes;
  const variacoes = Array.isArray(produto.product_variations)
    ? produto.product_variations.map((variacao: Linha) =>
        variacao && typeof variacao === "object" ? { ...variacao, price: 0, cost_adjustment: 0 } : variacao
      )
    : produto.product_variations;
  return {
    ...produto,
    price: 0,
    price_on_request: true,
    preco_oculto: true,
    use_variation_price_only: false,
    valor_minimo_item: null,
    product_price_tiers: [],
    product_attributes: atributos,
    product_variations: variacoes,
    /* Custos nunca deveriam chegar ao navegador; aqui e a ultima barreira. */
    cost_total: undefined,
    cost_breakdown: undefined,
    estimated_base_cost: undefined,
    direct_cost: undefined,
  };
}

/**
 * O estoque so limita a venda quando o produto controla estoque e NAO permite
 * estoque negativo. Com "Permitir estoque negativo" ligado, a loja vende como
 * se houvesse saldo, sem aviso nenhum ao cliente: o estoque fica negativo e o
 * pedido nasce com "Reposicao pendente" no painel.
 */
export function estoqueLimitaAVenda(
  produto:
    | {
        track_inventory?: boolean | null;
        allow_negative_stock?: boolean | null;
        /* Estoque v2 (marcado pelo servidor, lib/estoque-v2-da-loja.server.ts):
           quem decide e o banco (estoque_disponivel), pela composicao ou pelo
           item pronto, com o "permitir negativo" de cada item. */
        estoque_v2?: boolean | null;
        estoque_v2_limita?: boolean | null;
      }
    | null
    | undefined
): boolean {
  if (produto?.estoque_v2 === true) return produto.estoque_v2_limita === true;
  return Boolean(produto) && produto?.track_inventory !== false && produto?.allow_negative_stock !== true;
}

/** Produto esgotado some da loja (regra "esconder"). O criterio e o mesmo do selo "Esgotado" dos cards. */
export function estaEsgotado(produto: Linha): boolean {
  /* Estoque v2: a mesma conta do banco, sem escolha e quantidade 1. Produto
     que ainda precisa de tamanho/cor nao e "esgotado" na lista. */
  if (produto?.estoque_v2 === true) return produto.estoque_v2_esgotado === true;
  return estoqueLimitaAVenda(produto) && Number(produto.stock ?? 0) <= 0;
}

export function semEsgotados(produtos: Linha[] | null | undefined): Linha[] {
  return (Array.isArray(produtos) ? produtos : []).filter((produto) => !estaEsgotado(produto));
}

export function semPrecosNaLista(produtos: Linha[] | null | undefined): Linha[] {
  return Array.isArray(produtos) ? produtos.map(semPrecos) : [];
}

/** Os servicos opcionais sem valor: "Grátis"/"R$" some, a escolha fica. */
export function semPrecosNosServicos<T extends { servicos?: Linha[]; grupos?: Linha[] }>(dados: T): T {
  if (!dados || typeof dados !== "object") return dados;
  return {
    ...dados,
    servicos: Array.isArray(dados.servicos)
      ? dados.servicos.map((servico: Linha) =>
          servico && typeof servico === "object" ? { ...servico, price: 0, preco_oculto: true } : servico
        )
      : dados.servicos,
  };
}

/** Os combos ("compre junto") sem preco nem desconto: a sugestao fica, o valor nao. */
export function semPrecosNosCombos(combos: Linha[] | null | undefined): Linha[] {
  if (!Array.isArray(combos)) return [];
  return combos.map((combo: Linha) =>
    combo && typeof combo === "object"
      ? {
          ...combo,
          discount_type: "none",
          discount_value: 0,
          preco_oculto: true,
          combo_product:
            combo.combo_product && typeof combo.combo_product === "object"
              ? { ...combo.combo_product, price: 0, price_on_request: true, preco_oculto: true, use_variation_price_only: false }
              : combo.combo_product,
        }
      : combo
  );
}

/** Os stories sem o preco do produto embutido. */
export function semPrecosNosStories(grupos: Linha[] | null | undefined): Linha[] {
  if (!Array.isArray(grupos)) return [];
  return grupos.map((grupo: Linha) =>
    grupo && typeof grupo === "object" && Array.isArray(grupo.items)
      ? {
          ...grupo,
          items: grupo.items.map((item: Linha) =>
            item && typeof item === "object" && item.product && typeof item.product === "object"
              ? { ...item, product: { ...item.product, price: 0, price_on_request: true, preco_oculto: true } }
              : item
          ),
        }
      : grupo
  );
}

/* ------------------------------------------------------------------------ */
/* A equipe da loja                                                          */
/* ------------------------------------------------------------------------ */

export const COOKIE_DA_EQUIPE = "ktl_equipe";

/**
 * O navegador em que alguem da empresa usa o painel leva um cookie com os
 * ids das empresas dele (apps/admin/src/lib/navegador-da-equipe.ts). Se o
 * id desta loja esta la, quem olha e a equipe: ve preco em qualquer modo e
 * entra na loja fechada para conferir.
 */
export function navegadorEhDaEquipe(valorDoCookie: string | null | undefined, lojaId: string | null | undefined): boolean {
  const id = String(lojaId || "").trim().toLowerCase();
  if (!id) return false;
  let valor = String(valorDoCookie || "");
  try {
    valor = decodeURIComponent(valor);
  } catch {
    /* valor cru mesmo */
  }
  return valor
    .split(",")
    .map((parte) => parte.trim().toLowerCase())
    .filter(Boolean)
    .includes(id);
}

/* ------------------------------------------------------------------------ */
/* Pedido minimo                                                             */
/* ------------------------------------------------------------------------ */

export function formatarReais(valor: number) {
  return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(valor);
}

/**
 * O que falta para o pedido minimo, ou nulo quando esta tudo certo. O
 * minimo em reais so e conferido quando o visitante ve os precos: sem
 * preco na tela, "faltam R$ X" entregaria o que a loja escondeu.
 */
export function faltaParaOPedidoMinimo(
  regras: RegrasDaLoja,
  carrinho: { total: number; unidades: number; mostrarPreco: boolean }
): string | null {
  if (regras.pedidoMinimoItens && carrinho.unidades < regras.pedidoMinimoItens) {
    const faltam = Math.ceil(regras.pedidoMinimoItens - carrinho.unidades);
    return `Pedido mínimo de ${regras.pedidoMinimoItens} ${regras.pedidoMinimoItens === 1 ? "unidade" : "unidades"}: ${
      faltam === 1 ? "falta 1 unidade" : `faltam ${faltam} unidades`
    }.`;
  }
  if (regras.pedidoMinimoValor && carrinho.mostrarPreco && carrinho.total < regras.pedidoMinimoValor) {
    return `Pedido mínimo de ${formatarReais(regras.pedidoMinimoValor)}: ${
      carrinho.total > 0 ? `faltam ${formatarReais(regras.pedidoMinimoValor - carrinho.total)}` : "adicione mais itens"
    }.`;
  }
  return null;
}
