"use client";

import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
import Link from "next/link";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { trackCatalogEvent } from "@/lib/catalog-analytics";
import { destinoDoLink } from "@/lib/destino-do-link";
import { BoutiqueCardDoProduto, fotoDoProduto, segundaFotoDoProduto, type BoutiqueProduto } from "./BoutiqueCardDoProduto";
import type { BoutiqueCategoria } from "./BoutiqueShell";

/*
  Os blocos da home da Boutique, no desenho da referência:

    - BoutiqueHero: banner de largura total que sobe por baixo do cabeçalho,
      setas quadradas brancas nas bordas, troca a cada 10 s, sem bolinhas;
      vídeo toca mudo e em loop; a arte do celular entra quando existe;
    - BoutiqueTituloDeVitrine: o "eyebrow" pequeno e espaçado, o título
      enorme em caixa alta à esquerda e a linha de borda a borda embaixo;
    - BoutiqueCarrossel: quatro cards por tela (dois no celular, arrastando),
      anda sozinho a cada 3 s e para com o mouse em cima, setas redondas e
      pontos;
    - BoutiqueTopSellers: título centralizado com abas das categorias
      principais trocando a grade com fade;
    - BoutiqueCardsDeCategoria: as duas categorias com foto, lado a lado, com
      degradê e o título em Georgia.

  A home montada pelo editor (BoutiqueSecoesDaHome) usa os mesmos blocos.
*/

export type BoutiqueBanner = {
  id: string;
  image_url: string;
  /* A arte vertical do celular, quando o banner tiver uma. */
  image_url_mobile?: string | null;
  link_type?: "none" | "product" | "url" | null;
  product_id?: string | null;
  link_url?: string | null;
  open_in_new_tab?: boolean | null;
};

export const PRODUTOS_POR_SECAO = 4;

/* Banner que é vídeo: o endereço termina em .mp4, .webm ou .mov. */
function ehVideo(url: string | null | undefined) {
  return /\.(mp4|webm|mov|m4v)(\?|#|$)/i.test(String(url || ""));
}

export function LinkDoBanner({
  banner,
  slug,
  children,
  className,
  style,
}: {
  banner: BoutiqueBanner;
  slug: string;
  children: ReactNode;
  className?: string;
  style?: CSSProperties;
}) {
  if (banner.link_type === "product" && banner.product_id) {
    return (
      <Link href={`/${slug}/p/${banner.product_id}`} className={className} style={style}>
        {children}
      </Link>
    );
  }
  /* O lojista digita o endereço à mão: sem o https:// ele viraria pasta da
     própria loja (ver lib/destino-do-link.ts). */
  const destino = banner.link_type === "url" ? destinoDoLink(banner.link_url) : null;
  if (destino && !destino.externo) {
    return (
      <Link
        href={destino.href}
        target={banner.open_in_new_tab ? "_blank" : undefined}
        rel={banner.open_in_new_tab ? "noopener noreferrer" : undefined}
        className={className}
        style={style}
      >
        {children}
      </Link>
    );
  }
  if (destino) {
    return (
      <a
        href={destino.href}
        target={banner.open_in_new_tab ? "_blank" : undefined}
        rel={banner.open_in_new_tab ? "noopener noreferrer" : undefined}
        className={className}
        style={style}
      >
        {children}
      </a>
    );
  }
  return (
    <div className={className} style={style}>
      {children}
    </div>
  );
}

/* ------------------------------------------------------------------ hero */

function MidiaDoBanner({
  url,
  primeiro,
  aoMedir,
  className,
}: {
  url: string;
  primeiro: boolean;
  aoMedir?: (proporcao: number) => void;
  className?: string;
}) {
  if (ehVideo(url)) {
    return (
      <video
        src={url}
        muted
        autoPlay
        loop
        playsInline
        preload={primeiro ? "auto" : "metadata"}
        className={`absolute inset-0 h-full w-full object-cover ${className || ""}`}
        onLoadedMetadata={(e) => {
          const v = e.currentTarget;
          if (aoMedir && v.videoWidth && v.videoHeight) aoMedir(v.videoWidth / v.videoHeight);
        }}
      />
    );
  }
  return (
    /* eslint-disable-next-line @next/next/no-img-element */
    <img
      src={url}
      alt=""
      loading={primeiro ? "eager" : "lazy"}
      fetchPriority={primeiro ? "high" : undefined}
      className={`absolute inset-0 h-full w-full object-cover ${className || ""}`}
      onLoad={(e) => {
        const img = e.currentTarget;
        if (aoMedir && img.naturalWidth && img.naturalHeight) aoMedir(img.naturalWidth / img.naturalHeight);
      }}
    />
  );
}

/* O banner principal. Desliza por transform (sempre pousa num quadro
   inteiro), troca a cada 10 s e para com o mouse em cima. A altura segue a
   proporção da arte que a loja subiu (entre 16:9 e 4:1), para o texto da
   arte não ser cortado; a referência usa 1920×800 (2,4:1). */
export function BoutiqueHero({ banners, slug }: { banners: BoutiqueBanner[]; slug: string }) {
  const [indice, setIndice] = useState(0);
  const [parado, setParado] = useState(false);
  const [proporcao, setProporcao] = useState<number | null>(null);
  const [proporcaoCelular, setProporcaoCelular] = useState<number | null>(null);
  const toque = useRef<number | null>(null);
  const total = banners.length;
  const temArteDoCelular = banners.some((b) => b.image_url_mobile);

  useEffect(() => {
    if (total <= 1 || parado) return;
    const relogio = window.setInterval(() => setIndice((i) => (i + 1) % total), 10000);
    return () => window.clearInterval(relogio);
  }, [total, parado]);

  if (!total) return null;

  const ir = (passo: number) => setIndice((i) => (i + passo + total) % total);
  const limitar = (p: number) => Math.min(4, Math.max(16 / 9, p));
  const limitarCelular = (p: number) => Math.min(4, Math.max(0.5, p));

  return (
    <section
      data-bq-hero=""
      /* No celular o banner só passa por baixo do cabeçalho quando tem a arte
         vertical: a arte deitada fica baixa demais e o cabeçalho a cobriria. */
      {...(temArteDoCelular ? { "data-bq-hero-celular": "" } : {})}
      className="bq-sob-o-cabecalho relative w-full overflow-hidden bg-[var(--bq-foto-fundo)]"
      onMouseEnter={() => setParado(true)}
      onMouseLeave={() => setParado(false)}
      onTouchStart={(e) => {
        toque.current = e.touches[0]?.clientX ?? null;
        setParado(true);
      }}
      onTouchEnd={(e) => {
        const inicio = toque.current;
        const fim = e.changedTouches[0]?.clientX ?? null;
        toque.current = null;
        setParado(false);
        if (inicio === null || fim === null || Math.abs(fim - inicio) < 40) return;
        ir(fim < inicio ? 1 : -1);
      }}
      aria-roledescription="carrossel"
    >
      {/* A caixa: proporção da arte do computador; no celular, a da arte do
          celular quando existe. */}
      <div
        className="relative w-full"
        style={
          {
            "--bq-hero-proporcao": String(limitar(proporcao || 2.4)),
            "--bq-hero-proporcao-celular": String(
              temArteDoCelular ? limitarCelular(proporcaoCelular || 588 / 1164) : limitar(proporcao || 2.4)
            ),
          } as CSSProperties
        }
      >
        <div className="aspect-[var(--bq-hero-proporcao-celular)] w-full lg:aspect-[var(--bq-hero-proporcao)]" />
        <div
          className="absolute inset-0 flex will-change-transform"
          style={{ transform: `translateX(-${indice * 100}%)`, transition: "transform 0.6s var(--bq-curva)" }}
        >
          {banners.map((banner, i) => (
            <LinkDoBanner key={banner.id} banner={banner} slug={slug} className="relative block h-full min-w-full shrink-0">
              <MidiaDoBanner
                url={banner.image_url}
                primeiro={i === 0}
                aoMedir={i === 0 ? setProporcao : undefined}
                className={banner.image_url_mobile ? "hidden lg:block" : ""}
              />
              {banner.image_url_mobile ? (
                <MidiaDoBanner
                  url={banner.image_url_mobile}
                  primeiro={i === 0}
                  aoMedir={i === 0 ? setProporcaoCelular : undefined}
                  className="lg:hidden"
                />
              ) : null}
            </LinkDoBanner>
          ))}
        </div>
      </div>

      {total > 1 ? (
        <>
          <button
            type="button"
            aria-label="Banner anterior"
            onClick={() => ir(-1)}
            className="absolute left-0 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center bg-white/70 text-[#444444] transition-colors hover:bg-white lg:h-11 lg:w-11 lg:bg-white"
          >
            <ChevronLeft className="h-5 w-5" strokeWidth={1.4} />
          </button>
          <button
            type="button"
            aria-label="Próximo banner"
            onClick={() => ir(1)}
            className="absolute right-0 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center bg-white/70 text-[#444444] transition-colors hover:bg-white lg:h-11 lg:w-11 lg:bg-white"
          >
            <ChevronRight className="h-5 w-5" strokeWidth={1.4} />
          </button>
        </>
      ) : null}
    </section>
  );
}

/* --------------------------------------------------------------- títulos */

/** O título de vitrine: eyebrow + título enorme à esquerda + linha embaixo. */
export function BoutiqueTituloDeVitrine({
  eyebrow,
  titulo,
  apoio,
  lado,
}: {
  eyebrow?: string | null;
  titulo: string;
  /* Um texto curto opcional à direita (11 px, até 390 px). */
  apoio?: string | null;
  /* Algo à direita do título no lugar do texto (o relógio da oferta). */
  lado?: ReactNode;
}) {
  return (
    <div className="border-b" style={{ borderColor: "var(--bq-linha)" }}>
      <div className="flex flex-wrap items-end justify-between gap-x-6 gap-y-4 px-4 pb-[22px] pt-[46px] md:pb-[30px] md:pl-6 md:pr-10 md:pt-[68px]">
        <div className="min-w-0">
          {eyebrow ? (
            <p className="mb-4 text-[9px] font-medium uppercase leading-none tracking-[0.25em] md:mb-[22px] md:text-[13px] md:tracking-[0.28em]" style={{ color: "var(--bq-eyebrow)" }}>
              {eyebrow}
            </p>
          ) : null}
          <h2 className="bq-titulo-secao">{titulo}</h2>
        </div>
        {lado ?? null}
        {apoio && !lado ? (
          <p className="hidden max-w-[390px] text-right text-[11px] leading-relaxed md:block" style={{ color: "var(--bq-benef)" }}>
            {apoio}
          </p>
        ) : null}
      </div>
    </div>
  );
}

/** O título "de revista" dos blocos de conteúdo (cupom, perguntas, onde
 *  estamos, vídeo...): eyebrow espaçado e o título em Georgia. */
export function BoutiqueTituloEditorial({
  eyebrow,
  titulo,
  centro = false,
  claro = false,
  className = "",
}: {
  eyebrow?: string | null;
  titulo?: string | null;
  centro?: boolean;
  /* Sobre foto ou fundo escuro. */
  claro?: boolean;
  className?: string;
}) {
  const texto = String(titulo || "").trim();
  if (!texto && !eyebrow) return null;
  return (
    <div className={`${centro ? "text-center" : ""} ${className}`}>
      {eyebrow ? (
        <p className={`text-[9px] font-bold uppercase tracking-[0.2em] md:text-[10px] ${claro ? "text-white/85" : "text-[var(--bq-tinta)]"}`}>{eyebrow}</p>
      ) : null}
      {texto ? (
        <h2 className={`bq-revista bq-titulo-editorial ${eyebrow ? "mt-3" : ""} ${claro ? "text-white" : "text-[var(--bq-tinta)]"}`}>{texto}</h2>
      ) : null}
    </div>
  );
}

/** O eyebrow de cada tipo de vitrine ("NOVIDADES FEMME", "DESTAQUES FEMME"). */
export function eyebrowDaVitrine(tipo: "novidades" | "destaques" | "selecao" | "colecao" | "historico" | "oferta", nomeDaLoja?: string | null) {
  const nome = String(nomeDaLoja || "").trim();
  const palavra = { novidades: "Novidades", destaques: "Destaques", selecao: "Seleção", colecao: "Coleção", historico: "Seu histórico", oferta: "Oferta" }[tipo];
  return tipo === "historico" || !nome ? palavra : `${palavra} ${nome}`;
}

/** O lugar de um bloco que ainda não tem conteúdo (só no editor). */
export function BoutiqueVazio({
  texto,
  detalhe,
  icone,
  style,
  className = "",
}: {
  texto: string;
  detalhe?: string;
  icone?: ReactNode;
  style?: CSSProperties;
  className?: string;
}) {
  return (
    <div
      className={`flex min-h-[140px] flex-col items-center justify-center gap-2 border border-dashed px-6 py-7 text-center ${className}`}
      style={{ borderColor: "rgba(17,17,17,0.25)", backgroundColor: "var(--bq-papel)", color: "#6d6a66", ...style }}
    >
      {icone ?? null}
      <span className="text-[10px] font-bold uppercase tracking-[0.14em]">{texto}</span>
      {detalhe ? <span className="max-w-md text-[11px] leading-relaxed">{detalhe}</span> : null}
    </div>
  );
}

/* ------------------------------------------------------------- carrossel */

/** O trilho de produtos: 4 por tela (2 no celular), autoplay de 3 s. */
export function BoutiqueCarrossel({
  produtos,
  slug,
  clienteId,
  favoritos,
  corDaMarca,
}: {
  produtos: BoutiqueProduto[];
  slug: string;
  clienteId?: string | null;
  favoritos?: string[] | null;
  corDaMarca?: string;
}) {
  const trilho = useRef<HTMLDivElement | null>(null);
  const [atual, setAtual] = useState(0);
  const [porTela, setPorTela] = useState(4);
  const [parado, setParado] = useState(false);
  const [naTela, setNaTela] = useState(false);
  const total = produtos.length;
  const ultimo = Math.max(0, total - porTela);

  /* Quantos cabem e qual está no começo: lidos do próprio trilho. */
  const medir = useCallback(() => {
    const caixa = trilho.current;
    const primeiro = caixa?.firstElementChild as HTMLElement | null;
    if (!caixa || !primeiro) return;
    const passo = primeiro.getBoundingClientRect().width + 6;
    setPorTela(Math.max(1, Math.round((caixa.clientWidth + 6) / passo)));
    setAtual(Math.round(caixa.scrollLeft / passo));
  }, []);

  useEffect(() => {
    medir();
    window.addEventListener("resize", medir);
    return () => window.removeEventListener("resize", medir);
  }, [medir]);

  /* Só anda sozinho quando está na tela. */
  useEffect(() => {
    const caixa = trilho.current;
    if (!caixa || typeof IntersectionObserver === "undefined") return;
    const observador = new IntersectionObserver(([entrada]) => setNaTela(Boolean(entrada?.isIntersecting)), { threshold: 0.35 });
    observador.observe(caixa);
    return () => observador.disconnect();
  }, []);

  const irPara = useCallback(
    (indice: number) => {
      const caixa = trilho.current;
      const primeiro = caixa?.firstElementChild as HTMLElement | null;
      if (!caixa || !primeiro) return;
      const passo = primeiro.getBoundingClientRect().width + 6;
      caixa.scrollTo({ left: Math.max(0, Math.min(indice, ultimo)) * passo, behavior: "smooth" });
    },
    [ultimo]
  );

  useEffect(() => {
    if (parado || !naTela || ultimo <= 0) return;
    if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
    const relogio = window.setInterval(() => irPara(atual >= ultimo ? 0 : atual + 1), 3000);
    return () => window.clearInterval(relogio);
  }, [atual, ultimo, parado, naTela, irPara]);

  if (!total) return null;

  return (
    <div
      className="relative pt-[30px] md:pt-[30px]"
      onMouseEnter={() => setParado(true)}
      onMouseLeave={() => setParado(false)}
      onTouchStart={() => setParado(true)}
      onTouchEnd={() => window.setTimeout(() => setParado(false), 4000)}
    >
      <div
        ref={trilho}
        onScroll={medir}
        className="bq-sem-barra flex snap-x snap-mandatory gap-[6px] overflow-x-auto px-[3px] md:px-[3px]"
      >
        {produtos.map((produto) => (
          <div key={produto.id} className="w-[calc((100%-6px)/2)] shrink-0 snap-start md:w-[calc((100%-18px)/4)]">
            <BoutiqueCardDoProduto produto={produto} slug={slug} clienteId={clienteId} favoritos={favoritos} corDaMarca={corDaMarca} proporcao="4 / 5" />
          </div>
        ))}
      </div>

      {ultimo > 0 ? (
        <>
          <button
            type="button"
            aria-label="Anteriores"
            disabled={atual <= 0}
            onClick={() => irPara(atual - 1)}
            className="bq-seta-redonda absolute left-2 top-[42%] z-20 hidden h-[38px] w-[38px] -translate-y-1/2 items-center justify-center rounded-full md:flex"
          >
            <ChevronLeft className="h-4 w-4" strokeWidth={2.5} />
          </button>
          <button
            type="button"
            aria-label="Próximos"
            disabled={atual >= ultimo}
            onClick={() => irPara(atual + 1)}
            className="bq-seta-redonda absolute right-2 top-[42%] z-20 hidden h-[38px] w-[38px] -translate-y-1/2 items-center justify-center rounded-full md:flex"
          >
            <ChevronRight className="h-4 w-4" strokeWidth={2.5} />
          </button>
          {ultimo + 1 <= 12 ? (
            <div className="flex justify-center pt-2">
              {Array.from({ length: ultimo + 1 }).map((_, i) => (
                <button
                  key={i}
                  type="button"
                  aria-label={`Ir para o produto ${i + 1}`}
                  onClick={() => irPara(i)}
                  className="flex h-[25px] w-[22px] items-center justify-center"
                >
                  <span className="bq-ponto" {...(i === Math.min(atual, ultimo) ? { "data-ativo": "" } : {})} />
                </button>
              ))}
            </div>
          ) : (
            /* Muitos produtos: mais de doze pontos viram ruído; a linha diz
               o mesmo (onde está, quanto falta). */
            <div className="mx-auto mt-4 h-px w-[min(320px,60%)] bg-[rgba(17,17,17,0.16)]" aria-hidden="true">
              <span
                className="block h-full bg-[var(--bq-tinta)] transition-[width] duration-300"
                style={{ width: `${((Math.min(atual, ultimo) + 1) / (ultimo + 1)) * 100}%` }}
              />
            </div>
          )}
        </>
      ) : null}
    </div>
  );
}

/** Uma vitrine: título no desenho da Boutique + carrossel + "ver tudo". */
export function BoutiqueSecaoDeProdutos({
  titulo,
  eyebrow,
  produtos,
  slug,
  verTudoHref,
  clienteId,
  favoritos,
  corDaMarca,
  limite = 12,
  lado,
}: {
  titulo: string;
  eyebrow?: string | null;
  produtos: BoutiqueProduto[];
  slug: string;
  verTudoHref?: string | null;
  clienteId?: string | null;
  favoritos?: string[] | null;
  corDaMarca?: string;
  /* Quantos cards no trilho. A home montada pelo editor manda o da seção. */
  limite?: number;
  lado?: ReactNode;
}) {
  if (!produtos.length) return null;
  return (
    <section className="w-full pb-10 md:pb-14">
      <BoutiqueTituloDeVitrine eyebrow={eyebrow} titulo={titulo} lado={lado} />
      <BoutiqueCarrossel
        produtos={produtos.slice(0, Math.max(1, limite))}
        slug={slug}
        clienteId={clienteId}
        favoritos={favoritos}
        corDaMarca={corDaMarca}
      />
      {verTudoHref ? (
        <div className="mt-6 flex justify-center">
          <Link
            href={verTudoHref}
            onClick={() => {
              /* O "ver todos" conta para os relatórios, como nos outros temas:
                 é o sinal de que a seção despertou interesse. */
              void trackCatalogEvent({ slug, eventName: "view_more_click", meta: { secao: titulo } });
            }}
            className="bq-link-sublinhado text-[var(--bq-tinta)]"
          >
            Ver tudo <span className="bq-seta-texto" aria-hidden="true">→</span>
          </Link>
        </div>
      ) : null}
    </section>
  );
}

/* ------------------------------------------------------------ top sellers */

export type BoutiqueAbaDaVitrine = { chave: string; rotulo: string; href?: string | null; produtos: BoutiqueProduto[] };

/**
 * As abas do TOP SELLERS. A primeira é "Destaques": os primeiros produtos da
 * loja, que é o que a seção "Primeiros produtos" sempre mostrou. Depois, as
 * duas categorias principais com mais produto (com as filhas). Loja com
 * menos de duas categorias fica só com a grade, sem abas.
 */
export function abasDoTopSellers(
  produtos: BoutiqueProduto[],
  categorias: BoutiqueCategoria[],
  slug: string,
  quantidade: number
): BoutiqueAbaDaVitrine[] {
  const paiDe = new Map(categorias.map((c) => [c.id, c.parent_id || null]));
  const raizDe = (id: string | null | undefined) => {
    let atual = id || null;
    for (let i = 0; atual && i < 6; i += 1) {
      const pai = paiDe.get(atual);
      if (!pai) return atual;
      atual = pai;
    }
    return atual;
  };
  const porRaiz = new Map<string, BoutiqueProduto[]>();
  for (const produto of produtos) {
    const raiz = raizDe((produto as BoutiqueProduto & { category_id?: string | null }).category_id);
    if (!raiz) continue;
    const lista = porRaiz.get(raiz) || [];
    lista.push(produto);
    porRaiz.set(raiz, lista);
  }
  const principais = categorias
    .filter((c) => !c.parent_id)
    .map((categoria) => ({ categoria, produtos: porRaiz.get(categoria.id) || [] }))
    .filter((item) => item.produtos.length > 0)
    .sort((a, b) => b.produtos.length - a.produtos.length);
  const abas: BoutiqueAbaDaVitrine[] = [{ chave: "destaques", rotulo: "Destaques", href: null, produtos: produtos.slice(0, quantidade) }];
  if (principais.length >= 2) {
    for (const { categoria, produtos: lista } of principais.slice(0, 2)) {
      abas.push({
        chave: categoria.id,
        rotulo: categoria.name,
        href: `/${slug}/c/${categoria.slug || categoria.id}`,
        produtos: lista.slice(0, quantidade),
      });
    }
  }
  return abas;
}

/** "TOP SELLERS": título centralizado, abas e a grade de quatro. */
export function BoutiqueTopSellers({
  titulo,
  abas,
  slug,
  clienteId,
  favoritos,
  corDaMarca,
}: {
  titulo: string;
  abas: BoutiqueAbaDaVitrine[];
  slug: string;
  clienteId?: string | null;
  favoritos?: string[] | null;
  corDaMarca?: string;
}) {
  const [ativa, setAtiva] = useState(0);
  const lista = abas.filter((aba) => aba.produtos.length);
  if (!lista.length) return null;
  const aba = lista[Math.min(ativa, lista.length - 1)];

  return (
    <section className="w-full pb-12 pt-[46px] md:pb-[72px] md:pt-[68px]">
      <h2 className="bq-titulo-secao px-4 text-center">{titulo}</h2>

      {lista.length > 1 ? (
        <div role="tablist" className="bq-sem-barra mt-6 flex justify-start gap-7 overflow-x-auto px-6 md:mt-[34px] md:justify-center md:gap-12">
          {lista.map((item, i) => {
            const marcada = item.chave === aba.chave;
            return (
              <button
                key={item.chave}
                type="button"
                role="tab"
                aria-selected={marcada}
                onClick={() => setAtiva(i)}
                className={`relative shrink-0 pb-2 text-[11px] transition-colors min-[391px]:text-[11px] md:text-[13px] ${marcada ? "font-medium" : "font-normal"}`}
                style={{ color: marcada ? "var(--bq-tinta)" : "var(--bq-aba)" }}
              >
                {item.rotulo}
                <span
                  className="absolute inset-x-0 bottom-0 h-px origin-center bg-[var(--bq-tinta)]"
                  style={{ transform: marcada ? "scaleX(1)" : "scaleX(0)", transition: "transform 0.28s var(--bq-curva)" }}
                />
              </button>
            );
          })}
        </div>
      ) : null}

      <div key={aba.chave} className="bq-aparece mt-6 grid grid-cols-2 gap-x-[2px] gap-y-8 px-0 md:mt-[34px] md:grid-cols-4 md:gap-x-[6px] md:gap-y-10">
        {aba.produtos.map((produto) => (
          <div key={produto.id} className="md:px-[5px]">
            <BoutiqueCardDoProduto produto={produto} slug={slug} clienteId={clienteId} favoritos={favoritos} corDaMarca={corDaMarca} />
          </div>
        ))}
      </div>

      {aba.href ? (
        <div className="mt-6 flex justify-center md:mt-[34px]">
          <Link
            href={aba.href}
            onClick={() => {
              void trackCatalogEvent({ slug, eventName: "view_more_click", meta: { secao: `${titulo} · ${aba.rotulo}` } });
            }}
            className="bq-link-sublinhado text-[var(--bq-tinta)]"
          >
            Ver {aba.rotulo} <span className="bq-seta-texto" aria-hidden="true">→</span>
          </Link>
        </div>
      ) : null}
    </section>
  );
}

/* -------------------------------------------------- cards de categoria */

export type BoutiqueCardDeCategoria = { chave: string; nome: string; href: string; foto: string | null; kicker: string; chamada: string };

function CardDeCategoria({ card, menor = false, emTres = false }: { card: BoutiqueCardDeCategoria; menor?: boolean; emTres?: boolean }) {
  const caixa = useRef<HTMLAnchorElement | null>(null);
  const [visivel, setVisivel] = useState(false);

  /* O texto entra subindo quando o card aparece na tela, como na referência. */
  useEffect(() => {
    const alvo = caixa.current;
    if (!alvo || typeof IntersectionObserver === "undefined") return;
    const observador = new IntersectionObserver(
      ([entrada]) => {
        if (entrada?.isIntersecting) {
          setVisivel(true);
          observador.disconnect();
        }
      },
      { threshold: 0.25 }
    );
    observador.observe(alvo);
    return () => observador.disconnect();
  }, []);

  return (
    <Link
      ref={caixa}
      href={card.href}
      {...(emTres ? { "data-tres": "" } : {})}
      className={`group relative block overflow-hidden bg-[var(--bq-foto-fundo)] ${
        menor
          ? "aspect-[3/4] w-[78vw] shrink-0 snap-start md:aspect-auto md:h-[clamp(380px,36vw,560px)] md:w-auto md:shrink md:grow md:basis-[calc(25%-2px)] md:data-[tres]:basis-[calc(33.333%-2px)]"
          : "min-h-[70svh] md:min-h-[min(78svh,820px)]"
      }`}
    >
      {card.foto ? (
        /* eslint-disable-next-line @next/next/no-img-element */
        <img src={card.foto} alt="" loading="lazy" draggable={false} className="bq-zoom-lento absolute inset-0 h-full w-full object-cover object-top" />
      ) : (
        <span className="absolute inset-0 bg-[#e8e3dd]" />
      )}
      <span className="absolute inset-0" style={{ background: "linear-gradient(180deg, transparent 48%, rgba(0,0,0,0.48) 100%)" }} />
      <span
        className={`bq-entra absolute left-6 right-6 text-white ${menor ? "bottom-7 md:left-8" : "bottom-10 md:left-[42px]"}`}
        {...(visivel ? { "data-visivel": "" } : {})}
      >
        <span className="block text-[9px] font-bold uppercase tracking-[0.18em]">{card.kicker}</span>
        <span
          className={`bq-revista mt-2 block leading-none tracking-[-0.04em] ${
            menor ? "text-[32px] md:text-[clamp(28px,2.6vw,40px)]" : "text-[38px] md:text-[clamp(38px,4vw,62px)]"
          }`}
        >
          {card.nome}
        </span>
        <span className="mt-5 inline-block border-b border-white pb-[5px] text-[10px] font-bold uppercase tracking-[0.12em]">{card.chamada}</span>
      </span>
    </Link>
  );
}

/** Os cards grandes de categoria. Um ocupa a largura; dois ficam lado a
 *  lado (1,05fr / 0,95fr), como na referência; três ou mais viram uma grade
 *  de cards 3:4 (no celular, um trilho que arrasta). */
export function BoutiqueCardsDeCategoria({ cards }: { cards: BoutiqueCardDeCategoria[] }) {
  if (!cards.length) return null;
  if (cards.length > 2) {
    /* Três por linha quando a conta fecha em três (3, 6, 9); senão quatro.
       A última linha incompleta se estica: nada de buraco na grade. */
    const emTres = cards.length % 3 === 0 && cards.length % 4 !== 0;
    return (
      <section className="bq-sem-barra flex w-full snap-x snap-mandatory gap-[2px] overflow-x-auto md:flex-wrap md:overflow-visible">
        {cards.map((card) => (
          <CardDeCategoria key={card.chave} card={card} menor emTres={emTres} />
        ))}
      </section>
    );
  }
  return (
    <section className={`grid w-full gap-[2px] ${cards.length > 1 ? "md:grid-cols-[1.05fr_0.95fr]" : ""}`}>
      {cards.map((card) => (
        <CardDeCategoria key={card.chave} card={card} />
      ))}
    </section>
  );
}

/** A foto que representa a categoria: a 2ª foto (a da modelo) do primeiro
 *  produto dela que tiver uma; senão, a foto principal. */
export function fotoDaCategoria(produtos: BoutiqueProduto[]) {
  for (const produto of produtos) {
    const segunda = segundaFotoDoProduto(produto);
    if (segunda) return segunda;
  }
  for (const produto of produtos) {
    const foto = fotoDoProduto(produto);
    if (foto) return foto;
  }
  return null;
}

/* ---------------------------------------------------- círculos (editor) */

/* Um círculo: a categoria das principais ou a escolhida no editor (atalhos
   de categorias). */
export type BoutiqueCirculo = { chave: string; href: string; nome: string; foto?: string | null };

/* Os círculos de categoria. No computador ficam centralizados; no celular
   viram um trilho que mostra três por vez. */
export function BoutiqueCirculosDeItens({
  itens,
  noComputador = 6,
  noCelular = 10,
}: {
  itens: BoutiqueCirculo[];
  noComputador?: number;
  noCelular?: number;
}) {
  if (!itens.length) return null;

  /* O círculo da Boutique: anel fino com a foto recuada (como um broche), o
     nome em caixa alta bem espaçado e o traço que cresce no hover. */
  const item = (circulo: BoutiqueCirculo, largura: string) => (
    <Link key={circulo.chave} href={circulo.href} className="bq-circulo group flex shrink-0 snap-start flex-col items-center gap-3.5" style={{ width: largura }}>
      <div className="w-full rounded-full border p-[5px] transition-colors group-hover:border-[var(--bq-tinta)]" style={{ aspectRatio: "1 / 1", borderColor: "var(--bq-linha)" }}>
        <div className="h-full w-full overflow-hidden rounded-full bg-[var(--bq-papel)]">
          {circulo.foto ? (
            /* eslint-disable-next-line @next/next/no-img-element */
            <img src={circulo.foto} alt="" loading="lazy" draggable={false} className="bq-zoom-lento h-full w-full object-cover" />
          ) : (
            <span className="bq-revista flex h-full w-full items-center justify-center text-[30px] text-[var(--bq-tinta)]">
              {circulo.nome.slice(0, 1).toUpperCase()}
            </span>
          )}
        </div>
      </div>
      <span className="bq-circulo-nome relative pb-1 text-center text-[10px] font-semibold uppercase leading-tight tracking-[0.14em] text-[var(--bq-tinta)]">
        {circulo.nome}
      </span>
    </Link>
  );

  return (
    <div className="pb-2 pt-8 md:pt-10">
      <div className={`hidden ${Math.min(itens.length, noComputador) > 7 ? "flex-wrap " : ""}items-start justify-center gap-x-8 gap-y-6 px-6 lg:flex`}>
        {itens.slice(0, noComputador).map((circulo) => item(circulo, "128px"))}
      </div>
      <div className="bq-sem-barra flex snap-x gap-4 overflow-x-auto scroll-px-4 px-4 lg:hidden">
        {itens.slice(0, noCelular).map((circulo) => item(circulo, "calc(28vw - 12px)"))}
      </div>
    </div>
  );
}

export function BoutiqueCirculos({
  categorias,
  slug,
  fotos,
}: {
  categorias: BoutiqueCategoria[];
  slug: string;
  fotos: Map<string, string>;
}) {
  return (
    <BoutiqueCirculosDeItens
      itens={categorias.map((categoria) => ({
        chave: categoria.id,
        href: `/${slug}/c/${categoria.slug || categoria.id}`,
        nome: categoria.name,
        foto: fotos.get(categoria.id) || null,
      }))}
    />
  );
}

/* --------------------------------------------------- banner duplo (editor) */

export function BoutiqueBannerDuplo({ banners, slug }: { banners: BoutiqueBanner[]; slug: string }) {
  const par = useMemo(() => banners.slice(0, 2), [banners]);
  if (par.length < 2) return null;
  return (
    <div className="grid w-full gap-[2px] sm:grid-cols-2">
      {par.map((banner) => (
        <LinkDoBanner
          key={banner.id}
          banner={banner}
          slug={slug}
          className="group relative block w-full overflow-hidden bg-[var(--bq-foto-fundo)]"
          style={{ aspectRatio: "8 / 5" }}
        >
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img src={banner.image_url} alt="" loading="lazy" className="bq-zoom-lento absolute inset-0 h-full w-full object-cover" />
        </LinkDoBanner>
      ))}
    </div>
  );
}
