"use client";

import { useEffect, useRef, useState, type ReactElement } from "react";
import type { LucideIcon } from "lucide-react";
import { BarChart3, Bell, Boxes, Building2, CalendarDays, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, ClipboardList, CornerDownLeft, CreditCard, DollarSign, ExternalLink, FileText, FolderOpen, LayoutDashboard, LayoutGrid, MonitorPlay, Loader2, Lock, Megaphone, Menu, MessageCircle, Moon, Package, PackageCheck, ReceiptText, Search, Settings, ShoppingBag, ShoppingCart, Star, Store, Sun, Tag, Truck, UserCircle2, UserCog, Users, Wallet, X } from "lucide-react";
import Link from "next/link";
import { decidirAcessoDaRota, permissoesDaRota as permissionsForPath } from "@/lib/acesso-de-rota";
import { ProvedorPermissoes } from "@/lib/permissoes-do-usuario";
import { usePathname, useRouter } from "next/navigation";
import { clsx } from "clsx";
import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image";
import UserNav, { EmployeeNav } from "@/components/user-nav";
import { SystemNoticesHost } from "@/components/system-notices-host";
import { SystemPopupHost } from "@/components/system-popup-host";
import { FaixaDaAssinaturaDoPainel } from "@/components/assinatura/faixa-da-assinatura";
import { BrandLoader } from "@/components/brand-loader";
import BalaoDeConversa from "@/components/chats/balao-de-conversa";
import MarcaDoDesign from "@/components/design/marca-do-design";
import { DesignLiberadoContexto } from "@/components/design/liberacao-do-design";
import PainelDoChats from "@/components/chats/painel-do-chats";
import { getSupabaseClient } from "@/lib/supabase";
import { marcarNavegadorDaEquipe } from "@/lib/navegador-da-equipe";
import {
  canCompany,
  getCompanyContext,
  getEffectiveCompanyRole,
  shouldUseEmployeeUi,
  type CompanyContext,
  type CompanyPermission,
} from "@/lib/company-context";

type NavSubItem = {
  label: string;
  href: string;
  badge?: {
    label: string;
  };
};

interface NavItem {
  /* Quase todos vêm do lucide; o Chats usa o desenho da casa. O que o menu
     precisa é só de um componente que aceite tamanho e traço. */
  icon: LucideIcon | ((props: { size?: number; strokeWidth?: number; className?: string }) => ReactElement);
  label: string;
  href: string;
  section?: string;
  subItems?: NavSubItem[];
  external?: boolean;
  /* Fica no rodapé do trilho, junto do Chats (ferramenta à parte, como o KtaloguDesign). */
  rodape?: boolean;
  /* Mesmo selo que os subitens ja usavam — so faltava o item de primeiro
     nivel poder carregar um. */
  badge?: { label: string };
  /* Só fica marcado no endereço exato: a raiz de uma área cujas telas moram
     embaixo dela (a Visão geral dos produtos digitais). */
  exato?: boolean;
}

type AdminNotification = {
  id: string;
  kind:
    | "chat"
    | "system"
    | "order"
    | "other"
    | "art_approved"
    | "art_rejected"
    | "quote_approved"
    | "product_review"
    | "catalog_order"
    | "payment_link_receipt"
    | "client_signup"
    | "chat_message"
    | "sale_approved"
    | "fiscal_recebida"
    | "comentario_digital"
    | "modelo_da_comunidade";
  quoteId: string | null;
  artId: string | null;
  reviewId: string | null;
  catalogOrderId: string | null;
  clientId: string | null;
  paymentLinkPaymentId: string | null;
  paymentLinkId: string | null;
  paymentLinkCode: string | null;
  paymentLinkTitle: string | null;
  message: string;
  createdAt: string;
  clientName: string;
  clientAvatarUrl: string | null;
  quoteNumber: number | null;
  catalogOrderNumber: number | null;
  productName: string | null;
  rating: number | null;
  itemCount: number | null;
  orderTotal: number | null;
  status: "pending" | "approved" | "rejected" | "canceled" | null;
  /* Conversa do Chats: o aviso leva para os dois lugares onde ela mora. */
  threadId?: string | null;
  orderId?: string | null;
  orderNumber?: number | null;
  /* Venda aprovada: o que a loja quer ler de relance. */
  saleAmount?: number | null;
  saleMethod?: string | null;
  saleOrigin?: "sistema" | "loja" | null;
  /* Pedido do catálogo: "quote_request" é o pedido de orçamento feito pela
     loja (o cliente não viu preço), e o sino avisa com outro título. */
  catalogOrderKind?: "order" | "quote_request" | null;
  /* Cadastro pela loja que espera a aprovação da equipe (modo "com aprovação"). */
  clientSignupPending?: boolean;
};

const MAX_ADMIN_NOTIFICATIONS = 20;
const DASHBOARD_CONFETTI_STORAGE_KEY = "ktl:onboarding-dashboard-confetti";

const getAdminNotificationReadStorageKey = (userId: string) => `ktl_admin_notifications_read:${userId}`;
const getAdminNotificationDismissedStorageKey = (userId: string) => `ktl_admin_notifications_dismissed:${userId}`;

const getNotificationTimestamp = (value: string) => {
  const timestamp = new Date(value).getTime();
  return Number.isFinite(timestamp) ? timestamp : 0;
};

/* Pix, cartao, dinheiro: o nome que se usa no balcao, e nao o do provedor. */
const rotuloDoMeioDePagamento = (metodo?: string | null) => {
  const chave = String(metodo || "").toLowerCase();
  if (chave.includes("pix") || chave === "bank_transfer") return "Pix";
  if (chave.includes("credit")) return "cartão de crédito";
  if (chave.includes("debit")) return "cartão de débito";
  if (chave.includes("card") || chave.includes("cartao")) return "cartão";
  if (chave.includes("boleto") || chave.includes("ticket")) return "boleto";
  if (chave.includes("cash") || chave.includes("dinheiro")) return "dinheiro";
  if (chave.includes("transfer")) return "transferência";
  return "pagamento";
};

const sortAdminNotifications = (items: AdminNotification[]) =>
  [...items].sort((a, b) => getNotificationTimestamp(b.createdAt) - getNotificationTimestamp(a.createdAt));

const getHrefPath = (href: string) => href.split("#")[0].split("?")[0];
const isSubPathActive = (pathname: string, subPath: string, parentPath: string) =>
  pathname === subPath || (subPath !== parentPath && pathname.startsWith(`${subPath}/`));
const rotaBateNoItem = (pathname: string, item: { href: string; exato?: boolean }) => {
  const itemPath = getHrefPath(item.href);
  return pathname === itemPath || (!item.exato && itemPath !== "/dashboard" && pathname.startsWith(itemPath));
};

/*
  A sidebar do desktop tem dois andares. O trilho (a barra estreita e escura)
  leva uma entrada por seção do menu — Vendas, Operação, Financeiro... — e o
  painel ao lado lista os itens da seção. Dashboard, Chats e "Minhas
  Comissões" não têm seção: são atalhos diretos no trilho.
*/
type EntradaDoTrilho =
  | { tipo: "secao"; chave: string; rotulo: string; icone: NavItem["icon"]; itens: NavItem[] }
  | { tipo: "atalho"; chave: string; item: NavItem };

const ICONE_DA_SECAO: Record<string, NavItem["icon"]> = {
  Vendas: ShoppingBag,
  Operação: Boxes,
  Financeiro: Wallet,
  Canais: Megaphone,
  Empresa: Building2,
  "Produtos digitais": MonitorPlay,
};

const montarTrilho = (items: NavItem[]): EntradaDoTrilho[] => {
  const entradas: EntradaDoTrilho[] = [];
  for (const item of items) {
    const secao = item.section;
    if (!secao) {
      entradas.push({ tipo: "atalho", chave: item.href, item });
      continue;
    }
    const existente = entradas.find((entrada) => entrada.tipo === "secao" && entrada.chave === secao);
    if (existente && existente.tipo === "secao") {
      existente.itens.push(item);
    } else {
      entradas.push({ tipo: "secao", chave: secao, rotulo: secao, icone: ICONE_DA_SECAO[secao] ?? LayoutDashboard, itens: [item] });
    }
  }
  return entradas;
};

/* A seção da página aberta: a do item (ou subitem) mais específico que bate
   com a rota. Dashboard não tem seção, e aí volta null. */
const secaoDaRota = (items: NavItem[], pathname: string): string | null => {
  let melhor: { secao: string | null; tamanho: number } | null = null;
  const considerar = (secao: string | null, tamanho: number) => {
    if (!melhor || tamanho > melhor.tamanho) melhor = { secao, tamanho };
  };
  for (const item of items) {
    const itemPath = getHrefPath(item.href);
    if (rotaBateNoItem(pathname, item)) considerar(item.section ?? null, itemPath.length);
    for (const sub of item.subItems ?? []) {
      const subPath = getHrefPath(sub.href);
      if (isSubPathActive(pathname, subPath, itemPath)) considerar(item.section ?? null, subPath.length);
    }
  }
  return melhor ? (melhor as { secao: string | null }).secao : null;
};

/*
  O painel de uma seção: título e os itens em grupos. Um item com subitens é
  um grupo que abre e fecha; sem subitens, é a própria linha que leva à tela.
  Serve tanto encaixado ao lado do trilho quanto solto, por cima da tela.
*/
function PainelDaSecao({
  titulo,
  itens,
  pathname,
  gruposFechados,
  aoAlternarGrupo,
  aoNavegar,
  aoFechar,
}: {
  titulo: string;
  itens: NavItem[];
  pathname: string;
  gruposFechados: Set<string>;
  aoAlternarGrupo: (href: string) => void;
  aoNavegar?: () => void;
  /* Na gaveta do celular o painel é que fecha o menu. */
  aoFechar?: () => void;
}) {
  return (
    <>
      <div className="ks-painel-cabecalho">
        <span className="ks-painel-titulo">{titulo}</span>
        {aoFechar ? (
          <button type="button" onClick={aoFechar} className="ks-painel-fechar" aria-label="Fechar menu">
            <X size={16} strokeWidth={2.2} />
          </button>
        ) : null}
      </div>
      <nav className="ks-painel-nav" aria-label={titulo}>
        {itens.map((item) => {
          const itemPath = getHrefPath(item.href);
          const ativo = rotaBateNoItem(pathname, item);
          const filhos = item.subItems ?? [];

          if (filhos.length === 0) {
            return (
              <Link
                key={item.href}
                href={item.href}
                onClick={aoNavegar}
                className={clsx("ks-painel-linha", ativo && "ks-painel-linha-ativa")}
                aria-current={ativo ? "page" : undefined}
              >
                <span className="ks-painel-linha-icone">
                  <item.icon size={17} strokeWidth={1.9} />
                </span>
                <span className="ks-painel-rotulo">{item.label}</span>
                {item.badge ? <span className="ks-nav-item-pill">{item.badge.label}</span> : null}
              </Link>
            );
          }

          const fechado = gruposFechados.has(item.href);
          return (
            <div key={item.href} className="ks-painel-grupo">
              {/* Fechado com a página dentro, o cabeçalho é que mostra onde
                  a pessoa está. */}
              <button
                type="button"
                onClick={() => aoAlternarGrupo(item.href)}
                aria-expanded={!fechado}
                className={clsx("ks-painel-linha", fechado && ativo && "ks-painel-linha-ativa")}
              >
                <span className="ks-painel-linha-icone">
                  <item.icon size={17} strokeWidth={1.9} />
                </span>
                <span className="ks-painel-rotulo">{item.label}</span>
                {item.badge ? <span className="ks-nav-item-pill">{item.badge.label}</span> : null}
                <span className={clsx("ks-painel-seta", !fechado && "ks-painel-seta-aberta")}>
                  <ChevronDown size={14} strokeWidth={2.2} />
                </span>
              </button>
              {!fechado ? (
                <div className="ks-painel-filhos">
                  {filhos.map((sub) => {
                    const subPath = getHrefPath(sub.href);
                    const subAtivo = isSubPathActive(pathname, subPath, itemPath);
                    return (
                      <Link
                        key={sub.href}
                        href={sub.href}
                        onClick={aoNavegar}
                        className={clsx("ks-painel-sub", subAtivo && "ks-painel-sub-ativa")}
                        aria-current={subAtivo ? "page" : undefined}
                      >
                        <span className="ks-painel-rotulo">{sub.label}</span>
                        {sub.badge ? <span className="ks-nav-item-pill">{sub.badge.label}</span> : null}
                      </Link>
                    );
                  })}
                </div>
              ) : null}
            </div>
          );
        })}
      </nav>
    </>
  );
}

/*
  O trilho: a logo, uma entrada por seção (ou atalho) e, no rodapé, o Chats, o
  tema e a conta. É o mesmo na sidebar do desktop e na gaveta do celular.
*/
function Trilho({
  entradas,
  rotaAtual,
  secaoDaPagina,
  secaoEscolhida,
  aoEscolherSecao,
  aoIrParaAtalho,
  atalhoDoChats,
  atalhosDoRodape,
  tema,
  aoTrocarTema,
  mostrarConta,
}: {
  entradas: EntradaDoTrilho[];
  rotaAtual: string;
  secaoDaPagina: string | null;
  secaoEscolhida: string | null;
  aoEscolherSecao: (secao: string) => void;
  aoIrParaAtalho: () => void;
  atalhoDoChats: NavItem | null;
  atalhosDoRodape: NavItem[];
  tema: "light" | "dark";
  aoTrocarTema: () => void;
  mostrarConta: boolean;
}) {
  return (
    <div className="ks-trilho">
      <Link href="/dashboard" className="ks-trilho-logo" aria-label="Ir para o início" onClick={aoIrParaAtalho}>
        <Image
          src="/brand-symbol.svg"
          alt=""
          className="h-6 w-auto brightness-0 invert"
          width={30}
          height={22}
        />
      </Link>

      <nav className="ks-trilho-nav" aria-label="Seções do sistema">
        {entradas.map((entrada) => {
          if (entrada.tipo === "atalho") {
            if (entrada.item.external || entrada.item.rodape) return null;
            const ativo = rotaBateNoItem(rotaAtual, entrada.item);
            return (
              <Link
                key={entrada.chave}
                href={entrada.item.href}
                className={clsx("ks-trilho-item", ativo && "ks-trilho-item-ativo")}
                aria-current={ativo ? "page" : undefined}
                onClick={aoIrParaAtalho}
              >
                <entrada.item.icon size={20} strokeWidth={1.9} />
                <span className="ks-trilho-dica">{entrada.item.label}</span>
              </Link>
            );
          }
          const ativa = entrada.chave === secaoDaPagina;
          const escolhida = !ativa && entrada.chave === secaoEscolhida;
          return (
            <button
              key={entrada.chave}
              type="button"
              className={clsx(
                "ks-trilho-item",
                ativa && "ks-trilho-item-ativo",
                escolhida && "ks-trilho-item-escolhido"
              )}
              aria-pressed={ativa || escolhida}
              onClick={() => aoEscolherSecao(entrada.chave)}
            >
              <entrada.icone size={20} strokeWidth={1.9} />
              <span className="ks-trilho-dica">{entrada.rotulo}</span>
            </button>
          );
        })}
      </nav>

      <div className="ks-trilho-rodape">
        {atalhosDoRodape.map((item) => {
          const ativo = rotaBateNoItem(rotaAtual, item);
          const conteudo = (
            <>
              <item.icon size={20} strokeWidth={1.9} />
              {item.badge ? <span className="ks-trilho-selo">{item.badge.label}</span> : null}
              <span className="ks-trilho-dica">{item.badge ? `${item.label} (${item.badge.label.toLowerCase()})` : item.label}</span>
            </>
          );
          const classe = clsx("ks-trilho-item ks-trilho-item-redondo", ativo && "ks-trilho-item-ativo");
          /* O KtaloguDesign abre carregando a página (ela é "isolada", ver proxy.ts): link comum, não do Next. */
          return item.href === "/dashboard/design" ? (
            <a key={item.href} href={item.href} className={classe} aria-current={ativo ? "page" : undefined}>
              {conteudo}
            </a>
          ) : (
            <Link key={item.href} href={item.href} className={classe} aria-current={ativo ? "page" : undefined} onClick={aoIrParaAtalho}>
              {conteudo}
            </Link>
          );
        })}
        {atalhoDoChats ? (
          <a
            href={atalhoDoChats.href}
            target="_blank"
            rel="noopener noreferrer"
            className="ks-trilho-item ks-trilho-item-redondo"
          >
            <atalhoDoChats.icon size={20} strokeWidth={1.9} />
            <span className="ks-trilho-dica">{atalhoDoChats.label}</span>
          </a>
        ) : null}
        <button
          type="button"
          onClick={aoTrocarTema}
          className="ks-trilho-item ks-trilho-item-redondo"
          aria-label={tema === "dark" ? "Ativar modo claro" : "Ativar modo escuro"}
        >
          {tema === "dark" ? <Sun size={18} strokeWidth={1.9} /> : <Moon size={18} strokeWidth={1.9} />}
          <span className="ks-trilho-dica">{tema === "dark" ? "Modo claro" : "Modo escuro"}</span>
        </button>
        {mostrarConta ? (
          <div className="ks-trilho-conta">
            <UserNav mobile />
          </div>
        ) : null}
      </div>
    </div>
  );
}

const navItems: NavItem[] = [
  { icon: LayoutDashboard, label: "Dashboard", href: "/dashboard" },
  /* O Chats abre em aba propria: e uma ferramenta de conversa, nao mais uma
     tela de gestao. */
  { icon: BalaoDeConversa, label: "Chats", href: "/chats", external: true },
  /* O editor de artes é uma ferramenta à parte (não é o catálogo): fica no rodapé do trilho, junto do Chats. */
  /* Em beta (lib/banner-editor/liberacao.ts): só aparece para as empresas liberadas. */
  { icon: MarcaDoDesign, label: "KtaloguDesign", href: "/dashboard/design", rodape: true, badge: { label: "BETA" } },
  /* Vendas começa por quem compra: o cliente vem antes do orçamento. */
  {
    icon: Users,
    label: "Clientes",
    href: "/dashboard/clients",
    section: "Vendas",
    subItems: [
      { label: "Meus Clientes", href: "/dashboard/clients" },
      { label: "Ranking de clientes", href: "/dashboard/clients/ranking" }
    ]
  },
  { icon: FileText, label: "Orçamentos", href: "/dashboard/quotes", section: "Vendas" },
  { icon: ClipboardList, label: "Pedidos", href: "/dashboard/orders", section: "Vendas" },
  { icon: ShoppingCart, label: "PDV", href: "/dashboard/pdv", section: "Vendas" },
  {
    icon: ReceiptText,
    label: "Notas fiscais",
    href: "/dashboard/notas-fiscais",
    section: "Vendas",
    badge: { label: "Novo" },
    subItems: [
      { label: "Emissão de notas", href: "/dashboard/notas-fiscais" },
      { label: "Notas recebidas", href: "/dashboard/notas-fiscais/recebidas" },
      { label: "Configuração", href: "/dashboard/notas-fiscais/configuracao" },
    ],
  },
  {
    icon: Package,
    label: "Produtos",
    href: "/dashboard/products",
    section: "Operação",
    subItems: [
      { label: "Catálogo", href: "/dashboard/products" },
      { label: "Estoque", href: "/dashboard/products/estoque" },
      { label: "Categorias", href: "/dashboard/categories" },
    ],
  },
  {
    icon: Truck,
    label: "Fornecedores",
    href: "/dashboard/fornecedores",
    section: "Operação",
    subItems: [
      { label: "Meus Fornecedores", href: "/dashboard/fornecedores/meus-fornecedores" },
      { label: "XBZ", href: "/dashboard/fornecedores/xbz" },
    ],
  },
  {
    icon: PackageCheck,
    label: "Produção",
    href: "/dashboard/production",
    section: "Operação",
    subItems: [
      { label: "Painel", href: "/dashboard/production" },
      { label: "Processos", href: "/dashboard/production/operations" },
    ],
  },
  {
    icon: CalendarDays,
    label: "Agenda",
    href: "/dashboard/agenda",
    section: "Operação",
    badge: { label: "Novo" },
  },
  {
    icon: Tag,
    label: "Precificação",
    href: "/dashboard/precificacao",
    section: "Financeiro",
    badge: { label: "Novo" },
    subItems: [
      { label: "Nova precificação", href: "/dashboard/precificacao/nova" },
      { label: "Configuração", href: "/dashboard/precificacao" },
    ],
  },
  {
    icon: DollarSign,
    label: "Financeiro",
    href: "/dashboard/financial",
    section: "Financeiro",
    subItems: [
      { label: "Visão Geral", href: "/dashboard/financial/overview" },
      { label: "Ponto de Equilíbrio", href: "/dashboard/precificacao/saude" },
      { label: "Impostos e Taxas", href: "/dashboard/precificacao/custos-da-venda" },
      { label: "Contas a Receber", href: "/dashboard/financial/receivables" },
      { label: "Pagamentos", href: "/dashboard/financial/payments" },
      { label: "Links de Pagamento", href: "/dashboard/financial/payment-links" },
      { label: "Fluxo de Caixa", href: "/dashboard/financial/cash-flow" },
      { label: "Contas a Pagar", href: "/dashboard/financial/payables" },
      { label: "Contas Bancárias", href: "/dashboard/financial/bank-accounts" },
      { label: "Comissões", href: "/dashboard/financial/commissions" },
      { label: "DRE", href: "/dashboard/financial/dre" },
      {
        label: "DRE Analítico",
        href: "/dashboard/financial/dre-analytic",
        badge: { label: "Novo" },
      },
    ],
  },
  {
    icon: BarChart3,
    label: "Relatórios",
    href: "/dashboard/reports",
    section: "Financeiro",
  },
  /* Painéis montados pela empresa (ou pelo assistente), com os números ao vivo. */
  { icon: LayoutGrid, label: "Painéis", href: "/dashboard/paineis", section: "Financeiro", badge: { label: "Novo" } },
  {
    icon: Store,
    label: "Catálogo",
    href: "/dashboard/catalog",
    section: "Canais",
    subItems: [
      { label: "Dashboard", href: "/dashboard/catalog/dashboard" },
      { label: "Personalização", href: "/dashboard/catalog" },
      { label: "Produtos", href: "/dashboard/catalog/products" },
      { label: "Pedidos", href: "/dashboard/catalog/orders" },
      { label: "Avaliações", href: "/dashboard/catalog/reviews" },
      { label: "Banners e faixas", href: "/dashboard/catalog/banners" },
      { label: "Stories", href: "/dashboard/catalog/stories" },
      { label: "Marketing", href: "/dashboard/catalog/marketing" },
    ],
  },
  { icon: MessageCircle, label: "WhatsApp", href: "/dashboard/whatsapp", section: "Canais" },
  { icon: UserCog, label: "Funcionários", href: "/dashboard/company/users", section: "Empresa" },
  {
    icon: Settings,
    label: "Configurações",
    href: "/dashboard/settings",
    section: "Empresa",
    subItems: [
      { label: "Geral", href: "/dashboard/settings" },
      { label: "Senha", href: "/dashboard/settings/password" },
      { label: "Integrações", href: "/dashboard/settings/shipping" },
      { label: "Campos próprios", href: "/dashboard/settings/campos-proprios" },
      { label: "Regras automáticas", href: "/dashboard/settings/regras" },
    ],
  },
  /* A assinatura só aparece para quem pode gerenciá-la: o filtro de permissão
     do menu usa a mesma regra da rota (`subscription.manage`). */
  { icon: CreditCard, label: "Assinatura", href: "/dashboard/assinatura", section: "Empresa" },
];

const employeeOnlyNavItems: NavItem[] = [
  { icon: DollarSign, label: "Minhas Comissões", href: "/dashboard/my-commissions" },
];


/*
  Produtos digitais: área própria, separada do resto do painel. Não entra no
  menu padrão (o que aparece enquanto as permissões carregam): só depois de o
  painel saber que a empresa está liberada (DIGITAIS_LIBERADOS ou a tabela
  digitais_ajustes, lido por /api/company/users) e que a pessoa
  pode ver cada tela. Fica logo depois da Operação no trilho.
*/
const SECAO_DOS_DIGITAIS = "Produtos digitais";
const navItemsDosDigitais: NavItem[] = [
  { icon: LayoutDashboard, label: "Visão geral", href: "/dashboard/digitais", section: SECAO_DOS_DIGITAIS, exato: true },
  {
    icon: MonitorPlay,
    label: "O que você vende",
    /* Endereço só do grupo (ele abre e fecha, não leva a tela nenhuma): com
       um endereço próprio, o editor de um produto marca o item certo. */
    href: "/dashboard/digitais/o-que-voce-vende",
    section: SECAO_DOS_DIGITAIS,
    subItems: [
      { label: "Produtos digitais", href: "/dashboard/digitais/produtos" },
      { label: "Conteúdos em vídeo", href: "/dashboard/digitais/cursos" },
      { label: "Lives", href: "/dashboard/digitais/lives" },
    ],
  },
  {
    icon: Users,
    label: "Quem comprou",
    href: "/dashboard/digitais/quem-comprou",
    section: SECAO_DOS_DIGITAIS,
    subItems: [
      { label: "Clientes e acessos", href: "/dashboard/digitais/alunos" },
      { label: "Comentários", href: "/dashboard/digitais/comentarios" },
      { label: "Entregas", href: "/dashboard/digitais/entregas" },
    ],
  },
  { icon: FolderOpen, label: "Arquivos e espaço", href: "/dashboard/digitais/arquivos", section: SECAO_DOS_DIGITAIS },
  { icon: Settings, label: "Configurações", href: "/dashboard/digitais/configuracoes", section: SECAO_DOS_DIGITAIS },
];

const filtrarPorPermissao = (items: NavItem[], ctx: CompanyContext, userId: string): NavItem[] => {
  const isAllowedHref = (href: string) => {
    const permissions = permissionsForPath(href);
    if (permissions.length === 0) return true;
    return permissions.some((permission) => canCompany(ctx, userId, permission));
  };

  return items.flatMap((item) => {
    if (item.subItems && item.subItems.length > 0) {
      const allowedSubItems = item.subItems.filter((sub) => isAllowedHref(sub.href));
      if (allowedSubItems.length === 0) return [];
      return [{ ...item, subItems: allowedSubItems }];
    }
    return isAllowedHref(item.href) ? [item] : [];
  });
};

const getAllowedNavItems = (ctx: CompanyContext, userId: string) => {
  const isEmployee = shouldUseEmployeeUi(ctx, userId);
  const sourceItems = isEmployee
    ? [navItems[0], ...employeeOnlyNavItems, ...navItems.slice(1)]
    : navItems;
  return filtrarPorPermissao(sourceItems, ctx, userId);
};

/* Põe a seção dos digitais logo depois do último item da Operação. */
const comDigitais = (itens: NavItem[], digitais: NavItem[]): NavItem[] => {
  if (!digitais.length) return itens;
  const ultimaDaOperacao = itens.map((item) => item.section).lastIndexOf("Operação");
  const posicao = ultimaDaOperacao >= 0 ? ultimaDaOperacao + 1 : itens.length;
  return [...itens.slice(0, posicao), ...digitais, ...itens.slice(posicao)];
};

type NavDestination = { label: string; href: string; section: string | null };

const normalizeTerm = (value: string) =>
  value.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();

/** Achata a navegação permitida em destinos pesquisáveis pela busca do topo. */
const flattenNavDestinations = (items: NavItem[]): NavDestination[] => {
  const destinations: NavDestination[] = [];
  for (const item of items) {
    if (item.external) continue;
    if (item.subItems?.length) {
      for (const sub of item.subItems) {
        destinations.push({ label: sub.label, href: sub.href, section: item.label });
      }
      continue;
    }
    destinations.push({ label: item.label, href: item.href, section: item.section ?? null });
  }
  return destinations;
};

/** Deriva a trilha "Seção / Página" da rota atual, escolhendo o match mais específico. */
const getBreadcrumb = (items: NavItem[], pathname: string) => {
  let best: { section: string | null; title: string; length: number } | null = null;

  for (const item of items) {
    const itemPath = getHrefPath(item.href);
    const itemMatches =
      pathname === itemPath || (itemPath !== "/dashboard" && pathname.startsWith(`${itemPath}/`));

    if (itemMatches && (!best || itemPath.length > best.length)) {
      best = { section: item.section ?? null, title: item.label, length: itemPath.length };
    }

    for (const sub of item.subItems ?? []) {
      const subPath = getHrefPath(sub.href);
      if (
        isSubPathActive(pathname, subPath, itemPath) &&
        (!best || subPath.length >= best.length)
      ) {
        best = { section: item.label, title: sub.label, length: subPath.length };
      }
    }
  }

  return best
    ? { section: best.section, title: best.title }
    : { section: null, title: "Dashboard" };
};

/* Enquanto a resposta nao chega. Sem texto de "carregando": a espera dura
   fracao de segundo, e uma palavra que aparece e some rapido incomoda mais do
   que a pausa em si. */
function EsperandoAcesso() {
  return (
    <div className="flex min-h-[50vh] items-center justify-center">
      <span
        aria-hidden
        className="h-5 w-5 animate-spin rounded-full border-2 border-[var(--ks-color-border-subtle)] border-t-[var(--ks-color-primary)]"
      />
      <span className="sr-only">Verificando seu acesso…</span>
    </div>
  );
}

/* Aparece no lugar da pagina enquanto o redirecionamento acontece. Curto de
   proposito: e um estado de passagem, nao uma tela para ler. Diz o que houve e
   para quem pedir, porque quem esbarra aqui costuma achar que quebrou. */
function SemAcesso() {
  return (
    <div className="flex min-h-[50vh] items-center justify-center px-4">
      <div className="max-w-[380px] text-center">
        <div className="mx-auto mb-3 flex h-11 w-11 items-center justify-center rounded-full bg-[var(--ks-color-bg-soft)]">
          <Lock size={18} className="text-[var(--ks-color-text-soft)]" />
        </div>
        <p className="text-[15px] font-semibold text-[var(--ks-color-text-primary)]">
          Você não tem acesso a esta área
        </p>
        <p className="mt-1 text-[13px] leading-relaxed text-[var(--ks-color-text-soft)]">
          Peça a quem administra a empresa para liberar em Funcionários.
        </p>
      </div>
    </div>
  );
}

const getFirstAllowedPath = (items: NavItem[]) => {
  for (const item of items) {
    if (item.external) continue;
    if (item.subItems && item.subItems.length > 0) {
      const firstSub = item.subItems.find((sub) => !!sub.href);
      if (firstSub?.href) return firstSub.href;
    }
    if (item.href) return item.href;
  }
  return "/dashboard/settings";
};

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const pathname = usePathname();
  const router = useRouter();

  /* Saiu do KtaloguDesign (página "isolada", ver proxy.ts) para outra tela sem
     recarregar: recarrega, senão a tela nova herda o isolamento e os iframes de
     outros sites (prévia da loja, vídeos) não abrem. */
  useEffect(() => {
    if (typeof window !== "undefined" && window.crossOriginIsolated && pathname !== "/dashboard/design") window.location.reload();
  }, [pathname]);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  const [accessStatus, setAccessStatus] = useState<"loading" | "allowed" | "blocked">("loading");
  /* A empresa já tem a área de produtos digitais? Vem da mesma consulta da assinatura. */
  const [digitaisLiberados, setDigitaisLiberados] = useState(false);
  /* O KtaloguDesign está em beta: só para as empresas liberadas (vem da mesma consulta). */
  const [designLiberado, setDesignLiberado] = useState(false);
  const designLiberadoRef = useRef(false);
  /* Perguntas de alunos sem resposta: o número no item Comentários do menu. */
  const [comentariosSemResposta, setComentariosSemResposta] = useState(0);
  const [permissionState, setPermissionState] = useState<{
    userId: string;
    ctx: CompanyContext;
    items: NavItem[];
  } | null>(null);

  /* Em que pe esta a checagem de acesso a rota atual.
     `null` sozinho nao servia para decidir: e o mesmo valor para "ainda nao
     carreguei" e para "falhei ao carregar". Confundir os dois ou deixa a
     pagina abrir antes da hora, ou tranca todo mundo para fora quando a rede
     oscila. */
  const [checagemAcesso, setChecagemAcesso] = useState<"carregando" | "pronto" | "indisponivel">(
    "carregando"
  );
  const [tenantIds, setTenantIds] = useState<string[]>([]);
  const [companyName, setCompanyName] = useState<string>("");
  const [isEmployeeAccount, setIsEmployeeAccount] = useState(false);
  const [profileStatus, setProfileStatus] = useState<"loading" | "complete" | "incomplete">("loading");
  const [confettiSeed, setConfettiSeed] = useState(0);
  const [showConfetti, setShowConfetti] = useState(false);
  const [notifications, setNotifications] = useState<AdminNotification[]>([]);
  const [notificationActionId, setNotificationActionId] = useState<string | null>(null);
  const [notificationState, setNotificationState] = useState<{ open: boolean; pathname: string | null }>({
    open: false,
    pathname: null,
  });
  const [confettiPieces, setConfettiPieces] = useState<
    Array<{
      left: number;
      size: number;
      rotate: number;
      duration: number;
      delay: number;
      color: string;
      xDrift: number;
    }>
  >([]);
  const audioContextRef = useRef<AudioContext | null>(null);
  const soundUnlockedRef = useRef(false);
  const clientNameCacheRef = useRef<Record<string, { clientName: string; quoteNumber: number | null }>>({});
  const seenReviewNotificationIdsRef = useRef<Set<string>>(new Set());
  const seenCatalogOrderNotificationIdsRef = useRef<Set<string>>(new Set());
  const seenPaymentLinkNotificationIdsRef = useRef<Set<string>>(new Set());
  const seenClientSignupNotificationIdsRef = useRef<Set<string>>(new Set());
  const readNotificationIdsRef = useRef<Set<string>>(new Set());
  const dismissedNotificationIdsRef = useRef<Set<string>>(new Set());
  const isProduction = pathname?.startsWith("/dashboard/production");
  /* A página de um pedido ocupa a tela toda: abas largas, tabela larga. */
  const isOrderDetail = /^\/dashboard\/orders\/[^/]+$/.test(pathname ?? "");
  const subscriptionPath = "/dashboard/assinatura";
  const isSubscriptionPage = pathname === subscriptionPath || pathname?.startsWith(`${subscriptionPath}/`);
  const notificationOpen = notificationState.open && notificationState.pathname === pathname;
  const isPdv = pathname?.startsWith("/dashboard/pdv");
  const isReports = pathname?.startsWith("/dashboard/reports");
  /* Catálogo > Produtos tem o editor da página inicial, que mostra a loja
     inteira ao lado dos ajustes: a tela usa a largura toda. */
  const isCatalogProducts = pathname === "/dashboard/catalog/products";
  /* Catálogo > Personalização também: os cartões dos temas mostram a loja de
     verdade em miniatura, e cada pixel a mais é prévia maior. */
  const isCatalogSettings = pathname === "/dashboard/catalog";
  const isProductionKanban = pathname === "/dashboard/production";
  const isReceivables = pathname === "/dashboard/financial/receivables";
  const isPaymentLinks = pathname === "/dashboard/financial/payment-links";
  const isMySuppliers = pathname?.startsWith("/dashboard/fornecedores/meus-fornecedores");
  /* A agenda entra na lista das telas largas pelo mesmo motivo das outras:
     e uma grade de sete colunas, e cada pixel a mais de largura vira coluna
     mais larga — ou seja, menos titulo de compromisso cortado. */
  const isAgenda = pathname?.startsWith("/dashboard/agenda");
  const isNotasFiscais = pathname?.startsWith("/dashboard/notas-fiscais");
  const [isDesktopSidebarCollapsed, setIsDesktopSidebarCollapsed] = useState(false);
  const [globalSearch, setGlobalSearch] = useState("");
  const [globalSearchOpen, setGlobalSearchOpen] = useState(false);
  /* Seção que o painel mostra por escolha (clique no trilho). Guarda a rota
     junto: ao navegar, o painel volta a seguir a página. */
  const [secaoEscolhida, setSecaoEscolhida] = useState<{ secao: string; pathname: string } | null>(null);
  const [gruposFechados, setGruposFechados] = useState<Set<string>>(() => new Set());
  const [theme, setTheme] = useState<"light" | "dark">("light");
  const [notificationStateHydrated, setNotificationStateHydrated] = useState(false);
  /* O painel do Chats por cima do sistema, e quantas conversas esperam. */
  const [chatsAbertos, setChatsAbertos] = useState(false);
  const [chatsPendentes, setChatsPendentes] = useState(0);
  const isCompactLayout = isPdv || isProductionKanban || Boolean(isReports);
  const notificationStorageKey = permissionState?.userId
    ? getAdminNotificationReadStorageKey(permissionState.userId)
    : null;
  const notificationDismissedStorageKey = permissionState?.userId
    ? getAdminNotificationDismissedStorageKey(permissionState.userId)
    : null;
  const sortedNotifications = sortAdminNotifications(notifications);
  /*
    O atalho do atendimento so aparece para quem pode atender. Enquanto a
    checagem nao volta ele fica de fora: e melhor o botao chegar meio segundo
    depois do que piscar para quem nao vai poder usar.
  */
  const podeVerChats = permissionState
    ? canCompany(permissionState.ctx, permissionState.userId, "chats.read")
    : false;
  /* Os comentários sem resposta: lidos ao abrir o painel e ao andar pela
     área dos digitais (responder lá atualiza o número). */
  const podeVerComentarios =
    digitaisLiberados && permissionState ? canCompany(permissionState.ctx, permissionState.userId, "digital.comments.read") : false;
  const rotaDosDigitais = pathname?.startsWith("/dashboard/digitais") ? pathname : "fora";
  /* O sino lê os comentários de aluno só quando os digitais estão liberados e
     a pessoa pode ver os comentários (a liberação chega depois do sino ligar). */
  const sinoDosComentarios = useRef(false);
  useEffect(() => {
    sinoDosComentarios.current = podeVerComentarios;
    if (podeVerComentarios) window.dispatchEvent(new Event("ks:sino-dos-comentarios"));
  }, [podeVerComentarios]);
  useEffect(() => {
    if (!podeVerComentarios) return;
    let cancelado = false;
    const contar = () => {
      fetch("/api/digitais/comentarios?contar=1", { cache: "no-store" })
        .then((resposta) => (resposta.ok ? resposta.json() : null))
        .then((corpo: { semResposta?: unknown } | null) => {
          if (!cancelado && typeof corpo?.semResposta === "number") setComentariosSemResposta(corpo.semResposta);
        })
        .catch(() => undefined);
    };
    contar();
    /* A tela de Comentários avisa quando responde, oculta ou apaga. */
    window.addEventListener("ks:comentarios-mudaram", contar);
    return () => {
      cancelado = true;
      window.removeEventListener("ks:comentarios-mudaram", contar);
    };
  }, [podeVerComentarios, rotaDosDigitais]);
  /* O menu já filtrado pelas permissões, com os digitais quando liberados. */
  const itensDosDigitais =
    digitaisLiberados && permissionState
      ? filtrarPorPermissao(navItemsDosDigitais, permissionState.ctx, permissionState.userId).map((item) =>
          item.subItems && podeVerComentarios && comentariosSemResposta > 0
            ? {
                ...item,
                subItems: item.subItems.map((sub) =>
                  sub.href === "/dashboard/digitais/comentarios" ? { ...sub, badge: { label: String(Math.min(99, comentariosSemResposta)) } } : sub
                ),
              }
            : item
        )
      : [];
  const itensDoMenu = comDigitais(permissionState?.items ?? navItems, itensDosDigitais).filter((item) => designLiberado || item.href !== "/dashboard/design");
  const breadcrumb = getBreadcrumb(itensDoMenu, pathname ?? "/dashboard");
  const searchTerm = normalizeTerm(globalSearch.trim());
  const searchResults = searchTerm
    ? flattenNavDestinations(itensDoMenu)
        .filter((destination) =>
          normalizeTerm(`${destination.section ?? ""} ${destination.label}`).includes(searchTerm)
        )
        .slice(0, 6)
    : [];

  const goToDestination = (href: string) => {
    setGlobalSearch("");
    setGlobalSearchOpen(false);
    router.push(href);
  };

  /* O trilho e o painel, a partir do menu já filtrado pelas permissões. */
  const rotaAtual = pathname ?? "/dashboard";
  const trilho = montarTrilho(itensDoMenu);
  const secoesDoTrilho = trilho.filter(
    (entrada): entrada is Extract<EntradaDoTrilho, { tipo: "secao" }> => entrada.tipo === "secao"
  );
  const secaoDaPagina = secaoDaRota(itensDoMenu, rotaAtual);
  const secaoAberta = secaoEscolhida?.pathname === rotaAtual ? secaoEscolhida.secao : null;
  /* Sem escolha, o painel segue a página. Numa página sem seção (Dashboard)
     ele se recolhe sozinho: mostrar a primeira seção parecia dizer que ela
     estava ativa. Clicar numa seção do trilho abre o painel com ela. */
  const secaoNoPainel =
    secoesDoTrilho.find((secao) => secao.chave === (secaoAberta ?? secaoDaPagina)) ?? null;
  const primeiraSecao = secoesDoTrilho[0] ?? null;
  const painelVisivel = !isDesktopSidebarCollapsed && secaoNoPainel !== null;
  /* A gaveta do celular é o menu inteiro: sem seção, mostra a primeira. */
  const secaoNaGaveta = secaoNoPainel ?? primeiraSecao;
  const atalhoDoChats = itensDoMenu.find((item) => item.external) ?? null;
  const atalhosDoRodape = itensDoMenu.filter((item) => item.rodape);

  /* Clicar numa seção do trilho sempre abre o painel encaixado com ela,
     mesmo com a barra recolhida: nada de painel solto por cima da tela. */
  const escolherSecao = (secao: string) => {
    setSecaoEscolhida({ secao, pathname: rotaAtual });
    setIsDesktopSidebarCollapsed(false);
  };

  const alternarGrupo = (href: string) => {
    setGruposFechados((prev) => {
      const proximo = new Set(prev);
      if (proximo.has(href)) proximo.delete(href);
      else proximo.add(href);
      return proximo;
    });
  };

  const alternarSidebar = () => setIsDesktopSidebarCollapsed((prev) => !prev);

  const alternarTema = () => setTheme((prev) => (prev === "dark" ? "light" : "dark"));

  const persistNotificationIds = (storageKey: string | null, ids: Set<string>) => {
    if (!storageKey || typeof window === "undefined") return;
    try {
      window.localStorage.setItem(storageKey, JSON.stringify(Array.from(ids).slice(-500)));
    } catch {
      return;
    }
  };

  const isNotificationRead = (notificationId: string) => readNotificationIdsRef.current.has(notificationId);
  const isNotificationDismissed = (notificationId: string) => dismissedNotificationIdsRef.current.has(notificationId);
  const isNotificationHidden = (notificationId: string) =>
    isNotificationRead(notificationId) || isNotificationDismissed(notificationId);

  const persistNotificationState = async (notificationIds: string[], status: "read" | "dismissed") => {
    if (!permissionState?.userId || notificationIds.length === 0) return;
    try {
      const supabase = getSupabaseClient();
      await supabase.from("admin_notification_states").upsert(
        notificationIds.map((notificationId) => ({
          user_id: permissionState.userId,
          notification_id: notificationId,
          status,
          updated_at: new Date().toISOString(),
        })),
        { onConflict: "user_id,notification_id" }
      );
    } catch (error) {
      console.error("Erro ao persistir estado das notificações:", error);
    }
  };

  const syncSeenNotification = (notification: AdminNotification) => {
    if (notification.kind === "product_review" && notification.reviewId) {
      seenReviewNotificationIdsRef.current.add(notification.reviewId);
    }
    if (notification.kind === "catalog_order" && notification.catalogOrderId) {
      seenCatalogOrderNotificationIdsRef.current.add(notification.catalogOrderId);
    }
    if (notification.kind === "payment_link_receipt" && notification.paymentLinkPaymentId) {
      seenPaymentLinkNotificationIdsRef.current.add(notification.paymentLinkPaymentId);
    }
    if (notification.kind === "client_signup" && notification.clientId) {
      seenClientSignupNotificationIdsRef.current.add(notification.clientId);
    }
  };

  const upsertNotification = (notification: AdminNotification) => {
    if (isNotificationHidden(notification.id)) return false;
    syncSeenNotification(notification);
    let inserted = false;
    setNotifications((prev) => {
      const withoutCurrent = prev.filter((item) => item.id !== notification.id);
      inserted = withoutCurrent.length === prev.length;
      return sortAdminNotifications([notification, ...withoutCurrent]).slice(0, MAX_ADMIN_NOTIFICATIONS);
    });
    return inserted;
  };

  const markNotificationAsRead = (notification: AdminNotification) => {
    syncSeenNotification(notification);
    readNotificationIdsRef.current.add(notification.id);
    dismissedNotificationIdsRef.current.delete(notification.id);
    persistNotificationIds(notificationStorageKey, readNotificationIdsRef.current);
    persistNotificationIds(notificationDismissedStorageKey, dismissedNotificationIdsRef.current);
    void persistNotificationState([notification.id], "read");
    setNotifications((prev) => prev.filter((item) => item.id !== notification.id));
  };

  const markAllNotificationsAsRead = () => {
    if (notifications.length === 0) return;
    const notificationIds = notifications.map((notification) => notification.id);
    notifications.forEach((notification) => {
      syncSeenNotification(notification);
      readNotificationIdsRef.current.add(notification.id);
      dismissedNotificationIdsRef.current.delete(notification.id);
    });
    persistNotificationIds(notificationStorageKey, readNotificationIdsRef.current);
    persistNotificationIds(notificationDismissedStorageKey, dismissedNotificationIdsRef.current);
    void persistNotificationState(notificationIds, "read");
    setNotifications([]);
  };

  const dismissNotification = (notificationId: string) => {
    dismissedNotificationIdsRef.current.add(notificationId);
    persistNotificationIds(notificationDismissedStorageKey, dismissedNotificationIdsRef.current);
    void persistNotificationState([notificationId], "dismissed");
    setNotifications((prev) => prev.filter((item) => item.id !== notificationId));
  };

  useEffect(() => {
    setIsDesktopSidebarCollapsed(Boolean(isCompactLayout));
  }, [isCompactLayout]);

  /* Este navegador é da empresa: quando ele abrir o link de um orçamento, a
     abertura não conta como do cliente (lib/navegador-da-equipe.ts). */
  useEffect(() => {
    if (tenantIds.length) marcarNavegadorDaEquipe(tenantIds);
  }, [tenantIds]);

  useEffect(() => {
    let cancelled = false;

    async function hydrateNotificationState() {
      if (typeof window === "undefined" || !permissionState?.userId) {
        readNotificationIdsRef.current = new Set();
        dismissedNotificationIdsRef.current = new Set();
        if (!cancelled) setNotificationStateHydrated(false);
        return;
      }

      if (!cancelled) setNotificationStateHydrated(false);

      let nextReadIds = new Set<string>();
      let nextDismissedIds = new Set<string>();

      try {
        const storedRead = notificationStorageKey ? window.localStorage.getItem(notificationStorageKey) : null;
        const parsedRead = storedRead ? (JSON.parse(storedRead) as unknown) : [];
        nextReadIds = new Set(Array.isArray(parsedRead) ? parsedRead.filter((item) => typeof item === "string") : []);
      } catch {
        nextReadIds = new Set();
      }

      try {
        const storedDismissed = notificationDismissedStorageKey
          ? window.localStorage.getItem(notificationDismissedStorageKey)
          : null;
        const parsedDismissed = storedDismissed ? (JSON.parse(storedDismissed) as unknown) : [];
        nextDismissedIds = new Set(
          Array.isArray(parsedDismissed) ? parsedDismissed.filter((item) => typeof item === "string") : []
        );
      } catch {
        nextDismissedIds = new Set();
      }

      try {
        const supabase = getSupabaseClient();
        const { data } = await supabase
          .from("admin_notification_states")
          .select("notification_id,status")
          .eq("user_id", permissionState.userId);

        (Array.isArray(data) ? data : []).forEach((row) => {
          if (row.status === "dismissed") nextDismissedIds.add(String(row.notification_id));
          else nextReadIds.add(String(row.notification_id));
        });
      } catch (error) {
        console.error("Erro ao carregar estado persistido das notificações:", error);
      }

      readNotificationIdsRef.current = nextReadIds;
      dismissedNotificationIdsRef.current = nextDismissedIds;
      persistNotificationIds(notificationStorageKey, nextReadIds);
      persistNotificationIds(notificationDismissedStorageKey, nextDismissedIds);

      const legacyIds = [...nextReadIds, ...nextDismissedIds];
      if (legacyIds.length > 0) {
        void persistNotificationState([...nextReadIds], "read");
        void persistNotificationState([...nextDismissedIds], "dismissed");
      }

      if (!cancelled) {
        setNotifications((prev) => prev.filter((item) => !isNotificationHidden(item.id)));
        setNotificationStateHydrated(true);
      }
    }

    void hydrateNotificationState();
    return () => {
      cancelled = true;
    };
  }, [permissionState?.userId, notificationStorageKey, notificationDismissedStorageKey]);

  useEffect(() => {
    const storedTheme = typeof window !== "undefined" ? window.localStorage.getItem("ks-theme") : null;
    const preferred = storedTheme === "dark" || storedTheme === "light" ? storedTheme : "light";
    setTheme(preferred);
  }, []);

  useEffect(() => {
    if (typeof document === "undefined") return;
    document.documentElement.setAttribute("data-theme", theme);
    if (typeof window !== "undefined") {
      window.localStorage.setItem("ks-theme", theme);
    }
  }, [theme]);

  useEffect(() => {
    let cancelled = false;

    async function checkAccess() {
      try {
        const supabase = getSupabaseClient();
        const { data: { user } } = await supabase.auth.getUser();
        if (!user) {
          if (!cancelled) setAccessStatus("allowed");
          return;
        }

        const res = await fetch("/api/company/users", { method: "GET" });
        const json = (await res.json().catch(() => null)) as
          | {
              plan?: Record<string, unknown> | null;
              company?: Record<string, unknown> | null;
              recursos?: { digitais?: unknown; design?: unknown } | null;
            }
          | null;

        if (!cancelled) setDigitaisLiberados(res.ok && json?.recursos?.digitais === true);
        if (!cancelled) {
          designLiberadoRef.current = res.ok && json?.recursos?.design === true;
          setDesignLiberado(designLiberadoRef.current);
        }

        const planRaw = res.ok ? json?.plan ?? null : null;
        const companyRaw = res.ok ? json?.company ?? null : null;
        const planData = planRaw || companyRaw ? { ...(companyRaw ?? {}), ...(planRaw ?? {}) } : null;
        const status =
          typeof planData?.status === "string"
            ? planData.status
            : typeof planData?.plan_status === "string"
              ? planData.plan_status
              : typeof planData?.state === "string"
                ? planData.state
                : typeof planData?.subscription_status === "string"
                  ? planData.subscription_status
                : null;
        const expiresAtRaw =
          typeof planData?.expires_at === "string"
            ? planData.expires_at
            : typeof planData?.valid_until === "string"
              ? planData.valid_until
              : typeof planData?.expiration_date === "string"
                ? planData.expiration_date
                : typeof planData?.expiresAt === "string"
                  ? planData.expiresAt
                  : typeof planData?.subscription_expires_at === "string"
                    ? planData.subscription_expires_at
                  : null;

        if (!status) {
          // Fallback: consulta direta ao users_app (mesma lógica do UserNav)
          const { data: directPlan } = await supabase
            .from("users_app")
            .select("status, expires_at")
            .eq("email", user.email)
            .maybeSingle();

          if (directPlan?.status) {
            const directNorm = String(directPlan.status).trim().toLowerCase();
            const directExpires = directPlan.expires_at ? new Date(String(directPlan.expires_at)) : null;
            const directIsExpired = directExpires ? directExpires.getTime() <= Date.now() : false;
            const directIsTrial = directNorm === "trial" || directNorm === "teste" || directNorm.startsWith("trial");
            const directIsActive =
              directNorm === "active" || directNorm === "paid" || directNorm.startsWith("ativ") || directIsTrial;

            if (!cancelled) setAccessStatus(directIsActive && !directIsExpired ? "allowed" : "blocked");
            return;
          }

          if (!cancelled) setAccessStatus("blocked");
          return;
        }

        const expiresAt = expiresAtRaw ? new Date(expiresAtRaw) : null;
        const isExpired = expiresAt ? expiresAt.getTime() <= Date.now() : false;
        const normalizedStatus = status.trim().toLowerCase();
        const isTrialStatus =
          normalizedStatus === "trial" ||
          normalizedStatus === "teste" ||
          normalizedStatus.startsWith("trial");
        const isActiveStatus =
          normalizedStatus === "active" ||
          normalizedStatus === "paid" ||
          normalizedStatus.startsWith("ativ") ||
          isTrialStatus;
        const isActive = isActiveStatus && !isExpired;

        if (!cancelled) {
          setAccessStatus(isActive ? "allowed" : "blocked");
        }
      } catch {
        if (!cancelled) setAccessStatus("blocked");
      }
    }

    checkAccess();
    /* Uma vez por sessao e a cada 10 minutos. Antes rodava a cada troca de
       rota: /api/company/users com 8 a 10 consultas em fila e a tela parada
       esperando, em todo clique de menu. O middleware continua conferindo o
       acesso em cada request; aqui e so o aviso de assinatura. */
    const relogio = window.setInterval(checkAccess, 10 * 60 * 1000);
    return () => {
      cancelled = true;
      window.clearInterval(relogio);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    let cancelled = false;

    async function loadPermissions() {
      try {
        const supabase = getSupabaseClient();
        const { data: { user } } = await supabase.auth.getUser();
        if (!user) {
          if (!cancelled) {
            setPermissionState(null);
            /* Sem sessao nao ha o que checar aqui — quem trata isso e o
               middleware, que manda para o login. */
            setChecagemAcesso("indisponivel");
            setProfileStatus("complete");
          }
          return;
        }
        const ctx = await getCompanyContext(supabase, user);
        const items = getAllowedNavItems(ctx, user.id);
        const resolvedTenantIds = ctx.tenantIds?.length ? ctx.tenantIds : [user.id];

        const isEmployee = shouldUseEmployeeUi(ctx, user.id);
        let resolvedCompanyName = "";

        if (isEmployee) {
          let ownerProfileId: string | null = ctx.ownerId || null;

          if (!ownerProfileId) {
            const ownerMembership = await supabase
              .from("company_users")
              .select("user_id,active")
              .eq("company_id", ctx.companyId)
              .eq("role", "owner")
              .maybeSingle();

            if (
              !ownerMembership.error &&
              ownerMembership.data &&
              (ownerMembership.data as { active?: unknown }).active !== false
            ) {
              const ownerIdCandidate = (ownerMembership.data as { user_id?: unknown }).user_id;
              if (typeof ownerIdCandidate === "string" && ownerIdCandidate) ownerProfileId = ownerIdCandidate;
            }
          }

          if (ownerProfileId) {
            const profileByOwner = await supabase
              .from("profiles")
              .select("store_name")
              .eq("id", ownerProfileId)
              .maybeSingle();
            if (
              !profileByOwner.error &&
              profileByOwner.data &&
              typeof (profileByOwner.data as { store_name?: unknown }).store_name === "string"
            ) {
              resolvedCompanyName = String((profileByOwner.data as { store_name?: unknown }).store_name || "").trim();
            }
          }
        }

        if (!resolvedCompanyName) {
          const profileById = await supabase
            .from("profiles")
            .select("store_name")
            .eq("id", ctx.companyId)
            .maybeSingle();
          if (
            !profileById.error &&
            profileById.data &&
            typeof (profileById.data as { store_name?: unknown }).store_name === "string"
          ) {
            resolvedCompanyName = String((profileById.data as { store_name?: unknown }).store_name || "").trim();
          }
        }

        if (!resolvedCompanyName) {
          const companyById = await supabase
            .from("companies")
            .select("name")
            .eq("id", ctx.companyId)
            .maybeSingle();

          if (
            !companyById.error &&
            companyById.data &&
            typeof (companyById.data as { name?: unknown }).name === "string"
          ) {
            resolvedCompanyName = String((companyById.data as { name?: unknown }).name || "").trim();
          }
        }

        if (!cancelled) {
          setPermissionState({ userId: user.id, ctx, items });
          setChecagemAcesso("pronto");
          setIsEmployeeAccount(isEmployee);
          setCompanyName(resolvedCompanyName || "Empresa");
          setTenantIds(resolvedTenantIds);
        }
      } catch {
        if (!cancelled) {
          setPermissionState(null);
          /* Falhou a leitura do vinculo. Nao tranca ninguem: a API continua
             barrando o que precisa ser barrado, e prender quem tem acesso por
             causa de uma oscilacao de rede seria pior que o problema. */
          setChecagemAcesso("indisponivel");
          setProfileStatus("complete");
        }
      }
    }

    loadPermissions();
    return () => {
      cancelled = true;
    };
  }, []);

  /* Permissao exigida pela rota aberta. `null` quer dizer rota sem exigencia,
     que segue liberada como sempre foi — fechar essas e assunto de outra
     etapa, com chave propria para cada modulo. */
  const permissaoDaRota = permissionsForPath(pathname);

  /* A pagina so entra na tela depois de a resposta ser conhecida.
     Antes, `children` renderizava junto com o layout e o redirecionamento
     vinha depois, num efeito: quem nao tinha acesso via o conteudo piscar e,
     pior, as consultas da tela ja tinham saido.

     A decisao mora em `decidirAcessoDaRota`, e nao aqui, para poder ser testada
     sem uma conta de funcionario por cenario. */
  const papelEfetivo = permissionState
    ? getEffectiveCompanyRole(permissionState.ctx, permissionState.userId)
    : null;

  const decisaoAcesso = decidirAcessoDaRota({
    checagem: checagemAcesso,
    permissaoExigida: permissaoDaRota,
    papel: papelEfetivo,
    ajustes: permissionState?.ctx.membership?.permissions,
  });

  const acessoNegado = decisaoAcesso === "negado";
  const aguardandoChecagem = decisaoAcesso === "aguardando";

  useEffect(() => {
    if (!acessoNegado || !permissionState) return;
    const fallbackPath = getFirstAllowedPath(permissionState.items);
    if (getHrefPath(fallbackPath) !== getHrefPath(pathname)) {
      router.replace(fallbackPath);
    }
  }, [acessoNegado, pathname, permissionState, router]);

  useEffect(() => {
    if (!permissionState) return;
    const currentState = permissionState;
    let cancelled = false;

    async function checkProfileCompletion() {
      try {
        const { userId, ctx } = currentState;
        const role = getEffectiveCompanyRole(ctx, userId);
        if (role !== "owner") {
          if (!cancelled) setProfileStatus("complete");
          return;
        }
        const supabase = getSupabaseClient();
        const { data } = await supabase
          .from("profiles")
          .select("store_name, cnpj, phone, address_zip, address_street, address_number, address_neighborhood, address_city, address_state")
          .eq("id", userId)
          .maybeSingle();
        const profile = data ?? {};
        const cleanCnpj =
          typeof (profile as { cnpj?: unknown }).cnpj === "string"
            ? String((profile as { cnpj?: unknown }).cnpj).replace(/\D/g, "")
            : "";
        const isComplete =
          String((profile as { store_name?: unknown }).store_name ?? "").trim() &&
          (cleanCnpj.length === 14 || cleanCnpj.length === 11) &&
          String((profile as { phone?: unknown }).phone ?? "").trim() &&
          String((profile as { address_zip?: unknown }).address_zip ?? "").trim() &&
          String((profile as { address_street?: unknown }).address_street ?? "").trim() &&
          String((profile as { address_number?: unknown }).address_number ?? "").trim() &&
          String((profile as { address_neighborhood?: unknown }).address_neighborhood ?? "").trim() &&
          String((profile as { address_city?: unknown }).address_city ?? "").trim() &&
          String((profile as { address_state?: unknown }).address_state ?? "").trim();
        if (!cancelled) setProfileStatus(isComplete ? "complete" : "incomplete");
      } catch {
        if (!cancelled) setProfileStatus("incomplete");
      }
    }

    checkProfileCompletion();
    return () => {
      cancelled = true;
    };
  }, [permissionState]);

  useEffect(() => {
    if (accessStatus === "blocked" && !isSubscriptionPage) {
      router.replace(subscriptionPath);
    }
  }, [accessStatus, isSubscriptionPage, router, subscriptionPath]);

  useEffect(() => {
    if (profileStatus === "incomplete") {
      router.replace("/onboarding");
    }
  }, [profileStatus, router]);

  useEffect(() => {
    if (!showConfetti) return;
    const timer = setTimeout(() => setShowConfetti(false), 2200);
    return () => clearTimeout(timer);
  }, [showConfetti]);

  useEffect(() => {
    if (
      accessStatus !== "allowed" ||
      profileStatus !== "complete" ||
      typeof window === "undefined"
    ) {
      return;
    }

    const shouldCelebrate = window.sessionStorage.getItem(DASHBOARD_CONFETTI_STORAGE_KEY) === "1";
    if (!shouldCelebrate) return;

    window.sessionStorage.removeItem(DASHBOARD_CONFETTI_STORAGE_KEY);
    setConfettiSeed((value) => value + 1);
    setConfettiPieces(createConfettiPieces());
    setShowConfetti(true);
  }, [accessStatus, profileStatus, pathname]);

  useEffect(() => {
    const Context = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
    if (!Context) return;
    const unlockSound = () => {
      if (!audioContextRef.current) {
        audioContextRef.current = new Context();
      }
      audioContextRef.current
        ?.resume()
        .then(() => {
          soundUnlockedRef.current = true;
        })
        .catch(() => {
          soundUnlockedRef.current = true;
        });
    };
    window.addEventListener("pointerdown", unlockSound, { once: true });
    window.addEventListener("keydown", unlockSound, { once: true });
    return () => {
      window.removeEventListener("pointerdown", unlockSound);
      window.removeEventListener("keydown", unlockSound);
    };
  }, []);

  useEffect(() => {
    if (!permissionState?.userId || tenantIds.length === 0 || !notificationStateHydrated) return;
    const supabase = getSupabaseClient();
    const channel = supabase.channel("admin-notifications");
    let pollingCancelled = false;
    /* O aviso de cadastro so vai para quem pode abrir a ficha do cliente. Sem
       permissao o sino levaria a pessoa a uma porta fechada. */
    const podeVerClientes = canCompany(permissionState.ctx, permissionState.userId, "clients.read");
    /* Nota contra o CNPJ é assunto de quem responde por ela: sem a permissão, o
       sino levaria a pessoa a uma tela que ela não abre. */
    const podeVerRecebidas = canCompany(permissionState.ctx, permissionState.userId, "fiscal.recebidas");
    const resolveClientName = async (quoteId: string) => {
      const cached = clientNameCacheRef.current[quoteId];
      if (cached) return cached;
      const { data } = await supabase
        .from("quotes")
        .select("id, user_id, number, client:clients(name)")
        .eq("id", quoteId)
        .in("user_id", tenantIds)
        .maybeSingle();
      if (!data) return null;
      const rawName =
        typeof (data as { client?: { name?: unknown } } | null)?.client?.name === "string"
          ? String((data as { client?: { name?: unknown } } | null)?.client?.name || "")
          : "";
      const firstName = rawName.trim().split(/\s+/)[0] || "Cliente";
      const quoteNumber =
        typeof (data as { number?: unknown } | null)?.number === "number"
          ? (data as { number?: number } | null)?.number ?? null
          : null;
      const payload = { clientName: firstName, quoteNumber };
      clientNameCacheRef.current[quoteId] = payload;
      return payload;
    };
    const resolveReviewNotification = async (reviewId: string) => {
      try {
        const response = await fetch(`/api/catalog/reviews?reviewId=${encodeURIComponent(reviewId)}`, {
          cache: "no-store",
        });
        const payload = (await response.json().catch(() => null)) as
          | {
              review?: {
                id: string;
                status: "pending" | "approved" | "rejected";
                comment: string | null;
                created_at: string;
                rating: number;
                client_name: string;
                client_avatar_url: string | null;
                product_name: string;
              } | null;
            }
          | null;
        if (!response.ok || !payload?.review) return null;
        return payload.review;
      } catch {
        return null;
      }
    };
    const resolveCatalogOrderNotification = async (catalogOrderId: string) => {
      try {
        const response = await fetch(`/api/catalog/orders?orderId=${encodeURIComponent(catalogOrderId)}`, {
          cache: "no-store",
        });
        const payload = (await response.json().catch(() => null)) as
          | {
              order?: {
                id: string;
                number: number;
                status: "pending" | "approved" | "canceled";
                kind?: "order" | "quote_request" | null;
                customer_name: string | null;
                total: number | null;
                created_at: string;
                item_count: number;
                item_preview: string[];
                clients?: {
                  avatar_url: string | null;
                } | null;
              } | null;
            }
          | null;
        if (!response.ok || !payload?.order) return null;
        return payload.order;
      } catch {
        return null;
      }
    };
    const resolvePaymentLinkReceiptNotification = async (paymentLinkPaymentId: string) => {
      const { data } = await supabase
        .from("payment_link_payments")
        .select("id,payment_link_id,user_id,status,amount,created_at,payer_name,payment_method,payment_links(title,code)")
        .eq("id", paymentLinkPaymentId)
        .in("user_id", tenantIds)
        .maybeSingle();
      if (!data || data.status !== "approved") return null;

      const paymentLink =
        data.payment_links && typeof data.payment_links === "object" && !Array.isArray(data.payment_links)
          ? (data.payment_links as { title?: string | null; code?: string | null })
          : null;

      return {
        id: data.id,
        payment_link_id: data.payment_link_id,
        status: data.status,
        amount: typeof data.amount === "number" ? data.amount : Number(data.amount || 0) || 0,
        created_at: data.created_at,
        payer_name: typeof data.payer_name === "string" && data.payer_name.trim() ? data.payer_name.trim() : "Cliente",
        payment_method: typeof data.payment_method === "string" ? data.payment_method : null,
        payment_link_title: paymentLink?.title?.trim() || "Link de pagamento",
        payment_link_code: paymentLink?.code?.trim() || null,
      };
    };
    const playNotificationSound = async () => {
      const Context = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
      if (!Context) return;
      const context = audioContextRef.current ?? new Context();
      audioContextRef.current = context;
      if (context.state === "suspended") {
        try {
          await context.resume();
          soundUnlockedRef.current = true;
        } catch {
          if (!soundUnlockedRef.current) return;
        }
      }
      const oscillator = context.createOscillator();
      const gain = context.createGain();
      oscillator.type = "sine";
      oscillator.frequency.value = 880;
      gain.gain.value = 0.0001;
      gain.gain.exponentialRampToValueAtTime(0.18, context.currentTime + 0.02);
      gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.45);
      oscillator.connect(gain);
      gain.connect(context.destination);
      oscillator.start();
      oscillator.stop(context.currentTime + 0.5);
    };
    /* Som de mensagem: duas notas curtas subindo. Precisa ser diferente do
       aviso comum e do som de dinheiro — quem trabalha com o sistema aberto o
       dia todo reconhece pelo ouvido antes de olhar a tela. */
    const playMessageNotificationSound = async () => {
      const Context = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
      if (!Context) return;
      const context = audioContextRef.current ?? new Context();
      audioContextRef.current = context;
      if (context.state === "suspended") {
        try {
          await context.resume();
          soundUnlockedRef.current = true;
        } catch {
          if (!soundUnlockedRef.current) return;
        }
      }

      [
        { frequency: 587.33, start: 0, duration: 0.09, gain: 0.1 },
        { frequency: 880, start: 0.085, duration: 0.16, gain: 0.13 },
      ].forEach((tone) => {
        const oscillator = context.createOscillator();
        const gain = context.createGain();
        oscillator.type = "sine";
        oscillator.frequency.setValueAtTime(tone.frequency, context.currentTime + tone.start);
        gain.gain.setValueAtTime(0.0001, context.currentTime + tone.start);
        gain.gain.exponentialRampToValueAtTime(tone.gain, context.currentTime + tone.start + 0.012);
        gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + tone.start + tone.duration);
        oscillator.connect(gain);
        gain.connect(context.destination);
        oscillator.start(context.currentTime + tone.start);
        oscillator.stop(context.currentTime + tone.start + tone.duration + 0.02);
      });
    };

    const playMoneyNotificationSound = async () => {
      const Context = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
      if (!Context) return;
      const context = audioContextRef.current ?? new Context();
      audioContextRef.current = context;
      if (context.state === "suspended") {
        try {
          await context.resume();
          soundUnlockedRef.current = true;
        } catch {
          if (!soundUnlockedRef.current) return;
        }
      }

      const sequence = [
        { frequency: 740, start: 0, duration: 0.08, gain: 0.09 },
        { frequency: 987, start: 0.1, duration: 0.1, gain: 0.13 },
        { frequency: 1318, start: 0.23, duration: 0.18, gain: 0.16 },
      ];

      sequence.forEach((tone) => {
        const oscillator = context.createOscillator();
        const gain = context.createGain();
        oscillator.type = "triangle";
        oscillator.frequency.setValueAtTime(tone.frequency, context.currentTime + tone.start);
        gain.gain.setValueAtTime(0.0001, context.currentTime + tone.start);
        gain.gain.exponentialRampToValueAtTime(tone.gain, context.currentTime + tone.start + 0.015);
        gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + tone.start + tone.duration);
        oscillator.connect(gain);
        gain.connect(context.destination);
        oscillator.start(context.currentTime + tone.start);
        oscillator.stop(context.currentTime + tone.start + tone.duration + 0.02);
      });
    };
    const handleIncoming = async (record: {
      id?: string;
      quote_id?: string;
      art_id?: string | null;
      message?: string;
      created_at?: string;
      kind?: "chat" | "system" | "order" | "other" | "art_approved" | "art_rejected" | "quote_approved";
      quote_number?: number | null;
      target_user_id?: string | null;
    }) => {
      const recordId = record.id;
      const quoteId = record.quote_id;
      if (!recordId || !quoteId) return;
      if (isNotificationRead(recordId)) return;
      const targetUserId =
        typeof record.target_user_id === "string" ? record.target_user_id.trim() : "";
      if (targetUserId && !tenantIds.includes(targetUserId)) return;
      const resolved = await resolveClientName(quoteId);
      if (!resolved?.clientName) return;
      const quoteNumber =
        typeof record.quote_number === "number" ? record.quote_number : resolved.quoteNumber ?? null;
      const inserted = upsertNotification({
        id: recordId,
        kind: record.kind ?? "chat",
        quoteId,
        artId: record.art_id ?? null,
        reviewId: null,
        catalogOrderId: null,
        clientId: null,
        paymentLinkPaymentId: null,
        paymentLinkId: null,
        paymentLinkCode: null,
        paymentLinkTitle: null,
        message: typeof record.message === "string" ? record.message : "",
        createdAt: record.created_at ?? new Date().toISOString(),
        clientName: resolved.clientName,
        clientAvatarUrl: null,
        quoteNumber,
        catalogOrderNumber: null,
        productName: null,
        rating: null,
        itemCount: null,
        orderTotal: null,
        status: null,
      });
      if (inserted) {
        await playNotificationSound();
      }
    };
    const handleIncomingReview = async (record: {
      id?: string;
      catalog_user_id?: string | null;
      status?: "pending" | "approved" | "rejected" | null;
    }) => {
      const reviewId = typeof record.id === "string" ? record.id.trim() : "";
      const catalogUserId = typeof record.catalog_user_id === "string" ? record.catalog_user_id.trim() : "";
      if (!reviewId || !catalogUserId || !tenantIds.includes(catalogUserId)) return;
      if (isNotificationRead(`review:${reviewId}`)) return;

      const review = await resolveReviewNotification(reviewId);
      if (!review || review.status !== "pending") return;

      const inserted = upsertNotification({
        id: `review:${review.id}`,
        kind: "product_review",
        quoteId: null,
        artId: null,
        reviewId: review.id,
        catalogOrderId: null,
        clientId: null,
        paymentLinkPaymentId: null,
        paymentLinkId: null,
        paymentLinkCode: null,
        paymentLinkTitle: null,
        message: review.comment || "",
        createdAt: review.created_at,
        clientName: review.client_name,
        clientAvatarUrl: review.client_avatar_url,
        quoteNumber: null,
        catalogOrderNumber: null,
        productName: review.product_name,
        rating: typeof review.rating === "number" ? review.rating : null,
        itemCount: null,
        orderTotal: null,
        status: review.status,
      });
      if (inserted) {
        await playNotificationSound();
      }
    };
    const handleIncomingCatalogOrder = async (record: {
      id?: string;
      user_id?: string | null;
      status?: "pending" | "approved" | "canceled" | null;
    }) => {
      const catalogOrderId = typeof record.id === "string" ? record.id.trim() : "";
      const catalogUserId = typeof record.user_id === "string" ? record.user_id.trim() : "";
      const status = record.status ?? "pending";
      if (!catalogOrderId || !catalogUserId || !tenantIds.includes(catalogUserId) || status !== "approved") return;
      if (seenCatalogOrderNotificationIdsRef.current.has(catalogOrderId)) return;
      if (isNotificationRead(`catalog-order:${catalogOrderId}`)) return;

      const order = await resolveCatalogOrderNotification(catalogOrderId);
      if (!order || order.status !== "approved") return;

      const inserted = upsertNotification({
        id: `catalog-order:${order.id}`,
        kind: "catalog_order",
        quoteId: null,
        artId: null,
        reviewId: null,
        catalogOrderId: order.id,
        clientId: null,
        paymentLinkPaymentId: null,
        paymentLinkId: null,
        paymentLinkCode: null,
        paymentLinkTitle: null,
        message: order.item_preview.length
          ? order.item_preview.join(" • ")
          : "Pagamento aprovado no checkout do catálogo.",
        createdAt: order.created_at,
        clientName: order.customer_name || "Cliente do catálogo",
        clientAvatarUrl: order.clients?.avatar_url || null,
        quoteNumber: null,
        catalogOrderNumber: typeof order.number === "number" ? order.number : null,
        productName: null,
        rating: null,
        itemCount: typeof order.item_count === "number" ? order.item_count : null,
        orderTotal: typeof order.total === "number" ? order.total : null,
        status: order.status,
        catalogOrderKind: order.kind === "quote_request" ? "quote_request" : "order",
      });
      if (inserted) {
        await playNotificationSound();
      }
    };
    const handleIncomingPaymentLinkReceipt = async (record: {
      id?: string;
      user_id?: string | null;
      status?: "initiated" | "pending" | "approved" | "rejected" | "cancelled" | null;
    }) => {
      const paymentLinkPaymentId = typeof record.id === "string" ? record.id.trim() : "";
      const ownerId = typeof record.user_id === "string" ? record.user_id.trim() : "";
      const status = record.status ?? "initiated";
      if (!paymentLinkPaymentId || !ownerId || !tenantIds.includes(ownerId) || status !== "approved") return;
      if (isNotificationRead(`payment-link:${paymentLinkPaymentId}`)) return;

      const receipt = await resolvePaymentLinkReceiptNotification(paymentLinkPaymentId);
      if (!receipt) return;

      const inserted = upsertNotification({
        id: `payment-link:${receipt.id}`,
        kind: "payment_link_receipt",
        quoteId: null,
        artId: null,
        reviewId: null,
        catalogOrderId: null,
        clientId: null,
        paymentLinkPaymentId: receipt.id,
        paymentLinkId: receipt.payment_link_id,
        paymentLinkCode: receipt.payment_link_code,
        paymentLinkTitle: receipt.payment_link_title,
        message: `Recebimento aprovado em ${receipt.payment_link_title}.`,
        createdAt: receipt.created_at,
        clientName: receipt.payer_name,
        clientAvatarUrl: null,
        quoteNumber: null,
        catalogOrderNumber: null,
        productName: null,
        rating: null,
        itemCount: null,
        orderTotal: receipt.amount,
        status: "approved",
      });
      if (inserted) {
        await playMoneyNotificationSound();
      }
    };
    const seedPendingReviewNotifications = async () => {
      try {
        const response = await fetch("/api/catalog/reviews", { cache: "no-store" });
        const payload = (await response.json().catch(() => null)) as
          | {
              reviews?: Array<{
                id: string;
                status: "pending" | "approved" | "rejected";
                comment: string | null;
                created_at: string;
                rating: number;
                client_name: string;
                client_avatar_url: string | null;
                product_name: string;
              }>;
            }
          | null;
        if (!response.ok || !Array.isArray(payload?.reviews) || pollingCancelled) return;

        const pendingReviews = payload.reviews
          .filter((review) => review.status === "pending")
          .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
          .slice(0, 10);

        const unseenReviews = pendingReviews.filter(
          (review) =>
            !seenReviewNotificationIdsRef.current.has(review.id) &&
            !isNotificationRead(`review:${review.id}`)
        );
        if (unseenReviews.length === 0) return;

        unseenReviews.forEach((review) => {
          void upsertNotification({
            id: `review:${review.id}`,
            kind: "product_review",
            quoteId: null,
            artId: null,
            reviewId: review.id,
            catalogOrderId: null,
            clientId: null,
            paymentLinkPaymentId: null,
            paymentLinkId: null,
            paymentLinkCode: null,
            paymentLinkTitle: null,
            message: review.comment || "",
            createdAt: review.created_at,
            clientName: review.client_name,
            clientAvatarUrl: review.client_avatar_url,
            quoteNumber: null,
            catalogOrderNumber: null,
            itemCount: null,
            orderTotal: null,
            productName: review.product_name,
            rating: typeof review.rating === "number" ? review.rating : null,
            status: review.status,
          });
        });
      } catch {
        return;
      }
    };
    type PedidoDoCatalogoNaLista = {
      id: string;
      number: number;
      status: "pending" | "approved" | "canceled";
      kind?: "order" | "quote_request" | null;
      customer_name: string | null;
      total: number | null;
      created_at: string;
      item_count: number;
      item_preview: string[];
      payment_status?: string | null;
      clients?: { avatar_url: string | null } | null;
    };
    const seedApprovedCatalogOrderNotifications = async () => {
      try {
        const response = await fetch("/api/catalog/orders?status=approved&limit=10", { cache: "no-store" });
        const payload = (await response.json().catch(() => null)) as { orders?: PedidoDoCatalogoNaLista[] } | null;
        if (!response.ok || !Array.isArray(payload?.orders) || pollingCancelled) return;

        const pendingOrders = payload.orders
          .filter((order) => order.status === "approved")
          .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());

        const unseenOrders = pendingOrders.filter(
          (order) =>
            !seenCatalogOrderNotificationIdsRef.current.has(order.id) &&
            !isNotificationRead(`catalog-order:${order.id}`)
        );
        if (unseenOrders.length === 0) return;

        unseenOrders.forEach((order) => {
          void upsertNotification({
            id: `catalog-order:${order.id}`,
            kind: "catalog_order",
            quoteId: null,
            artId: null,
            reviewId: null,
            catalogOrderId: order.id,
            clientId: null,
            paymentLinkPaymentId: null,
            paymentLinkId: null,
            paymentLinkCode: null,
            paymentLinkTitle: null,
            message: order.item_preview.length
              ? order.item_preview.join(" • ")
              : "Pagamento aprovado no checkout do catálogo.",
            createdAt: order.created_at,
            clientName: order.customer_name || "Cliente do catálogo",
            clientAvatarUrl: order.clients?.avatar_url || null,
            quoteNumber: null,
            catalogOrderNumber: typeof order.number === "number" ? order.number : null,
            productName: null,
            rating: null,
            itemCount: typeof order.item_count === "number" ? order.item_count : null,
            orderTotal: typeof order.total === "number" ? order.total : null,
            status: order.status,
            catalogOrderKind: order.kind === "quote_request" ? "quote_request" : "order",
          });
        });
      } catch {
        return;
      }
    };
    /* O pedido que chegou pela loja e ainda está pendente: o pedido de
       orçamento, o pedido pelo WhatsApp e o que espera pagamento. O aviso em
       tempo real chega pelo canal da venda (evento "pedido-da-loja"); esta
       busca pega o que entrou enquanto o painel estava fechado, só das
       últimas 48 horas, para não ressuscitar a lista antiga. */
    const seedPendingCatalogOrderNotifications = async () => {
      try {
        const response = await fetch("/api/catalog/orders?status=pending&limit=10", { cache: "no-store" });
        const payload = (await response.json().catch(() => null)) as { orders?: PedidoDoCatalogoNaLista[] } | null;
        if (!response.ok || !Array.isArray(payload?.orders) || pollingCancelled) return;

        const desdeQuando = Date.now() - 48 * 60 * 60 * 1000;
        const recentes = payload.orders.filter(
          (order) =>
            order.status === "pending" &&
            getNotificationTimestamp(order.created_at) >= desdeQuando &&
            !seenCatalogOrderNotificationIdsRef.current.has(order.id) &&
            !isNotificationRead(`catalog-order:${order.id}`)
        );
        if (recentes.length === 0) return;

        recentes.forEach((order) => {
          const orcamento = order.kind === "quote_request";
          const itens = typeof order.item_count === "number" ? order.item_count : 0;
          void upsertNotification({
            id: `catalog-order:${order.id}`,
            kind: "catalog_order",
            quoteId: null,
            artId: null,
            reviewId: null,
            catalogOrderId: order.id,
            clientId: null,
            paymentLinkPaymentId: null,
            paymentLinkId: null,
            paymentLinkCode: null,
            paymentLinkTitle: null,
            message: orcamento
              ? `Pedido de orçamento com ${itens} ${itens === 1 ? "item" : "itens"}`
              : order.payment_status === "pending"
                ? "Aguardando pagamento"
                : order.item_preview.length
                  ? order.item_preview.join(" • ")
                  : "Pedido pelo WhatsApp",
            createdAt: order.created_at,
            clientName: order.customer_name || "Cliente do catálogo",
            clientAvatarUrl: order.clients?.avatar_url || null,
            quoteNumber: null,
            catalogOrderNumber: typeof order.number === "number" ? order.number : null,
            productName: null,
            rating: null,
            itemCount: typeof order.item_count === "number" ? order.item_count : null,
            /* No pedido de orçamento o total é o do cadastro, que o cliente
               não viu: fica fora do aviso para ninguém citar por engano. */
            orderTotal: !orcamento && typeof order.total === "number" ? order.total : null,
            status: order.status,
            catalogOrderKind: orcamento ? "quote_request" : "order",
          });
        });
      } catch {
        return;
      }
    };
    /* Quem se cadastrou pelo formulario publico (o link do botao "Copiar link"
       na pagina de Clientes).
       O aviso nasce nesta consulta de 15 em 15 segundos, e nao no tempo real:
       a tabela `clients` nao esta na publicacao de realtime do banco, e a
       consulta ainda tem a vantagem de mostrar o cadastro que chegou enquanto
       o painel estava fechado. `origin` separa quem se cadastrou sozinho de
       quem o dono digitou no admin - sem isso o sino avisaria a pessoa sobre o
       cliente que ela mesma acabou de cadastrar. */
    const seedRecentClientSignups = async () => {
      if (!podeVerClientes) return;
      const colunas = "id,name,company_name,email,phone,whatsapp,address_city,address_state,avatar_url,created_at,portal_status,portal_requested_at";
      const desdeQuando = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
      /* Quem se cadastrou pela loja no modo "com aprova\u00e7\u00e3o" espera a equipe:
         o aviso vale por 48 horas, mesmo que o cadastro tenha sido feito pelo
         portal e n\u00e3o pelo formul\u00e1rio p\u00fablico. */
      const pendentesDesde = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
      const [{ data: doFormulario }, { data: aguardando }] = await Promise.all([
        supabase
          .from("clients")
          .select(colunas)
          .in("user_id", tenantIds)
          .eq("origin", "public_form")
          .eq("is_deleted", false)
          .gte("created_at", desdeQuando)
          .order("created_at", { ascending: false })
          .limit(10),
        supabase
          .from("clients")
          .select(colunas)
          .in("user_id", tenantIds)
          .eq("portal_status", "pending")
          .eq("is_deleted", false)
          .gte("portal_requested_at", pendentesDesde)
          .order("portal_requested_at", { ascending: false })
          .limit(10),
      ]);

      if (pollingCancelled) return;
      type CadastroRecente = {
        id: string;
        name: string | null;
        company_name: string | null;
        email: string | null;
        phone: string | null;
        whatsapp: string | null;
        address_city: string | null;
        address_state: string | null;
        avatar_url: string | null;
        created_at: string;
        portal_status: string | null;
        portal_requested_at: string | null;
      };
      /* O mesmo cliente pode estar nas duas listas: um aviso s\u00f3, o pendente
         ganha, porque \u00e9 o que pede uma a\u00e7\u00e3o. */
      const porId = new Map<string, CadastroRecente>();
      for (const item of [...((doFormulario || []) as CadastroRecente[]), ...((aguardando || []) as CadastroRecente[])]) {
        porId.set(item.id, item);
      }
      const novosCadastros = Array.from(porId.values()).filter(
        (item) =>
          !seenClientSignupNotificationIdsRef.current.has(item.id) &&
          !isNotificationRead(`client-signup:${item.id}`)
      );
      if (novosCadastros.length === 0) return;

      novosCadastros.forEach((item) => {
        const texto = (valor: unknown) => (typeof valor === "string" && valor.trim() ? valor.trim() : null);
        const cidade = texto(item.address_city);
        const estado = texto(item.address_state);
        const contato = [
          texto(item.whatsapp) || texto(item.phone),
          texto(item.email),
          cidade ? `${cidade}${estado ? `/${estado}` : ""}` : null,
        ].filter((parte): parte is string => Boolean(parte));
        const pendente = item.portal_status === "pending";
        const detalhes = contato.length ? contato.join(" \u00b7 ") : "Cadastro enviado sem telefone, e-mail ou cidade.";

        void upsertNotification({
          id: `client-signup:${item.id}`,
          kind: "client_signup",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: null,
          clientId: item.id,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: pendente ? `Aguardando aprova\u00e7\u00e3o \u00b7 ${detalhes}` : detalhes,
          createdAt: (pendente && item.portal_requested_at) || item.created_at,
          clientName: texto(item.company_name) || texto(item.name) || "Novo cliente",
          clientAvatarUrl: texto(item.avatar_url),
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: null,
          rating: null,
          itemCount: null,
          orderTotal: null,
          status: null,
          clientSignupPending: pendente,
        });
      });
    };
    /* Notas que outras empresas emitiram contra o CNPJ desta empresa. A rota
       devolve só o que ainda não virou aviso e marca na mesma hora, então o
       mesmo fornecedor não aparece duas vezes em duas abas abertas. */
    const seedNotasRecebidas = async () => {
      if (!podeVerRecebidas) return;
      const resposta = await fetch("/api/fiscal/recebidas/avisos", { cache: "no-store" }).catch(() => null);
      if (!resposta?.ok || pollingCancelled) return;
      const payload = (await resposta.json().catch(() => null)) as {
        avisos?: Array<{ id: string; emitente: string; valor: number; emitidaEm: string; modelo: string; status: string }>;
      } | null;
      for (const aviso of payload?.avisos ?? []) {
        if (isNotificationRead(`fiscal-recebida:${aviso.id}`)) continue;
        void upsertNotification({
          id: `fiscal-recebida:${aviso.id}`,
          kind: "fiscal_recebida",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message:
            aviso.status === "cancelada"
              ? `${aviso.emitente} cancelou uma nota emitida para a sua empresa.`
              : `${aviso.emitente} emitiu uma nota de ${aviso.modelo === "nfse" ? "serviço" : "produto"} para o CNPJ da sua empresa.`,
          createdAt: aviso.emitidaEm,
          clientName: aviso.emitente,
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: null,
          rating: null,
          itemCount: null,
          orderTotal: typeof aviso.valor === "number" ? aviso.valor : Number(aviso.valor || 0) || 0,
          status: null,
        });
      }
    };
    /* Comentário de aluno num curso ("Me avisar" nas Configurações dos digitais).
       O som só toca para o que chegou depois da primeira leitura. */
    const comentariosNoSino = new Set<string>();
    let primeiraLeituraDosComentarios = true;
    const seedComentariosDigitais = async () => {
      if (!sinoDosComentarios.current) return;
      const resposta = await fetch("/api/digitais/comentarios?avisos=1", { cache: "no-store" }).catch(() => null);
      if (!resposta?.ok || pollingCancelled) return;
      const payload = (await resposta.json().catch(() => null)) as {
        avisos?: Array<{ id: string; aluno: string; curso: string; aula: string; texto: string; pendente: boolean; criadoEm: string }>;
      } | null;
      let chegou = false;
      for (const aviso of payload?.avisos ?? []) {
        const id = `comentario-digital:${aviso.id}`;
        if (isNotificationHidden(id)) continue;
        if (!comentariosNoSino.has(id)) {
          comentariosNoSino.add(id);
          if (!primeiraLeituraDosComentarios) chegou = true;
        }
        void upsertNotification({
          id,
          kind: "comentario_digital",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: aviso.texto,
          createdAt: aviso.criadoEm,
          clientName: aviso.aluno,
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: `${aviso.curso} · ${aviso.aula}`,
          rating: null,
          itemCount: null,
          orderTotal: null,
          status: aviso.pendente ? "pending" : null,
        });
      }
      primeiraLeituraDosComentarios = false;
      if (chegou) void playNotificationSound();
    };
    /*
     * A resposta do cliente à arte (aprovou ou pediu alteração, pelo link do
     * orçamento ou pela prova ligada a um item): as que ninguém leu ainda,
     * também as que chegaram com o painel fechado (o tempo real só pega quem
     * está com a tela aberta). Some quando a arte é vista no orçamento.
     */
    const respostasNoSino = new Set<string>();
    let primeiraLeituraDasRespostas = true;
    const seedRespostasDeArte = async () => {
      const desde = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
      const { data } = await supabase
        .from("quote_item_art_messages")
        .select("id,quote_id,art_id,message,status,created_at")
        .eq("sender", "client")
        .in("status", ["approved", "rejected"])
        .is("read_at", null)
        .gte("created_at", desde)
        .order("created_at", { ascending: false })
        .limit(30);
      if (pollingCancelled) return;
      let chegou = false;
      for (const m of (data ?? []) as Array<{ id: string; quote_id: string; art_id: string | null; message: string | null; status: string; created_at: string }>) {
        if (isNotificationHidden(m.id) || isNotificationRead(m.id)) continue;
        const doOrcamento = await resolveClientName(m.quote_id);
        if (!doOrcamento) continue;
        if (!respostasNoSino.has(m.id)) {
          respostasNoSino.add(m.id);
          if (!primeiraLeituraDasRespostas) chegou = true;
        }
        upsertNotification({
          id: m.id,
          kind: m.status === "approved" ? "art_approved" : "art_rejected",
          quoteId: m.quote_id,
          artId: m.art_id,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: m.message || "",
          createdAt: m.created_at,
          clientName: doOrcamento.clientName,
          clientAvatarUrl: null,
          quoteNumber: doOrcamento.quoteNumber,
          catalogOrderNumber: null,
          productName: null,
          rating: null,
          itemCount: null,
          orderTotal: null,
          status: null,
        });
      }
      primeiraLeituraDasRespostas = false;
      if (chegou) void playNotificationSound();
    };
    /*
     * A resposta da prova (o link "Aprovação" do KtaloguDesign) que não entrou
     * num item do orçamento (prova solta): um aviso por resposta; o clique
     * abre a arte no editor, onde a Aprovação mostra a conversa.
     */
    const seedRespostasDasProvas = async () => {
      /* Sem trava do KtaloguDesign: na primeira rodada a liberação ainda não chegou, e quem não tem o editor não tem provas. */
      const desde = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
      const { data, error } = await supabase
        .from("provas_de_arte")
        .select("id,arte_id,nome,respostas,respondida_em")
        .in("user_id", tenantIds)
        .gte("respondida_em", desde)
        .order("respondida_em", { ascending: false })
        .limit(20);
      if (error || pollingCancelled) return;
      let chegou = false;
      for (const p of (data ?? []) as Array<{ id: string; arte_id: string | null; nome: string; respostas: Array<{ quando: string; situacao: string; nome?: string; comentario?: string; no_orcamento?: boolean }> | null }>) {
        const respostas = Array.isArray(p.respostas) ? p.respostas : [];
        const ultima = respostas.at(-1);
        if (!ultima || ultima.no_orcamento) continue;
        const id = `prova:${p.id}:${respostas.length}`;
        if (isNotificationHidden(id) || isNotificationRead(id)) continue;
        if (!respostasNoSino.has(id)) {
          respostasNoSino.add(id);
          if (!primeiraLeituraDasRespostas) chegou = true;
        }
        const aprovou = ultima.situacao === "aprovada";
        upsertNotification({
          id,
          kind: aprovou ? "art_approved" : "art_rejected",
          quoteId: null,
          artId: p.arte_id,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: `${p.nome}${ultima.comentario ? ` — ${ultima.comentario}` : ""}`,
          createdAt: ultima.quando,
          clientName: ultima.nome?.trim() || "Cliente",
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: p.nome,
          rating: null,
          itemCount: null,
          orderTotal: null,
          status: null,
        });
      }
      if (chegou) void playNotificationSound();
    };
    /* Modelo novo na comunidade de quem a pessoa segue (KtaloguDesign): o clique abre a prévia dele. */
    const seedModelosDeQuemSigo = async () => {
      if (!designLiberadoRef.current) return;
      const resposta = await fetch("/api/artes/comunidade/avisos", { cache: "no-store" }).catch(() => null);
      if (!resposta?.ok || pollingCancelled) return;
      const payload = (await resposta.json().catch(() => null)) as {
        avisos?: Array<{ id: string; nome: string; tipo: string; autor: string; avatar: string | null; miniatura: string | null; em: string }>;
      } | null;
      for (const aviso of payload?.avisos ?? []) {
        const id = `modelo-da-comunidade:${aviso.id}`;
        if (isNotificationHidden(id)) continue;
        void upsertNotification({
          id,
          kind: "modelo_da_comunidade",
          quoteId: null,
          artId: aviso.id,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: `Publicou "${aviso.nome}" na comunidade de modelos.`,
          createdAt: aviso.em,
          clientName: aviso.autor,
          clientAvatarUrl: aviso.avatar,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: aviso.nome,
          rating: null,
          itemCount: null,
          orderTotal: null,
          status: null,
        });
      }
    };
    const seedRecentPaymentLinkNotifications = async () => {
      const { data } = await supabase
        .from("payment_link_payments")
        .select("id,payment_link_id,user_id,status,amount,created_at,payer_name,payment_method,payment_links(title,code)")
        .in("user_id", tenantIds)
        .eq("status", "approved")
        .order("created_at", { ascending: false })
        .limit(10);

      if (!Array.isArray(data) || pollingCancelled) return;
      const unseenPayments = data.filter(
        (item) =>
          !seenPaymentLinkNotificationIdsRef.current.has(item.id) &&
          !isNotificationRead(`payment-link:${item.id}`)
      );
      if (unseenPayments.length === 0) return;

      unseenPayments.forEach((item) => {
        const paymentLink =
          item.payment_links && typeof item.payment_links === "object" && !Array.isArray(item.payment_links)
            ? (item.payment_links as { title?: string | null; code?: string | null })
            : null;

        void upsertNotification({
          id: `payment-link:${item.id}`,
          kind: "payment_link_receipt",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: item.id,
          paymentLinkId: item.payment_link_id,
          paymentLinkCode: paymentLink?.code?.trim() || null,
          paymentLinkTitle: paymentLink?.title?.trim() || "Link de pagamento",
          message: `Recebimento aprovado em ${paymentLink?.title?.trim() || "Link de pagamento"}.`,
          createdAt: item.created_at,
          clientName: typeof item.payer_name === "string" && item.payer_name.trim() ? item.payer_name.trim() : "Cliente",
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: null,
          rating: null,
          itemCount: null,
          orderTotal: typeof item.amount === "number" ? item.amount : Number(item.amount || 0) || 0,
          status: "approved",
        });
      });
    };
    channel.on(
      "postgres_changes",
      { event: "INSERT", schema: "public", table: "quote_item_art_messages", filter: "sender=eq.client" },
      async (payload) => {
        const record = payload.new as {
          id?: string;
          quote_id?: string;
          art_id?: string | null;
          message?: string;
          created_at?: string;
        };
        await handleIncoming(record);
      }
    );
    channel.on(
      "postgres_changes",
      { event: "INSERT", schema: "public", table: "product_reviews" },
      async (payload) => {
        const record = payload.new as {
          id?: string;
          catalog_user_id?: string | null;
          status?: "pending" | "approved" | "rejected" | null;
        };
        await handleIncomingReview(record);
      }
    );
    channel.on(
      "postgres_changes",
      { event: "UPDATE", schema: "public", table: "catalog_orders" },
      async (payload) => {
        const record = payload.new as {
          id?: string;
          user_id?: string | null;
          status?: "pending" | "approved" | "canceled" | null;
        };
        await handleIncomingCatalogOrder(record);
      }
    );
    channel.on(
      "postgres_changes",
      { event: "UPDATE", schema: "public", table: "payment_link_payments" },
      async (payload) => {
        const record = payload.new as {
          id?: string;
          user_id?: string | null;
          status?: "initiated" | "pending" | "approved" | "rejected" | "cancelled" | null;
        };
        await handleIncomingPaymentLinkReceipt(record);
      }
    );
    channel.on("broadcast", { event: "client_message" }, async ({ payload }) => {
      const record = payload as {
        id?: string;
        quote_id?: string;
        art_id?: string | null;
        message?: string;
        created_at?: string;
        kind?: "chat" | "system" | "order" | "other" | "quote_approved" | "art_approved" | "art_rejected";
        quote_number?: number | null;
        target_user_id?: string | null;
      };
      await handleIncoming(record);
    });
    channel.subscribe();

    /*
      Conversa do Chats.

      Dois canais: o da loja traz o que o cliente escreveu no portal; o da
      pessoa traz o que um colega escreveu na conversa interna. O que a propria
      loja manda nao passa por aqui — ninguem precisa de aviso da propria fala.

      Se o Chats estiver aberto em outra aba, ele mesmo avisa: o sino se cala
      para nao tocar duas vezes pela mesma mensagem.
    */
    const chatsAbertoAgora = () => {
      try {
        const marca = Number(window.localStorage.getItem("ktl:chats-ativo") || 0);
        return Date.now() - marca < 15000;
      } catch {
        return false;
      }
    };

    /* Não perturbe, ligado no próprio Chats: silencia o som neste aparelho sem
       perder mensagem nenhuma — o cartão continua aparecendo e a conversa
       continua marcada como não lida. */
    const noSilencio = () => {
      try {
        return Number(window.localStorage.getItem("ktl:chats-silencio") || 0) > Date.now();
      } catch {
        return false;
      }
    };

    const canaisDaConversa = [
      ...tenantIds.map((id) => `chat-loja-${id}`),
      `chat-pessoa-${permissionState.userId}`,
    ].map((nome) => {
      const canalDoChat = supabase.channel(nome);
      canalDoChat.on("broadcast", { event: "novidade" }, ({ payload }) => {
        const dados = (payload || {}) as {
          threadId?: string;
          autorNome?: string;
          resumo?: string;
          orderId?: string | null;
          orderNumber?: number | null;
          em?: string;
        };
        if (!dados.threadId || chatsAbertoAgora()) return;

        upsertNotification({
          /* Um aviso por conversa: dez mensagens seguidas do mesmo cliente
             viram uma linha atualizada, e nao dez. */
          id: `chat-message:${dados.threadId}`,
          kind: "chat_message",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: dados.resumo || "Nova mensagem na conversa.",
          createdAt: dados.em || new Date().toISOString(),
          clientName: dados.autorNome || "Cliente",
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: null,
          rating: null,
          itemCount: null,
          orderTotal: null,
          status: null,
          threadId: dados.threadId,
          orderId: dados.orderId || null,
          orderNumber: dados.orderNumber || null,
        });
        if (!noSilencio()) void playMessageNotificationSound();
      });
      canalDoChat.subscribe();
      return canalDoChat;
    });

    /*
      Venda aprovada.

      E o aviso que a loja quer ouvir de longe, entao ele tem som proprio — o
      mesmo do dinheiro entrando — e chega tanto da venda no balcao quanto da
      compra paga na loja online.
    */
    const canaisDeVenda = tenantIds.map((id) => {
      const canalDaVenda = supabase.channel(`venda-${id}`);
      canalDaVenda.on("broadcast", { event: "aprovada" }, ({ payload }) => {
        const venda = (payload || {}) as {
          valor?: number;
          metodo?: string | null;
          numero?: number | string | null;
          cliente?: string | null;
          orderId?: string | null;
          origem?: "sistema" | "loja";
          em?: string;
        };
        if (typeof venda.valor !== "number") return;

        upsertNotification({
          id: `sale:${venda.orderId || venda.numero || venda.em || Date.now()}`,
          kind: "sale_approved",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: null,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: venda.cliente || "",
          createdAt: venda.em || new Date().toISOString(),
          clientName: venda.cliente || "Venda aprovada",
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: null,
          productName: null,
          rating: null,
          itemCount: null,
          orderTotal: venda.valor,
          status: null,
          orderId: venda.orderId || null,
          orderNumber: typeof venda.numero === "number" ? venda.numero : null,
          saleAmount: venda.valor,
          saleMethod: venda.metodo || null,
          saleOrigin: venda.origem || null,
        });
        void playMoneyNotificationSound();
      });
      /*
        Pedido que chegou pela loja e ainda não é venda: o pedido de
        orçamento (o cliente não viu preço), o pedido pelo WhatsApp e o que
        espera pagamento. É aviso comum, com o som comum — o som de dinheiro
        fica para quando o dinheiro entra.
      */
      canalDaVenda.on("broadcast", { event: "pedido-da-loja" }, ({ payload }) => {
        const pedido = (payload || {}) as {
          orderId?: string | null;
          kind?: "order" | "quote_request" | null;
          numero?: number | string | null;
          cliente?: string | null;
          total?: number | null;
          itens?: number | null;
          aguardandoPagamento?: boolean | null;
          em?: string | null;
        };
        const orderId = typeof pedido.orderId === "string" ? pedido.orderId.trim() : "";
        if (!orderId) return;
        if (seenCatalogOrderNotificationIdsRef.current.has(orderId)) return;
        if (isNotificationRead(`catalog-order:${orderId}`)) return;

        const orcamento = pedido.kind === "quote_request";
        const itens = typeof pedido.itens === "number" && Number.isFinite(pedido.itens) ? Math.max(0, Math.round(pedido.itens)) : null;
        const inserted = upsertNotification({
          id: `catalog-order:${orderId}`,
          kind: "catalog_order",
          quoteId: null,
          artId: null,
          reviewId: null,
          catalogOrderId: orderId,
          clientId: null,
          paymentLinkPaymentId: null,
          paymentLinkId: null,
          paymentLinkCode: null,
          paymentLinkTitle: null,
          message: orcamento
            ? `Pedido de orçamento com ${itens ?? 0} ${itens === 1 ? "item" : "itens"}`
            : pedido.aguardandoPagamento
              ? "Aguardando pagamento"
              : "Pedido pelo WhatsApp",
          createdAt: pedido.em || new Date().toISOString(),
          clientName: pedido.cliente || "Cliente do catálogo",
          clientAvatarUrl: null,
          quoteNumber: null,
          catalogOrderNumber: typeof pedido.numero === "number" ? pedido.numero : Number(pedido.numero) || null,
          productName: null,
          rating: null,
          itemCount: itens,
          orderTotal: !orcamento && typeof pedido.total === "number" ? pedido.total : null,
          status: "pending",
          catalogOrderKind: orcamento ? "quote_request" : "order",
        });
        if (inserted) void playNotificationSound();
      });
      canalDaVenda.subscribe();
      return canalDaVenda;
    });

    void seedPendingReviewNotifications();
    void seedApprovedCatalogOrderNotifications();
    void seedPendingCatalogOrderNotifications();
    void seedRecentPaymentLinkNotifications();
    void seedRecentClientSignups();
    void seedNotasRecebidas();
    void seedComentariosDigitais();
    void seedModelosDeQuemSigo();
    void seedRespostasDeArte();
    void seedRespostasDasProvas();
    let voltasDoSino = 0;
    const aoLiberarComentarios = () => void seedComentariosDigitais();
    window.addEventListener("ks:sino-dos-comentarios", aoLiberarComentarios);
    /* A cada 60 s. Eram 4 buscas a cada 15 s por aba aberta, e as novidades
       que importam (venda, pedido, mensagem) ja chegam pelo tempo real. */
    const pollingTimer = window.setInterval(() => {
      /* Aba em segundo plano não consulta (a maioria das abas abertas fica
         esquecida; eram ~100 abas x 8 consultas por minuto no banco). Ao voltar
         para a aba, o `visibilitychange` abaixo faz a rodada na hora. */
      if (document.visibilityState === "hidden") return;
      void seedPendingReviewNotifications();
      void seedApprovedCatalogOrderNotifications();
      void seedPendingCatalogOrderNotifications();
      void seedRecentPaymentLinkNotifications();
      void seedRecentClientSignups();
      void seedNotasRecebidas();
      void seedComentariosDigitais();
      void seedRespostasDeArte();
      void seedRespostasDasProvas();
      /* Os modelos de quem a pessoa segue mudam devagar: de 5 em 5 minutos. */
      voltasDoSino += 1;
      if (voltasDoSino % 5 === 0) void seedModelosDeQuemSigo();
    }, 60000);
    /* Voltou para a aba: confere o que chegou enquanto ela estava escondida (no máximo uma vez por minuto). */
    let ultimaVolta = Date.now();
    const aoVoltarParaAba = () => {
      if (document.visibilityState !== "visible" || Date.now() - ultimaVolta < 60000) return;
      ultimaVolta = Date.now();
      void seedPendingReviewNotifications();
      void seedApprovedCatalogOrderNotifications();
      void seedPendingCatalogOrderNotifications();
      void seedRecentPaymentLinkNotifications();
      void seedRecentClientSignups();
      void seedNotasRecebidas();
      void seedComentariosDigitais();
      void seedRespostasDeArte();
      void seedRespostasDasProvas();
    };
    document.addEventListener("visibilitychange", aoVoltarParaAba);
    return () => {
      pollingCancelled = true;
      document.removeEventListener("visibilitychange", aoVoltarParaAba);
      window.removeEventListener("ks:sino-dos-comentarios", aoLiberarComentarios);
      window.clearInterval(pollingTimer);
      supabase.removeChannel(channel);
      canaisDaConversa.forEach((canalDoChat) => supabase.removeChannel(canalDoChat));
      canaisDeVenda.forEach((canalDaVenda) => supabase.removeChannel(canalDaVenda));
    };
  }, [permissionState?.userId, permissionState?.ctx, tenantIds, notificationStateHydrated]);

  // Fecha o menu mobile ao navegar
  const handleMobileLinkClick = () => {
    setMobileMenuOpen(false);
  };

  /* O Chats vive em aba propria: mandar a pessoa para la nao pode fechar a tela
     em que ela estava trabalhando. */
  const abrirNoChats = (notification: AdminNotification) => {
    markNotificationAsRead(notification);
    setNotificationState({ open: false, pathname });
    const destino = notification.threadId ? `/chats?conversa=${notification.threadId}` : "/chats";
    window.open(destino, "_blank");
  };

  const openNotification = (notification: AdminNotification) => {
    markNotificationAsRead(notification);
    setNotificationState({ open: false, pathname });

    if (notification.kind === "product_review" && notification.reviewId) {
      const params = new URLSearchParams();
      params.set("review", notification.reviewId);
      router.push(`/dashboard/catalog/reviews?${params.toString()}`);
      return;
    }

    if (notification.kind === "catalog_order" && notification.catalogOrderId) {
      const params = new URLSearchParams();
      params.set("order", notification.catalogOrderId);
      router.push(`/dashboard/catalog/orders?${params.toString()}`);
      return;
    }

    if (notification.kind === "payment_link_receipt") {
      router.push("/dashboard/financial/payment-links");
      return;
    }

    if (notification.kind === "fiscal_recebida") {
      router.push("/dashboard/notas-fiscais/recebidas");
      return;
    }

    if (notification.kind === "modelo_da_comunidade") {
      router.push(notification.artId ? `/dashboard/design?modelo=${encodeURIComponent(notification.artId)}` : "/dashboard/design");
      return;
    }

    if (notification.kind === "comentario_digital") {
      router.push(notification.status === "pending" ? "/dashboard/digitais/comentarios?filtro=pendentes" : "/dashboard/digitais/comentarios");
      return;
    }

    if (notification.kind === "client_signup" && notification.clientId) {
      router.push(`/dashboard/clients/${notification.clientId}`);
      return;
    }

    if (notification.kind === "sale_approved") {
      if (notification.orderId) router.push(`/dashboard/orders/${notification.orderId}`);
      else router.push("/dashboard/catalog/orders");
      return;
    }

    if (notification.kind === "chat_message") {
      if (notification.orderId) {
        router.push(`/dashboard/orders/${notification.orderId}?aba=conversa`);
      } else if (notification.threadId) {
        window.open(`/chats?conversa=${notification.threadId}`, "_blank");
      }
      return;
    }

    /* Resposta de uma prova solta do KtaloguDesign (sem orçamento): abre a arte, onde a Aprovação mostra a resposta. */
    if ((notification.kind === "art_approved" || notification.kind === "art_rejected") && !notification.quoteId) {
      window.open(notification.artId ? `/dashboard/design?arte=${encodeURIComponent(notification.artId)}` : "/dashboard/design", "_blank");
      return;
    }

    if (notification.quoteId) {
      const params = new URLSearchParams();
      if (notification.kind === "chat" || notification.kind === "art_approved" || notification.kind === "art_rejected") {
        params.set("chat", "1");
      }
      if (notification.artId) params.set("art", notification.artId);
      router.push(`/dashboard/quotes/${notification.quoteId}?${params.toString()}`);
    }
  };

  const approveReviewNotification = async (notification: AdminNotification) => {
    if (!notification.reviewId) return;
    setNotificationActionId(notification.id);
    try {
      const response = await fetch("/api/catalog/reviews", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          reviewId: notification.reviewId,
          comment: notification.message || "Comentário aprovado pelo admin.",
          status: "approved",
        }),
      });
      const payload = (await response.json().catch(() => null)) as { error?: string } | null;
      if (!response.ok || payload?.error) {
        throw new Error(payload?.error || "Não foi possível aprovar a avaliação.");
      }
      markNotificationAsRead(notification);
    } catch (error) {
      console.error("Erro ao aprovar avaliação pela notificação:", error);
    } finally {
      setNotificationActionId(null);
    }
  };

  const unreadCount = sortedNotifications.length;
  /*
    O atalho do atendimento.

    Ao lado do sino, e com o número de quem está esperando: o sino avisa que
    chegou, este diz que ainda não foi respondido. Quem está no meio de um
    pedido responde ali mesmo e volta ao que estava fazendo.
  */
  const chatButton = !podeVerChats ? null : (
    <button
      type="button"
      onClick={() => setChatsAbertos((antes) => !antes)}
      className="ks-icon-button"
      aria-label="Conversas esperando resposta"
      title="Conversas"
    >
      <BalaoDeConversa size={17} />
      {chatsPendentes > 0 ? <span className="ks-icon-button-dot">{Math.min(chatsPendentes, 99)}</span> : null}
    </button>
  );

  const notificationBell = (
    <div className="relative">
      <button
        type="button"
        onClick={() =>
          setNotificationState((prev) => ({
            open: prev.pathname === pathname ? !prev.open : true,
            pathname,
          }))
        }
        className="ks-icon-button"
        aria-label="Notificações"
      >
        <Bell size={16} strokeWidth={1.75} />
        {unreadCount > 0 ? (
          <span className="ks-icon-button-dot">{Math.min(unreadCount, 99)}</span>
        ) : null}
      </button>
      {notificationOpen ? (
        /* No celular o sino fica quase na borda direita e o painel tem 23rem:
           ancorado so em `right-0` ele saia pela esquerda e o titulo aparecia
           cortado ("otificacoes"). Aqui ele vira uma faixa presa a viewport,
           com folga dos dois lados. No desktop segue preso ao sino. */
        <div className="fixed left-3 right-3 top-[4.25rem] z-[60] w-auto max-h-[calc(100dvh-5.5rem)] overflow-hidden rounded-[var(--ks-radius-lg)] border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-elevated)] shadow-[var(--ks-shadow-overlay)] md:absolute md:left-auto md:right-0 md:top-auto md:z-auto md:mt-2 md:w-[23rem] md:max-w-[92vw]">
          <div className="flex items-center justify-between gap-3 border-b border-[var(--ks-color-border-subtle)] px-3.5 py-2.5">
            <span className="text-[13px] font-semibold text-[var(--ks-color-text-primary)]">Notificações</span>
            {sortedNotifications.length > 0 ? (
              <button
                type="button"
                onClick={markAllNotificationsAsRead}
                className="ks-button ks-button-ghost ks-button-sm"
              >
                <Check className="h-3 w-3" />
                Marcar todas como lidas
              </button>
            ) : null}
          </div>
          <div className="max-h-[380px] overflow-y-auto bg-[var(--ks-color-bg-inset)] p-2">
            {sortedNotifications.length > 0 ? (
              sortedNotifications.map((notification) => {
                const kindLabel =
                  notification.kind === "modelo_da_comunidade"
                    ? "Comunidade"
                    : notification.kind === "comentario_digital"
                    ? "Comentário"
                    : notification.kind === "fiscal_recebida"
                    ? "Nota recebida"
                    : notification.kind === "sale_approved"
                    ? "Venda"
                    : notification.kind === "chat_message"
                    ? "Mensagem"
                    : notification.kind === "client_signup"
                    ? "Cadastro"
                    : notification.kind === "product_review"
                    ? "Avaliação"
                    : notification.kind === "payment_link_receipt"
                      ? "Recebimento"
                    : notification.kind === "catalog_order"
                      ? "Catálogo"
                    : notification.kind === "art_approved"
                    ? "Aprovada"
                    : notification.kind === "art_rejected"
                      ? "Alteração"
                      : notification.kind === "chat"
                        ? "Chat"
                        : notification.kind === "quote_approved"
                          ? "Orçamento"
                        : notification.kind === "order"
                          ? "Pedido"
                          : notification.kind === "system"
                            ? "Sistema"
                            : "Aviso";
                const badgeClass =
                  notification.kind === "modelo_da_comunidade"
                    ? "bg-violet-50 text-violet-700"
                    : notification.kind === "comentario_digital"
                    ? "bg-fuchsia-50 text-fuchsia-700"
                    : notification.kind === "fiscal_recebida"
                    ? "bg-sky-50 text-sky-700"
                    : notification.kind === "sale_approved"
                    ? "bg-emerald-50 text-emerald-700"
                    : notification.kind === "chat_message"
                    ? "bg-violet-50 text-violet-700"
                    : notification.kind === "client_signup"
                    ? "bg-indigo-50 text-indigo-700"
                    : notification.kind === "product_review"
                    ? "bg-amber-50 text-amber-700"
                    : notification.kind === "payment_link_receipt"
                      ? "bg-emerald-50 text-emerald-700"
                    : notification.kind === "catalog_order"
                      ? "bg-violet-50 text-violet-700"
                    : notification.kind === "art_approved"
                    ? "bg-emerald-50 text-emerald-700"
                    : notification.kind === "art_rejected"
                      ? "bg-rose-50 text-rose-700"
                      : notification.kind === "chat"
                        ? "bg-indigo-50 text-indigo-700"
                        : notification.kind === "quote_approved"
                          ? "bg-emerald-50 text-emerald-700"
                        : notification.kind === "order"
                          ? "bg-amber-50 text-amber-700"
                          : "bg-zinc-100 text-zinc-600";
                const headline =
                  notification.kind === "modelo_da_comunidade"
                    ? `${notification.clientName} lançou um modelo novo`
                    : notification.kind === "comentario_digital"
                    ? notification.status === "pending"
                      ? "Comentário esperando aprovação"
                      : "Comentário novo no conteúdo"
                    : notification.kind === "fiscal_recebida"
                    ? "Nota emitida contra o seu CNPJ"
                    : notification.kind === "sale_approved"
                    ? notification.saleMethod
                      ? `Venda aprovada no ${rotuloDoMeioDePagamento(notification.saleMethod)}`
                      : "Venda aprovada"
                    : notification.kind === "chat_message"
                    ? `${notification.clientName} mandou uma mensagem`
                    : notification.kind === "client_signup"
                    ? notification.clientSignupPending
                      ? "Cadastro aguardando aprovação"
                      : "Novo cliente se cadastrou"
                    : notification.kind === "product_review"
                    ? "Nova avaliação de produto"
                    : notification.kind === "payment_link_receipt"
                      ? "Pagamento de link aprovado"
                    : notification.kind === "catalog_order"
                      ? notification.catalogOrderKind === "quote_request"
                        ? "Novo pedido de orçamento"
                        : "Novo pedido do catálogo"
                    : notification.kind === "art_approved"
                    ? `${notification.clientName} aprovou a arte`
                    : notification.kind === "art_rejected"
                      ? `${notification.clientName} pediu alteração na arte`
                      : notification.kind === "chat"
                        ? "Mensagem do cliente"
                        : notification.kind === "quote_approved"
                          ? "Orçamento aprovado"
                        : notification.kind === "order"
                          ? "Atualização de pedido"
                          : notification.kind === "system"
                            ? "Atualização do sistema"
                            : "Nova notificação";
                const quoteLabel = notification.quoteNumber ? `Orçamento #${notification.quoteNumber}` : "Orçamento";
                const originLabel =
                  notification.kind === "chat_message"
                    ? notification.orderNumber
                      ? `Pedido #${notification.orderNumber}`
                      : "Conversa"
                    : notification.kind === "client_signup"
                    ? notification.clientSignupPending
                      ? "Cadastro pela loja"
                      : "Formulário de cadastro"
                    : notification.kind === "product_review"
                    ? notification.productName || "Produto"
                    : notification.kind === "payment_link_receipt"
                      ? notification.paymentLinkCode
                        ? `${notification.paymentLinkTitle || "Link de pagamento"} · ${notification.paymentLinkCode}`
                        : notification.paymentLinkTitle || "Link de pagamento"
                    : notification.kind === "catalog_order"
                      ? notification.catalogOrderKind === "quote_request"
                        ? notification.catalogOrderNumber
                          ? `Pedido de orçamento #${notification.catalogOrderNumber}`
                          : "Pedido de orçamento"
                        : notification.catalogOrderNumber
                          ? `Pedido #${notification.catalogOrderNumber}`
                          : "Pedido do catálogo"
                    : notification.kind === "art_approved" || notification.kind === "art_rejected"
                      ? "Resposta do cliente"
                      : notification.kind === "quote_approved"
                        ? "Aprovado online"
                      : notification.artId
                        ? "Sobre uma arte"
                        : "Sobre o orçamento";
                const isReviewNotification = notification.kind === "product_review";
                const isPaymentLinkReceiptNotification = notification.kind === "payment_link_receipt";
                const isCatalogOrderNotification = notification.kind === "catalog_order";
                const isClientSignupNotification = notification.kind === "client_signup";
                const isChatMessageNotification = notification.kind === "chat_message";
                const isSaleNotification = notification.kind === "sale_approved";
                const isComentarioDigital = notification.kind === "comentario_digital";
                return (
                  <div
                    key={notification.id}
                    className="mb-2 rounded-2xl border border-zinc-100 bg-white p-4 shadow-[0_8px_24px_rgba(15,23,42,0.06)] last:mb-0"
                  >
                    <div className="flex items-start justify-between gap-3">
                      <div className="min-w-0 flex-1">
                        <div className="flex items-center gap-2">
                          <span className={`rounded-full px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] ${badgeClass}`}>
                            {kindLabel}
                          </span>
                        </div>
                        <div className="mt-2 text-[11px] font-medium text-zinc-400">
                          {new Date(notification.createdAt).toLocaleString("pt-BR")}
                        </div>
                      </div>
                      <div className="flex items-center gap-1">
                        <button
                          type="button"
                          onClick={() => markNotificationAsRead(notification)}
                          className="inline-flex h-7 w-7 items-center justify-center rounded-full border border-zinc-200 text-zinc-500 transition hover:border-emerald-200 hover:bg-emerald-50 hover:text-emerald-700"
                          aria-label="Marcar como lida"
                          title="Marcar como lida"
                        >
                          <Check className="h-3.5 w-3.5" />
                        </button>
                        <button
                          type="button"
                          onClick={() => dismissNotification(notification.id)}
                          className="inline-flex h-7 w-7 items-center justify-center rounded-full border border-zinc-200 text-zinc-500 transition hover:border-zinc-300 hover:bg-zinc-100 hover:text-zinc-700"
                          aria-label="Fechar notificação"
                          title="Fechar notificação"
                        >
                          <X className="h-3.5 w-3.5" />
                        </button>
                      </div>
                    </div>

                    <div className="mt-3">
                    <div className="flex items-center justify-between gap-3 text-[10px] uppercase tracking-[0.12em] text-zinc-400">
                    </div>

                    {isReviewNotification ? (
                      <div className="space-y-3">
                        <div className="flex items-start gap-3">
                          <div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full border border-zinc-200 bg-zinc-50">
                            {notification.clientAvatarUrl ? (
                              <Image
                                src={notification.clientAvatarUrl}
                                alt={notification.clientName}
                                width={40}
                                height={40}
                                className="h-10 w-10 object-cover"
                                unoptimized
                              />
                            ) : (
                              <UserCircle2 className="h-5 w-5 text-zinc-400" />
                            )}
                          </div>
                          <div className="min-w-0 flex-1">
                            <div className="text-xs font-semibold text-zinc-900">{headline}</div>
                            <div className="mt-1 text-xs text-zinc-600">
                              {notification.clientName} • {originLabel}
                            </div>
                            {typeof notification.rating === "number" ? (
                              <div className="mt-1 flex items-center gap-0.5 text-amber-500">
                                {Array.from({ length: 5 }).map((_, index) => (
                                  (() => {
                                    const ratingValue = notification.rating ?? 0;
                                    return (
                                  <Star
                                    key={`${notification.id}-star-${index}`}
                                    className={index < ratingValue ? "h-3.5 w-3.5 fill-current" : "h-3.5 w-3.5 text-zinc-300"}
                                  />
                                    );
                                  })()
                                ))}
                              </div>
                            ) : null}
                          </div>
                        </div>
                        <div className="rounded-2xl bg-zinc-50 px-3 py-2.5 text-xs leading-5 text-zinc-700">
                          {notification.message || "Comentário enviado sem texto adicional."}
                        </div>
                        <div className="flex items-center gap-2">
                          <button
                            type="button"
                            onClick={() => void approveReviewNotification(notification)}
                            disabled={notificationActionId === notification.id}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-lg bg-emerald-600 px-3 text-xs font-semibold text-white transition hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-60"
                          >
                            {notificationActionId === notification.id ? (
                              <Loader2 className="h-3.5 w-3.5 animate-spin" />
                            ) : (
                              <CheckCircle2 className="h-3.5 w-3.5" />
                            )}
                            Aprovar
                          </button>
                          <button
                            type="button"
                            onClick={() => openNotification(notification)}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-lg border border-zinc-200 px-3 text-xs font-semibold text-zinc-700 transition hover:bg-zinc-50"
                          >
                            <ExternalLink className="h-3.5 w-3.5" />
                            Abrir
                          </button>
                        </div>
                      </div>
                    ) : isPaymentLinkReceiptNotification ? (
                      <div className="space-y-3">
                        <div className="flex items-start gap-3">
                          <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl border border-emerald-200 bg-emerald-50 text-emerald-700">
                            <DollarSign className="h-5 w-5" />
                          </div>
                          <div className="min-w-0 flex-1">
                            <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                            <div className="mt-1.5 text-[13px] leading-5 text-zinc-600">
                              {notification.clientName} - {originLabel}
                            </div>
                            <div className="mt-2 flex flex-wrap items-center gap-2 text-[12px] text-emerald-700">
                              {typeof notification.orderTotal === "number" ? (
                                <span className="font-semibold">
                                  {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(notification.orderTotal)}
                                </span>
                              ) : null}
                              <span>Pagamento aprovado</span>
                            </div>
                          </div>
                        </div>
                        <div className="rounded-2xl border border-emerald-200 bg-emerald-50 px-3 py-3 text-xs leading-5 text-emerald-800">
                          {notification.message || "Recebimento aprovado por link de pagamento."}
                        </div>
                        <div className="flex items-center gap-2">
                          <button
                            type="button"
                            onClick={() => openNotification(notification)}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-xl bg-emerald-600 px-3.5 text-xs font-semibold text-white transition hover:bg-emerald-700"
                          >
                            <ExternalLink className="h-3.5 w-3.5" />
                            Abrir links
                          </button>
                        </div>
                      </div>
                    ) : isCatalogOrderNotification ? (
                      <div className="space-y-3">
                        <div className="flex items-start gap-3">
                          <div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full border border-zinc-200 bg-zinc-50">
                            {notification.clientAvatarUrl ? (
                              <Image
                                src={notification.clientAvatarUrl}
                                alt={notification.clientName}
                                width={40}
                                height={40}
                                className="h-10 w-10 object-cover"
                                unoptimized
                              />
                            ) : (
                              <ClipboardList className="h-5 w-5 text-zinc-400" />
                            )}
                          </div>
                          <div className="min-w-0 flex-1">
                            <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                            <div className="mt-1.5 text-[13px] leading-5 text-zinc-600">
                              {notification.clientName} • {originLabel}
                            </div>
                            <div className="mt-2 flex flex-wrap items-center gap-2 text-[12px] text-zinc-500">
                              {typeof notification.itemCount === "number" ? (
                                <span>{notification.itemCount} {notification.itemCount === 1 ? "item" : "itens"}</span>
                              ) : null}
                              {typeof notification.orderTotal === "number" ? (
                                <span>
                                  {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(notification.orderTotal)}
                                </span>
                              ) : null}
                            </div>
                          </div>
                        </div>
                        <div className="rounded-2xl bg-zinc-50 px-3 py-3 text-xs leading-5 text-zinc-700">
                          {notification.message || "Novo pedido enviado pelo checkout do catálogo."}
                        </div>
                        <div className="flex items-center gap-2">
                          <button
                            type="button"
                            onClick={() => openNotification(notification)}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-lg bg-violet-600 px-3 text-xs font-semibold text-white transition hover:bg-violet-700"
                          >
                            <ExternalLink className="h-3.5 w-3.5" />
                            Abrir pedido
                          </button>
                        </div>
                      </div>
                    ) : isSaleNotification ? (
                      <div className="space-y-3">
                        <div className="flex items-start gap-3">
                          <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl border border-emerald-200 bg-emerald-50 text-emerald-700">
                            <DollarSign className="h-5 w-5" />
                          </div>
                          <div className="min-w-0 flex-1">
                            <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                            {/* O valor e o que a pessoa procura primeiro: vem grande, logo abaixo. */}
                            <div className="mt-1 text-[22px] font-extrabold leading-7 text-emerald-700">
                              {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(
                                Number(notification.saleAmount || 0)
                              )}
                            </div>
                            <div className="mt-1 text-[12.5px] text-zinc-500">
                              {[
                                notification.orderNumber ? `Pedido #${notification.orderNumber}` : null,
                                notification.clientName && notification.clientName !== "Venda aprovada"
                                  ? notification.clientName
                                  : null,
                                notification.saleOrigin === "loja" ? "Loja online" : "No sistema",
                              ]
                                .filter(Boolean)
                                .join(" · ")}
                            </div>
                          </div>
                        </div>
                        {notification.orderId ? (
                          <div className="flex items-center gap-2">
                            <button
                              type="button"
                              onClick={() => openNotification(notification)}
                              className="inline-flex h-9 items-center justify-center gap-2 rounded-lg bg-emerald-600 px-3 text-xs font-semibold text-white transition hover:bg-emerald-700"
                            >
                              <ExternalLink className="h-3.5 w-3.5" />
                              Ver o pedido
                            </button>
                          </div>
                        ) : null}
                      </div>
                    ) : isChatMessageNotification ? (
                      <div className="space-y-3">
                        <div className="flex items-start gap-3">
                          <div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full border border-zinc-200 bg-zinc-50">
                            {notification.clientAvatarUrl ? (
                              <Image
                                src={notification.clientAvatarUrl}
                                alt={notification.clientName}
                                width={40}
                                height={40}
                                className="h-10 w-10 object-cover"
                                unoptimized
                              />
                            ) : (
                              <MessageCircle className="h-5 w-5 text-violet-500" />
                            )}
                          </div>
                          <div className="min-w-0 flex-1">
                            <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                            <div className="mt-1.5 text-[13px] leading-5 text-zinc-600">{originLabel}</div>
                          </div>
                        </div>
                        <div className="rounded-2xl bg-zinc-50 px-3 py-3 text-xs leading-5 text-zinc-700 line-clamp-3">
                          {notification.message || "Nova mensagem na conversa."}
                        </div>
                        {/* Dois caminhos porque sao dois jeitos de trabalhar: quem
                            esta atendendo quer o Chats; quem esta tocando a
                            producao quer o pedido inteiro na frente. */}
                        <div className="flex flex-wrap items-center gap-2">
                          <button
                            type="button"
                            onClick={() => abrirNoChats(notification)}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-lg bg-violet-600 px-3 text-xs font-semibold text-white transition hover:bg-violet-700"
                          >
                            <MessageCircle className="h-3.5 w-3.5" />
                            Abrir no Chats
                          </button>
                          {notification.orderId ? (
                            <button
                              type="button"
                              onClick={() => openNotification(notification)}
                              className="inline-flex h-9 items-center justify-center gap-2 rounded-lg border border-zinc-200 px-3 text-xs font-semibold text-zinc-700 transition hover:bg-zinc-50"
                            >
                              <ClipboardList className="h-3.5 w-3.5" />
                              Ver no pedido
                            </button>
                          ) : null}
                        </div>
                      </div>
                    ) : isComentarioDigital ? (
                      <div className="space-y-3">
                        <div>
                          <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                          <div className="mt-1.5 text-[13px] leading-5 text-zinc-600">
                            {notification.clientName} • {notification.productName}
                          </div>
                        </div>
                        <div className="rounded-2xl bg-zinc-50 px-3 py-3 text-xs leading-5 text-zinc-700 line-clamp-3">{notification.message}</div>
                        <div className="flex items-center gap-2">
                          <button
                            type="button"
                            onClick={() => openNotification(notification)}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-lg bg-fuchsia-600 px-3 text-xs font-semibold text-white transition hover:bg-fuchsia-700"
                          >
                            <ExternalLink className="h-3.5 w-3.5" />
                            {notification.status === "pending" ? "Aprovar ou responder" : "Responder"}
                          </button>
                        </div>
                      </div>
                    ) : isClientSignupNotification ? (
                      <div className="space-y-3">
                        <div className="flex items-start gap-3">
                          <div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-full border border-zinc-200 bg-zinc-50">
                            {notification.clientAvatarUrl ? (
                              <Image
                                src={notification.clientAvatarUrl}
                                alt={notification.clientName}
                                width={40}
                                height={40}
                                className="h-10 w-10 object-cover"
                                unoptimized
                              />
                            ) : (
                              <UserCircle2 className="h-5 w-5 text-zinc-400" />
                            )}
                          </div>
                          <div className="min-w-0 flex-1">
                            <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                            <div className="mt-1.5 text-[13px] leading-5 text-zinc-600">
                              {notification.clientName} • {originLabel}
                            </div>
                          </div>
                        </div>
                        <div className="rounded-2xl bg-zinc-50 px-3 py-3 text-xs leading-5 text-zinc-700">
                          {notification.message}
                        </div>
                        <div className="flex items-center gap-2">
                          <button
                            type="button"
                            onClick={() => openNotification(notification)}
                            className="inline-flex h-9 items-center justify-center gap-2 rounded-lg bg-indigo-600 px-3 text-xs font-semibold text-white transition hover:bg-indigo-700"
                          >
                            <ExternalLink className="h-3.5 w-3.5" />
                            Abrir cliente
                          </button>
                        </div>
                      </div>
                    ) : (
                      <button
                        type="button"
                        onClick={() => openNotification(notification)}
                        className="w-full rounded-2xl px-1 py-1 text-left transition-colors hover:bg-zinc-50"
                      >
                        <div className="text-[15px] font-semibold leading-5 text-zinc-900">{headline}</div>
                        <div className="mt-1.5 text-[13px] leading-5 text-zinc-600">
                          {notification.clientName} • {quoteLabel}
                        </div>
                        <div className="mt-1 text-xs text-zinc-500">{originLabel}</div>
                        <div className="mt-3 rounded-2xl bg-zinc-50 px-3 py-3 text-xs leading-5 text-zinc-700 line-clamp-3">
                          {notification.message || "Você recebeu uma nova mensagem."}
                        </div>
                      </button>
                    )}
                    </div>
                  </div>
                );
              })
            ) : (
              <div className="px-4 py-6 text-center text-xs text-zinc-500">Sem notificações</div>
            )}
          </div>
        </div>
      ) : null}
    </div>
  );

  if (accessStatus === "loading") {
    return (
      <BrandLoader message="Carregando seu acesso…" />
    );
  }

  if (accessStatus === "blocked" && !isSubscriptionPage) {
    return (
      <BrandLoader message="Redirecionando para assinatura…" />
    );
  }

  if (profileStatus === "loading") {
    return (
      <BrandLoader message="Verificando configuração inicial…" />
    );
  }

  if (profileStatus === "incomplete") {
    return (
      <BrandLoader message="Redirecionando para configuração inicial…" />
    );
  }

  const createConfettiPieces = () =>
    Array.from({ length: 180 }).map(() => {
      const left = Math.random() * 100;
      const size = 6 + Math.random() * 10;
      const rotate = Math.random() * 360;
      const duration = 2.2 + Math.random() * 1.3;
      const delay = Math.random() * 0.45;
      const palette = ["#6D28D9", "#7C3AED", "#0AC0D6", "#A855F7", "#22D3EE"];
      const color = palette[Math.floor(Math.random() * palette.length)];
      const xDrift = (Math.random() - 0.5) * 140;
      return { left, size, rotate, duration, delay, color, xDrift };
    });

  return (
    <div
      className={clsx("ks-dashboard-shell", isPdv && "ks-dashboard-shell-pdv")}
      style={{ paddingTop: "var(--ktl-system-notices-offset, 0px)" }}
    >
      {/* Sidebar do desktop: o trilho escuro leva as seções e o painel branco,
          os itens da seção. Recolhida, fica só o trilho; clicar numa seção
          abre o painel de novo. */}
      <aside
        className={clsx("ks-dashboard-sidebar", !painelVisivel && "ks-dashboard-sidebar-collapsed")}
      >
        <Trilho
          entradas={trilho}
          rotaAtual={rotaAtual}
          secaoDaPagina={secaoDaPagina}
          secaoEscolhida={secaoAberta}
          aoEscolherSecao={escolherSecao}
          /* Ir para um atalho (Dashboard) larga a seção que estava aberta. */
          aoIrParaAtalho={() => setSecaoEscolhida(null)}
          atalhoDoChats={atalhoDoChats}
          atalhosDoRodape={atalhosDoRodape}
          tema={theme}
          aoTrocarTema={alternarTema}
          mostrarConta={!isEmployeeAccount}
        />

        <div
          className={clsx("ks-painel", !painelVisivel && "ks-painel-fechado")}
          inert={!painelVisivel || undefined}
        >
          {secaoNoPainel ? (
            <PainelDaSecao
              titulo={secaoNoPainel.rotulo}
              itens={secaoNoPainel.itens}
              pathname={rotaAtual}
              gruposFechados={gruposFechados}
              aoAlternarGrupo={alternarGrupo}
            />
          ) : null}
        </div>

        {/* O botão da borda só existe quando há uma seção para mostrar: numa
            página sem seção (Dashboard) não há o que expandir, e abrir a
            primeira seção ali parecia dizer que ela estava ativa. */}
        {secaoNoPainel ? (
          <button
            type="button"
            className="ks-painel-recolher"
            onClick={alternarSidebar}
            /* Os roteiros de captura de tela (landing, help, Motion) acham o
               botão por este rótulo: manter. */
            aria-label={painelVisivel ? "Recolher barra lateral" : "Abrir barra lateral"}
          >
            {!painelVisivel ? (
              <ChevronRight size={13} strokeWidth={2.6} />
            ) : (
              <ChevronLeft size={13} strokeWidth={2.6} />
            )}
          </button>
        ) : null}

      </aside>

      <div className={clsx("ks-dashboard-main", isPdv && "ks-dashboard-main-pdv")}>
        {isPdv ? null : (
          <header className="ks-dashboard-topbar hidden md:flex no-print">
            <div className="ks-dashboard-topbar-left">
              <div className="ks-topbar-heading">
                {breadcrumb.section ? (
                  <span className="ks-topbar-eyebrow">{breadcrumb.section}</span>
                ) : null}
                <span className="ks-topbar-title">{breadcrumb.title}</span>
              </div>
            </div>

            <div className="relative hidden flex-1 max-w-[420px] xl:block">
              <form
                className="ks-search-pill"
                onSubmit={(event) => {
                  event.preventDefault();
                  if (searchResults[0]) goToDestination(searchResults[0].href);
                }}
              >
                <input
                  value={globalSearch}
                  onChange={(event) => {
                    setGlobalSearch(event.target.value);
                    setGlobalSearchOpen(true);
                  }}
                  onFocus={() => setGlobalSearchOpen(true)}
                  onBlur={() => window.setTimeout(() => setGlobalSearchOpen(false), 140)}
                  placeholder="Ir para uma página…"
                  aria-label="Navegação rápida"
                />
                <button type="submit" className="ks-search-pill-go" aria-label="Ir">
                  <Search size={16} strokeWidth={2.4} />
                </button>
              </form>

              {globalSearchOpen && globalSearch.trim() ? (
                <div className="absolute left-0 right-0 top-full z-50 mt-2 overflow-hidden rounded-[var(--ks-radius-md)] bg-[var(--ks-color-bg-elevated)] p-1.5 shadow-[var(--ks-shadow-overlay)]">
                  {searchResults.length === 0 ? (
                    <p className="px-3 py-2.5 text-[13px] text-[var(--ks-color-text-soft)]">
                      Nenhuma página encontrada
                    </p>
                  ) : (
                    searchResults.map((destination) => (
                      <button
                        key={destination.href}
                        type="button"
                        onMouseDown={(event) => event.preventDefault()}
                        onClick={() => goToDestination(destination.href)}
                        className="group flex w-full items-center gap-2 rounded-[var(--ks-radius-xs)] px-3 py-2 text-left transition-colors hover:bg-[var(--ks-brand-50)]"
                      >
                        <span className="truncate text-[13px] font-semibold text-[var(--ks-color-text-primary)]">
                          {destination.label}
                        </span>
                        {destination.section ? (
                          <span className="truncate text-[11px] text-[var(--ks-color-text-soft)]">
                            {destination.section}
                          </span>
                        ) : null}
                        <CornerDownLeft
                          size={13}
                          strokeWidth={2.2}
                          className="ml-auto shrink-0 text-[var(--ks-brand-400)] opacity-0 transition-opacity group-hover:opacity-100"
                        />
                      </button>
                    ))
                  )}
                </div>
              ) : null}
            </div>

            <div className="ks-dashboard-topbar-right">
              {isEmployeeAccount && companyName ? (
                <span className="ks-chip ks-chip-soft mr-1">{companyName}</span>
              ) : null}
              {chatButton}
              {notificationBell}
              {isEmployeeAccount ? <EmployeeNav /> : null}
            </div>
          </header>
        )}

        {/* Cabeçalho do celular */}
        <div className="md:hidden fixed top-0 left-0 right-0 flex items-center justify-between gap-2 px-4 bg-[var(--ks-trilho-fundo)] z-50 h-16 no-print">
          <Link href="/dashboard" className="flex items-center gap-2">
            <Image
              src="/brand-logo.svg"
              alt="Logo"
              className="h-7 w-auto brightness-0 invert"
              width={124}
              height={32}
            />
          </Link>
          <div className="flex items-center gap-2">
            {chatButton}
            {notificationBell}
            {isEmployeeAccount ? <EmployeeNav mobile /> : null}
            <button
              onClick={() => setMobileMenuOpen(true)}
              className="ks-icon-button"
              aria-label="Abrir menu"
            >
              <Menu size={20} strokeWidth={2.2} />
            </button>
          </div>
        </div>

        {/* Spacer for Fixed Header */}
        <div className="md:hidden h-16 w-full flex-shrink-0 no-print" />

        {/* Menu do celular: a mesma dupla trilho + painel, numa gaveta. Tocar
            numa seção só troca o painel; tocar num item navega e fecha. */}
        <AnimatePresence>
          {mobileMenuOpen && (
            <>
              <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                onClick={() => setMobileMenuOpen(false)}
                className="fixed inset-0 bg-black/50 z-[60] md:hidden backdrop-blur-sm"
              />
              <motion.aside
                initial={{ x: "-100%" }}
                animate={{ x: 0 }}
                exit={{ x: "-100%" }}
                transition={{ type: "spring", damping: 25, stiffness: 200 }}
                className="ks-gaveta-celular"
                aria-label="Menu"
              >
                <Trilho
                  entradas={trilho}
                  rotaAtual={rotaAtual}
                  secaoDaPagina={secaoDaPagina}
                  secaoEscolhida={secaoAberta ?? (secaoNoPainel ? null : (primeiraSecao?.chave ?? null))}
                  aoEscolherSecao={(secao) => setSecaoEscolhida({ secao, pathname: rotaAtual })}
                  aoIrParaAtalho={handleMobileLinkClick}
                  atalhoDoChats={atalhoDoChats}
                  atalhosDoRodape={atalhosDoRodape}
                  tema={theme}
                  aoTrocarTema={alternarTema}
                  mostrarConta={!isEmployeeAccount}
                />
                <div className="ks-painel">
                  {secaoNaGaveta ? (
                    <PainelDaSecao
                      titulo={secaoNaGaveta.rotulo}
                      itens={secaoNaGaveta.itens}
                      pathname={rotaAtual}
                      gruposFechados={gruposFechados}
                      aoAlternarGrupo={alternarGrupo}
                      aoNavegar={handleMobileLinkClick}
                      aoFechar={() => setMobileMenuOpen(false)}
                    />
                  ) : null}
                </div>
              </motion.aside>
            </>
          )}
        </AnimatePresence>

        <main
          className={clsx(
            "ks-dashboard-content",
            (isProduction || isAgenda) && "ks-dashboard-content-tight",
            isPdv && "ks-dashboard-content-pdv"
          )}
        >
          <div
            className={clsx(
              "ks-page",
              isPaymentLinks && "ks-page-wide",
              (isProduction || isReceivables || isMySuppliers || isReports || isAgenda || isOrderDetail || isNotasFiscais || isSubscriptionPage || isCatalogProducts || isCatalogSettings || pathname === "/dashboard/design") &&
                "ks-page-full",
              pathname === "/dashboard" && "ks-page-wide",
              isPdv && "ks-page-pdv"
            )}
          >
            <SystemNoticesHost />
            <SystemPopupHost />
            {!isPdv && !isSubscriptionPage && !isEmployeeAccount ? <FaixaDaAssinaturaDoPainel /> : null}
            {aguardandoChecagem ? (
              <EsperandoAcesso />
            ) : acessoNegado ? (
              <SemAcesso />
            ) : (
              /* O contexto ja foi resolvido aqui para montar o menu; as telas
                 recebem a resposta pronta em vez de buscarem de novo. */
              <ProvedorPermissoes
                papel={papelEfetivo}
                ajustes={permissionState?.ctx.membership?.permissions}
                resolvido={checagemAcesso === "pronto" && papelEfetivo !== null}
              >
                <DesignLiberadoContexto.Provider value={designLiberado}>{children}</DesignLiberadoContexto.Provider>
              </ProvedorPermissoes>
            )}
          </div>
        </main>
      </div>

      <AnimatePresence>
        {showConfetti ? (
          <div className="fixed inset-0 pointer-events-none z-[140] overflow-hidden">
            {confettiPieces.map((piece, index) => (
              <motion.span
                key={`${confettiSeed}-${index}`}
                initial={{ opacity: 0, y: -60, x: 0, rotate: piece.rotate, scale: 0.8 }}
                animate={{
                  opacity: 1,
                  y: 900,
                  x: piece.xDrift,
                  rotate: piece.rotate + 360,
                  scale: 1,
                }}
                exit={{ opacity: 0 }}
                transition={{ duration: piece.duration, delay: piece.delay, ease: "easeOut" }}
                style={{
                  left: `${piece.left}%`,
                  width: `${piece.size}px`,
                  height: `${piece.size * 1.4}px`,
                  backgroundColor: piece.color,
                }}
                className="absolute top-0 rounded-sm"
              />
            ))}
          </div>
        ) : null}
      </AnimatePresence>

      {/* O atendimento por cima de qualquer tela: responde e volta ao que
          estava fazendo, sem trocar de aba. */}
      {podeVerChats ? (
        <PainelDoChats
          aberto={chatsAbertos}
          aoFechar={() => setChatsAbertos(false)}
          aoMudarPendentes={setChatsPendentes}
        />
      ) : null}
    </div>
  );
}
