"use client";

import React, { createContext, useContext, useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { calculateEffectiveQuantity, checkSaleQuantityLimits, type ProductPriceTier } from "@/lib/price-tiers";
import { precoDaLinha, somarAdicionais, valorMinimoDoItem, type OpcaoComPreco } from "@/lib/adicionais";
import { trackCatalogEvent } from "@/lib/catalog-analytics";
import {
  buildShippingPackageFromItems,
  isPickupShippingOption,
  sanitizePostalCode,
  type ShippingItemInput,
  type ShippingSelection,
} from "@/lib/catalog-shipping";

type ProductForCart = {
  id: string;
  name: string;
  price: number;
  image_url?: string | null;
  stock?: number | null;
  track_inventory?: boolean | null;
  /* "Permitir estoque negativo": o saldo nao limita a venda. */
  allow_negative_stock?: boolean | null;
  /* Estoque v2: o servidor confere o carrinho inteiro (soma por celula). */
  estoque_v2?: boolean | null;
  user_id: string;
  unit_type?: string | null;
  price_on_request?: boolean | null;
  use_variation_price_only?: boolean | null;
  product_price_tiers?: ProductPriceTier[] | 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;
  shipping_weight_kg?: number | null;
  shipping_width_cm?: number | null;
  shipping_height_cm?: number | null;
  shipping_length_cm?: number | null;
  production_time_value?: number | null;
  production_time_unit?: string | null;
  /* Valor mínimo do item (migration 194): só em produto por m²/metro. */
  valor_minimo_item?: number | string | null;
  valor_minimo_loja?: boolean | null;
};

export type CartItem = {
  id: string;
  product_id: string;
  name: string;
  base_price: number;
  price: number;
  image_url?: string;
  stock?: number | null;
  track_inventory?: boolean;
  allow_negative_stock?: boolean;
  /* Estoque v2: o carrinho pergunta ao servidor se ainda cabe (soma por celula). */
  estoque_v2?: boolean;
  quantity: number;
  store_id: string;
  unit_type: string;
  width_m?: number;
  height_m?: number;
  length_m?: number;
  price_on_request?: boolean;
  use_variation_price_only?: boolean;
  product_price_tiers?: ProductPriceTier[];
  min_sale_qty_enabled?: boolean;
  min_sale_qty?: number | null;
  max_sale_qty_enabled?: boolean;
  max_sale_qty?: number | null;
  allowed_sale_qty_enabled?: boolean;
  allowed_sale_quantities?: number[] | null;
  variation_id?: string;
  variation_description?: string;
  selected_options?: Record<string, unknown> | null;
  is_combo_item?: boolean;
  combo_parent_id?: string; // Links this item to a main product in the cart
  shipping_weight_kg?: number | null;
  shipping_width_cm?: number | null;
  shipping_height_cm?: number | null;
  shipping_length_cm?: number | null;
  production_time_value?: number | null;
  production_time_unit?: string | null;
  /** O mínimo que vale na loja para este produto (já resolvido); null = sem. */
  valor_minimo_item?: number | null;
  /** A linha pegou o mínimo: vale `valor_minimo_aplicado`, não preço × quantidade. */
  valor_minimo_aplicado?: number | null;
  valor_sem_minimo?: number | null;
};

/** O total de uma linha do carrinho: o mínimo do item, quando pegou. */
export function totalDaLinhaDoCarrinho(item: Pick<CartItem, "price" | "quantity" | "unit_type" | "width_m" | "height_m" | "length_m" | "price_on_request" | "valor_minimo_aplicado">) {
  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: item.quantity,
    unit_type: item.unit_type,
    width_m: item.width_m,
    height_m: item.height_m,
    length_m: item.length_m,
  });
  return item.price * effectiveQty;
}

/*
  O cupom que ficou no carrinho, como o servidor respondeu (/api/catalog/cupom).
  O carrinho nunca calcula o desconto sozinho: mostra o que o servidor conferiu
  (onde vale, quem pode usar, limites, frete), e o pedido confere de novo.
*/
export type CartCoupon = {
  code: string;
  /** Nome que o cliente vê ("Desconto aplicado: NOME"). */
  name: string;
  type: "percent" | "value" | "free_shipping";
  /** Aplicado sem código (a loja ligou "automático"). */
  automatic: boolean;
  /** "10% OFF", "Frete grátis". */
  label: string;
  /** "10% de desconto em Canecas, para compras a partir de R$ 50". */
  description: string;
  /** Frete grátis esperando o cliente escolher a entrega. */
  waitingShipping: boolean;
  /** A vaga do pedido: um de desconto e um de frete grátis podem ir juntos. */
  slot: "desconto" | "frete";
  /** Desconto que este cupom dá nos produtos e no frete. */
  discount: number;
  shippingDiscount: number;
  /** O cliente digitou (ou trouxe no link): pode tirar. O automático não sai. */
  removable: boolean;
};

/** Cupom que a loja deixou à vista, para o cliente tocar e usar. */
export type CartAvailableCoupon = { code: string; nome: string; rotulo: string; frase: string; vaga?: "desconto" | "frete" };

type CupomDaPrevia = {
  code: string;
  nome: string;
  tipo: CartCoupon["type"];
  vaga?: "desconto" | "frete";
  automatico: boolean;
  desconto: number;
  descontoNoFrete: number;
  esperandoFrete: boolean;
  rotulo: string;
  frase: string;
};

type PreviaDoCupom = {
  aplicados: CupomDaPrevia[];
  avisos: string[];
  erros: Array<{ code: string; mensagem: string }>;
  digitados: Array<{ code: string; vaga: "desconto" | "frete" | null; aplicado: boolean; erro: string | null }>;
  disponiveis: CartAvailableCoupon[];
};

type CartContextType = {
  items: CartItem[];
  addToCart: (
    product: ProductForCart, 
    quantity?: number, 
    opts?: { width_m?: number; height_m?: number; price_override?: number; is_combo_item?: boolean; combo_parent_id?: string },
    variation?: { id: string; description: string; selected_options?: Record<string, unknown> | null }
  ) => { ok: boolean; message?: string; cart_item_id?: string };
  removeFromCart: (productId: string) => void;
  updateQuantity: (productId: string, quantity: number) => { ok: boolean; message?: string };
  clearCart: () => void;
  isCartOpen: boolean;
  toggleCart: () => void;
  applyCoupon: (code: string) => Promise<{ ok: boolean; message: string }>;
  clearCoupon: () => void;
  /** O cupom de desconto (ou o de frete, quando é o único). Para quem mostra um só. */
  appliedCoupon: CartCoupon | null;
  /** Os cupons que ficaram: até um de desconto e um de frete grátis. */
  appliedCoupons: CartCoupon[];
  /** Tira um código digitado (o automático não sai). */
  removeCoupon: (code: string) => void;
  /** Os códigos que o cliente digitou ou trouxe no link (?cupom=), aplicados ou não. */
  typedCouponCodes: string[];
  /** Por que um código digitado não ficou ("faltam R$ 20", "venceu", "o automático é melhor"). */
  couponNotices: Array<{ code: string; message: string }>;
  /** Os cupons que a loja deixou à vista no carrinho. */
  availableCoupons: CartAvailableCoupon[];
  /** Desconto do cupom de frete grátis (o shippingTotal continua o frete cheio). */
  shippingDiscount: number;
  /** Os códigos que vão no pedido: só os digitados que estão aplicados (os automáticos o servidor acha sozinho). */
  orderCouponCodes: string[];
  /** O código que veio no link da loja (?cupom=CODIGO), para o aviso de "cupom aplicado". */
  couponFromLink: string | null;
  dismissCouponFromLink: () => void;
  shippingSelection: ShippingSelection | null;
  setShippingSelection: (selection: ShippingSelection | null) => void;
  clearShippingSelection: () => void;
  shippingTotal: number;
  shippingPackageReady: boolean;
  shippingItems: ShippingItemInput[];
  cartSubtotal: number;
  discountTotal: number;
  cartTotal: number;
  /** Estoque v2: a frase de falta por item do carrinho (id do item → frase), conferida no servidor. */
  avisosDeEstoque: Record<string, string>;
  cartCount: number;
};

const CartContext = createContext<CartContextType | undefined>(undefined);

export function CartProvider({
  children,
  cartScope,
  storeSlug,
}: {
  children: React.ReactNode;
  cartScope?: string;
  storeSlug?: string;
}) {
  const storageScope = encodeURIComponent((cartScope || "global").trim() || "global");
  const catalogSlug = String(storeSlug || "").trim();
  const cartStorageKey = `ktalogu_cart:${storageScope}`;
  const couponStorageKey = `ktalogu_coupon:${storageScope}`;
  const shippingStorageKey = `ktalogu_shipping:${storageScope}`;

  const makeLineItemId = (productId: string, unitType: string, widthM?: number, heightM?: number, variationId?: string, variationDesc?: string, isComboItem?: boolean, comboParentId?: string) => {
    const w = typeof widthM === "number" ? String(widthM) : "";
    const h = typeof heightM === "number" ? String(heightM) : "";
    const v = variationId || "";
    // Keep the line item id deterministic in browser without relying on Node's Buffer.
    const d = variationDesc ? encodeURIComponent(variationDesc) : "";
    const c = isComboItem ? ":combo" : "";
    const p = comboParentId ? `:${comboParentId}` : "";
    return `${productId}:${unitType}:${w}x${h}:${v}:${d}${c}${p}`;
  };

  const resolveDynamicLine = (item: {
    base_price: number;
    quantity: number;
    unit_type: string;
    width_m?: number;
    height_m?: number;
    length_m?: number;
    price_on_request?: boolean;
    use_variation_price_only?: boolean;
    product_price_tiers?: ProductPriceTier[] | null;
    selected_options?: Record<string, unknown> | null;
    valor_minimo_item?: number | null;
    is_combo_item?: boolean;
  }): { price: number; valor_minimo_aplicado: number | null; valor_sem_minimo: number | null } => {
    if (item.price_on_request) return { price: 0, valor_minimo_aplicado: null, valor_sem_minimo: null };
    const effectiveQty = calculateEffectiveQuantity({
      quantity: item.quantity,
      unit_type: item.unit_type,
      width_m: item.width_m,
      height_m: item.height_m,
      length_m: item.length_m,
    });
    const selectedOptionsRows = Array.isArray((item.selected_options as { options?: unknown[] } | null)?.options)
      ? (((item.selected_options as { options?: unknown[] } | null)?.options || []) as Array<Record<string, unknown>>)
      : [];
    /* A mesma conta da página do produto e do checkout: faixa pela área
       total, adicional por m² × área, por peça × peças, uma vez no pedido.
       O que o carrinho guarda é o preço por medida com tudo rateado, e a
       linha volta a fechar multiplicando pela quantidade efetiva. */
    const conta = precoDaLinha({
      precoBase: item.base_price,
      faixas: item.product_price_tiers,
      pecas: item.quantity,
      quantidadeEfetiva: effectiveQty,
      adicionais: somarAdicionais(selectedOptionsRows as OpcaoComPreco[], item.unit_type),
      soVariacao: item.use_variation_price_only,
      /* O valor mínimo do item vale para a linha inteira; o "compre junto" fica fora. */
      valorMinimo: item.is_combo_item ? null : item.valor_minimo_item ?? null,
    });
    return {
      price: conta.precoPorMedida,
      valor_minimo_aplicado: conta.minimoAplicado,
      valor_sem_minimo: conta.minimoAplicado !== null ? Math.round(conta.totalSemMinimo * 100) / 100 : null,
    };
  };

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

  const getQuantityRuleMessage = (input: {
    quantity: number;
    unit_type: string;
    width_m?: number;
    height_m?: number;
    length_m?: number;
    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;
  }) => {
    const check = checkSaleQuantityLimits({
      min_sale_qty_enabled: input.min_sale_qty_enabled,
      min_sale_qty: input.min_sale_qty,
      max_sale_qty_enabled: input.max_sale_qty_enabled,
      max_sale_qty: input.max_sale_qty,
      allowed_sale_qty_enabled: input.allowed_sale_qty_enabled,
      allowed_sale_quantities: input.allowed_sale_quantities,
      effectiveQuantity: calculateEffectiveQuantity({
        quantity: input.quantity,
        unit_type: input.unit_type,
        width_m: input.width_m,
        height_m: input.height_m,
        length_m: input.length_m,
      }),
    });

    if (check.isBelowMin && check.minQty !== null) {
      return `Quantidade minima deste produto: ${formatQuantityLimit(check.minQty)}.`;
    }
    if (check.isAboveMax && check.maxQty !== null) {
      return `Quantidade maxima deste produto: ${formatQuantityLimit(check.maxQty)}.`;
    }
    if (check.isNotAllowedValue && check.allowedQuantities.length > 0) {
      const values = check.allowedQuantities.map((value) => formatQuantityLimit(value)).join(", ");
      return `Use uma quantidade permitida: ${values}.`;
    }
    return null;
  };
  const getStockRuleMessage = (input: {
    quantity: number;
    unit_type: string;
    width_m?: number;
    height_m?: number;
    length_m?: number;
    stock?: number | null;
    track_inventory?: boolean | null;
    allow_negative_stock?: boolean | null;
  }) => {
    /* Com "Permitir estoque negativo" o saldo nao limita: a venda entra como
       se houvesse estoque, e o painel marca a reposicao. */
    if (!input.track_inventory || input.allow_negative_stock) return null;
    const stock = Number(input.stock);
    if (!Number.isFinite(stock)) return null;
    const availableStock = Math.max(0, stock);
    const effectiveQuantity = calculateEffectiveQuantity({
      quantity: input.quantity,
      unit_type: input.unit_type,
      width_m: input.width_m,
      height_m: input.height_m,
      length_m: input.length_m,
    });
    if (effectiveQuantity > availableStock + 0.000001) {
      return `Estoque disponível: ${formatQuantityLimit(availableStock)}.`;
    }
    return null;
  };

  /*
    Ler o localStorage no inicializador do useState quebrava a hidratacao:
    o servidor renderiza o carrinho vazio e o cliente renderiza cheio. O
    React entao descartava a arvore e remontava, e nessa remontagem o efeito
    de persistencia gravava [] por cima do carrinho salvo. Era assim que o
    carrinho sumia na recarga completa que o login provoca.

    Agora o estado nasce vazio dos dois lados e o conteudo salvo entra no
    efeito de hidratacao abaixo, depois da montagem.
  */
  const lerItensSalvos = (): CartItem[] => {
    if (typeof window === "undefined") return [];
    const savedCart = localStorage.getItem(cartStorageKey);
    if (!savedCart) return [];
    try {
      const parsed = JSON.parse(savedCart) as unknown;
      if (!Array.isArray(parsed)) return [];

      const converterItem = (raw: unknown): CartItem | null => {
          const r = raw as Record<string, unknown>;
          const productId = typeof r.product_id === "string" ? r.product_id : (typeof r.id === "string" ? r.id : "");
          const unitType = typeof r.unit_type === "string" ? r.unit_type : "unit";
          const widthM = typeof r.width_m === "number" ? r.width_m : undefined;
          const heightM = typeof r.height_m === "number" ? r.height_m : undefined;
          const lengthM = typeof r.length_m === "number" ? r.length_m : undefined;
          const variationId = typeof r.variation_id === "string" ? r.variation_id : undefined;
          const variationDesc = typeof r.variation_description === "string" ? r.variation_description : undefined;
          const selectedOptions = r.selected_options && typeof r.selected_options === "object" && !Array.isArray(r.selected_options)
            ? (r.selected_options as Record<string, unknown>)
            : null;
          const stockRaw = r.stock;
          const stock = stockRaw === null || stockRaw === undefined ? null : Number(stockRaw);
          const trackInventory = Boolean(r.track_inventory);
          const allowNegativeStock = r.allow_negative_stock === true;
          const minSaleQtyEnabled = Boolean(r.min_sale_qty_enabled);
          const maxSaleQtyEnabled = Boolean(r.max_sale_qty_enabled);
          const allowedSaleQtyEnabled = Boolean(r.allowed_sale_qty_enabled);
          const minSaleQtyRaw = r.min_sale_qty;
          const maxSaleQtyRaw = r.max_sale_qty;
          const allowedSaleQtyRaw = r.allowed_sale_quantities;
          const minSaleQty =
            minSaleQtyRaw === null || minSaleQtyRaw === undefined ? null : Number(minSaleQtyRaw);
          const maxSaleQty =
            maxSaleQtyRaw === null || maxSaleQtyRaw === undefined ? null : Number(maxSaleQtyRaw);
          const allowedSaleQuantities = Array.isArray(allowedSaleQtyRaw)
            ? allowedSaleQtyRaw
                .map((value) => Number(value))
                .filter((value) => Number.isFinite(value) && value > 0)
                .map((value) => Number(value.toFixed(3)))
            : [];
          const basePriceRaw =
            typeof r.base_price === "number"
              ? r.base_price
              : typeof r.price === "number"
                ? r.price
                : Number(r.base_price ?? r.price ?? 0);
          const basePrice = Number.isFinite(basePriceRaw) ? basePriceRaw : 0;
          const shippingWeightKg = r.shipping_weight_kg === null || r.shipping_weight_kg === undefined ? null : Number(r.shipping_weight_kg);
          const shippingWidthCm = r.shipping_width_cm === null || r.shipping_width_cm === undefined ? null : Number(r.shipping_width_cm);
          const shippingHeightCm = r.shipping_height_cm === null || r.shipping_height_cm === undefined ? null : Number(r.shipping_height_cm);
          const shippingLengthCm = r.shipping_length_cm === null || r.shipping_length_cm === undefined ? null : Number(r.shipping_length_cm);
          const productionTimeValue =
            r.production_time_value === null || r.production_time_value === undefined
              ? null
              : Number(r.production_time_value);
          const productionTimeUnit = typeof r.production_time_unit === "string" ? r.production_time_unit : null;
          const tiers = Array.isArray(r.product_price_tiers)
            ? (r.product_price_tiers as unknown[])
                .map((tierRaw) => {
                  if (!tierRaw || typeof tierRaw !== "object" || Array.isArray(tierRaw)) return null;
                  const tier = tierRaw as Record<string, unknown>;
                  const minQty = Number(tier.min_qty);
                  const maxRaw = tier.max_qty;
                  const maxQty =
                    maxRaw === null || maxRaw === undefined ? null : Number(maxRaw);
                  const unitPrice = Number(tier.unit_price);
                  if (!Number.isFinite(minQty) || !Number.isFinite(unitPrice)) return null;
                  return {
                    min_qty: minQty,
                    max_qty: maxQty !== null && Number.isFinite(maxQty) ? maxQty : null,
                    unit_price: unitPrice,
                    sort_order: typeof tier.sort_order === "number" ? tier.sort_order : undefined,
                    active: tier.active === false ? false : true,
                  } satisfies ProductPriceTier;
                })
                .filter(Boolean) as ProductPriceTier[]
            : [];
          
          // Re-generate ID to ensure consistency with new logic
          const id = makeLineItemId(productId, unitType, widthM, heightM, variationId, variationDesc, Boolean(r.is_combo_item), typeof r.combo_parent_id === "string" ? r.combo_parent_id : undefined);

          if (!productId) return null;

          const quantity = typeof r.quantity === "number" ? r.quantity : Number(r.quantity ?? 1);
          const normalizedQuantity = Number.isFinite(quantity) && quantity > 0 ? quantity : 1;
          const priceOnRequest = Boolean(r.price_on_request);
          const minimoGuardado = Number(r.valor_minimo_item);
          const valorMinimoItem = Number.isFinite(minimoGuardado) && minimoGuardado > 0 ? minimoGuardado : null;
          const linha = resolveDynamicLine({
            base_price: basePrice,
            quantity: normalizedQuantity,
            unit_type: unitType,
            width_m: widthM,
            height_m: heightM,
            length_m: lengthM,
            price_on_request: priceOnRequest,
            use_variation_price_only: Boolean(r.use_variation_price_only),
            product_price_tiers: tiers,
            selected_options: selectedOptions,
            valor_minimo_item: valorMinimoItem,
            is_combo_item: Boolean(r.is_combo_item),
          });

          return {
            id,
            product_id: productId,
            name: typeof r.name === "string" ? r.name : "",
            base_price: basePrice,
            price: linha.price,
            valor_minimo_item: valorMinimoItem,
            valor_minimo_aplicado: linha.valor_minimo_aplicado,
            valor_sem_minimo: linha.valor_sem_minimo,
            image_url: typeof r.image_url === "string" ? r.image_url : undefined,
            stock: Number.isFinite(stock) ? stock : null,
            track_inventory: trackInventory,
            allow_negative_stock: allowNegativeStock,
            ...(r.estoque_v2 === true ? { estoque_v2: true } : {}),
            quantity: normalizedQuantity,
            store_id:
              typeof r.store_id === "string"
                ? r.store_id
                : typeof r.user_id === "string"
                  ? r.user_id
                  : "",
            unit_type: unitType,
            width_m: widthM,
            height_m: heightM,
            length_m: lengthM,
            price_on_request: priceOnRequest,
            use_variation_price_only: Boolean(r.use_variation_price_only),
            product_price_tiers: tiers,
            min_sale_qty_enabled: minSaleQtyEnabled,
            min_sale_qty: Number.isFinite(minSaleQty) ? minSaleQty : null,
            max_sale_qty_enabled: maxSaleQtyEnabled,
            max_sale_qty: Number.isFinite(maxSaleQty) ? maxSaleQty : null,
            allowed_sale_qty_enabled: allowedSaleQtyEnabled,
            allowed_sale_quantities: allowedSaleQuantities.length ? allowedSaleQuantities : null,
            variation_id: variationId,
            variation_description: variationDesc,
            selected_options: selectedOptions,
            is_combo_item: Boolean(r.is_combo_item),
            combo_parent_id: typeof r.combo_parent_id === "string" ? r.combo_parent_id : undefined,
            shipping_weight_kg: Number.isFinite(shippingWeightKg) ? shippingWeightKg : null,
            shipping_width_cm: Number.isFinite(shippingWidthCm) ? shippingWidthCm : null,
            shipping_height_cm: Number.isFinite(shippingHeightCm) ? shippingHeightCm : null,
            shipping_length_cm: Number.isFinite(shippingLengthCm) ? shippingLengthCm : null,
            production_time_value: Number.isFinite(productionTimeValue) ? productionTimeValue : null,
            production_time_unit: productionTimeUnit,
          } satisfies CartItem;
      };

      // Um item corrompido nao pode derrubar o carrinho inteiro: antes, uma
      // excecao em qualquer item caia no catch de fora e zerava tudo.
      return parsed
        .map((raw) => {
          try {
            return converterItem(raw);
          } catch {
            return null;
          }
        })
        .filter(Boolean) as CartItem[];
    } catch {
      return [];
    }
  };

  /* Só o código fica guardado (o carrinho de antes guardava o cupom inteiro:
     o código dele continua valendo). */
  const lerCupomSalvo = (): string[] => {
    if (typeof window === "undefined") return [];
    try {
      const savedCoupon = localStorage.getItem(couponStorageKey);
      if (!savedCoupon) return [];
      const parsed = JSON.parse(savedCoupon) as { code?: unknown; codes?: unknown } | null;
      const lista = Array.isArray(parsed?.codes) ? (parsed?.codes as unknown[]) : [parsed?.code];
      return Array.from(new Set(lista.map((c) => String(c || "").trim().toUpperCase().slice(0, 60)).filter(Boolean))).slice(0, 4);
    } catch {
      return [];
    }
  };

  const lerFreteSalvo = (): ShippingSelection | null => {
    if (typeof window === "undefined") return null;
    const saved = localStorage.getItem(shippingStorageKey);
    if (!saved) return null;
    try {
      const parsed = JSON.parse(saved) as ShippingSelection | null;
      if (!parsed?.option?.id) return null;
      /* Retirada e gravada sem CEP de destino, entao exigir um aqui apagava a
         escolha do balcao em toda recarga da pagina. */
      if (!parsed.destination_zip && !isPickupShippingOption(parsed.option)) return null;
      return {
        ...parsed,
        destination_zip: sanitizePostalCode(parsed.destination_zip),
      };
    } catch {
      return null;
    }
  };

  const [items, setItems] = useState<CartItem[]>([]);
  const [typedCouponCodes, setTypedCouponCodes] = useState<string[]>([]);
  const [previaDoCupom, setPreviaDoCupom] = useState<PreviaDoCupom | null>(null);
  const [couponFromLink, setCouponFromLink] = useState<string | null>(null);
  const [isCartOpen, setIsCartOpen] = useState(false);
  const [shippingSelection, setShippingSelectionState] = useState<ShippingSelection | null>(null);
  /* Enquanto false, nada e gravado: nunca sobrescrever o que esta salvo
     antes de te-lo lido. */
  const [hidratado, setHidratado] = useState(false);

  useEffect(() => {
    setItems(lerItensSalvos());
    /* O link da loja com ?cupom=CODIGO guarda o código no carrinho e some do
       endereço (recarregar não repete o aviso). */
    let doLink = "";
    try {
      const endereco = new URL(window.location.href);
      doLink = String(endereco.searchParams.get("cupom") || "").trim().toUpperCase().slice(0, 60);
      if (doLink) {
        endereco.searchParams.delete("cupom");
        window.history.replaceState(window.history.state, "", endereco.toString());
      }
    } catch {
      doLink = "";
    }
    {
      const salvos = lerCupomSalvo();
      setTypedCouponCodes(doLink ? [...salvos.filter((c) => c !== doLink), doLink].slice(-4) : salvos);
    }
    if (doLink) setCouponFromLink(doLink);
    setShippingSelectionState(lerFreteSalvo());
    setHidratado(true);
    // Os leitores sao recriados a cada render; depender deles reexecutaria o
    // efeito em loop. As chaves sao a identidade real do que ha para ler, e o
    // StoreLayout ja remonta o provider quando o escopo muda.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [cartStorageKey, couponStorageKey, shippingStorageKey]);

  useEffect(() => {
    if (!hidratado) return;
    localStorage.setItem(cartStorageKey, JSON.stringify(items));
  }, [items, cartStorageKey, hidratado]);

  useEffect(() => {
    if (!hidratado) return;
    try {
      if (typedCouponCodes.length) {
        localStorage.setItem(couponStorageKey, JSON.stringify({ codes: typedCouponCodes, code: typedCouponCodes[0] }));
      } else {
        localStorage.removeItem(couponStorageKey);
      }
    } catch {
      /* Navegador sem armazenamento: o cupom vale só nesta visita. */
    }
  }, [typedCouponCodes, couponStorageKey, hidratado]);

  /*
    Peso, medidas da embalagem e prazo de producao vindos do cadastro.

    O frete e cotado com esses numeros aqui, na finalizacao e de novo no
    servidor, que usa os do cadastro. O "adicionar" do card le a grade enxuta
    da loja, sem eles, e o Vendor montava o item a mao tambem sem eles: a
    cotacao por transportadora falhava com "faltam peso e medidas". E um
    carrinho salvo antes de a loja mudar uma medida guardava o pacote velho,
    que nao bateria com o do servidor. Entao, uma vez por visita, o carrinho
    confere os produtos que tem, e cada produto novo e conferido ao entrar.
  */
  const medidasConferidasRef = useRef<Set<string>>(new Set());
  const montadoRef = useRef(true);
  useEffect(() => {
    montadoRef.current = true;
    return () => {
      montadoRef.current = false;
    };
  }, []);

  useEffect(() => {
    if (!hidratado || !catalogSlug) return;
    const conferidas = medidasConferidasRef.current;
    const ids = Array.from(new Set(items.map((item) => item.product_id).filter((id) => id && !conferidas.has(id))));
    if (ids.length === 0) return;
    ids.forEach((id) => conferidas.add(id));

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

    void (async () => {
      try {
        const resposta = await fetch(
          `/api/catalog/medidas?slug=${encodeURIComponent(catalogSlug)}&ids=${ids.join(",")}`,
          { cache: "no-store" }
        );
        if (!resposta.ok) return;
        const corpo = (await resposta.json().catch(() => null)) as { produtos?: Array<Record<string, unknown>> } | null;
        const linhas = Array.isArray(corpo?.produtos) ? corpo.produtos : [];
        if (!linhas.length || !montadoRef.current) return;

        const doCadastro = new Map(
          linhas.map((linha) => [
            String(linha.id),
            {
              shipping_weight_kg: positivo(linha.shipping_weight_kg),
              shipping_width_cm: positivo(linha.shipping_width_cm),
              shipping_height_cm: positivo(linha.shipping_height_cm),
              shipping_length_cm: positivo(linha.shipping_length_cm),
              production_time_value: numeroOuNulo(linha.production_time_value),
              production_time_unit: typeof linha.production_time_unit === "string" ? linha.production_time_unit : null,
              /* So a marcacao que libera: um carrinho montado antes dela (ou
                 antes desta correcao) para de barrar a quantidade. O controle de
                 estoque em si nao e atualizado aqui, que so poderia travar. */
              allow_negative_stock: linha.allow_negative_stock === true,
            },
          ])
        );

        setItems((atuais) => {
          let mudou = false;
          const proximos = atuais.map((item) => {
            const doBanco = doCadastro.get(item.product_id);
            if (!doBanco) return item;
            /* Estoque v2: quem libera ou limita e o banco por combinacao (a
               conferencia logo abaixo); a marcacao do produto nao vale. */
            const medidas = item.estoque_v2 ? { ...doBanco, allow_negative_stock: item.allow_negative_stock === true } : doBanco;
            const iguais = (Object.keys(medidas) as Array<keyof typeof medidas>).every(
              (campo) => (item[campo] ?? null) === medidas[campo]
            );
            if (iguais) return item;
            mudou = true;
            return { ...item, ...medidas };
          });
          return mudou ? proximos : atuais;
        });
      } catch {
        /* Sem rede o carrinho segue com o que tinha; a cotacao avisa o que faltar. */
      }
    })();
  }, [items, hidratado, catalogSlug]);

  /*
    Estoque v2: o carrinho inteiro conferido pelo servidor (a mesma conta do
    checkout e da cobrança, estoque_conferir_itens): dois produtos que usam a
    mesma peça disputam o mesmo saldo, e o saldo pode ter mudado desde que o
    item entrou. A frase vai embaixo do item ("Camiseta (G / Preta):
    esgotado."). Só para itens marcados como v2 pelo servidor: nas outras
    lojas o carrinho segue exatamente como era, sem pergunta nenhuma.
  */
  const [avisosDeEstoque, setAvisosDeEstoque] = useState<Record<string, string>>({});
  const conferenciaDoEstoque = useMemo(() => {
    if (!hidratado || !catalogSlug || !items.some((item) => item.estoque_v2)) return "";
    return JSON.stringify({
      slug: catalogSlug,
      itens: items.map((item) => ({
        product_id: item.product_id,
        quantity: item.quantity,
        selected_options: item.selected_options ?? null,
        width_m: item.width_m ?? null,
        height_m: item.height_m ?? null,
        title: item.name,
      })),
      ids: items.map((item) => item.id),
    });
  }, [items, hidratado, catalogSlug]);
  useEffect(() => {
    if (!conferenciaDoEstoque) {
      setAvisosDeEstoque((antes) => (Object.keys(antes).length ? {} : antes));
      return;
    }
    const controle = new AbortController();
    const espera = window.setTimeout(async () => {
      try {
        const pedido = JSON.parse(conferenciaDoEstoque) as { slug: string; itens: unknown[]; ids: string[] };
        const resposta = await fetch("/api/catalog/estoque", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          signal: controle.signal,
          body: JSON.stringify({ slug: pedido.slug, itens: pedido.itens }),
        });
        if (!resposta.ok) return;
        const corpo = (await resposta.json().catch(() => null)) as { v2?: boolean; avisos?: Record<string, string> } | null;
        if (!corpo || corpo.v2 !== true || !montadoRef.current) return;
        const porItem: Record<string, string> = {};
        for (const [indice, frase] of Object.entries(corpo.avisos || {})) {
          const id = pedido.ids[Number(indice)];
          if (id) porItem[id] = frase;
        }
        setAvisosDeEstoque(porItem);
      } catch {
        /* Sem rede: fica o que havia; o checkout confere de novo antes de cobrar. */
      }
    }, 400);
    return () => {
      controle.abort();
      window.clearTimeout(espera);
    };
  }, [conferenciaDoEstoque]);

  const addToCart = (
    product: ProductForCart, 
    quantity: number = 1, 
    opts?: { width_m?: number; height_m?: number; price_override?: number; is_combo_item?: boolean; combo_parent_id?: string },
    variation?: { id: string; description: string; selected_options?: Record<string, unknown> | null }
  ) => {
    const unitType = (product.unit_type || "unit") as string;
    const widthM = unitType === "square_meter" ? opts?.width_m : undefined;
    const heightM = unitType === "square_meter" ? opts?.height_m : undefined;
    const lineItemId = makeLineItemId(product.id, unitType, widthM, heightM, variation?.id, variation?.description, opts?.is_combo_item, opts?.combo_parent_id);
    const productTiers = Array.isArray(product.product_price_tiers) ? product.product_price_tiers : [];
    const basePrice = opts?.price_override !== undefined ? opts.price_override : (Number(product.price) || 0);
    const normalizedRequestedQuantity = Math.max(1, quantity);
    const minSaleQtyEnabled = Boolean(product.min_sale_qty_enabled);
    const maxSaleQtyEnabled = Boolean(product.max_sale_qty_enabled);
    const allowedSaleQtyEnabled = Boolean(product.allowed_sale_qty_enabled);
    const minSaleQty =
      product.min_sale_qty === null || product.min_sale_qty === undefined ? null : Number(product.min_sale_qty);
    const maxSaleQty =
      product.max_sale_qty === null || product.max_sale_qty === undefined ? null : Number(product.max_sale_qty);
    const allowedSaleQuantities = Array.isArray(product.allowed_sale_quantities)
      ? product.allowed_sale_quantities
          .map((value) => Number(value))
          .filter((value) => Number.isFinite(value) && value > 0)
          .map((value) => Number(value.toFixed(3)))
      : [];
    let addResult: { ok: boolean; message?: string } = { ok: true };

    setItems(prev => {
      const existing = prev.find(item => item.id === lineItemId);
      if (existing) {
        const nextQuantity = existing.quantity + normalizedRequestedQuantity;
        const quantityRuleMessage = getQuantityRuleMessage({
          quantity: nextQuantity,
          unit_type: existing.unit_type,
          width_m: existing.width_m,
          height_m: existing.height_m,
          length_m: existing.length_m,
          min_sale_qty_enabled: existing.min_sale_qty_enabled,
          min_sale_qty: existing.min_sale_qty,
          max_sale_qty_enabled: existing.max_sale_qty_enabled,
          max_sale_qty: existing.max_sale_qty,
          allowed_sale_qty_enabled: existing.allowed_sale_qty_enabled,
          allowed_sale_quantities: existing.allowed_sale_quantities,
        });
        if (quantityRuleMessage) {
          addResult = { ok: false, message: quantityRuleMessage };
          return prev;
        }
        const stockRuleMessage = getStockRuleMessage({
          quantity: nextQuantity,
          unit_type: existing.unit_type,
          width_m: existing.width_m,
          height_m: existing.height_m,
          length_m: existing.length_m,
          stock: existing.stock,
          track_inventory: existing.track_inventory,
          allow_negative_stock: existing.allow_negative_stock,
        });
        if (stockRuleMessage) {
          addResult = { ok: false, message: stockRuleMessage };
          return prev;
        }
        const nextLine = resolveDynamicLine({
          base_price: existing.base_price,
          quantity: nextQuantity,
          unit_type: existing.unit_type,
          width_m: existing.width_m,
          height_m: existing.height_m,
          length_m: existing.length_m,
          price_on_request: existing.price_on_request,
          use_variation_price_only: existing.use_variation_price_only,
          product_price_tiers: existing.product_price_tiers,
          selected_options: existing.selected_options,
          valor_minimo_item: existing.valor_minimo_item ?? null,
          is_combo_item: existing.is_combo_item,
        });
        return prev.map(item => 
          item.id === lineItemId
            ? { ...item, quantity: nextQuantity, ...nextLine }
            : item
        );
      }
      const normalizedQuantity = normalizedRequestedQuantity;
      const quantityRuleMessage = getQuantityRuleMessage({
        quantity: normalizedQuantity,
        unit_type: unitType,
        width_m: widthM,
        height_m: heightM,
        min_sale_qty_enabled: minSaleQtyEnabled,
        min_sale_qty: Number.isFinite(minSaleQty) ? minSaleQty : null,
        max_sale_qty_enabled: maxSaleQtyEnabled,
        max_sale_qty: Number.isFinite(maxSaleQty) ? maxSaleQty : null,
        allowed_sale_qty_enabled: allowedSaleQtyEnabled,
        allowed_sale_quantities: allowedSaleQuantities.length ? allowedSaleQuantities : null,
      });
      if (quantityRuleMessage) {
        addResult = { ok: false, message: quantityRuleMessage };
        return prev;
      }
      const stockRuleMessage = getStockRuleMessage({
        quantity: normalizedQuantity,
        unit_type: unitType,
        width_m: widthM,
        height_m: heightM,
        stock: product.stock,
        track_inventory: product.track_inventory,
        allow_negative_stock: product.allow_negative_stock,
      });
      if (stockRuleMessage) {
        addResult = { ok: false, message: stockRuleMessage };
        return prev;
      }
      const valorMinimoItem = opts?.is_combo_item ? null : valorMinimoDoItem(product, "loja");
      const resolvedLine = resolveDynamicLine({
        base_price: basePrice,
        quantity: normalizedQuantity,
        unit_type: unitType,
        width_m: widthM,
        height_m: heightM,
        price_on_request: Boolean(product.price_on_request),
        use_variation_price_only: Boolean(product.use_variation_price_only),
        product_price_tiers: productTiers,
        selected_options: variation?.selected_options || null,
        valor_minimo_item: valorMinimoItem,
        is_combo_item: opts?.is_combo_item,
      });
      return [...prev, { 
        id: lineItemId,
        product_id: product.id,
        name: product.name, 
        base_price: basePrice,
        price: resolvedLine.price,
        valor_minimo_item: valorMinimoItem,
        valor_minimo_aplicado: resolvedLine.valor_minimo_aplicado,
        valor_sem_minimo: resolvedLine.valor_sem_minimo,
        image_url: product.image_url || undefined,
        stock: product.stock ?? null,
        track_inventory: Boolean(product.track_inventory),
        allow_negative_stock: product.allow_negative_stock === true,
        ...(product.estoque_v2 === true ? { estoque_v2: true } : {}),
        quantity: normalizedQuantity,
        store_id: product.user_id,
        unit_type: unitType,
        width_m: widthM,
        height_m: heightM,
        price_on_request: Boolean(product.price_on_request),
        use_variation_price_only: Boolean(product.use_variation_price_only),
        product_price_tiers: productTiers,
        min_sale_qty_enabled: minSaleQtyEnabled,
        min_sale_qty: Number.isFinite(minSaleQty) ? minSaleQty : null,
        max_sale_qty_enabled: maxSaleQtyEnabled,
        max_sale_qty: Number.isFinite(maxSaleQty) ? maxSaleQty : null,
        allowed_sale_qty_enabled: allowedSaleQtyEnabled,
        allowed_sale_quantities: allowedSaleQuantities.length ? allowedSaleQuantities : null,
        variation_id: variation?.id,
        variation_description: variation?.description,
        selected_options: variation?.selected_options || null,
        is_combo_item: opts?.is_combo_item,
        combo_parent_id: opts?.combo_parent_id,
        shipping_weight_kg: product.shipping_weight_kg ?? null,
        shipping_width_cm: product.shipping_width_cm ?? null,
        shipping_height_cm: product.shipping_height_cm ?? null,
        shipping_length_cm: product.shipping_length_cm ?? null,
        production_time_value: product.production_time_value ?? null,
        production_time_unit: product.production_time_unit ?? null,
      }];
    });
    if (addResult.ok) {
      setIsCartOpen(true);
      if (catalogSlug) {
        void trackCatalogEvent({
          slug: catalogSlug,
          eventName: "add_to_cart",
          productId: product.id,
          meta: {
            quantity: normalizedRequestedQuantity,
            unit_type: unitType,
            is_combo_item: Boolean(opts?.is_combo_item),
          },
        });
      }
    }
    return { ...addResult, cart_item_id: lineItemId };
  };

  const removeFromCart = (lineItemId: string) => {
    setItems(prev => {
      // Find if this item has any children (combo items)
      const childrenIds = prev.filter(i => i.combo_parent_id === lineItemId).map(i => i.id);
      
      return prev.filter(item => item.id !== lineItemId && !childrenIds.includes(item.id));
    });
  };

  const updateQuantity = (lineItemId: string, quantity: number) => {
    if (quantity < 1) {
      removeFromCart(lineItemId);
      return { ok: true };
    }
    let updateResult: { ok: boolean; message?: string } = { ok: true };
    setItems(prev => prev.map(item => {
      if (item.id !== lineItemId) return item;

      const quantityRuleMessage = getQuantityRuleMessage({
        quantity,
        unit_type: item.unit_type,
        width_m: item.width_m,
        height_m: item.height_m,
        length_m: item.length_m,
        min_sale_qty_enabled: item.min_sale_qty_enabled,
        min_sale_qty: item.min_sale_qty,
        max_sale_qty_enabled: item.max_sale_qty_enabled,
        max_sale_qty: item.max_sale_qty,
        allowed_sale_qty_enabled: item.allowed_sale_qty_enabled,
        allowed_sale_quantities: item.allowed_sale_quantities,
      });
      if (quantityRuleMessage) {
        updateResult = { ok: false, message: quantityRuleMessage };
        return item;
      }
      const stockRuleMessage = getStockRuleMessage({
        quantity,
        unit_type: item.unit_type,
        width_m: item.width_m,
        height_m: item.height_m,
        length_m: item.length_m,
        stock: item.stock,
        track_inventory: item.track_inventory,
        allow_negative_stock: item.allow_negative_stock,
      });
      if (stockRuleMessage) {
        updateResult = { ok: false, message: stockRuleMessage };
        return item;
      }

      const nextLine = resolveDynamicLine({
        base_price: item.base_price,
        quantity,
        unit_type: item.unit_type,
        width_m: item.width_m,
        height_m: item.height_m,
        length_m: item.length_m,
        price_on_request: item.price_on_request,
        use_variation_price_only: item.use_variation_price_only,
        product_price_tiers: item.product_price_tiers,
        selected_options: item.selected_options,
        valor_minimo_item: item.valor_minimo_item ?? null,
        is_combo_item: item.is_combo_item,
      });
      return { ...item, quantity, ...nextLine };
    }));
    return updateResult;
  };

  const clearCart = () => {
    setItems([]);
    setShippingSelectionState(null);
  };

  const toggleCart = () => setIsCartOpen(prev => !prev);

  const cartSubtotal = useMemo(() => {
    return items.reduce((total, item) => total + totalDaLinhaDoCarrinho(item), 0);
  }, [items]);

  /* Itens no formato que a cotacao de frete espera. Exposto no contexto
     para o carrinho e o checkout cotarem exatamente o mesmo pacote. */
  const shippingItems = useMemo(
    () =>
      items.map((item) => ({
        product_id: item.product_id,
        name: item.name,
        quantity: item.quantity,
        unit_type: item.unit_type,
        width_m: item.width_m,
        height_m: item.height_m,
        length_m: item.length_m,
        shipping_weight_kg: item.shipping_weight_kg,
        shipping_width_cm: item.shipping_width_cm,
        shipping_height_cm: item.shipping_height_cm,
        shipping_length_cm: item.shipping_length_cm,
        production_time_value: item.production_time_value,
        production_time_unit: item.production_time_unit,
        price_on_request: item.price_on_request,
      })),
    [items]
  );

  const cartPackageResult = useMemo(
    () => buildShippingPackageFromItems(shippingItems),
    [shippingItems]
  );

  const cartItemsCount = useMemo(
    () => items.reduce((count, item) => count + item.quantity, 0),
    [items]
  );

  /*
    Retirada no balcao nao passa pelas mesmas travas: o preco vem do balcao
    cadastrado, nao de uma cotacao por CEP. Exigir CEP e assinatura de pacote
    aqui faria a escolha do cliente sumir sozinha a cada item novo no carrinho,
    e impediria de vez as lojas que so retiram e nao cadastram peso e medidas.
  */
  const effectiveShippingSelection =
    shippingSelection &&
    (isPickupShippingOption(shippingSelection.option)
      ? true
      : shippingSelection.destination_zip.length === 8 &&
        cartPackageResult.ok &&
        shippingSelection.package_signature === cartPackageResult.packageSignature)
      ? shippingSelection
      : null;

  useEffect(() => {
    if (!hidratado) return;
    if (effectiveShippingSelection) {
      localStorage.setItem(shippingStorageKey, JSON.stringify(effectiveShippingSelection));
    } else {
      localStorage.removeItem(shippingStorageKey);
    }
  }, [effectiveShippingSelection, shippingStorageKey, hidratado]);

  const shippingTotal =
    effectiveShippingSelection
      ? Math.max(0, Number(effectiveShippingSelection.option.price) || 0)
      : 0;

  /*
    O cupom conferido no servidor. A cada mudança no carrinho, no código ou no
    frete, o carrinho pergunta de novo (com uma pausa curta para não perguntar
    a cada tecla): o digitado ou o automático que der mais, e quanto dá.
  */
  const corpoDaPrevia = useMemo(() => {
    if (!catalogSlug || items.length === 0) return null;
    return {
      slug: catalogSlug,
      items: items.map((item) => ({
        product_id: item.product_id,
        name: item.name,
        price: item.price,
        quantity: item.quantity,
        unit_type: item.unit_type,
        width_m: item.width_m,
        height_m: item.height_m,
        length_m: item.length_m,
        variation_description: item.variation_description,
        selected_options: item.selected_options ?? null,
        price_on_request: item.price_on_request,
        is_combo_item: item.is_combo_item,
        combo_parent_id: item.combo_parent_id,
      })),
      shipping: effectiveShippingSelection
        ? {
            price: Math.max(0, Number(effectiveShippingSelection.option.price) || 0),
            destination_zip: effectiveShippingSelection.destination_zip,
            pickup: isPickupShippingOption(effectiveShippingSelection.option),
          }
        : null,
    };
  }, [catalogSlug, items, effectiveShippingSelection]);
  const chaveDaPrevia = corpoDaPrevia ? JSON.stringify({ ...corpoDaPrevia, couponCodes: typedCouponCodes }) : "";
  const ultimaPrevia = useRef(0);

  const pedirPrevia = useCallback(async (corpo: Record<string, unknown>): Promise<PreviaDoCupom | null> => {
    const minha = ++ultimaPrevia.current;
    try {
      const resposta = await fetch("/api/catalog/cupom", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(corpo),
      });
      const dados = (await resposta.json().catch(() => null)) as PreviaDoCupom | null;
      if (minha !== ultimaPrevia.current) return null;
      const previa: PreviaDoCupom = {
        aplicados: Array.isArray(dados?.aplicados) ? dados.aplicados : [],
        avisos: Array.isArray(dados?.avisos) ? dados.avisos : [],
        erros: Array.isArray(dados?.erros) ? dados.erros : [],
        digitados: Array.isArray(dados?.digitados) ? dados.digitados : [],
        disponiveis: Array.isArray(dados?.disponiveis) ? dados.disponiveis : [],
      };
      setPreviaDoCupom(previa);
      return previa;
    } catch {
      return null;
    }
  }, []);

  useEffect(() => {
    if (!hidratado) return;
    if (!chaveDaPrevia) {
      ultimaPrevia.current += 1;
      setPreviaDoCupom(null);
      return;
    }
    const corpo = JSON.parse(chaveDaPrevia) as Record<string, unknown>;
    const espera = window.setTimeout(() => void pedirPrevia(corpo), 350);
    return () => window.clearTimeout(espera);
  }, [chaveDaPrevia, hidratado, pedirPrevia]);

  /* Uma vaga, um código: com dois digitados de desconto, fica o último (o
     servidor faz o mesmo). Código que não existe fica, com o aviso e o "Tirar". */
  useEffect(() => {
    const digitados = previaDoCupom?.digitados ?? [];
    if (!digitados.length) return;
    const ultimoDaVaga = new Map<string, string>();
    for (const code of typedCouponCodes) {
      const vaga = digitados.find((d) => d.code === code)?.vaga;
      if (vaga) ultimoDaVaga.set(vaga, code);
    }
    const limpos = typedCouponCodes.filter((code) => {
      const vaga = digitados.find((d) => d.code === code)?.vaga;
      return !vaga || ultimoDaVaga.get(vaga) === code;
    });
    if (limpos.length !== typedCouponCodes.length) setTypedCouponCodes(limpos);
  }, [previaDoCupom, typedCouponCodes]);

  const appliedCoupons: CartCoupon[] = (previaDoCupom?.aplicados ?? []).map((a) => ({
    code: a.code,
    name: a.nome,
    type: a.tipo,
    automatic: a.automatico,
    label: a.rotulo,
    description: a.frase,
    waitingShipping: a.esperandoFrete,
    slot: a.vaga ?? (a.tipo === "free_shipping" ? "frete" : "desconto"),
    discount: Math.max(Number(a.desconto) || 0, 0),
    shippingDiscount: Math.max(Number(a.descontoNoFrete) || 0, 0),
    removable: !a.automatico && typedCouponCodes.includes(a.code),
  }));
  const appliedCoupon = appliedCoupons.find((c) => c.slot === "desconto") ?? appliedCoupons[0] ?? null;
  const discountTotal = Math.min(appliedCoupons.reduce((soma, c) => soma + c.discount, 0), cartSubtotal);
  const shippingDiscount = Math.min(appliedCoupons.reduce((soma, c) => soma + c.shippingDiscount, 0), shippingTotal);
  const cartTotal = Math.max(cartSubtotal - discountTotal + shippingTotal - shippingDiscount, 0);
  const cartCount = cartItemsCount;
  const couponNotices = typedCouponCodes
    .filter((code) => !appliedCoupons.some((c) => c.code === code))
    .map((code) => {
      const d = previaDoCupom?.digitados.find((x) => x.code === code);
      const aviso = previaDoCupom?.avisos.find((a) => a.includes(code));
      const message = d?.erro || aviso || (d && !d.aplicado ? "Este cupom não ficou no pedido." : "");
      return { code, message };
    })
    .filter((n) => n.message);

  const applyCoupon = async (rawCode: string) => {
    const code = rawCode.trim().toUpperCase().slice(0, 60);
    if (!code) return { ok: false, message: "Informe um cupom válido." };
    if (!corpoDaPrevia) return { ok: false, message: "Coloque um produto no carrinho para usar o cupom." };
    const tentativa = [...typedCouponCodes.filter((c) => c !== code), code].slice(-4);
    const previa = await pedirPrevia({ ...corpoDaPrevia, couponCodes: tentativa });
    if (!previa) return { ok: false, message: "Não foi possível conferir o cupom agora. Tente de novo." };
    const novo = previa.aplicados.find((a) => a.code === code);
    if (novo) {
      /* O novo toma a vaga dele: o digitado antigo da mesma vaga sai. */
      const vaga = previa.digitados.find((d) => d.code === code)?.vaga;
      setTypedCouponCodes(
        tentativa.filter((c) => c === code || !vaga || previa.digitados.find((d) => d.code === c)?.vaga !== vaga)
      );
      return {
        ok: true,
        message: novo.esperandoFrete ? "Cupom aplicado. O frete grátis entra quando você escolher a entrega." : "Cupom aplicado.",
      };
    }
    /* Não ficou: o carrinho volta a mostrar o que valia sem ele. */
    void pedirPrevia({ ...corpoDaPrevia, couponCodes: typedCouponCodes });
    const erro = previa.digitados.find((d) => d.code === code)?.erro;
    const aviso = previa.avisos.find((a) => a.includes(code));
    return { ok: false, message: erro || aviso || "Cupom não encontrado." };
  };

  const removeCoupon = useCallback((code: string) => setTypedCouponCodes((antes) => antes.filter((c) => c !== code)), []);
  const clearCoupon = () => setTypedCouponCodes([]);
  const dismissCouponFromLink = useCallback(() => setCouponFromLink(null), []);
  /* Estaveis de proposito: quem consome coloca as duas em array de dependencia
     de efeito, e uma identidade nova a cada render fazia esses efeitos rodarem
     sem parar. */
  const clearShippingSelection = useCallback(() => setShippingSelectionState(null), []);
  const setShippingSelection = useCallback((selection: ShippingSelection | null) => {
    if (!selection) {
      setShippingSelectionState(null);
      return;
    }
    setShippingSelectionState({
      ...selection,
      destination_zip: sanitizePostalCode(selection.destination_zip),
    });
  }, []);

  return (
    <CartContext.Provider value={{
      items,
      addToCart,
      removeFromCart,
      updateQuantity,
      clearCart,
      isCartOpen,
      toggleCart,
      applyCoupon,
      clearCoupon,
      appliedCoupon,
      appliedCoupons,
      removeCoupon,
      typedCouponCodes,
      couponNotices,
      availableCoupons: previaDoCupom?.disponiveis ?? [],
      shippingDiscount,
      orderCouponCodes: appliedCoupons.filter((c) => !c.automatic).map((c) => c.code),
      couponFromLink,
      dismissCouponFromLink,
      shippingSelection: effectiveShippingSelection,
      setShippingSelection,
      clearShippingSelection,
      shippingTotal,
      shippingPackageReady: cartPackageResult.ok,
      shippingItems,
      cartSubtotal,
      discountTotal,
      cartTotal,
      cartCount,
      avisosDeEstoque
    }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  const context = useContext(CartContext);
  if (context === undefined) {
    throw new Error('useCart must be used within a CartProvider');
  }
  return context;
}
