import type { SupabaseClient } from "@supabase/supabase-js";
import { calculateEffectiveQuantity, checkSaleQuantityLimits } from "@/lib/price-tiers";
import { COLUNAS_DO_VALOR_MINIMO, precoDaLinha, ROTULO_DO_MINIMO, somarAdicionais, valorMinimoDoItem } from "@/lib/adicionais";
import {
  buildShippingPackageFromItems,
  isPickupShippingOption,
  resolveProductionBusinessDays,
  sanitizePostalCode,
  type ShippingSelection,
} from "@/lib/catalog-shipping";
import { calculateCatalogShippingQuotes, fetchCatalogPickupOptions } from "@/lib/catalog-shipping-server";
import { avisarPedidoDaLoja as avisarPedidoAoVivo } from "@/lib/chats-aviso";
import { avisarLojaPorPush } from "@/lib/chats-push";
import { preencherModelo } from "@/lib/loja/regras-da-loja";
import { COLUNAS_DO_CUPOM, avaliarCupom, lerCupom } from "@/lib/regras-do-cupom";
import { empresaNoEstoqueV2 } from "@/lib/estoque-v2-da-loja.server";
import { avisosDoCarrinho, fraseDoCarrinho } from "@/lib/estoque-v2-da-loja";
import { itensParaConferir } from "@/lib/estoque-antes-de-cobrar";
import { conferirItensNoBanco } from "@/lib/conferir-itens";

/*
  O pedido feito pela loja, visto do servidor.

  Tudo que o checkout com login e a rota de pedido/orcamento sem login tem em
  comum mora aqui: o preco vem do banco (nunca do navegador), o cupom e
  conferido de verdade, os limites de quantidade e o estoque sao olhados, o
  frete escolhido e validado contra a cotacao, e a loja e avisada (sino e
  celular) de todo pedido que entra, nao so do pago.
*/

export type ItemDoPedido = {
  product_id: string;
  name: string;
  price: number;
  quantity: number;
  unit_type: string;
  width_m: number;
  height_m: number;
  length_m: number;
  shipping_weight_kg: number;
  shipping_width_cm: number;
  shipping_height_cm: number;
  shipping_length_cm: number;
  variation_description: string;
  selected_options: Record<string, unknown> | null;
  price_on_request: boolean;
  is_combo_item: boolean;
  combo_parent_id: string | null;
  /** Valor mínimo do item (migration 194). Só o servidor preenche, em
      conferirPrecosNoServidor; o que o navegador manda não vale. */
  valor_minimo_aplicado?: number | null;
  valor_sem_minimo?: number | null;
};

export function normalizeText(value: unknown) {
  return String(value || "").trim();
}

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

export function formatNumber(value: number) {
  return new Intl.NumberFormat("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
}

/** Os itens como chegam do navegador, limpos e sem lixo. */
export function normalizarItens(brutos: unknown): ItemDoPedido[] {
  const lista = Array.isArray(brutos) ? (brutos as Array<Record<string, unknown>>) : [];
  return lista
    .map((item) => ({
      product_id: normalizeText(item?.product_id),
      name: normalizeText(item?.name).slice(0, 300),
      price: Number(item?.price || 0),
      quantity: Number(item?.quantity || 0),
      unit_type: normalizeText(item?.unit_type) || "unit",
      width_m: Number(item?.width_m || 0),
      height_m: Number(item?.height_m || 0),
      length_m: Number(item?.length_m || 0),
      shipping_weight_kg: Number(item?.shipping_weight_kg || 0),
      shipping_width_cm: Number(item?.shipping_width_cm || 0),
      shipping_height_cm: Number(item?.shipping_height_cm || 0),
      shipping_length_cm: Number(item?.shipping_length_cm || 0),
      variation_description: normalizeText(item?.variation_description).slice(0, 2000),
      selected_options:
        item?.selected_options && typeof item.selected_options === "object"
          ? (item.selected_options as Record<string, unknown>)
          : null,
      price_on_request: Boolean(item?.price_on_request),
      is_combo_item: Boolean(item?.is_combo_item),
      combo_parent_id: normalizeText(item?.combo_parent_id) || null,
      valor_minimo_aplicado: null,
      valor_sem_minimo: null,
    }))
    .filter((item) => item.name && Number.isFinite(item.quantity) && item.quantity > 0 && item.quantity < 1000000);
}

type OpcaoDoBanco = {
  id?: string | null;
  label?: string | null;
  price?: number | string | null;
  price_unit?: string | null;
  price_applies_per_quantity?: boolean | null;
};

type ProdutoDoBanco = {
  id: string;
  user_id: string;
  name: string | null;
  price: number | string | null;
  unit_type: string | null;
  use_variation_price_only: boolean | null;
  price_on_request: boolean | null;
  active: boolean | null;
  show_in_catalog: boolean | null;
  stock: number | string | null;
  track_inventory: boolean | null;
  allow_negative_stock: boolean | null;
  inventory_scope: string | null;
  shipping_weight_kg: number | string | null;
  shipping_width_cm: number | string | null;
  shipping_height_cm: number | string | null;
  shipping_length_cm: number | string | null;
  min_sale_qty_enabled: boolean | null;
  min_sale_qty: number | null;
  max_sale_qty_enabled: boolean | null;
  max_sale_qty: number | null;
  allowed_sale_qty_enabled: boolean | null;
  allowed_sale_quantities: number[] | null;
  product_price_tiers: Array<{ min_qty: number; max_qty: number | null; unit_price: number; active: boolean | null }> | null;
  product_attributes: Array<{ name: string | null; options: unknown }> | null;
  valor_minimo_item?: number | string | null;
  valor_minimo_orcamentos?: boolean | null;
  valor_minimo_loja?: boolean | null;
  valor_minimo_pdv?: boolean | null;
};

const COLUNAS_PARA_CONFERIR =
  "id,user_id,name,price,unit_type,use_variation_price_only,price_on_request,active,show_in_catalog,stock,track_inventory,allow_negative_stock,inventory_scope," +
  "shipping_weight_kg,shipping_width_cm,shipping_height_cm,shipping_length_cm," +
  "min_sale_qty_enabled,min_sale_qty,max_sale_qty_enabled,max_sale_qty,allowed_sale_qty_enabled,allowed_sale_quantities," +
  "product_price_tiers(min_qty,max_qty,unit_price,active),product_attributes(name,options)," +
  COLUNAS_DO_VALOR_MINIMO;

function linhasDasOpcoes(selected: Record<string, unknown> | null) {
  const linhas = (selected as { options?: unknown[] } | null)?.options;
  return Array.isArray(linhas) ? (linhas as Array<Record<string, unknown>>) : [];
}

function formatarQuantidade(valor: number | null) {
  if (valor === null || !Number.isFinite(valor)) return "0";
  const arredondado = Number(valor.toFixed(3));
  return Number.isInteger(arredondado)
    ? String(arredondado)
    : new Intl.NumberFormat("pt-BR", { minimumFractionDigits: 0, maximumFractionDigits: 3 }).format(arredondado);
}

/**
 * O preco vem do banco, nao do navegador. Para cada item, a conta e refeita a
 * partir do produto (preco, faixas, opcoes) e dos servicos da loja. "Sob
 * consulta" tambem vem do cadastro: a marca que o navegador manda nao vale,
 * entao um carrinho montado sem ver precos (regra da loja) sai daqui
 * precificado para o lojista. Item de "compre junto" fica com o preco que
 * veio, porque o desconto do combo e dele, mas nunca acima do preco normal.
 *
 * Produto que nao existe, esta inativo ou fora da loja derruba o pedido com
 * uma mensagem clara: e o cliente tirar do carrinho.
 */
export async function conferirPrecosNoServidor(admin: SupabaseClient, storeId: string, items: ItemDoPedido[]) {
  const ids = Array.from(new Set(items.map((item) => item.product_id).filter(Boolean)));
  if (!ids.length) return;
  /* Estoque v2: o estoque e conferido no fim, pelo banco, o carrinho inteiro
     somado por celula (a mesma conta da pagina, do PDV e do orcamento). */
  const estoqueV2 = await empresaNoEstoqueV2(admin, storeId);

  const { data: produtosBrutos } = await admin.from("products").select(COLUNAS_PARA_CONFERIR).in("id", ids);
  const produtos = new Map(
    ((produtosBrutos || []) as unknown as ProdutoDoBanco[]).map((produto) => [String(produto.id), produto])
  );

  const idsDeOpcoes = Array.from(
    new Set(items.flatMap((item) => linhasDasOpcoes(item.selected_options).map((linha) => String(linha.option_id || ""))))
  ).filter(Boolean);
  const servicos = new Map<string, { price: number; charge_per_unit: boolean }>();
  if (idsDeOpcoes.length) {
    const { data: servicosBrutos } = await admin.from("optional_services").select("id,price,charge_per_unit").in("id", idsDeOpcoes);
    for (const servico of (servicosBrutos || []) as Array<{ id: string; price: number | null; charge_per_unit: boolean | null }>) {
      servicos.set(String(servico.id), { price: Number(servico.price) || 0, charge_per_unit: servico.charge_per_unit === true });
    }
  }

  const acharOpcao = (produto: ProdutoDoBanco, linha: Record<string, unknown>): OpcaoDoBanco | null => {
    const id = String(linha.option_id || "");
    const atributo = String(linha.attribute_name || "").trim().toLowerCase();
    const rotulo = String(linha.label || "").trim().toLowerCase();
    for (const attr of produto.product_attributes || []) {
      const opcoes = Array.isArray(attr.options) ? (attr.options as unknown[]) : [];
      for (const opcao of opcoes) {
        if (!opcao || typeof opcao !== "object") continue;
        const dados = opcao as OpcaoDoBanco;
        if (id && String(dados.id || "") === id) return dados;
      }
    }
    for (const attr of produto.product_attributes || []) {
      if (String(attr.name || "").trim().toLowerCase() !== atributo) continue;
      const opcoes = Array.isArray(attr.options) ? (attr.options as unknown[]) : [];
      for (const opcao of opcoes) {
        if (!opcao || typeof opcao !== "object") continue;
        const dados = opcao as OpcaoDoBanco;
        if (String(dados.label || "").trim().toLowerCase() === rotulo) return dados;
      }
    }
    return null;
  };

  for (const item of items) {
    const produto = item.product_id ? produtos.get(item.product_id) : undefined;
    if (!produto || String(produto.user_id) !== storeId || produto.active === false || produto.show_in_catalog === false) {
      throw new Error(`"${item.name}" não está mais disponível nesta loja. Tire do carrinho e tente de novo.`);
    }

    /* Limites de quantidade e estoque sao os do cadastro, nao os do retrato
       que o navegador guardou quando o item entrou no carrinho. */
    const unitType = produto.unit_type || item.unit_type || "unit";
    const quantidadeEfetiva = calculateEffectiveQuantity({
      quantity: item.quantity,
      unit_type: unitType,
      width_m: item.width_m,
      height_m: item.height_m,
      length_m: item.length_m,
    });
    if (!item.is_combo_item) {
      const limites = checkSaleQuantityLimits({
        min_sale_qty_enabled: produto.min_sale_qty_enabled,
        min_sale_qty: produto.min_sale_qty,
        max_sale_qty_enabled: produto.max_sale_qty_enabled,
        max_sale_qty: produto.max_sale_qty,
        allowed_sale_qty_enabled: produto.allowed_sale_qty_enabled,
        allowed_sale_quantities: produto.allowed_sale_quantities,
        effectiveQuantity: quantidadeEfetiva,
      });
      if (!limites.isValid) {
        if (limites.isBelowMin && limites.minQty !== null) {
          throw new Error(`"${item.name}": quantidade mínima de ${formatarQuantidade(limites.minQty)}.`);
        }
        if (limites.isAboveMax && limites.maxQty !== null) {
          throw new Error(`"${item.name}": quantidade máxima de ${formatarQuantidade(limites.maxQty)}.`);
        }
        throw new Error(`"${item.name}": use uma das quantidades permitidas.`);
      }
    }
    const saldo = Number(produto.stock);
    if (
      !estoqueV2 &&
      produto.track_inventory === true &&
      produto.allow_negative_stock !== true &&
      (produto.inventory_scope || "product") === "product" &&
      Number.isFinite(saldo) &&
      quantidadeEfetiva > saldo + 0.000001
    ) {
      throw new Error(
        saldo <= 0 ? `"${item.name}" está esgotado.` : `"${item.name}": só ${formatarQuantidade(saldo)} em estoque.`
      );
    }

    item.unit_type = unitType;
    item.price_on_request = Boolean(produto.price_on_request);
    /* Peso e medidas tambem sao os do cadastro. O frete e cotado de novo aqui
       com este pacote; com o que o navegador mandava, bastava encolher o
       pacote no corpo da requisicao para pagar um frete menor. */
    item.shipping_weight_kg = Number(produto.shipping_weight_kg || 0);
    item.shipping_width_cm = Number(produto.shipping_width_cm || 0);
    item.shipping_height_cm = Number(produto.shipping_height_cm || 0);
    item.shipping_length_cm = Number(produto.shipping_length_cm || 0);
    item.valor_minimo_aplicado = null;
    item.valor_sem_minimo = null;
    if (item.price_on_request) {
      item.price = 0;
      continue;
    }

    const conferidas = linhasDasOpcoes(item.selected_options).map((linha) => {
      const doBanco = acharOpcao(produto, linha);
      if (doBanco) {
        return {
          price: Number(doBanco.price) || 0,
          price_unit: doBanco.price_unit ?? null,
          price_applies_per_quantity: doBanco.price_applies_per_quantity ?? null,
        };
      }
      const servico = servicos.get(String(linha.option_id || ""));
      if (servico) return { price: servico.price, price_unit: servico.charge_per_unit ? "piece" : "order" };
      /* Linha que o banco nao conhece (campo livre, opcao apagada): sem valor. */
      return { price: 0, price_unit: null, price_applies_per_quantity: null };
    });
    const conta = precoDaLinha({
      precoBase: Number(produto.price) || 0,
      faixas: produto.product_price_tiers || [],
      pecas: item.quantity,
      quantidadeEfetiva,
      adicionais: somarAdicionais(conferidas, unitType),
      soVariacao: Boolean(produto.use_variation_price_only),
      sobConsulta: false,
      /* O valor mínimo do item vale para a linha inteira; o "compre junto"
         tem o preço do combo e fica fora dele. */
      valorMinimo: item.is_combo_item ? null : valorMinimoDoItem(produto, "loja"),
    });
    const precoDoBanco = conta.precoPorMedida;
    if (item.is_combo_item) {
      if (!(item.price > 0) || item.price > precoDoBanco + 0.005) item.price = precoDoBanco;
      continue;
    }
    item.price = precoDoBanco;
    if (conta.minimoAplicado !== null) {
      item.valor_minimo_aplicado = conta.minimoAplicado;
      item.valor_sem_minimo = Math.round(conta.totalSemMinimo * 100) / 100;
    }
  }

  if (estoqueV2) {
    const lista = itensParaConferir(items.map((item) => ({ ...item, title: item.name })));
    if (lista.length) {
      /* Erro do banco ao conferir nao trava: a trava da criacao do pedido vale. */
      const resposta = await conferirItensNoBanco(admin, storeId, lista as never).catch(() => null);
      if (resposta && resposta.ok === false) {
        const frase = fraseDoCarrinho(avisosDoCarrinho(resposta.problemas as never, lista as never));
        throw new Error(frase || "Um dos itens não tem estoque suficiente. Ajuste o carrinho e tente de novo.");
      }
    }
  }
}

/* O total da linha pela quantidade efetiva: no item por m², preco do metro x
   area x pecas. Com o valor minimo do item, a linha vale o minimo, exato. */
export function calculateItemTotal(
  item: Pick<ItemDoPedido, "price" | "quantity" | "unit_type" | "width_m" | "height_m" | "length_m" | "price_on_request"> & {
    valor_minimo_aplicado?: number | null;
  }
) {
  if (item.price_on_request) return 0;
  if (item.valor_minimo_aplicado != null && item.valor_minimo_aplicado > 0) return item.valor_minimo_aplicado;
  const effectiveQty = calculateEffectiveQuantity({
    quantity: Number(item.quantity || 0),
    unit_type: item.unit_type || "unit",
    width_m: Number(item.width_m || 0),
    height_m: Number(item.height_m || 0),
    length_m: Number(item.length_m || 0),
  });
  return Number(item.price || 0) * effectiveQty;
}

export function calculateItemsSubtotal(items: ItemDoPedido[]) {
  return items.reduce((total, item) => total + calculateItemTotal(item), 0);
}

/** Pecas no carrinho (para o pedido minimo em unidades). */
export function unidadesDosItens(items: ItemDoPedido[]) {
  return items.filter((item) => !item.is_combo_item).reduce((total, item) => total + Number(item.quantity || 0), 0);
}

/**
 * O cupom conferido no banco pela regra antiga (sem itens nem cliente): existe
 * nesta loja, esta ativo, dentro do prazo e com os minimos atendidos. Devolve
 * o desconto, ou lanca com a razao. Fica pela compatibilidade: o checkout e o
 * pedido usam calcularCupomDoPedido (lib/cupom-da-loja.server.ts), que olha
 * onde o cupom vale, quem pode usar, os limites e o frete. Cupom com essas
 * regras novas so vale por la.
 */
export async function validarCupom(
  admin: SupabaseClient,
  storeId: string,
  codigo: string,
  subtotal: number,
  unidades: number
): Promise<{ code: string; discount: number }> {
  const code = normalizeText(codigo).toUpperCase();
  if (!code) return { code: "", discount: 0 };
  const { data } = await admin.from("catalog_coupons").select(COLUNAS_DO_CUPOM).eq("user_id", storeId).eq("code", code).limit(1).maybeSingle();
  if (!data) throw new Error("Cupom inválido ou inativo.");
  const cupom = lerCupom(data as unknown as Record<string, unknown>);
  const avaliacao = avaliarCupom(cupom, {
    itens: [{ product_id: "", category_id: null, total: subtotal, quantity: unidades }],
    cliente: null,
    frete: null,
    agora: Date.now(),
  });
  if (!avaliacao.ok) throw new Error(avaliacao.mensagem);
  return { code: cupom.code || code, discount: avaliacao.desconto };
}

/* Prazo de producao do pedido, lido do cadastro dos produtos. */
export async function resolveCheckoutProductionDays(admin: SupabaseClient, items: ItemDoPedido[]) {
  const productIds = Array.from(new Set(items.map((item) => String(item.product_id || "").trim()).filter(Boolean)));
  if (productIds.length === 0) return 0;
  const { data, error } = await admin.from("products").select("id,production_time_value,production_time_unit").in("id", productIds);
  if (error || !Array.isArray(data)) return 0;
  return resolveProductionBusinessDays(
    data.map((row) => ({
      quantity: 1,
      production_time_value: (row as { production_time_value?: number | null }).production_time_value ?? null,
      production_time_unit: (row as { production_time_unit?: string | null }).production_time_unit ?? null,
    }))
  );
}

/**
 * O frete escolhido, validado contra o balcao cadastrado ou contra a cotacao
 * refeita no servidor. O valor nunca vem do corpo da requisicao.
 */
export async function resolveValidatedShippingSelection(params: {
  admin: SupabaseClient;
  slug: string;
  items: ItemDoPedido[];
  selection: ShippingSelection | null | undefined;
}): Promise<ShippingSelection | null> {
  const { admin, slug, items, selection } = params;
  if (!selection?.option?.id) return null;

  if (isPickupShippingOption(selection.option)) {
    const pickupOptions = await fetchCatalogPickupOptions(admin, slug);
    const matchedPickup = pickupOptions.find((option) => String(option.id) === String(selection.option.id));
    if (!matchedPickup) throw new Error("O balcão de retirada escolhido não está mais disponível.");
    const productionDays = await resolveCheckoutProductionDays(admin, items);
    return {
      destination_zip: "",
      option: { ...matchedPickup, delivery_time: Math.max(0, Number(matchedPickup.delivery_time) || 0) + productionDays },
      package_signature: "",
    } satisfies ShippingSelection;
  }

  const destinationZip = sanitizePostalCode(selection.destination_zip);
  if (destinationZip.length !== 8) throw new Error("CEP do frete inválido.");

  const packageResult = buildShippingPackageFromItems(
    items.map((item) => ({
      product_id: item.product_id,
      quantity: Number(item.quantity || 0),
      unit_type: item.unit_type || "unit",
      width_m: Number(item.width_m || 0),
      height_m: Number(item.height_m || 0),
      length_m: Number(item.length_m || 0),
      shipping_weight_kg: Number(item.shipping_weight_kg || 0),
      shipping_width_cm: Number(item.shipping_width_cm || 0),
      shipping_height_cm: Number(item.shipping_height_cm || 0),
      shipping_length_cm: Number(item.shipping_length_cm || 0),
      price_on_request: Boolean(item.price_on_request),
    }))
  );
  if (!packageResult.ok || !packageResult.packageMetrics) {
    throw new Error(packageResult.reason || "Não foi possível validar o pacote do frete.");
  }
  if (selection.package_signature !== packageResult.packageSignature) {
    throw new Error("O frete precisa ser recalculado antes de finalizar.");
  }

  const providers = await calculateCatalogShippingQuotes(admin, { slug, destinationZip, packageMetrics: packageResult.packageMetrics });
  const selectedProvider = selection.option.provider;
  const selectedId = String(selection.option.id);
  const matched = providers
    .flatMap((provider) => provider.options)
    .find((option) => option.provider === selectedProvider && String(option.id) === selectedId);
  if (!matched) throw new Error("A opção de frete selecionada não está mais disponível.");

  return { destination_zip: destinationZip, option: matched, package_signature: packageResult.packageSignature } satisfies ShippingSelection;
}

/* ------------------------------------------------------------------------ */
/* A mensagem do WhatsApp                                                    */
/* ------------------------------------------------------------------------ */

export type ClienteDaMensagem = {
  name?: string | null;
  company_name?: string | null;
  document?: string | null;
  email?: string | null;
  phone?: string | null;
  whatsapp?: string | null;
  notes?: string | null;
  address_summary?: string | null;
};

function linhaDoItem(item: ItemDoPedido, mostrarPrecos: boolean) {
  const quantity = Number(item.quantity || 0);
  const variation = normalizeText(item.variation_description);
  let dims = "";
  if (String(item.unit_type || "") === "square_meter") {
    const width = Number(item.width_m || 0);
    const height = Number(item.height_m || 0);
    const areaEach = width > 0 && height > 0 ? width * height : 0;
    dims = areaEach > 0 ? ` (${formatNumber(width)}m x ${formatNumber(height)}m = ${formatNumber(areaEach * quantity)}m²)` : "";
  }
  const valor = !mostrarPrecos ? "" : item.price_on_request ? " | Sob consulta" : ` | ${formatMoney(calculateItemTotal(item))}`;
  const minimo =
    mostrarPrecos && !item.price_on_request && item.valor_minimo_aplicado != null
      ? `\n  _${ROTULO_DO_MINIMO}_`
      : "";
  return `- ${quantity}x ${normalizeText(item.name)}${dims}${valor}${minimo}${variation ? `\n  ${variation}` : ""}`;
}

/**
 * A mensagem que abre no WhatsApp da loja. No pedido de orcamento ela nao
 * leva valor nenhum: o cliente nao os viu, e e a loja quem responde.
 */
export function mensagemDoPedidoNoWhatsapp(params: {
  storeName: string;
  kind: "order" | "quote_request";
  numero: number | null;
  items: ItemDoPedido[];
  mostrarPrecos: boolean;
  cliente?: ClienteDaMensagem | null;
  subtotal: number;
  discount: number;
  shippingTotal: number;
  total: number;
  couponCode?: string | null;
  /** Desconto do cupom de frete gratis (o frete acima e o cheio). */
  shippingDiscount?: number;
  /** O codigo do cupom de frete gratis (o de desconto vai em couponCode). */
  shippingCouponCode?: string | null;
  shippingSelection?: ShippingSelection | null;
  /* Os modelos de WhatsApp > Mensagens (cabecalho e rodape do pedido). */
  modelos?: { cabecalhoDoPedido?: string | null; rodapeDoPedido?: string | null } | null;
}) {
  const { storeName, kind, numero, items, cliente, subtotal, discount, shippingTotal, total, couponCode, shippingSelection, modelos } = params;
  const descontoNoFrete = Math.max(Number(params.shippingDiscount) || 0, 0);
  const linhaDoFreteGratis =
    descontoNoFrete > 0
      ? `Frete grátis${params.shippingCouponCode ? ` (${params.shippingCouponCode})` : ""}: -${formatMoney(descontoNoFrete)}`
      : "";
  const mostrarPrecos = params.mostrarPrecos && kind === "order";
  const hasOnRequest = items.some((item) => Boolean(item.price_on_request));
  const cabecalhoDaLoja = mostrarPrecos ? normalizeText(modelos?.cabecalhoDoPedido) : "";
  const titulo =
    kind === "quote_request"
      ? `*Pedido de orçamento${numero ? ` nº ${numero}` : ""} - ${storeName}*`
      : cabecalhoDaLoja
        ? `${cabecalhoDaLoja}${numero ? ` (nº ${numero})` : ""}`
        : `*Novo pedido pelo catálogo ${storeName}*${numero ? ` (nº ${numero})` : ""}`;

  const dadosDoCliente = cliente
    ? [
        cliente.name ? `*Cliente:* ${cliente.name}` : "",
        cliente.company_name ? `*Empresa:* ${cliente.company_name}` : "",
        cliente.document ? `*CPF/CNPJ:* ${cliente.document}` : "",
        cliente.email ? `*E-mail:* ${cliente.email}` : "",
        cliente.phone ? `*Telefone:* ${cliente.phone}` : "",
        cliente.whatsapp ? `*WhatsApp:* ${cliente.whatsapp}` : "",
        cliente.address_summary ? `*Endereço:* ${cliente.address_summary}` : "",
        shippingSelection && isPickupShippingOption(shippingSelection.option)
          ? `*Retirada:* ${shippingSelection.option.service}${shippingSelection.option.pickup?.address_summary ? ` - ${shippingSelection.option.pickup.address_summary}` : ""}`
          : "",
        cliente.notes ? `*Observações:* ${cliente.notes}` : "",
      ].filter(Boolean)
    : [];

  const linhasDosItens = items.map((item) => linhaDoItem(item, mostrarPrecos)).join("\n");
  const rodapeDaLoja = mostrarPrecos ? normalizeText(modelos?.rodapeDoPedido) : "";
  if (rodapeDaLoja) {
    /* O rodape que a loja escreveu, com os marcadores preenchidos. Os itens
       entram onde a loja pos {{resumo_itens}}; sem o marcador, acima. */
    const rodape = preencherModelo(rodapeDaLoja, {
      resumo_itens: linhasDosItens,
      subtotal: `Subtotal: ${formatMoney(subtotal)}`,
      desconto: [discount > 0 ? `Desconto${couponCode ? ` (${couponCode})` : ""}: -${formatMoney(discount)}` : "", linhaDoFreteGratis]
        .filter(Boolean)
        .join("\n"),
      total: formatMoney(total),
    });
    const corpo = rodapeDaLoja.includes("{{resumo_itens}}") ? [rodape] : ["*Itens*", linhasDosItens, "", rodape];
    return [titulo, "", ...dadosDoCliente, dadosDoCliente.length ? "" : null, ...corpo]
      .filter((linha) => linha !== null && linha !== undefined)
      .join("\n")
      .replace(/\n{3,}/g, "\n\n");
  }

  const resumo = mostrarPrecos
    ? [
        `Subtotal: ${formatMoney(subtotal)}`,
        discount > 0 ? `Desconto${couponCode ? ` (${couponCode})` : ""}: -${formatMoney(discount)}` : "",
        shippingSelection
          ? isPickupShippingOption(shippingSelection.option)
            ? `Retirada no balcão (${shippingSelection.option.service}): ${shippingTotal > 0 ? formatMoney(shippingTotal) : "Grátis"}`
            : `Frete (${[shippingSelection.option.service, shippingSelection.option.name].filter(Boolean).join(" - ")}): ${formatMoney(shippingTotal)}`
          : "",
        linhaDoFreteGratis,
        hasOnRequest ? `*Total parcial: ${formatMoney(total)}*` : `*Total: ${formatMoney(total)}*`,
        hasOnRequest ? "\n_Itens “Sob consulta” não entram no total parcial._" : "",
      ].filter(Boolean)
    : kind === "quote_request"
      ? ["_Aguardo o orçamento. Obrigado!_"]
      : ["_Os valores ficam a combinar com a loja._"];

  return [titulo, "", ...dadosDoCliente, dadosDoCliente.length ? "" : null, "*Itens*", linhasDosItens, "", ...resumo]
    .filter((linha) => linha !== null && linha !== undefined)
    .join("\n")
    .replace(/\n{3,}/g, "\n\n");
}

/* ------------------------------------------------------------------------ */
/* Gravar e avisar                                                           */
/* ------------------------------------------------------------------------ */

export type PedidoParaGravar = {
  slug: string;
  storeId: string;
  kind: "order" | "quote_request";
  items: ItemDoPedido[];
  subtotal: number;
  discount: number;
  shipping: number;
  total: number;
  couponCode: string;
  customer: { name: string; phone: string; whatsapp: string; email?: string; document?: string; notes?: string };
  clientId: string | null;
  whatsappUrl: string;
  cartPayload: Record<string, unknown>;
  paymentProvider: string | null;
};

/** Grava o pedido da loja pela mesma funcao do banco de sempre, e completa o que ela nao conhece. */
export async function gravarPedidoDaLoja(admin: SupabaseClient, pedido: PedidoParaGravar): Promise<{ id: string; number: number | null }> {
  const payload = {
    currency: "BRL",
    subtotal: pedido.subtotal,
    discount: pedido.discount,
    total: pedido.total,
    coupon_code: pedido.couponCode || null,
    customer_name: pedido.customer.name || null,
    customer_phone: pedido.customer.phone || null,
    customer_whatsapp: pedido.customer.whatsapp || null,
    notes: pedido.customer.notes || "",
    whatsapp_url: pedido.whatsappUrl,
    items: pedido.items.map((item) => ({
      product_id: item.product_id || null,
      name: item.name,
      description: item.variation_description || "",
      quantity: Number(item.quantity || 0),
      unit_type: item.unit_type || "unit",
      width_m: item.width_m || 0,
      height_m: item.height_m || 0,
      length_m: item.length_m || 0,
      unit_price: Number(item.price || 0),
      total_price: Number(calculateItemTotal(item).toFixed(2)),
      variation_description: item.variation_description || "",
      selected_options: item.selected_options || null,
      raw_price_on_request: Boolean(item.price_on_request),
      /* Vai para catalog_order_items.raw_item: o painel mostra o aviso e o
         pedido interno leva a linha no valor do mínimo. */
      valor_minimo_aplicado: item.valor_minimo_aplicado ?? null,
      valor_sem_minimo: item.valor_minimo_aplicado != null ? item.valor_sem_minimo ?? null : null,
    })),
    cart_payload: pedido.cartPayload,
  };

  const resultado = await admin.rpc("create_public_catalog_order", { p_slug: pedido.slug, p_payload: payload });
  const id = (resultado.data as { id?: string } | null)?.id;
  if (resultado.error || !id) throw new Error("Não foi possível registrar o pedido.");

  await admin
    .from("catalog_orders")
    .update({
      kind: pedido.kind,
      client_id: pedido.clientId,
      customer_name: pedido.customer.name || null,
      customer_phone: pedido.customer.phone || null,
      customer_whatsapp: pedido.customer.whatsapp || null,
      notes: pedido.customer.notes || null,
      payment_provider: pedido.paymentProvider,
      payment_required: Boolean(pedido.paymentProvider),
      payment_status: "unpaid",
      updated_at: new Date().toISOString(),
    })
    .eq("id", id);

  return { id: String(id), number: (resultado.data as { number?: number } | null)?.number ?? null };
}

/**
 * Avisa a loja de que entrou um pedido: o sino de quem esta com o painel
 * aberto e o celular de quem ligou o aviso. Antes so a venda paga avisava;
 * o pedido pelo WhatsApp e o pedido de orcamento ficavam mudos.
 */
export async function avisarLojaDoPedido(
  admin: SupabaseClient,
  params: {
    storeId: string;
    orderId: string;
    kind: "order" | "quote_request";
    numero: number | null;
    cliente: string | null;
    total: number | null;
    itens: number;
    aguardandoPagamento?: boolean;
  }
) {
  const { storeId, orderId, kind, numero, cliente, total, itens, aguardandoPagamento } = params;
  const quem = cliente || "Cliente da loja";
  const titulo =
    kind === "quote_request"
      ? `Pedido de orçamento${numero ? ` nº ${numero}` : ""}`
      : aguardandoPagamento
        ? `Pedido${numero ? ` nº ${numero}` : ""} aguardando pagamento`
        : `Novo pedido pelo WhatsApp${numero ? ` nº ${numero}` : ""}`;
  const corpo =
    kind === "quote_request" || !(typeof total === "number" && total > 0)
      ? `${quem} pediu ${itens} ${itens === 1 ? "item" : "itens"}.`
      : `${quem}: ${itens} ${itens === 1 ? "item" : "itens"}, ${formatMoney(total)}.`;

  await Promise.all([
    avisarPedidoAoVivo(storeId, {
      orderId,
      kind,
      numero,
      cliente: quem,
      total: kind === "quote_request" ? null : total,
      itens,
      aguardandoPagamento: Boolean(aguardandoPagamento),
    }),
    avisarLojaPorPush(admin, {
      tenantId: storeId,
      titulo,
      corpo,
      rota: `/dashboard/catalog/orders/${orderId}`,
      tag: `ktalogu-pedido-${orderId}`,
    }).catch(() => ({ enviados: 0 })),
  ]);
}
