"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { preconnect } from "react-dom";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { ChevronLeft, Info, Menu, Search, ShoppingBag, ShoppingCart, User, X } from "lucide-react";
import { useCart } from "@/context/CartContext";
import { useVisaoDaLoja } from "@/components/VisaoDaLojaContext";
import { BoutiqueAviso, type BoutiqueCupomDoAviso } from "./BoutiqueAviso";
import { marcaDeRegiao, useNavegacaoDaLoja } from "@/components/navegacao/ContextoDaNavegacao";
import { itensProntos } from "@/lib/navegacao-da-loja";
import { BoutiqueGaveta, BoutiqueMenuDoComputador, nosDasCategorias, nosDoMenuProprio, type NoDoMenu } from "./BoutiqueMenuProprio";
import { BoutiqueRodape } from "./BoutiqueRodapeProprio";
import { moedaDaBoutique } from "./formato";

/*
  O shell do template Boutique: faixa de aviso, cabeçalho, gaveta do celular,
  busca, rodapé e os flutuantes.

  O desenho é o da referência (moda "editorial chique"):
    - cabeçalho de 85 px no computador (67 px no celular), sticky e sempre à
      vista: logo à esquerda, menu em caixa alta no centro, ícones de traço à
      direita; transparente só enquanto está sobre o banner da home e branco
      com sombra leve depois de rolar;
    - no celular: ☰, conta, logo no meio, busca e sacola; o menu é uma gaveta
      de 270 px que entra pela esquerda;
    - a busca desce sobre o cabeçalho e mostra as sugestões de
      /api/catalog/busca (poucas por vez, nunca o catálogo inteiro).

  A altura medida do <header> vira `--bq-header-h` no elemento raiz: a
  galeria da página de produto se fixa por ela.

  Escrito para este template. Não importa nada de desenho do Padrão, do
  Vitrine nem do Vendor.
*/

export type BoutiqueCategoria = {
  id: string;
  name: string;
  slug?: string | null;
  parent_id?: string | null;
};

export type BoutiqueContato = {
  whatsapp?: string | null;
  telefone?: string | null;
  email?: string | null;
  endereco?: string | null;
  cnpj?: string | null;
  instagram?: string | null;
  facebook?: string | null;
};

export type BoutiqueRedes = {
  instagram?: string | null;
  facebook?: string | null;
  twitter?: string | null;
  linkedin?: string | null;
  youtube?: string | null;
};

/* Tudo que a tela "personalização avançada" do catálogo oferece. Chega num
   objeto só para as páginas não precisarem repetir vinte props — e para
   nenhuma escolha do lojista ficar de fora por esquecimento. */
export type BoutiqueAparencia = {
  /* A trava `catalog_advanced_enabled`: desligada, a cara é a da referência. */
  avancadoLigado?: boolean | null;
  corDoCabecalho?: string | null;
  corDoTextoDoCabecalho?: string | null;
  corDoMenu?: string | null;
  corDoTextoDoMenu?: string | null;
  corDoFundo?: string | null;
  corDoBotaoDoCard?: string | null;
  fonteDosTitulos?: string | null;
  fonteDoTexto?: string | null;
  /* A barra de compra fixa do celular (usada na página de produto). */
  corDaBarraDeCompra?: string | null;
  corDoBotaoDaBarraDeCompra?: string | null;
  corDoTextoDaBarraDeCompra?: string | null;
  corDoRodape?: string | null;
  corDoTextoDoRodape?: string | null;
  corDosIconesDoCabecalho?: string | null;
  /* As redes sociais do cadastro (todas, não só Instagram e Facebook). */
  redes?: BoutiqueRedes | null;
  /* Nome e slogan ao lado da logo, com as cores escolhidas. */
  mostrarNome?: boolean | null;
  mostrarSlogan?: boolean | null;
  corDoNome?: string | null;
  corDoSlogan?: string | null;
  /* A faixa de aviso do topo, com tudo que a tela do catálogo configura:
     texto com destaque, relógio, cupom para copiar e botão. */
  avisoLigado?: boolean | null;
  avisoTexto?: string | null;
  avisoCorDeFundo?: string | null;
  avisoCorDoTexto?: string | null;
  avisoBotaoRotulo?: string | null;
  avisoBotaoUrl?: string | null;
  avisoMostrarSelo?: boolean | null;
  avisoCorDoSelo?: string | null;
  avisoContagemLigada?: boolean | null;
  avisoContagemAte?: string | null;
  avisoFixo?: boolean | null;
  /* O efeito da logo. */
  logoEfeitoLigado?: boolean | null;
  logoEfeitoTipo?: string | null;
  logoEfeitoVelocidade?: number | null;
  logoEfeitoIntensidade?: number | null;
  /* A tarja de benefícios ("Entrega rápida", "Compra segura"). */
  tarjaLigada?: boolean | null;
  tarjaItens?: Array<{ icon?: string | null; title?: string | null; subtitle?: string | null }> | null;
};

export type BoutiqueShellProps = {
  slug: string;
  nomeDaLoja: string;
  slogan?: string | null;
  logoUrl?: string | null;
  corDaMarca: string;
  aparencia?: BoutiqueAparencia | null;
  /* O cupom do aviso vem lido do banco pela página: o navegador não tem como
     descobrir sozinho o código a partir do id guardado no perfil. */
  cupomDoAviso?: BoutiqueCupomDoAviso | null;
  categorias: BoutiqueCategoria[];
  contato: BoutiqueContato;
  clienteLogado?: boolean;
  /* O lojista decide se o botão do WhatsApp aparece na loja. */
  whatsappFlutuante?: boolean | null;
  /* A categoria da página atual. Numa página de produto a rota não diz a
     categoria, e sem isto o menu ficaria apagado durante a compra inteira. */
  categoriaAtual?: string | null;
  children: React.ReactNode;
};

/* Montserrat (texto) e Gelasio (reserva da Georgia nos títulos de revista),
   pedidas ao Google Fonts em tempo de execução, como o FontesDaLoja faz.
   Nunca next/font/google: o build da VPS cai quando o Google não responde. */
const FONTES_DA_BOUTIQUE =
  "https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400..800;1,400&family=Gelasio:wght@400;500&display=swap";

type Sugestao = {
  id: string;
  name: string;
  image_url: string | null;
  price?: number;
  price_on_request?: boolean;
  product_price_tiers?: Array<{ unit_price?: number; active?: boolean | null }>;
};

/* Texto legível sobre a cor escolhida pelo lojista. */
function textoSobre(cor: string) {
  const hex = String(cor || "").replace("#", "");
  if (hex.length !== 6) return "#ffffff";
  const r = parseInt(hex.slice(0, 2), 16);
  const g = parseInt(hex.slice(2, 4), 16);
  const b = parseInt(hex.slice(4, 6), 16);
  const luz = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
  return luz > 0.62 ? "#111111" : "#ffffff";
}

/* O menor preço da sugestão (a menor faixa ativa ou o preço cheio). */
function precoDaSugestao(produto: Sugestao) {
  const faixas = (produto.product_price_tiers || []).filter((f) => f && f.active !== false && Number(f.unit_price) > 0);
  const base = Number(produto.price) || 0;
  if (!faixas.length) return base;
  return Math.min(...faixas.map((f) => Number(f.unit_price)));
}

export function BoutiqueShell({
  slug,
  nomeDaLoja,
  slogan,
  logoUrl,
  corDaMarca,
  aparencia,
  cupomDoAviso,
  categorias,
  contato,
  clienteLogado,
  whatsappFlutuante,
  categoriaAtual,
  children,
}: BoutiqueShellProps) {
  preconnect("https://fonts.googleapis.com");
  preconnect("https://fonts.gstatic.com", { crossOrigin: "anonymous" });

  const router = useRouter();
  const caminho = usePathname();
  const { toggleCart, cartCount } = useCart();
  /* Loja em pausa ou cadastro em análise: o visitante precisa saber antes de
     montar o pedido, e a faixa fina no topo diz isso em uma linha. */
  const visao = useVisaoDaLoja();
  const avisoDaSituacao =
    visao.lojaPausada || visao.aguardandoAprovacao || visao.cadastroRecusado
      ? `${visao.motivoDoBloqueio || ""}${visao.lojaPausada && visao.regras.voltaEm ? ` Previsão de volta: ${dataCurta(visao.regras.voltaEm)}.` : ""}`
      : null;

  const [busca, setBusca] = useState("");
  const [buscaAberta, setBuscaAberta] = useState(false);
  /* As sugestões da busca. Vêm do servidor, poucas por vez: com mil produtos
     no catálogo, carregar tudo no navegador para filtrar é caro e lento. */
  const [sugestoes, setSugestoes] = useState<Sugestao[]>([]);
  const [textoSemPreco, setTextoSemPreco] = useState<string | null>(null);
  const campoDaBusca = useRef<HTMLInputElement | null>(null);
  const campoDaBuscaCelular = useRef<HTMLInputElement | null>(null);
  const caixaDaBusca = useRef<HTMLDivElement | null>(null);
  const caixaDaBuscaComputador = useRef<HTMLDivElement | null>(null);
  const [menuAberto, setMenuAberto] = useState(false);
  const [rolado, setRolado] = useState(false);
  const cabecalho = useRef<HTMLElement | null>(null);
  const raiz = useRef<HTMLDivElement | null>(null);

  /* O cabeçalho, o menu e o rodapé publicados no editor (migration 197).
     Sem nada publicado, o shell desenha o de sempre. */
  const { navegacao, noEditor } = useNavegacaoDaLoja();
  const cabecalhoFixo = navegacao?.cabecalho?.fixo !== false;
  const mostrarBusca = navegacao?.cabecalho?.mostrar_busca !== false;
  const tamanhoDaLogo = navegacao?.cabecalho?.tamanho_da_logo ?? "normal";
  const menuProprio = navegacao?.menu ?? null;
  const rodapeProprio = navegacao?.rodape ?? null;

  const ap = aparencia || {};
  const avancado = ap.avancadoLigado === true;
  const fundoDoCabecalho = ap.corDoCabecalho?.trim() || null;
  const textoDoCabecalho = ap.corDoTextoDoCabecalho?.trim() || (fundoDoCabecalho ? textoSobre(fundoDoCabecalho) : null);
  const fundoDoMenu = ap.corDoMenu?.trim() || null;
  const textoDoMenu = ap.corDoTextoDoMenu?.trim() || (fundoDoMenu ? textoSobre(fundoDoMenu) : null);
  /* Fixa, a faixa entra dentro do cabeçalho grudado; solta, fica acima dele
     e sobe com a página (como na referência). */
  const avisoFixo = ap.avisoFixo !== false;

  /* O efeito da logo: mesma conta do resto do sistema, classes próprias. */
  const velocidade = Math.min(200, Math.max(50, Math.round(Number(ap.logoEfeitoVelocidade) || 100)));
  const forca = Math.min(180, Math.max(40, Math.round(Number(ap.logoEfeitoIntensidade) || 100))) / 100;
  const tempo = 100 / velocidade;
  const classeDoEfeito = ap.logoEfeitoLigado
    ? ap.logoEfeitoTipo === "rotate_3d"
      ? "bq-logo-rotate3d"
      : ap.logoEfeitoTipo === "blink"
        ? "bq-logo-blink"
        : ap.logoEfeitoTipo === "pulse_glow"
          ? "bq-logo-pulse"
          : ""
    : "";

  const mostrarNome = Boolean(ap.mostrarNome && nomeDaLoja.trim());
  const mostrarSlogan = Boolean(ap.mostrarSlogan && String(slogan || "").trim());
  const corDosIcones = ap.corDosIconesDoCabecalho?.trim() || null;

  /* As categorias de primeiro nível, com filhas e netas. */
  const arvore = useMemo(() => {
    const raizes = categorias.filter((c) => !c.parent_id);
    return raizes.map((r) => ({
      ...r,
      filhas: categorias
        .filter((c) => c.parent_id === r.id)
        .map((filha) => ({ ...filha, netas: categorias.filter((n) => n.parent_id === filha.id) })),
    }));
  }, [categorias]);

  const linkDaCategoria = (categoria: BoutiqueCategoria) => `/${slug}/c/${categoria.slug || categoria.id}`;

  /* Uma árvore só para o menu do computador e a gaveta do celular. */
  const nosDoMenu = useMemo<NoDoMenu[]>(() => {
    const link = (categoria: BoutiqueCategoria) => `/${slug}/c/${categoria.slug || categoria.id}`;
    const dasCategorias = nosDasCategorias(arvore, link);
    if (menuProprio && menuProprio.itens.length) {
      const proprios = nosDoMenuProprio(itensProntos(menuProprio.itens, { slug, categorias, paginas: navegacao?.paginas }));
      return menuProprio.mostrar_categorias !== false && dasCategorias.length
        ? [...proprios, { id: "__categorias__", rotulo: "Categorias", href: null, filhos: dasCategorias }]
        : proprios;
    }
    return dasCategorias;
  }, [arvore, categorias, menuProprio, navegacao?.paginas, slug]);

  const itemAtivo = (no: NoDoMenu) => {
    if (no.href && caminho && caminho.replace(/\/+$/, "") === no.href.replace(/\/+$/, "") && no.href !== `/${slug}`) return true;
    return Boolean(categoriaAtual) && Boolean(no.ids?.includes(String(categoriaAtual)));
  };

  /* Espera o dedo parar antes de perguntar ao servidor. */
  useEffect(() => {
    const termo = busca.trim();
    /* Termo curto demais não vira consulta: a lista some sozinha porque o
       render já ignora sugestão com menos de dois caracteres. */
    if (termo.length < 2) return;
    let ativo = true;
    const relogio = window.setTimeout(() => {
      fetch(`/api/catalog/busca?slug=${encodeURIComponent(slug)}&q=${encodeURIComponent(termo)}`)
        .then((r) => (r.ok ? r.json() : { produtos: [] }))
        .then((j) => {
          if (!ativo) return;
          setSugestoes(Array.isArray(j?.produtos) ? j.produtos : []);
          setTextoSemPreco(typeof j?.texto_sem_preco === "string" ? j.texto_sem_preco : null);
        })
        .catch(() => {
          if (ativo) setSugestoes([]);
        });
    }, 220);
    return () => {
      ativo = false;
      window.clearTimeout(relogio);
    };
  }, [busca, slug]);

  /* A busca aberta fecha com Esc e com clique fora do painel. */
  useEffect(() => {
    if (!buscaAberta) return;
    const fora = (evento: MouseEvent) => {
      const alvo = evento.target as Node;
      if (!caixaDaBusca.current?.contains(alvo) && !caixaDaBuscaComputador.current?.contains(alvo)) setBuscaAberta(false);
    };
    const esc = (evento: KeyboardEvent) => {
      if (evento.key === "Escape") setBuscaAberta(false);
    };
    document.addEventListener("mousedown", fora);
    document.addEventListener("keydown", esc);
    const foco = window.setTimeout(() => {
      const celular = window.matchMedia("(max-width: 1023px)").matches;
      (celular ? campoDaBuscaCelular.current : campoDaBusca.current)?.focus();
    }, 80);
    return () => {
      document.removeEventListener("mousedown", fora);
      document.removeEventListener("keydown", esc);
      window.clearTimeout(foco);
    };
  }, [buscaAberta]);

  /* Rolou a página: a barra ganha fundo (deixa de ser transparente na home). */
  useEffect(() => {
    const medir = () => setRolado(window.scrollY > 8);
    medir();
    window.addEventListener("scroll", medir, { passive: true });
    return () => window.removeEventListener("scroll", medir);
  }, []);

  /* Deixa registrado que esta loja é Boutique: o esqueleto de carregamento do
     sistema consulta isso para não piscar numa navegação deste template. */
  useEffect(() => {
    try {
      window.localStorage.setItem("ks_tema_da_loja", "boutique");
    } catch {
      /* Sem armazenamento, o esqueleto aparece — feio, não quebrado. */
    }
  }, []);

  /* A gaveta não pode deixar a página rolar por baixo dela. */
  useEffect(() => {
    if (!menuAberto) return;
    const antes = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = antes;
    };
  }, [menuAberto]);

  /* A galeria da página de produto se fixa logo abaixo do cabeçalho, e a
     altura dele muda: o aviso pode estar desligado, e no celular ele ocupa
     duas linhas. Medir é mais barato que manter um número cravado. */
  useEffect(() => {
    const alvo = cabecalho.current;
    const casa = raiz.current;
    if (!alvo || !casa) return;
    const publicar = () => {
      const altura = Math.round(alvo.getBoundingClientRect().height);
      if (altura > 0) casa.style.setProperty("--bq-header-h", cabecalhoFixo ? `${altura}px` : "0px");
    };
    publicar();
    const observador = new ResizeObserver(publicar);
    observador.observe(alvo);
    window.addEventListener("resize", publicar);
    return () => {
      observador.disconnect();
      window.removeEventListener("resize", publicar);
    };
  }, [cabecalhoFixo]);

  function buscar(evento?: React.FormEvent) {
    evento?.preventDefault();
    const termo = busca.trim();
    if (!termo) return;
    router.push(`/${slug}/buscar?search=${encodeURIComponent(termo)}`);
    setBuscaAberta(false);
    setMenuAberto(false);
  }

  const whatsappLimpo = String(contato.whatsapp || "").replace(/\D/g, "");
  const linkDoWhats = whatsappLimpo
    ? `https://wa.me/${whatsappLimpo.length <= 11 ? `55${whatsappLimpo}` : whatsappLimpo}`
    : null;

  /* A faixa é montada uma vez e usada num dos dois lugares: dentro do
     cabeçalho grudado, ou acima dele. */
  const faixaDeAviso = (
    <BoutiqueAviso
      slug={slug}
      ligado={ap.avisoLigado}
      texto={ap.avisoTexto}
      corDeFundo={ap.avisoCorDeFundo}
      corDoTexto={ap.avisoCorDoTexto}
      botaoRotulo={ap.avisoBotaoRotulo}
      botaoUrl={ap.avisoBotaoUrl}
      cupom={cupomDoAviso}
      mostrarSelo={ap.avisoMostrarSelo}
      corDoSelo={ap.avisoCorDoSelo}
      contagemLigada={ap.avisoContagemLigada}
      contagemAte={ap.avisoContagemAte}
    />
  );

  const alturaDaLogo = tamanhoDaLogo === "pequeno" ? 44 : tamanhoDaLogo === "grande" ? 74 : 62;
  const alturaDaLogoCelular = tamanhoDaLogo === "pequeno" ? 30 : tamanhoDaLogo === "grande" ? 46 : 38;

  const logo = (celular: boolean) => (
    <Link
      href={`/${slug}`}
      aria-label={`${nomeDaLoja} — página inicial`}
      /* No computador a logo vai até 250 px e nunca passa da coluna dela
         (100cqw = a largura de .bq-logo-e-nome): com o menu cheio, em 1024 a
         1280 px, a imagem vazava por cima do menu e do slogan. */
      className={`flex items-center ${celular ? "max-w-[46vw] shrink-0 justify-center" : "shrink-0"}`}
      style={{ height: celular ? alturaDaLogoCelular : alturaDaLogo }}
    >
      {logoUrl ? (
        /* eslint-disable-next-line @next/next/no-img-element */
        <img
          src={logoUrl}
          alt={nomeDaLoja}
          className={`h-full w-auto max-w-full object-contain ${celular ? "" : "bq-logo-da-coluna"} ${classeDoEfeito}`}
        />
      ) : (
        <span
          className={`bq-revista truncate leading-none ${celular ? "text-[26px]" : "text-[34px]"} ${classeDoEfeito}`}
          style={{ letterSpacing: "-0.03em" }}
        >
          {nomeDaLoja}
        </span>
      )}
    </Link>
  );

  const corDoIcone = corDosIcones ? { color: corDosIcones } : undefined;

  /* As sugestões: miniatura, nome e o preço em verde, como na referência. */
  const listaDeSugestoes =
    buscaAberta && busca.trim().length >= 2 && sugestoes.length ? (
      <div
        className="absolute inset-x-0 top-full z-10 max-h-[70vh] overflow-y-auto bg-white text-left"
        style={{ boxShadow: "0 12px 30px rgba(0,0,0,0.12)" }}
      >
        {sugestoes.map((produto) => {
          const semPreco = produto.price_on_request === true;
          return (
            <Link
              key={produto.id}
              href={`/${slug}/p/${produto.id}`}
              onClick={() => {
                setBuscaAberta(false);
                setBusca("");
              }}
              className="flex h-[49px] items-center gap-4 border-b px-4 transition-colors hover:bg-[#fafafa]"
              style={{ borderColor: "#f0f0f0" }}
            >
              <span className="flex h-10 w-[30px] shrink-0 items-center justify-center overflow-hidden">
                {produto.image_url ? (
                  /* eslint-disable-next-line @next/next/no-img-element */
                  <img src={produto.image_url} alt="" className="h-full w-full object-contain" />
                ) : null}
              </span>
              <span className="min-w-0 flex-1 truncate text-[12px] text-[#333333]">{produto.name}</span>
              <span className="shrink-0 text-[12px] font-bold" style={{ color: semPreco ? "#666666" : "var(--bq-verde)" }}>
                {semPreco ? textoSemPreco || "" : moedaDaBoutique(precoDaSugestao(produto))}
              </span>
            </Link>
          );
        })}
        <button
          type="button"
          onClick={() => buscar()}
          className="flex h-11 w-full items-center justify-center text-[11px] font-bold uppercase tracking-[0.1em] text-[#111111] hover:bg-[#fafafa]"
        >
          Ver todos os resultados →
        </button>
      </div>
    ) : null;

  return (
    <div
      ref={raiz}
      /* A barra fixa de compra é montada aqui dentro por portal: é neste
         elemento que moram as variáveis `--bq-*` da loja. No `body` ela
         chegava sem cor nenhuma. */
      data-bq-raiz="1"
      /* Cabeçalho com cor escolhida na personalização avançada não fica
         transparente sobre o banner: a cor (e a do texto, pensada para ela)
         é decisão do lojista. */
      {...(fundoDoCabecalho ? { "data-bq-cor-no-cabecalho": "" } : {})}
      className="bq-fonte-base flex min-h-screen flex-col overflow-x-clip"
      style={
        {
          /* Ação é preta, como na referência. A cor da marca entra como acento
             só com a personalização avançada ligada. */
          ...(avancado && corDaMarca ? { "--bq-accent": corDaMarca } : {}),
          ...(ap.corDoFundo?.trim() ? { "--bq-bg": ap.corDoFundo.trim() } : {}),
          ...(fundoDoCabecalho ? { "--bq-header-bg": fundoDoCabecalho } : {}),
          ...(textoDoCabecalho ? { "--bq-header-fg": textoDoCabecalho } : {}),
          ...(fundoDoMenu ? { "--bq-menu-bg": fundoDoMenu } : {}),
          ...(textoDoMenu ? { "--bq-menu-fg": textoDoMenu } : {}),
          /* Painel e gaveta pintados com a cor do menu: o destaque usa a cor
             do texto do menu (o vermelho sumia no fundo colorido). */
          ...(fundoDoMenu ? { "--bq-destaque-no-painel": "var(--bq-menu-fg)" } : {}),
          ...(ap.corDoBotaoDoCard?.trim() ? { "--bq-card-button": ap.corDoBotaoDoCard.trim() } : {}),
          /* Rodapé com cor do lojista: o acordeão do celular puxa um tom da
             própria cor (o cinza #CCD1D7 da referência sumia o texto claro). */
          ...(ap.corDoRodape?.trim()
            ? {
                "--bq-footer-bg": ap.corDoRodape.trim(),
                "--bq-footer-acordeao": "color-mix(in srgb, var(--bq-footer-bg) 90%, var(--bq-footer-fg))",
                "--bq-footer-border": "color-mix(in srgb, var(--bq-footer-bg) 80%, var(--bq-footer-fg))",
              }
            : {}),
          ...(ap.corDoTextoDoRodape?.trim()
            ? { "--bq-footer-fg": ap.corDoTextoDoRodape.trim(), "--bq-footer-titulo": ap.corDoTextoDoRodape.trim() }
            : ap.corDoRodape?.trim()
              ? { "--bq-footer-fg": textoSobre(ap.corDoRodape.trim()), "--bq-footer-titulo": textoSobre(ap.corDoRodape.trim()) }
              : {}),
          ...(ap.corDaBarraDeCompra?.trim() ? { "--bq-barra-de-compra-bg": ap.corDaBarraDeCompra.trim() } : {}),
          ...(ap.corDoBotaoDaBarraDeCompra?.trim() ? { "--bq-barra-de-compra-botao": ap.corDoBotaoDaBarraDeCompra.trim() } : {}),
          ...(ap.corDoTextoDaBarraDeCompra?.trim() ? { "--bq-barra-de-compra-texto": ap.corDoTextoDaBarraDeCompra.trim() } : {}),
          /* As fontes NÃO vêm da personalização: a tipografia é a identidade
             da Boutique — Montserrat em tudo e Georgia nos títulos de revista,
             exatamente como a referência (pedido do dono, 21/10/2026). As
             cores da personalização continuam valendo. */
          "--bq-logo-forca": String(forca),
          "--bq-logo-tempo-rotate": `${(5.2 * tempo).toFixed(2)}s`,
          "--bq-logo-tempo-pulse": `${(3.2 * tempo).toFixed(2)}s`,
          "--bq-logo-tempo-blink": `${(2.4 * tempo).toFixed(2)}s`,
          backgroundColor: "var(--bq-bg)",
          color: "var(--bq-foreground)",
        } as React.CSSProperties
      }
    >
      {/* As fontes da Boutique (sempre: a personalização não troca a
          tipografia deste tema). */}
      <link rel="stylesheet" href={FONTES_DA_BOUTIQUE} precedence="default" />

      {!avisoFixo ? faixaDeAviso : null}

      <header
        ref={cabecalho}
        {...marcaDeRegiao(noEditor, "cabecalho")}
        className={`${cabecalhoFixo ? "sticky top-[-1px]" : "relative"} z-40`}
      >
        {/* A situação da loja (pausa, cadastro em análise). Fica grudada junto
            com o cabeçalho: muda o que o visitante pode fazer na página. */}
        {avisoDaSituacao ? (
          <div
            className="border-b px-4 py-1.5 text-center text-[12.5px] font-medium leading-snug"
            style={{ backgroundColor: "#fffbeb", color: "#92400e", borderColor: "#fde68a" }}
            role="status"
          >
            <span className="inline-flex max-w-full items-center gap-1.5">
              <Info className="h-3.5 w-3.5 shrink-0" />
              <span>{avisoDaSituacao}</span>
            </span>
          </div>
        ) : null}

        {avisoFixo ? faixaDeAviso : null}

        <div className="bq-barra relative" {...(rolado || buscaAberta ? { "data-rolado": "" } : {})}>
          {/* Computador */}
          <div className="mx-auto hidden h-full w-full max-w-[1700px] items-center gap-6 px-[60px] lg:grid lg:grid-cols-[minmax(180px,1fr)_auto_minmax(180px,1fr)]">
            {/* O nome/slogan ao lado da logo só aparece quando sobra lugar para
                ele (consulta de contêiner em theme.css, .bq-nome-ao-lado): com o
                menu cheio a coluna fica estreita e o texto virava uma tripa. */}
            <div className="bq-logo-e-nome flex min-w-0 items-center gap-3">
              {logo(false)}
              {mostrarNome || mostrarSlogan ? (
                <span className="bq-nome-ao-lado min-w-0 max-w-[200px] flex-1">
                  {mostrarNome && logoUrl ? (
                    <span className="block truncate text-[15px] font-semibold leading-tight" style={ap.corDoNome ? { color: ap.corDoNome } : undefined}>
                      {nomeDaLoja}
                    </span>
                  ) : null}
                  {mostrarSlogan ? (
                    <span className="line-clamp-2 text-[11.5px] leading-tight opacity-75" style={ap.corDoSlogan ? { color: ap.corDoSlogan } : undefined}>
                      {slogan}
                    </span>
                  ) : null}
                </span>
              ) : null}
            </div>

            <nav aria-label="Menu principal" className="h-full min-w-0">
              <BoutiqueMenuDoComputador nos={nosDoMenu} ativo={itemAtivo} />
            </nav>

            <div className="flex items-center justify-end gap-8" style={corDoIcone}>
              {mostrarBusca ? (
                <button type="button" aria-label="Buscar" onClick={() => setBuscaAberta(true)} className="transition-opacity hover:opacity-60">
                  <Search className="h-[22px] w-[22px]" strokeWidth={1.5} />
                </button>
              ) : null}

              {/* A conta: o balão aparece com o mouse em cima. */}
              <div className="group/conta relative">
                <Link
                  href={clienteLogado ? `/${slug}/cliente` : `/${slug}/cliente/login`}
                  aria-label={clienteLogado ? "Minha conta" : "Entrar"}
                  className="flex h-[30px] w-[30px] items-center justify-center border border-transparent transition-colors group-hover/conta:border-[#e1e1e1] group-hover/conta:bg-white group-hover/conta:text-black"
                >
                  <User className="h-[22px] w-[22px]" strokeWidth={1.5} />
                </Link>
                <div className="invisible absolute right-[-12px] top-full z-50 pt-3 opacity-0 transition-[opacity,visibility] duration-200 group-hover/conta:visible group-hover/conta:opacity-100 group-focus-within/conta:visible group-focus-within/conta:opacity-100">
                  <div className="w-[240px] border bg-white p-5 text-center text-[#252525]" style={{ borderColor: "#e8eaed", boxShadow: "var(--bq-shadow-balao)" }}>
                    {clienteLogado ? (
                      <>
                        <p className="text-[13px] font-semibold">Olá! Que bom ter você aqui.</p>
                        <Link href={`/${slug}/cliente`} className="mt-3 flex h-10 items-center justify-center bg-black text-[12px] font-semibold uppercase tracking-[0.08em] text-white transition-colors hover:bg-[#333333]">
                          Minha conta
                        </Link>
                        <p className="mt-3 text-[12px]">
                          Seus pedidos?{" "}
                          <Link href={`/${slug}/cliente`} className="font-bold underline underline-offset-2">
                            acompanhe aqui
                          </Link>
                        </p>
                      </>
                    ) : (
                      <>
                        <p className="text-[13px] leading-snug">
                          Já possui cadastro?
                          <br />
                          <span className="font-semibold">Acesse sua conta</span>
                        </p>
                        <Link href={`/${slug}/cliente/login`} className="mt-3 flex h-10 items-center justify-center bg-black text-[12px] font-semibold uppercase tracking-[0.08em] text-white transition-colors hover:bg-[#333333]">
                          Entrar
                        </Link>
                        <p className="mt-3 text-[12px]">
                          Cliente novo?{" "}
                          <Link href={`/${slug}/cadastro-cliente`} className="font-bold underline underline-offset-2">
                            Cadastrar
                          </Link>
                        </p>
                        <p className="mt-1 text-[12px]">
                          Consulte o seu pedido{" "}
                          <Link href={`/${slug}/cliente`} className="font-bold underline underline-offset-2">
                            acompanhe aqui
                          </Link>
                        </p>
                      </>
                    )}
                  </div>
                </div>
              </div>

              <button type="button" onClick={toggleCart} aria-label="Abrir carrinho" className="relative transition-opacity hover:opacity-60">
                <ShoppingCart className="h-[22px] w-[22px]" strokeWidth={1.5} />
                <span
                  className="absolute -right-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-semibold leading-none"
                  style={{ backgroundColor: "var(--bq-primary)", color: "var(--bq-primary-fg)" }}
                >
                  {cartCount}
                </span>
              </button>
            </div>
          </div>

          {/* Celular */}
          <div className="flex h-full items-center justify-between px-3 lg:hidden" style={corDoIcone}>
            {buscaAberta ? (
              <div ref={caixaDaBusca} className="relative flex w-full items-center gap-2">
                <button type="button" aria-label="Fechar busca" onClick={() => setBuscaAberta(false)} className="p-1">
                  <ChevronLeft className="h-7 w-7" strokeWidth={1.5} />
                </button>
                <form onSubmit={buscar} className="relative flex-1">
                  <Search className="pointer-events-none absolute left-4 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-[#9a9a9a]" strokeWidth={1.6} />
                  <input
                    ref={campoDaBuscaCelular}
                    value={busca}
                    onChange={(e) => setBusca(e.target.value)}
                    enterKeyHint="search"
                    placeholder="Procurando algo? Pesquise aqui..."
                    aria-label="Buscar produtos"
                    className="h-11 w-full rounded-full border bg-white pl-11 pr-4 text-[14px] text-[#111111] outline-none placeholder:text-[#9a9a9a]"
                    style={{ borderColor: "#d9d9d9" }}
                  />
                </form>
                <div className="absolute inset-x-[-12px] top-[calc(100%+12px)]">{listaDeSugestoes}</div>
              </div>
            ) : (
              <>
                <div className="flex flex-1 items-center gap-1">
                  <button type="button" aria-label="Abrir menu" onClick={() => setMenuAberto(true)} className="p-1.5">
                    <Menu className="h-7 w-7" strokeWidth={1.4} />
                  </button>
                  <Link href={clienteLogado ? `/${slug}/cliente` : `/${slug}/cliente/login`} aria-label={clienteLogado ? "Minha conta" : "Entrar"} className="p-1.5">
                    <User className="h-7 w-7" strokeWidth={1.4} />
                  </Link>
                </div>
                {logo(true)}
                <div className="flex flex-1 items-center justify-end gap-1">
                  {mostrarBusca ? (
                    <button type="button" aria-label="Buscar" onClick={() => setBuscaAberta(true)} className="p-1.5">
                      <Search className="h-7 w-7" strokeWidth={1.4} />
                    </button>
                  ) : null}
                  <button type="button" onClick={toggleCart} aria-label="Abrir carrinho" className="relative p-1.5">
                    <ShoppingBag className="h-7 w-7" strokeWidth={1.4} fill="currentColor" />
                    <span className="absolute inset-x-0 bottom-[9px] text-center text-[11px] font-semibold leading-none" style={{ color: "var(--bq-header-bg, #ffffff)" }}>
                      {cartCount}
                    </span>
                  </button>
                </div>
              </>
            )}
          </div>

          {/* A busca do computador desce sobre a barra. */}
          {mostrarBusca ? (
            <div
              className={`absolute inset-x-0 top-0 z-50 hidden lg:block ${buscaAberta ? "visible" : "invisible"}`}
              style={{
                transform: buscaAberta ? "translateY(0)" : "translateY(-20px)",
                opacity: buscaAberta ? 1 : 0,
                transition: "transform 0.5s var(--bq-curva), opacity 0.3s ease, visibility 0s linear " + (buscaAberta ? "0s" : "0.5s"),
              }}
            >
              <div className="mx-[60px] mt-[10px] bg-white px-6" style={{ boxShadow: "0 10px 30px rgba(0,0,0,0.08)" }}>
                <div className="relative mx-auto max-w-[1100px] pb-6 pt-10">
                  <div className="relative" ref={caixaDaBuscaComputador}>
                    <label htmlFor="bq-busca" className="mb-2 block text-[14px] text-[#6c7484]">
                      O que você está procurando?
                    </label>
                    <form onSubmit={buscar} className="flex items-center gap-4">
                      <input
                        id="bq-busca"
                        ref={campoDaBusca}
                        value={busca}
                        onChange={(e) => setBusca(e.target.value)}
                        enterKeyHint="search"
                        autoComplete="off"
                        className="h-10 min-w-0 flex-1 bg-transparent text-[18px] text-[#111111] outline-none"
                      />
                      {busca ? (
                        <button type="button" aria-label="Limpar" onClick={() => setBusca("")} className="text-[#9a9a9a] hover:text-black">
                          <X className="h-5 w-5" strokeWidth={1.4} />
                        </button>
                      ) : null}
                      <button type="submit" aria-label="Buscar" className="text-[#111111] hover:opacity-60">
                        <Search className="h-5 w-5" strokeWidth={1.5} />
                      </button>
                    </form>
                    {listaDeSugestoes}
                    <button
                      type="button"
                      aria-label="Fechar busca"
                      onClick={() => setBuscaAberta(false)}
                      className="absolute -top-6 right-[-10px] text-[#111111] hover:opacity-60"
                    >
                      <X className="h-5 w-5" strokeWidth={1.5} />
                    </button>
                  </div>
                </div>
              </div>
            </div>
          ) : null}
        </div>
      </header>

      <BoutiqueGaveta aberta={menuAberto} aoFechar={() => setMenuAberto(false)} nos={nosDoMenu} slug={slug} clienteLogado={clienteLogado} />

      <main className="flex-1">{children}</main>

      <BoutiqueRodape
        regiao={marcaDeRegiao(noEditor, "rodape")}
        slug={slug}
        nomeDaLoja={nomeDaLoja}
        logoUrl={logoUrl}
        /* Rodapé escuro: a logo sai em branco para não sumir no fundo. */
        logoClara={Boolean(ap.corDoRodape?.trim() && textoSobre(ap.corDoRodape.trim()) === "#ffffff")}
        contato={contato}
        redes={{
          instagram: ap.redes?.instagram ?? contato.instagram ?? null,
          facebook: ap.redes?.facebook ?? contato.facebook ?? null,
          twitter: ap.redes?.twitter ?? null,
          linkedin: ap.redes?.linkedin ?? null,
          youtube: ap.redes?.youtube ?? null,
        }}
        linkDoWhats={linkDoWhats}
        categorias={arvore}
        linkDaCategoria={linkDaCategoria}
        paginas={navegacao?.paginas ?? []}
        garantias={ap.tarjaLigada === false ? [] : ap.tarjaItens || []}
        publicado={
          rodapeProprio
            ? {
                sobre: rodapeProprio.sobre,
                colunas: rodapeProprio.colunas.map((coluna) => ({
                  id: coluna.id,
                  titulo: coluna.titulo,
                  links: itensProntos(coluna.links, { slug, categorias, paginas: navegacao?.paginas })
                    .filter((link) => link.destino)
                    .map((link) => ({ id: link.id, rotulo: link.rotulo, destino: link.destino! })),
                })),
                mostrarContato: rodapeProprio.mostrar_contato,
                mostrarRedes: rodapeProprio.mostrar_redes,
                mostrarPagamento: rodapeProprio.mostrar_pagamento,
                mostrarCategorias: rodapeProprio.mostrar_categorias,
              }
            : null
        }
      />

      {/* O WhatsApp: aba "Fale conosco" colada no canto do computador e
          círculo no celular. O balão do chat da loja mede o que flutua no
          canto e se coloca ao lado (ChatDaLoja). */}
      {whatsappFlutuante !== false && linkDoWhats ? (
        <>
          <a
            href={linkDoWhats}
            target="_blank"
            rel="noopener noreferrer"
            data-flutuante-da-loja=""
            className="bq-whats fixed bottom-0 right-[10px] z-40 hidden h-8 items-center gap-2 rounded-t-[10px] px-[30px] text-[14px] text-white transition-[filter] hover:brightness-110 lg:flex"
            style={{ backgroundColor: "var(--bq-whats)" }}
          >
            <IconeDoWhatsApp className="h-[15px] w-[15px]" />
            Fale conosco
          </a>
          <a
            href={linkDoWhats}
            target="_blank"
            rel="noopener noreferrer"
            aria-label="Falar no WhatsApp"
            data-flutuante-da-loja=""
            className="bq-whats fixed bottom-[calc(20px+env(safe-area-inset-bottom))] right-[10px] z-40 flex h-[55px] w-[55px] items-center justify-center rounded-full text-white shadow-[0_6px_18px_rgba(0,0,0,0.18)] lg:hidden"
            style={{ backgroundColor: "var(--bq-whats)" }}
          >
            <IconeDoWhatsApp className="h-[30px] w-[30px]" />
          </a>
        </>
      ) : null}
    </div>
  );
}

function IconeDoWhatsApp({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 24 24" className={className} fill="currentColor" aria-hidden="true">
      <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z" />
    </svg>
  );
}

/** 2026-10-13 vira 13/10/2026, como a faixa dos outros temas. */
function dataCurta(valor: string) {
  const partes = String(valor).slice(0, 10).split("-");
  if (partes.length !== 3) return valor;
  return `${partes[2]}/${partes[1]}/${partes[0]}`;
}
