"use client";

import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import {
  Check,
  ExternalLink,
  History,
  Layers,
  Loader2,
  Maximize2,
  Minimize2,
  Monitor,
  MoreHorizontal,
  MousePointerClick,
  Plus,
  Redo2,
  RotateCcw,
  Settings2,
  Smartphone,
  Tablet,
  Undo2,
  Wand2,
  X,
} from "lucide-react";
import { getSupabaseClient } from "@/lib/supabase";
import { canAnyCompany, getCompanyContext } from "@/lib/company-context";
import { avisarALojaAgora } from "@/lib/gravacoes-da-vitrine";
import CaixaDeConfirmacao, { type Confirmacao } from "@/components/ui/caixa-de-confirmacao";
import { Aviso } from "@/components/ui/aviso";
import {
  CANAL_DO_EDITOR,
  NOME_DO_MODO,
  chaveNova,
  configDaColuna,
  duplicada,
  ehModo,
  ehGradeFixa,
  indiceNaLista,
  lerMensagemDaLoja,
  mover,
  moverNaPagina,
  mudou,
  paginaParaGravar,
  podeAdicionar,
  problemasDaPagina,
  rascunhoInicial,
  rascunhoParaALoja,
  secaoDaLinha,
  secaoNova,
  secoesQueVaoAoBanco,
  temaDaLoja,
  ehFamiliaVendor,
  NOME_DO_TEMA,
  type LinhaDaSecao,
  type Modo,
  type SecaoDoEditor,
  type Tema,
} from "@/lib/pagina-inicial";
import { AjustesDaSecao } from "./ajustes";
import { EstruturaDaPagina } from "./estrutura";
import { PaletaDeSecoes, type BlocoSalvo } from "./paleta";
import { ContextoDoPainel, type ContextoDoEditorDoPainel } from "./contexto";
import { HistoricoDePublicacoes, type PublicacaoDoHistorico } from "./historico";
import { JanelaDosModelos } from "./modelos";
import { MODELOS_PRONTOS, paginaDoModelo, type ChaveDoModelo, type DadosParaOModelo } from "@/lib/modelos-da-pagina-inicial";
import { textoDeBrasilia } from "@/lib/widgets-da-home";
import type { CategoriaDoEditor, ProdutoDoEditor } from "./campos";
import { navegacaoParaALoja, navegacaoParaGravar, lerRegiaoDaLoja, problemasDaNavegacao } from "@/lib/navegacao-da-loja";
import { AjustesDoCabecalho, AjustesDoRodape, EntradasDaNavegacao, useNavegacaoDoEditor, type Regiao } from "./navegacao";

/*
  O editor da página inicial da loja (Catálogo > Produtos > Página inicial).

  No meio, a loja DE VERDADE, aberta num quadro em modo editor (a loja confere
  a passagem assinada e passa a desenhar o rascunho que este painel manda).
  À esquerda, a página como lista e os tipos de seção para pôr; à direita,
  os ajustes da seção escolhida. Nada vai para a loja até "Publicar", que
  grava tudo de uma vez (salvar_pagina_inicial, migration 196) e avisa a loja
  para esquecer a lembrança de dois minutos.

  Nada muda na loja de quem não publica por aqui: a loja Vendor continua com
  a home automática, e as outras com as seções de sempre.
*/

type Aparelho = "computador" | "tablet" | "celular";
const LARGURA_DO_APARELHO: Record<Aparelho, number> = { computador: 1280, tablet: 820, celular: 390 };

type Passagem = { endereco: string; origemDaLoja: string; enderecoPublico: string; venceEm: number };
type Recado = { tom: "bom" | "ruim" | "neutro"; texto: string } | null;
type PainelDoCelular = "estrutura" | "adicionar" | "ajustes" | null;

const LIMITE_DO_HISTORICO = 60;

function dataCurta(iso: string) {
  try {
    return new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
  } catch {
    return "";
  }
}

export function EditorDaPaginaInicial({
  podeEditar,
  pagina = null,
  seletorDePagina,
  aoMudarSujo,
}: {
  podeEditar: boolean;
  /* Uma página extra (migration 197); null = a página inicial. */
  pagina?: { id: string; titulo: string; slug: string } | null;
  /* O seletor de página que toma o lugar do título "Página inicial". */
  seletorDePagina?: ReactNode;
  /* Avisa quem contém o editor que há rascunho sem publicar (para não trocar
     de página e perder). */
  aoMudarSujo?: (sujo: boolean) => void;
}) {
  /* As regras da grade fixa "Todos os produtos" valem só na home dos temas
     Padrão e Vitrine: numa página extra elas seguem as do Vendor (nada fixo,
     nada inventado). O desenho continua o do tema da loja. */
  const ehPaginaExtra = Boolean(pagina);
  const idDaPagina = pagina?.id ?? null;
  const slugDaPagina = pagina?.slug ?? null;
  const quadro = useRef<HTMLIFrameElement | null>(null);
  const tela = useRef<HTMLDivElement | null>(null);
  const caixa = useRef<HTMLDivElement | null>(null);
  const [alturaDaCaixa, setAlturaDaCaixa] = useState<number | null>(null);

  const [carregando, setCarregando] = useState(true);
  const [erroDeCarga, setErroDeCarga] = useState("");
  const [dono, setDono] = useState("");
  const [tema, setTema] = useState<Tema>("default");
  const temaDasRegras: Tema = ehPaginaExtra ? "vendor" : tema;
  const [produtos, setProdutos] = useState<ProdutoDoEditor[]>([]);
  const [categorias, setCategorias] = useState<CategoriaDoEditor[]>([]);
  const [secoesDoBanco, setSecoesDoBanco] = useState<SecaoDoEditor[] | null>(null);
  const [homeEditada, setHomeEditada] = useState(false);
  const [ultimaPublicacao, setUltimaPublicacao] = useState<string | null>(null);

  const [passagem, setPassagem] = useState<Passagem | null>(null);
  const [lojaPronta, setLojaPronta] = useState(false);
  const [arranjo, setArranjo] = useState<SecaoDoEditor[] | null>(null);
  const [semResposta, setSemResposta] = useState(false);

  const [publicado, setPublicado] = useState<SecaoDoEditor[] | null>(null);
  const [rascunho, setRascunho] = useState<SecaoDoEditor[]>([]);
  const [passado, setPassado] = useState<SecaoDoEditor[][]>([]);
  const [futuro, setFuturo] = useState<SecaoDoEditor[][]>([]);
  const ultimaGravacaoDoHistorico = useRef<{ chave: string; em: number } | null>(null);

  const [selecionada, setSelecionada] = useState<string | null>(null);
  const [abaDaEsquerda, setAbaDaEsquerda] = useState<"estrutura" | "adicionar">("estrutura");
  const [painel, setPainel] = useState<PainelDoCelular>(null);
  const [lugar, setLugar] = useState<number | null>(null);
  const [aparelho, setAparelho] = useState<Aparelho>("computador");
  const [telaCheia, setTelaCheia] = useState(false);
  const [maisAberto, setMaisAberto] = useState(false);
  const [publicando, setPublicando] = useState(false);
  const [recado, setRecado] = useState<Recado>(null);
  const [confirmacao, setConfirmacao] = useState<Confirmacao | null>(null);
  const [medida, setMedida] = useState({ largura: 900, altura: 600 });

  /* Recursos de toda seção (migration 199): a prévia das agendadas, os
     cliques da loja, Meus blocos, os modelos prontos e o histórico. */
  const [previaNoAr, setPreviaNoAr] = useState<Set<string>>(() => new Set());
  const [cliques, setCliques] = useState<Map<string, number> | null>(null);
  const [corDaLoja, setCorDaLoja] = useState("#6D28D9");
  const [podeGerarBanner, setPodeGerarBanner] = useState(false);
  const [blocos, setBlocos] = useState<BlocoSalvo[] | null>(null);
  const [blocoParaSalvar, setBlocoParaSalvar] = useState<SecaoDoEditor | null>(null);
  const [modelosAbertos, setModelosAbertos] = useState(false);
  const [modeloCarregando, setModeloCarregando] = useState<ChaveDoModelo | null>(null);
  const [historicoAberto, setHistoricoAberto] = useState(false);
  const [historico, setHistorico] = useState<PublicacaoDoHistorico[]>([]);
  const [historicoCarregando, setHistoricoCarregando] = useState(false);
  const [versaoVista, setVersaoVista] = useState<{ id: string; quando: string; secoes: SecaoDoEditor[]; comMenu: boolean } | null>(null);

  /* ------------------------------------------------------- carregar */

  const lerDoBanco = useCallback(async () => {
    const supabase = getSupabaseClient();
    const {
      data: { user },
    } = await supabase.auth.getUser();
    if (!user) throw new Error("Entre no painel de novo.");
    const ctx = await getCompanyContext(supabase, user);
    const donoDaLoja = ctx.ownerId ?? user.id;
    const tenantIds = ctx.tenantIds?.length ? ctx.tenantIds : [user.id];

    /* Os cliques de cada seção na loja publicada, nos últimos 30 dias. */
    const desde = textoDeBrasilia(Date.now() - 29 * 24 * 3600 * 1000).slice(0, 10);
    const [perfil, secoes, listaDeProdutos, listaDeCategorias, publicacoes, cliquesLidos] = await Promise.all([
      supabase.from("profiles").select("catalog_theme,primary_color").eq("id", donoDaLoja).maybeSingle(),
      (idDaPagina
        ? supabase.from("catalog_sections").select("*").eq("user_id", donoDaLoja).eq("pagina_id", idDaPagina)
        : supabase.from("catalog_sections").select("*").eq("user_id", donoDaLoja).is("pagina_id", null)
      ).order("position", { ascending: true }),
      supabase
        .from("products")
        .select("id,name,price,image_url,category_id,show_in_catalog,is_catalog_exclusive,active,product_kind,catalog_position,created_at")
        .in("user_id", tenantIds)
        .eq("active", true)
        .order("catalog_position", { ascending: true, nullsFirst: false })
        .order("created_at", { ascending: false })
        .limit(2000),
      supabase.from("categories").select("id,name,slug,parent_id").eq("user_id", donoDaLoja).eq("is_visible", true).order("name", { ascending: true }),
      (idDaPagina
        ? supabase.from("catalog_home_publicacoes").select("id,created_at,secoes_mexidas").eq("user_id", donoDaLoja).eq("pagina_id", idDaPagina)
        : supabase.from("catalog_home_publicacoes").select("id,created_at,secoes_mexidas").eq("user_id", donoDaLoja).is("pagina_id", null)
      )
        .order("created_at", { ascending: false })
        .limit(15),
      supabase.from("catalog_cliques_das_secoes").select("section_id,cliques").eq("user_id", donoDaLoja).gte("dia", desde).limit(5000),
    ]);
    if (secoes.error) throw new Error("Não deu para ler as seções da loja.");
    const somaDosCliques = new Map<string, number>();
    for (const linha of (cliquesLidos.data || []) as Array<{ section_id: string; cliques: number }>) {
      somaDosCliques.set(linha.section_id, (somaDosCliques.get(linha.section_id) || 0) + Number(linha.cliques || 0));
    }

    const linhas = (secoes.data || []) as LinhaDaSecao[];
    const ids = linhas.map((l) => l.id);
    const produtosDasSecoes = ids.length
      ? await supabase.from("catalog_section_products").select("section_id,product_id,position").in("section_id", ids).order("position", { ascending: true })
      : { data: [] as Array<{ section_id: string; product_id: string }>, error: null };
    const porSecao = new Map<string, string[]>();
    for (const linha of (produtosDasSecoes.data || []) as Array<{ section_id: string; product_id: string | null }>) {
      if (!linha.product_id) continue;
      const lista = porSecao.get(linha.section_id) || [];
      lista.push(linha.product_id);
      porSecao.set(linha.section_id, lista);
    }

    const visiveis = ((listaDeProdutos.data || []) as Array<ProdutoDoEditor & { show_in_catalog?: boolean; is_catalog_exclusive?: boolean; product_kind?: string | null }>).filter(
      (p) => (p.show_in_catalog || p.is_catalog_exclusive) && p.product_kind !== "digital"
    );

    return {
      dono: donoDaLoja,
      tema: temaDaLoja((perfil.data as { catalog_theme?: string | null } | null)?.catalog_theme),
      secoes: linhas.map((linha) => secaoDaLinha(linha, porSecao.get(linha.id) || [])),
      produtos: visiveis,
      categorias: (listaDeCategorias.data || []) as CategoriaDoEditor[],
      publicacao: !publicacoes.error && publicacoes.data?.length ? String((publicacoes.data[0] as { created_at: string }).created_at) : null,
      cliques: cliquesLidos.error ? null : somaDosCliques,
      corDaLoja: String((perfil.data as { primary_color?: string | null } | null)?.primary_color || "#6D28D9"),
      podeGerarBanner: canAnyCompany(ctx, user.id, ["catalog.banners.write", "marketing.manage"]),
      /* A home montada pelo editor (o Vendor passa a mostrar as seções):
         publicação que mexeu nas seções. Publicar só o menu não conta. Uma
         página extra é sempre "montada". */
      homeEditada: idDaPagina
        ? true
        : !publicacoes.error && ((publicacoes.data || []) as Array<{ secoes_mexidas?: boolean | null }>).some((linha) => linha.secoes_mexidas !== false),
    };
  }, [idDaPagina]);

  const pedirPassagem = useCallback(async () => {
    const resposta = await fetch("/api/loja/editor", {
      method: "POST",
      headers: { "content-type": "application/json" },
      /* Só em desenvolvimento: ?loja=http://localhost:3005 abre a loja local
         que roda noutra porta (a rota ignora em produção). */
      body: JSON.stringify({ lojaLocal: new URLSearchParams(window.location.search).get("loja") || undefined, pagina: idDaPagina || undefined }),
      cache: "no-store",
    });
    const corpo = await resposta.json().catch(() => ({}));
    if (!resposta.ok) throw new Error(String(corpo?.error || "Não deu para abrir a loja no editor."));
    return corpo as Passagem;
    // eslint-disable-next-line react-hooks/exhaustive-deps -- o endereço da página muda a passagem
  }, [idDaPagina, slugDaPagina]);

  useEffect(() => {
    let cancelado = false;
    (async () => {
      try {
        const [dados, novaPassagem] = await Promise.all([lerDoBanco(), podeEditar ? pedirPassagem() : Promise.resolve(null)]);
        if (cancelado) return;
        setDono(dados.dono);
        setTema(dados.tema);
        setProdutos(dados.produtos);
        setCategorias(dados.categorias);
        setSecoesDoBanco(dados.secoes);
        setHomeEditada(dados.homeEditada);
        setUltimaPublicacao(dados.publicacao);
        setCliques(dados.cliques);
        setCorDaLoja(dados.corDaLoja);
        setPodeGerarBanner(dados.podeGerarBanner);
        /* A loja do quadro vai recarregar: o rascunho é mandado de novo
           quando ela disser que está pronta. */
        setLojaPronta(false);
        setPassagem(novaPassagem);
      } catch (erro) {
        if (!cancelado) setErroDeCarga(erro instanceof Error ? erro.message : "Não deu para abrir o editor.");
      } finally {
        if (!cancelado) setCarregando(false);
      }
    })();
    return () => {
      cancelado = true;
    };
  }, [lerDoBanco, pedirPassagem, podeEditar]);

  /* No celular a loja abre do tamanho do celular. */
  useEffect(() => {
    if (window.innerWidth < 768) setAparelho("celular");
  }, []);

  /* A passagem vale duas horas: renova sozinha antes de vencer. */
  useEffect(() => {
    if (!passagem) return;
    const falta = passagem.venceEm - Date.now() - 5 * 60 * 1000;
    const relogio = window.setTimeout(async () => {
      try {
        setPassagem(await pedirPassagem());
        setLojaPronta(false);
      } catch {
        /* a loja segue aberta; ao recarregar o editor pede de novo */
      }
    }, Math.max(60_000, falta));
    return () => window.clearTimeout(relogio);
  }, [passagem, pedirPassagem]);

  /* O rascunho começa quando o banco e a loja responderam (o Vendor que
     nunca publicou precisa da home automática que a loja manda). */
  useEffect(() => {
    if (publicado || !secoesDoBanco) return;
    if (ehFamiliaVendor(tema) && !homeEditada && !arranjo && !semResposta) return;
    const inicial = rascunhoInicial(temaDasRegras, secoesDoBanco, { homeEditada, arranjoAutomatico: arranjo });
    setPublicado(inicial);
    setRascunho(inicial);
  }, [arranjo, homeEditada, publicado, secoesDoBanco, semResposta, tema, temaDasRegras]);

  useEffect(() => {
    if (!passagem || lojaPronta) return;
    const relogio = window.setTimeout(() => setSemResposta(true), 12000);
    return () => window.clearTimeout(relogio);
  }, [lojaPronta, passagem]);

  /* ------------------------------------------------- conversa com a loja */

  const enviarParaALoja = useCallback(
    (mensagem: Record<string, unknown>) => {
      const janela = quadro.current?.contentWindow;
      if (!janela || !passagem) return;
      janela.postMessage({ canal: CANAL_DO_EDITOR, ...mensagem }, passagem.origemDaLoja);
    },
    [passagem]
  );

  /* O cabeçalho, o menu e o rodapé (migration 197): rascunho próprio, com o
     próprio desfazer, publicado junto com as seções. */
  const nav = useNavegacaoDoEditor(dono);
  const [regiao, setRegiao] = useState<Regiao | null>(null);
  const sujoDasSecoes = useMemo(() => (publicado ? mudou(rascunho, publicado) : false), [publicado, rascunho]);
  const sujo = sujoDasSecoes || nav.sujo;

  useEffect(() => {
    aoMudarSujo?.(sujo);
  }, [aoMudarSujo, sujo]);
  const ligadas = useMemo(() => rascunho.filter((s) => s.ativa), [rascunho]);
  const escolhida = useMemo(() => rascunho.find((s) => s.chave === selecionada) || null, [rascunho, selecionada]);

  useEffect(() => {
    if (!lojaPronta || !publicado) return;
    /* Vendo uma versão do histórico: a loja mostra ela, só para olhar. */
    if (versaoVista) {
      enviarParaALoja({ tipo: "rascunho", secoes: rascunhoParaALoja(versaoVista.secoes), selecionada: null });
      return;
    }
    enviarParaALoja({ tipo: "rascunho", secoes: rascunhoParaALoja(rascunho, previaNoAr), selecionada });
  }, [enviarParaALoja, lojaPronta, previaNoAr, publicado, rascunho, selecionada, versaoVista]);

  useEffect(() => {
    if (!lojaPronta || !nav.rascunho) return;
    enviarParaALoja({ tipo: "navegacao", navegacao: navegacaoParaALoja(nav.rascunho), paginas: nav.paginasVisiveis });
  }, [enviarParaALoja, lojaPronta, nav.paginasVisiveis, nav.rascunho]);

  useEffect(() => {
    if (lojaPronta) enviarParaALoja({ tipo: "regiao", id: regiao });
  }, [enviarParaALoja, lojaPronta, regiao]);

  const escolherRegiao = useCallback(
    (qual: Regiao | null, rolar = false) => {
      setRegiao(qual);
      if (qual) {
        setSelecionada(null);
        enviarParaALoja({ tipo: "selecionar", id: null });
        if (rolar) enviarParaALoja({ tipo: "regiao", id: qual, rolar: true });
      }
    },
    [enviarParaALoja]
  );

  /* ----------------------------------------------- mexer no rascunho */

  const aplicar = useCallback(
    (proximo: SecaoDoEditor[], agrupar?: string) => {
      setRascunho((atual) => {
        const agora = Date.now();
        const anterior = ultimaGravacaoDoHistorico.current;
        const junta = agrupar && anterior && anterior.chave === agrupar && agora - anterior.em < 900;
        if (!junta) setPassado((p) => [...p.slice(-(LIMITE_DO_HISTORICO - 1)), atual]);
        ultimaGravacaoDoHistorico.current = agrupar ? { chave: agrupar, em: agora } : null;
        setFuturo([]);
        return proximo;
      });
    },
    []
  );

  const desfazer = useCallback(() => {
    setPassado((p) => {
      if (!p.length) return p;
      const anterior = p[p.length - 1];
      setRascunho((atual) => {
        setFuturo((f) => [atual, ...f].slice(0, LIMITE_DO_HISTORICO));
        return anterior;
      });
      ultimaGravacaoDoHistorico.current = null;
      return p.slice(0, -1);
    });
  }, []);

  const refazer = useCallback(() => {
    setFuturo((f) => {
      if (!f.length) return f;
      const proximo = f[0];
      setRascunho((atual) => {
        setPassado((p) => [...p, atual].slice(-LIMITE_DO_HISTORICO));
        return proximo;
      });
      return f.slice(1);
    });
  }, []);

  const escolher = useCallback(
    (chave: string | null, rolar = false) => {
      setSelecionada(chave);
      if (chave) setRegiao(null);
      if (chave && rolar) enviarParaALoja({ tipo: "rolar", id: chave });
    },
    [enviarParaALoja]
  );

  const porNaPagina = useCallback(
    (nova: SecaoDoEditor) => {
      const recusa = podeAdicionar(nova.modo, rascunho);
      if (recusa) {
        setRecado({ tom: "neutro", texto: recusa });
        return;
      }
      const depoisDaEscolhida = selecionada ? rascunho.findIndex((s) => s.chave === selecionada) + 1 : -1;
      /* Sem lugar escolhido: antes da grade de produtos dos temas Padrão e
         Vitrine (que costuma ser a última), senão no fim. */
      const indiceDaGrade = !ehFamiliaVendor(temaDasRegras) ? rascunho.findIndex((s) => s.modo === "all_products" && s.ativa) : -1;
      const onde = lugar ?? (depoisDaEscolhida > 0 ? depoisDaEscolhida : indiceDaGrade >= 0 ? indiceDaGrade : rascunho.length);
      const lista = [...rascunho];
      lista.splice(Math.min(Math.max(onde, 0), lista.length), 0, nova);
      aplicar(lista);
      setLugar(null);
      setAbaDaEsquerda("estrutura");
      setPainel("ajustes");
      escolher(nova.chave);
      window.setTimeout(() => enviarParaALoja({ tipo: "rolar", id: nova.chave }), 80);
    },
    [aplicar, enviarParaALoja, escolher, lugar, rascunho, selecionada, temaDasRegras]
  );

  const adicionar = useCallback((modo: Modo) => porNaPagina(secaoNova(modo)), [porNaPagina]);

  const alterar = useCallback(
    (secao: SecaoDoEditor) => aplicar(rascunho.map((s) => (s.chave === secao.chave ? secao : s)), secao.chave),
    [aplicar, rascunho]
  );

  const moverSecao = useCallback(
    (chave: string, direcao: -1 | 1) => {
      const lista = moverNaPagina(rascunho, chave, direcao);
      if (lista !== rascunho) {
        aplicar(lista);
        window.setTimeout(() => enviarParaALoja({ tipo: "rolar", id: chave }), 60);
      }
    },
    [aplicar, enviarParaALoja, rascunho]
  );

  const duplicar = useCallback(
    (chave: string) => {
      const indice = rascunho.findIndex((s) => s.chave === chave);
      const secao = rascunho[indice];
      if (!secao || ehGradeFixa(temaDasRegras, secao) || secao.modo === "all_products") return;
      const copia = duplicada(secao);
      const lista = [...rascunho];
      lista.splice(indice + 1, 0, copia);
      aplicar(lista);
      escolher(copia.chave);
      window.setTimeout(() => enviarParaALoja({ tipo: "rolar", id: copia.chave }), 80);
    },
    [aplicar, enviarParaALoja, escolher, rascunho, temaDasRegras]
  );

  const alternar = useCallback(
    (chave: string) => {
      const secao = rascunho.find((s) => s.chave === chave);
      if (!secao || ehGradeFixa(temaDasRegras, secao)) return;
      if (!secao.ativa && secao.modo === "all_products" && rascunho.some((s) => s.modo === "all_products" && s.ativa)) {
        setRecado({ tom: "neutro", texto: 'A página já tem "Todos os produtos" à mostra.' });
        return;
      }
      aplicar(rascunho.map((s) => (s.chave === chave ? { ...s, ativa: !s.ativa } : s)));
      setRecado({ tom: "neutro", texto: secao.ativa ? "Seção escondida: continua guardada na lista." : "Seção de volta na loja." });
    },
    [aplicar, rascunho, temaDasRegras]
  );

  const apagar = useCallback(
    (chave: string) => {
      const secao = rascunho.find((s) => s.chave === chave);
      if (!secao || ehGradeFixa(temaDasRegras, secao)) return;
      setConfirmacao({
        titulo: `Apagar "${secao.title || NOME_DO_MODO[secao.modo]}"?`,
        texto: "A seção sai da página. Até publicar, dá para desfazer (Ctrl+Z). Para só tirar da loja e guardar, use Esconder.",
        confirmar: "Apagar seção",
        tom: "perigo",
        aoConfirmar: () => {
          setConfirmacao(null);
          aplicar(rascunho.filter((s) => s.chave !== chave));
          if (selecionada === chave) setSelecionada(null);
        },
      });
    },
    [aplicar, rascunho, selecionada, temaDasRegras]
  );

  /* As mensagens da loja: só da origem da passagem e só deste quadro. */
  useEffect(() => {
    if (!passagem) return;
    const aoReceber = (evento: MessageEvent) => {
      if (evento.origin !== passagem.origemDaLoja || evento.source !== quadro.current?.contentWindow) return;
      const mensagem = lerMensagemDaLoja(evento.data);
      if (!mensagem) return;
      if (mensagem.tipo === "pronto") {
        setLojaPronta(true);
        setSemResposta(false);
        if (mensagem.arranjoAutomatico && !arranjo) setArranjo(mensagem.arranjoAutomatico);
        return;
      }
      if (mensagem.tipo === "link-bloqueado") {
        setRecado({ tom: "neutro", texto: "No editor os links ficam parados. Para navegar pela loja, use “Ver loja”." });
        return;
      }
      const { acao, id } = mensagem;
      /* Vendo uma versão do histórico: a loja é só para olhar. */
      if (versaoVista) {
        setRecado({ tom: "neutro", texto: "Esta é uma versão antiga, só para ver. Saia da prévia para mexer no rascunho." });
        return;
      }
      if (acao === "inserir") {
        const onde = indiceNaLista(rascunho, mensagem.indice ?? ligadas.length);
        setLugar(onde);
        setAbaDaEsquerda("adicionar");
        setPainel("adicionar");
        return;
      }
      if (!id) return;
      if (acao === "selecionar") escolher(id);
      else if (acao === "editar") {
        escolher(id);
        setPainel("ajustes");
      } else if (acao === "subir") moverSecao(id, -1);
      else if (acao === "descer") moverSecao(id, 1);
      else if (acao === "duplicar") duplicar(id);
      else if (acao === "ocultar") alternar(id);
      else if (acao === "excluir") apagar(id);
    };
    window.addEventListener("message", aoReceber);
    return () => window.removeEventListener("message", aoReceber);
  }, [alternar, apagar, arranjo, duplicar, escolher, ligadas.length, moverSecao, passagem, rascunho, versaoVista]);

  /* Com o cabeçalho ou o rodapé aberto, desfazer e refazer mexem neles. */
  const desfazerAgora = useCallback(() => (regiao ? nav.desfazer() : desfazer()), [desfazer, nav, regiao]);
  const refazerAgora = useCallback(() => (regiao ? nav.refazer() : refazer()), [nav, refazer, regiao]);
  const podeDesfazer = regiao ? nav.podeDesfazer : passado.length > 0;
  const podeRefazer = regiao ? nav.podeRefazer : futuro.length > 0;

  /* A loja avisa quando o cabeçalho ou o rodapé foi clicado. */
  useEffect(() => {
    if (!passagem) return;
    const aoReceber = (evento: MessageEvent) => {
      if (evento.origin !== passagem.origemDaLoja || evento.source !== quadro.current?.contentWindow) return;
      const qual = lerRegiaoDaLoja(evento.data);
      if (!qual) return;
      escolherRegiao(qual);
      setPainel("ajustes");
    };
    window.addEventListener("message", aoReceber);
    return () => window.removeEventListener("message", aoReceber);
  }, [escolherRegiao, passagem]);

  /* Atalhos: Ctrl+Z desfaz, Ctrl+Shift+Z (ou Ctrl+Y) refaz. */
  useEffect(() => {
    const aoTeclar = (evento: KeyboardEvent) => {
      const alvo = evento.target as HTMLElement | null;
      if (alvo && (alvo.tagName === "INPUT" || alvo.tagName === "TEXTAREA" || alvo.isContentEditable)) return;
      if (!(evento.ctrlKey || evento.metaKey)) return;
      const tecla = evento.key.toLowerCase();
      if (tecla === "z" && !evento.shiftKey) {
        evento.preventDefault();
        desfazerAgora();
      } else if ((tecla === "z" && evento.shiftKey) || tecla === "y") {
        evento.preventDefault();
        refazerAgora();
      }
    };
    window.addEventListener("keydown", aoTeclar);
    return () => window.removeEventListener("keydown", aoTeclar);
  }, [desfazerAgora, refazerAgora]);

  /* Sair com mudança sem publicar pede confirmação do navegador (é o único
     aviso que o navegador deixa dar na saída da página). */
  useEffect(() => {
    if (!sujo) return;
    const aoSair = (evento: BeforeUnloadEvent) => {
      evento.preventDefault();
      evento.returnValue = "";
    };
    window.addEventListener("beforeunload", aoSair);
    return () => window.removeEventListener("beforeunload", aoSair);
  }, [sujo]);

  useEffect(() => {
    if (!recado) return;
    const relogio = window.setTimeout(() => setRecado(null), recado.tom === "ruim" ? 9000 : 4200);
    return () => window.clearTimeout(relogio);
  }, [recado]);

  /* O tamanho do palco, para a loja caber inteira na largura do aparelho. */
  useEffect(() => {
    const elemento = tela.current;
    if (!elemento) return;
    const observar = new ResizeObserver(([entrada]) => {
      const { width, height } = entrada.contentRect;
      setMedida({ largura: width, altura: height });
    });
    observar.observe(elemento);
    return () => observar.disconnect();
  }, [carregando]);

  /* -------------------------------------------------------- publicar */

  const recarregarDepoisDeGravar = useCallback(
    async (manterSelecao: Map<string, string>) => {
      const dados = await lerDoBanco();
      const novas = dados.secoes;
      const inicial = rascunhoInicial(ehPaginaExtra ? "vendor" : dados.tema, novas, { homeEditada: dados.homeEditada, arranjoAutomatico: arranjo });
      setSecoesDoBanco(novas);
      setProdutos(dados.produtos);
      setCategorias(dados.categorias);
      setHomeEditada(dados.homeEditada);
      setUltimaPublicacao(dados.publicacao);
      setCliques(dados.cliques);
      setPublicado(inicial);
      setRascunho(inicial);
      setPassado([]);
      setFuturo([]);
      setPreviaNoAr(new Set());
      setSelecionada((atual) => (atual ? manterSelecao.get(atual) || (inicial.some((s) => s.chave === atual) ? atual : null) : null));
    },
    [arranjo, ehPaginaExtra, lerDoBanco]
  );

  const publicar = useCallback(async () => {
    if (!podeEditar || publicando || !dono) return;
    const problemas = sujoDasSecoes ? problemasDaPagina(rascunho) : [];
    if (problemas.length) {
      setRecado({ tom: "ruim", texto: problemas[0].texto });
      escolher(problemas[0].chave, true);
      setPainel("ajustes");
      return;
    }
    const problemasDoMenu = nav.sujo && nav.rascunho ? problemasDaNavegacao(nav.rascunho) : [];
    if (problemasDoMenu.length) {
      setRecado({ tom: "ruim", texto: problemasDoMenu[0].texto });
      escolherRegiao(problemasDoMenu[0].regiao, true);
      setPainel("ajustes");
      return;
    }
    setPublicando(true);
    try {
      const supabase = getSupabaseClient();
      /* Vai só o que mudou: seções sem mudança ficam como estão (a home
         automática do Vendor continua automática quando só o menu mudou). */
      const vaoAoBanco = sujoDasSecoes ? secoesQueVaoAoBanco(rascunho) : [];
      const { data, error } = await supabase.rpc("publicar_no_editor_da_loja", {
        p_dono: dono,
        p_pagina: pagina?.id ?? null,
        p_secoes: sujoDasSecoes ? paginaParaGravar(rascunho) : null,
        p_tema: tema,
        p_navegacao: nav.sujo && nav.rascunho ? navegacaoParaGravar(nav.rascunho) : null,
      });
      if (error) throw new Error(error.message || "Não deu para publicar.");
      if (nav.sujo) await nav.recarregar();
      const ids = ((data as { ids?: string[] } | null)?.ids || []).map(String);
      const mapa = new Map<string, string>();
      vaoAoBanco.forEach((secao, i) => {
        if (ids[i]) mapa.set(secao.chave, ids[i]);
      });
      const ouviu = await avisarALojaAgora();
      await recarregarDepoisDeGravar(mapa);
      if (historicoAberto) void carregarHistorico();
      setRecado({
        tom: "bom",
        texto: ouviu ? "Publicado. A loja já mostra a mudança." : "Publicado. A loja mostra a mudança em até dois minutos.",
      });
    } catch (erro) {
      setRecado({ tom: "ruim", texto: erro instanceof Error ? erro.message : "Não deu para publicar." });
    } finally {
      setPublicando(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- carregarHistorico vem depois e só lê o banco
  }, [dono, escolher, escolherRegiao, historicoAberto, nav, pagina, podeEditar, publicando, rascunho, recarregarDepoisDeGravar, sujoDasSecoes, tema]);

  const pedirParaDescartar = useCallback(() => {
    if (!publicado) return;
    setMaisAberto(false);
    setConfirmacao({
      titulo: "Descartar as mudanças?",
      texto: "O rascunho volta a ser o que está publicado na loja (seções, cabeçalho, menu e rodapé).",
      confirmar: "Descartar",
      tom: "perigo",
      aoConfirmar: () => {
        setConfirmacao(null);
        if (sujoDasSecoes) aplicar(publicado);
        if (nav.sujo) nav.descartar();
        setSelecionada(null);
      },
    });
  }, [aplicar, nav, publicado, sujoDasSecoes]);

  const pedirParaDesfazerPublicacao = useCallback(() => {
    if (!ultimaPublicacao || !dono) return;
    setMaisAberto(false);
    setConfirmacao({
      titulo: "Desfazer a última publicação?",
      texto: `${pagina ? `A página "${pagina.titulo}"` : "A página inicial"} volta a ser como estava antes da publicação de ${dataCurta(ultimaPublicacao)} (o cabeçalho, o menu e o rodapé também, se mudaram nela).${
        ehFamiliaVendor(tema) && !pagina ? ` Se foi a primeira, a loja volta para a página automática do modelo ${NOME_DO_TEMA[tema]}.` : ""
      }${sujo ? " O rascunho que não foi publicado se perde." : ""}`,
      confirmar: "Desfazer publicação",
      tom: "perigo",
      aoConfirmar: async () => {
        setConfirmacao(null);
        setPublicando(true);
        try {
          const { data: desfeita, error } = await getSupabaseClient().rpc("desfazer_publicacao_no_editor", { p_dono: dono, p_pagina: pagina?.id ?? null });
          if (error) throw new Error(error.message || "Não deu para desfazer.");
          await avisarALojaAgora();
          await nav.recarregar();
          const menuMantido = (desfeita as { navegacao_mantida?: boolean } | null)?.navegacao_mantida === true;
          /* O Vendor pode ter voltado à home automática: abre a loja de novo
             para ela mandar o arranjo. */
          const dados = await lerDoBanco();
          const voltouParaAAutomatica = !pagina && ehFamiliaVendor(dados.tema) && !dados.homeEditada;
          setArranjo(null);
          setSemResposta(false);
          setSecoesDoBanco(dados.secoes);
          setHomeEditada(dados.homeEditada);
          setUltimaPublicacao(dados.publicacao);
          setPassado([]);
          setFuturo([]);
          setSelecionada(null);
          if (voltouParaAAutomatica) {
            /* O rascunho recomeça quando a loja mandar a home automática. */
            setPublicado(null);
            setRascunho([]);
          } else {
            const inicial = rascunhoInicial(ehPaginaExtra ? "vendor" : dados.tema, dados.secoes, { homeEditada: true, arranjoAutomatico: null });
            setPublicado(inicial);
            setRascunho(inicial);
          }
          setLojaPronta(false);
          setPassagem(await pedirPassagem());
          setRecado({
            tom: "bom",
            texto: menuMantido
              ? "Publicação desfeita. O menu e o rodapé ficaram como estão: outra publicação mexeu neles depois."
              : "Publicação desfeita. A loja voltou para o que era antes.",
          });
        } catch (erro) {
          setRecado({ tom: "ruim", texto: erro instanceof Error ? erro.message : "Não deu para desfazer." });
        } finally {
          setPublicando(false);
        }
      },
    });
  }, [dono, ehPaginaExtra, lerDoBanco, nav, pagina, pedirPassagem, sujo, tema, ultimaPublicacao]);

  /* ----------------------------------------- histórico com prévia (199) */

  const carregarHistorico = useCallback(async () => {
    if (!dono) return;
    setHistoricoCarregando(true);
    try {
      const supabase = getSupabaseClient();
      const consulta = supabase
        .from("catalog_home_publicacoes")
        .select("id,created_at,autor,secoes_mexidas,navegacao_mexida,navegacao_antes,navegacao_depois,restaurada_de")
        .eq("user_id", dono);
      const { data, error } = await (idDaPagina ? consulta.eq("pagina_id", idDaPagina) : consulta.is("pagina_id", null))
        .order("created_at", { ascending: false })
        .limit(15);
      if (error) throw new Error("Não deu para ler o histórico.");
      setHistorico((data || []) as PublicacaoDoHistorico[]);
    } catch (erro) {
      setRecado({ tom: "ruim", texto: erro instanceof Error ? erro.message : "Não deu para ler o histórico." });
    } finally {
      setHistoricoCarregando(false);
    }
  }, [dono, idDaPagina]);

  const abrirHistorico = useCallback(() => {
    setMaisAberto(false);
    setHistoricoAberto(true);
    setRegiao(null);
    setPainel("ajustes");
    void carregarHistorico();
  }, [carregarHistorico]);

  const fecharHistorico = useCallback(() => {
    setHistoricoAberto(false);
    setVersaoVista(null);
  }, []);

  const verVersao = useCallback(
    async (id: string) => {
      if (versaoVista?.id === id) {
        setVersaoVista(null);
        return;
      }
      try {
        const { data, error } = await getSupabaseClient().rpc("versao_da_publicacao", { p_dono: dono, p_publicacao: id });
        if (error) throw new Error(error.message || "Não deu para abrir essa versão.");
        const versao = data as { secoes?: Array<LinhaDaSecao & { produtos?: string[] }>; publicada_em?: string; navegacao_mexida?: boolean } | null;
        const linhas = versao?.secoes || [];
        const secoes = rascunhoInicial(temaDasRegras, linhas.map((linha) => secaoDaLinha(linha, Array.isArray(linha.produtos) ? linha.produtos.map(String) : [])), {
          homeEditada: true,
          arranjoAutomatico: null,
        });
        setVersaoVista({ id, quando: dataCurta(String(versao?.publicada_em || "")), secoes, comMenu: versao?.navegacao_mexida === true });
        setSelecionada(null);
      } catch (erro) {
        setRecado({ tom: "ruim", texto: erro instanceof Error ? erro.message : "Não deu para abrir essa versão." });
      }
    },
    [dono, temaDasRegras, versaoVista?.id]
  );

  const voltarParaVersao = useCallback(
    (id: string) => {
      const item = historico.find((p) => p.id === id);
      setConfirmacao({
        titulo: "Voltar para esta versão?",
        texto: `${pagina ? `A página "${pagina.titulo}"` : "A página inicial"} volta a ser como ficou na publicação de ${item ? dataCurta(item.created_at) : "antes"}${
          item?.navegacao_mexida ? " (o cabeçalho, o menu e o rodapé dessa publicação também voltam)" : ""
        }. Isso já publica na loja e entra no histórico: dá para desfazer depois.${sujo ? " O rascunho que não foi publicado se perde." : ""}`,
        confirmar: "Voltar para esta versão",
        tom: "perigo",
        aoConfirmar: async () => {
          setConfirmacao(null);
          setPublicando(true);
          try {
            const { data, error } = await getSupabaseClient().rpc("voltar_para_publicacao", { p_dono: dono, p_publicacao: id });
            if (error) throw new Error(error.message || "Não deu para voltar para essa versão.");
            await avisarALojaAgora();
            if ((data as { navegacao_mexida?: boolean } | null)?.navegacao_mexida) await nav.recarregar();
            setVersaoVista(null);
            await recarregarDepoisDeGravar(new Map());
            await carregarHistorico();
            setRecado({ tom: "bom", texto: "Pronto: a loja voltou para essa versão." });
          } catch (erro) {
            setRecado({ tom: "ruim", texto: erro instanceof Error ? erro.message : "Não deu para voltar para essa versão." });
          } finally {
            setPublicando(false);
          }
        },
      });
    },
    [carregarHistorico, dono, historico, nav, pagina, recarregarDepoisDeGravar, sujo]
  );

  /* ------------------------------------------------- modelos prontos */

  const lerDadosParaOModelo = useCallback(async (): Promise<DadosParaOModelo> => {
    const supabase = getSupabaseClient();
    const [perfil, banners, cupons, combos] = await Promise.all([
      supabase
        .from("profiles")
        .select("catalog_slide_enabled,production_cutoff_enabled,production_cutoff_time,address_street,address_city,address,whatsapp,company_whatsapp")
        .eq("id", dono)
        .maybeSingle(),
      supabase
        .from("catalog_banners")
        .select("image_url,link_type,product_id,link_url,open_in_new_tab,position")
        .eq("user_id", dono)
        .eq("type", "slide")
        .eq("active", true)
        .order("position", { ascending: true })
        .limit(8),
      supabase.from("catalog_coupons").select("id,code,starts_at,ends_at").eq("user_id", dono).eq("active", true).limit(20),
      supabase.from("product_combos").select("id", { count: "exact", head: true }).eq("user_id", dono).eq("active", true),
    ]);
    const p = (perfil.data || {}) as Record<string, unknown>;
    const agora = Date.now();
    const cupom = ((cupons.data || []) as Array<{ id: string; code: string; starts_at: string | null; ends_at: string | null }>).find(
      (c) => (!c.starts_at || new Date(c.starts_at).getTime() <= agora) && (!c.ends_at || new Date(c.ends_at).getTime() > agora)
    );
    return {
      tema: temaDasRegras,
      carrosselDoTopoLigado: p.catalog_slide_enabled === true,
      categorias,
      produtos,
      banners: (banners.data || []) as DadosParaOModelo["banners"],
      cupom: cupom ? { id: cupom.id, code: cupom.code } : null,
      corte: { ligado: p.production_cutoff_enabled === true, hora: p.production_cutoff_time ? String(p.production_cutoff_time) : null },
      temEndereco: Boolean(p.address_street || p.address_city || p.address),
      temWhatsapp: Boolean(p.whatsapp || p.company_whatsapp),
      temCombos: (combos.count || 0) > 0,
    };
  }, [categorias, dono, produtos, temaDasRegras]);

  const escolherModelo = useCallback(
    (chave: ChaveDoModelo) => {
      const nome = MODELOS_PRONTOS.find((m) => m.chave === chave)?.nome || "o modelo";
      setConfirmacao({
        titulo: `Montar a página com o modelo "${nome}"?`,
        texto: "As seções de agora saem do rascunho e entram as do modelo, com as categorias, os produtos, os banners e o cupom da sua loja. A loja só muda quando você publicar, e Ctrl+Z desfaz.",
        confirmar: "Usar o modelo",
        aoConfirmar: async () => {
          setConfirmacao(null);
          setModeloCarregando(chave);
          try {
            const dados = await lerDadosParaOModelo();
            let lista = paginaDoModelo(chave, dados);
            /* A grade fixa de hoje continua a mesma (não nasce outra). */
            const gradeDeHoje = rascunho.find((s) => s.modo === "all_products");
            if (gradeDeHoje && !ehFamiliaVendor(temaDasRegras)) {
              lista = lista.map((s) => (s.modo === "all_products" ? { ...gradeDeHoje, ativa: true } : s));
            }
            aplicar(lista);
            setSelecionada(null);
            setModelosAbertos(false);
            setAbaDaEsquerda("estrutura");
            setRecado({ tom: "bom", texto: `Modelo "${nome}" no rascunho. Confira, ajuste e publique quando quiser (Ctrl+Z desfaz).` });
          } catch {
            setRecado({ tom: "ruim", texto: "Não deu para montar o modelo agora. Tente de novo." });
          } finally {
            setModeloCarregando(null);
          }
        },
      });
    },
    [aplicar, lerDadosParaOModelo, rascunho, temaDasRegras]
  );

  /* ------------------------------------------------------ meus blocos */

  const carregarBlocos = useCallback(async () => {
    if (!dono) return;
    const { data, error } = await getSupabaseClient()
      .from("catalog_blocos_salvos")
      .select("id,nome,modo,secao,produtos,created_at")
      .eq("user_id", dono)
      .order("created_at", { ascending: false })
      .limit(100);
    setBlocos(error ? [] : ((data || []) as BlocoSalvo[]));
  }, [dono]);

  useEffect(() => {
    if (dono && blocos === null) void carregarBlocos();
  }, [blocos, carregarBlocos, dono]);

  const salvarBloco = useCallback(
    async (secao: SecaoDoEditor, nome: string) => {
      const { error } = await getSupabaseClient()
        .from("catalog_blocos_salvos")
        .insert({
          user_id: dono,
          nome: nome.trim().slice(0, 80),
          modo: secao.modo,
          secao: {
            title: secao.title,
            background_color: secao.background_color,
            text_color: secao.text_color,
            layout_width: secao.layout_width,
            title_align: secao.title_align,
            title_size: secao.title_size,
            config: { ...secao.config, mode: secao.modo },
          },
          produtos: secao.modo === "manual_products" ? secao.produtos.slice(0, 120) : [],
        });
      if (error) {
        setRecado({ tom: "ruim", texto: /100 blocos/.test(error.message) ? error.message : "Não deu para salvar o bloco. Tente de novo." });
        return;
      }
      setBlocoParaSalvar(null);
      await carregarBlocos();
      setRecado({ tom: "bom", texto: `"${nome.trim()}" está em Meus blocos (aba Adicionar).` });
    },
    [carregarBlocos, dono]
  );

  const inserirBloco = useCallback(
    (bloco: BlocoSalvo) => {
      if (!ehModo(bloco.modo)) return;
      const s = bloco.secao || {};
      const config = configDaColuna(s.config);
      const existentes = new Set(produtos.map((p) => p.id));
      const alinhamento = String(s.title_align);
      const tamanho = String(s.title_size);
      porNaPagina({
        chave: chaveNova(),
        id: null,
        modo: bloco.modo,
        title: String(s.title ?? "").slice(0, 200),
        background_color: String(s.background_color || "#ffffff"),
        text_color: String(s.text_color || "#111827"),
        layout_width: s.layout_width === "contained" ? "contained" : "full",
        title_align: alinhamento === "center" || alinhamento === "right" ? alinhamento : "left",
        title_size: tamanho === "sm" || tamanho === "md" || tamanho === "xl" ? tamanho : "lg",
        config: { ...config, mode: bloco.modo },
        produtos: bloco.modo === "manual_products" ? (bloco.produtos || []).map(String).filter((id) => existentes.has(id)) : [],
        ativa: true,
      });
    },
    [porNaPagina, produtos]
  );

  const renomearBloco = useCallback(
    async (bloco: BlocoSalvo, nome: string) => {
      const { error } = await getSupabaseClient().from("catalog_blocos_salvos").update({ nome, updated_at: new Date().toISOString() }).eq("id", bloco.id).eq("user_id", dono);
      if (error) setRecado({ tom: "ruim", texto: "Não deu para renomear o bloco." });
      await carregarBlocos();
    },
    [carregarBlocos, dono]
  );

  const apagarBloco = useCallback(
    (bloco: BlocoSalvo) => {
      setConfirmacao({
        titulo: `Apagar "${bloco.nome}" de Meus blocos?`,
        texto: "Só o bloco guardado sai. As seções que já estão nas páginas continuam como estão.",
        confirmar: "Apagar bloco",
        tom: "perigo",
        aoConfirmar: async () => {
          setConfirmacao(null);
          const { error } = await getSupabaseClient().from("catalog_blocos_salvos").delete().eq("id", bloco.id).eq("user_id", dono);
          if (error) setRecado({ tom: "ruim", texto: "Não deu para apagar o bloco." });
          await carregarBlocos();
        },
      });
    },
    [carregarBlocos, dono]
  );

  const alternarPrevia = useCallback((chave: string) => {
    setPreviaNoAr((atual) => {
      const novo = new Set(atual);
      if (novo.has(chave)) novo.delete(chave);
      else novo.add(chave);
      return novo;
    });
  }, []);

  const contextoDoPainel = useMemo<ContextoDoEditorDoPainel>(
    () => ({
      dono,
      tema,
      corDaLoja,
      produtos,
      podeGerarBanner,
      previaNoAr,
      alternarPrevia,
      salvarComoBloco: (secao) => setBlocoParaSalvar(secao),
    }),
    [alternarPrevia, corDaLoja, dono, podeGerarBanner, previaNoAr, produtos, tema]
  );

  /* ------------------------------------------------------------ tela */

  const larguraDoAparelho = LARGURA_DO_APARELHO[aparelho];
  const folga = aparelho === "computador" ? 32 : 56;
  const escala = Math.min(1, Math.max(0.2, (medida.largura - folga) / larguraDoAparelho));
  const alturaDoQuadro = Math.max(420, (medida.altura - (aparelho === "computador" ? 64 : 48)) / escala);

  useEffect(() => {
    if (lojaPronta) enviarParaALoja({ tipo: "escala", valor: escala });
  }, [enviarParaALoja, escala, lojaPronta]);

  /* Na tela cheia: Esc sai, e a página de trás não rola junto. O Esc só vale
     quando nenhuma janela do editor está aberta por cima (elas tratam o
     próprio Esc). */
  useEffect(() => {
    if (!telaCheia) return;
    const sair = (evento: KeyboardEvent) => {
      if (evento.key !== "Escape" || evento.defaultPrevented) return;
      if (document.querySelector('[role="dialog"]')) return;
      setTelaCheia(false);
    };
    const rolagemAntes = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    window.addEventListener("keydown", sair);
    return () => {
      document.body.style.overflow = rolagemAntes;
      window.removeEventListener("keydown", sair);
    };
  }, [telaCheia]);

  /* O editor ocupa a tela do ponto onde começa até o pé da janela. */
  useEffect(() => {
    if (carregando || telaCheia) return;
    const medir = () => {
      const topo = caixa.current?.getBoundingClientRect().top ?? 0;
      const rolado = window.scrollY || 0;
      setAlturaDaCaixa(Math.max(560, Math.round(window.innerHeight - (topo + rolado) - 20)));
    };
    medir();
    window.addEventListener("resize", medir);
    /* A aba escondida tem tamanho zero: quando ela aparece, mede de novo. */
    const pai = caixa.current?.parentElement;
    const observar = pai ? new ResizeObserver(medir) : null;
    if (pai && observar) observar.observe(pai);
    return () => {
      window.removeEventListener("resize", medir);
      observar?.disconnect();
    };
  }, [carregando, telaCheia]);

  const indiceDaEscolhida = escolhida ? rascunho.findIndex((s) => s.chave === escolhida.chave) : -1;
  const ajustes = escolhida ? (
    <AjustesDaSecao
      secao={escolhida}
      tema={tema}
      produtos={produtos}
      categorias={categorias}
      aoMudar={alterar}
      aoSubir={() => moverSecao(escolhida.chave, -1)}
      aoDescer={() => moverSecao(escolhida.chave, 1)}
      aoDuplicar={() => duplicar(escolhida.chave)}
      aoAlternar={() => alternar(escolhida.chave)}
      aoApagar={() => apagar(escolhida.chave)}
      fixa={ehGradeFixa(temaDasRegras, escolhida)}
      primeira={indiceDaEscolhida <= 0}
      ultima={indiceDaEscolhida >= rascunho.length - 1}
    />
  ) : null;

  const painelDaRegiao =
    regiao && nav.rascunho ? (
      regiao === "cabecalho" ? (
        <AjustesDoCabecalho
          rascunho={nav.rascunho}
          aoMudar={nav.aplicar}
          tema={tema}
          produtos={produtos}
          categorias={categorias}
          paginas={nav.paginas}
          aoFechar={() => escolherRegiao(null)}
        />
      ) : (
        <AjustesDoRodape
          rascunho={nav.rascunho}
          aoMudar={nav.aplicar}
          tema={tema}
          produtos={produtos}
          categorias={categorias}
          paginas={nav.paginas}
          aoFechar={() => escolherRegiao(null)}
        />
      )
    ) : null;
  const entradasDaNavegacao = (
    <EntradasDaNavegacao
      regiao={regiao}
      rascunho={nav.rascunho}
      aoEscolher={(qual) => {
        escolherRegiao(qual, true);
        setPainel("ajustes");
      }}
    />
  );

  const textoDoLugar = lugar !== null ? `na posição ${Math.min(lugar, rascunho.length) + 1}` : null;

  const painelDoHistorico = historicoAberto ? (
    <HistoricoDePublicacoes
      itens={historico}
      carregando={historicoCarregando}
      vendo={versaoVista?.id ?? null}
      ocupado={publicando}
      ehPagina={ehPaginaExtra}
      aoVer={verVersao}
      aoVoltar={voltarParaVersao}
      aoFechar={fecharHistorico}
    />
  ) : null;

  const paleta = (
    <PaletaDeSecoes
      secoes={rascunho}
      tema={tema}
      lugar={textoDoLugar}
      aoEscolher={adicionar}
      aoCancelarLugar={() => setLugar(null)}
      blocos={blocos}
      aoInserirBloco={inserirBloco}
      aoRenomearBloco={renomearBloco}
      aoApagarBloco={apagarBloco}
      aoAbrirModelos={ehPaginaExtra ? undefined : () => setModelosAbertos(true)}
    />
  );

  if (carregando) {
    return (
      <div className="flex h-[60vh] items-center justify-center rounded-2xl border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-elevated)]">
        <Loader2 className="h-6 w-6 animate-spin text-[var(--ks-color-primary)]" />
      </div>
    );
  }

  if (erroDeCarga) {
    return (
      <Aviso tom="ruim" titulo="O editor não abriu">
        {erroDeCarga}
      </Aviso>
    );
  }

  if (!podeEditar) {
    return (
      <Aviso tom="neutro" titulo="Sem permissão para mexer na página inicial">
        Quem pode mexer nos produtos da loja (permissão &quot;Loja: produtos&quot;) monta a página inicial por aqui.
      </Aviso>
    );
  }

  const botaoDeAparelho = (valor: Aparelho, Icone: typeof Monitor, rotulo: string) => (
    <button
      type="button"
      onClick={() => setAparelho(valor)}
      title={rotulo}
      aria-label={rotulo}
      aria-pressed={aparelho === valor}
      className={`flex h-8 w-9 items-center justify-center rounded-lg transition ${
        aparelho === valor ? "bg-[var(--ks-color-bg-elevated)] text-[var(--ks-color-primary)] shadow-[0_2px_8px_rgba(45,30,105,0.14)]" : "text-[var(--ks-color-text-soft)] hover:text-[var(--ks-color-text-primary)]"
      }`}
    >
      <Icone size={16} />
    </button>
  );

  const vazio = (
    <div className="flex h-full flex-col justify-between">
      <div className="px-6 pt-8 text-center">
        <span className="mx-auto flex h-12 w-12 items-center justify-center rounded-2xl text-white" style={{ background: "linear-gradient(135deg,#7346e8,#5228bd)", boxShadow: "var(--ks-shadow-brand)" }}>
          <MousePointerClick size={22} />
        </span>
        <p className="mt-4 text-[15px] font-extrabold text-[var(--ks-color-text-primary)]">Toque numa seção da loja</p>
        <p className="mx-auto mt-1.5 max-w-[260px] text-[12.5px] leading-5 text-[var(--ks-color-text-soft)]">
          Os ajustes dela aparecem aqui. Passe o mouse na loja para ver os botões de subir, descer e apagar, e o &quot;+&quot; entre as seções.
        </p>
      </div>
      <div className="m-4 rounded-2xl bg-[var(--ks-color-bg-inset)] p-4">
        <p className="text-[11px] font-bold uppercase tracking-[0.14em] text-[var(--ks-color-text-soft)]">Publicações</p>
        <p className="mt-1 text-[12.5px] text-[var(--ks-color-text-secondary)]">
          {ultimaPublicacao ? `Última em ${dataCurta(ultimaPublicacao)}.` : "A página ainda não foi publicada pelo editor."}
          {ehFamiliaVendor(tema) && !homeEditada && !pagina ? ` Até a primeira publicação, a loja segue com a página automática do modelo ${NOME_DO_TEMA[tema]}.` : ""}
        </p>
        {ultimaPublicacao ? (
          <div className="mt-3 flex flex-col items-start gap-2">
            <button type="button" onClick={abrirHistorico} className="inline-flex items-center gap-1.5 text-[12px] font-bold text-[var(--ks-color-primary)] hover:underline">
              <History size={14} /> Ver o histórico de publicações
            </button>
            <button type="button" onClick={pedirParaDesfazerPublicacao} className="inline-flex items-center gap-1.5 text-[12px] font-bold text-[var(--ks-color-text-soft)] hover:text-[var(--ks-color-primary)] hover:underline">
              <Undo2 size={14} /> Desfazer a última publicação
            </button>
          </div>
        ) : null}
      </div>
    </div>
  );

  return (
    <ContextoDoPainel.Provider value={contextoDoPainel}>
      <div
        ref={caixa}
        style={telaCheia ? undefined : { height: alturaDaCaixa ?? undefined }}
        className={
          telaCheia
            ? "fixed inset-0 z-[70] flex flex-col bg-[var(--ks-color-bg-elevated)]"
            : "flex h-[calc(100dvh-150px)] min-h-[560px] flex-col overflow-hidden rounded-2xl border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-elevated)] shadow-[var(--ks-shadow-soft)]"
        }
      >
        {/* Barra de cima */}
        <div data-editor="barra" className="flex items-center gap-2 border-b border-[var(--ks-n-100)] px-3 py-2 md:px-4">
          <div className="min-w-0 flex-1">
            <p className="flex items-center gap-2 truncate text-[13.5px] font-extrabold text-[var(--ks-color-text-primary)]">
              {seletorDePagina ?? "Página inicial"}
              {sujo ? (
                <span className="inline-flex items-center gap-1.5 rounded-full bg-[var(--ks-brand-50)] px-2 py-0.5 text-[10.5px] font-bold text-[var(--ks-color-primary)]">
                  <span className="h-1.5 w-1.5 rounded-full bg-[var(--ks-color-primary)]" /> Rascunho
                </span>
              ) : (
                <span className="hidden items-center gap-1.5 rounded-full bg-[var(--ks-emerald-soft)] px-2 py-0.5 text-[10.5px] font-bold text-[var(--ks-color-status-success)] sm:inline-flex">
                  <Check size={11} strokeWidth={3} /> Igual à loja
                </span>
              )}
            </p>
            <p className="hidden truncate text-[11.5px] text-[var(--ks-color-text-soft)] md:block">
              {ligadas.length} {ligadas.length === 1 ? "seção" : "seções"} na loja
              {rascunho.length > ligadas.length ? ` · ${rascunho.length - ligadas.length} escondida${rascunho.length - ligadas.length > 1 ? "s" : ""}` : ""}
              {` · modelo ${NOME_DO_TEMA[tema]}`}
            </p>
          </div>

          {/* Tela cheia à vista, no meio da barra: o editor cobre o painel
              inteiro (menu lateral e cabeçalho somem) para editar com folga. */}
          <div className="hidden flex-1 justify-center md:flex">
            <button
              type="button"
              onClick={() => setTelaCheia((t) => !t)}
              title={telaCheia ? "Sair da tela cheia (Esc)" : "Abrir o editor na tela toda"}
              aria-label={telaCheia ? "Sair da tela cheia" : "Abrir o editor na tela toda"}
              aria-pressed={telaCheia}
              className="inline-flex h-9 items-center gap-2 rounded-xl border border-[var(--ks-color-border-subtle)] px-3 text-[12.5px] font-bold text-[var(--ks-color-text-secondary)] transition hover:border-[var(--ks-color-primary)] hover:bg-[var(--ks-brand-50)] hover:text-[var(--ks-color-primary)]"
            >
              {telaCheia ? <Minimize2 size={15} /> : <Maximize2 size={15} />}
              {telaCheia ? "Sair da tela cheia" : "Tela cheia"}
            </button>
          </div>

          <div className="flex items-center rounded-xl bg-[var(--ks-color-bg-inset)] p-1" role="group" aria-label="Ver como">
            {botaoDeAparelho("computador", Monitor, "Computador")}
            <span className="hidden sm:block">{botaoDeAparelho("tablet", Tablet, "Tablet")}</span>
            {botaoDeAparelho("celular", Smartphone, "Celular")}
          </div>

          <div className="hidden items-center sm:flex">
            <button type="button" onClick={desfazerAgora} disabled={!podeDesfazer} title="Desfazer (Ctrl+Z)" aria-label="Desfazer" className="flex h-9 w-9 items-center justify-center rounded-lg text-[var(--ks-color-text-muted)] hover:bg-[var(--ks-color-bg-hover)] hover:text-[var(--ks-color-primary)] disabled:opacity-30">
              <Undo2 size={17} />
            </button>
            <button type="button" onClick={refazerAgora} disabled={!podeRefazer} title="Refazer (Ctrl+Shift+Z)" aria-label="Refazer" className="flex h-9 w-9 items-center justify-center rounded-lg text-[var(--ks-color-text-muted)] hover:bg-[var(--ks-color-bg-hover)] hover:text-[var(--ks-color-primary)] disabled:opacity-30">
              <Redo2 size={17} />
            </button>
          </div>

          <div className="hidden items-center gap-1 xl:flex">
            <button type="button" onClick={pedirParaDescartar} disabled={!sujo} className="ks-button ks-button-ghost ks-button-sm disabled:opacity-40">
              <RotateCcw size={14} /> Descartar
            </button>
            {passagem ? (
              <a href={passagem.enderecoPublico} target="_blank" rel="noopener noreferrer" className="ks-button ks-button-ghost ks-button-sm">
                <ExternalLink size={14} /> Ver loja
              </a>
            ) : null}
          </div>

          <div className="relative">
            <button
              type="button"
              onClick={() => setMaisAberto((a) => !a)}
              aria-label="Mais opções"
              aria-expanded={maisAberto}
              className="flex h-9 w-9 items-center justify-center rounded-lg text-[var(--ks-color-text-muted)] hover:bg-[var(--ks-color-bg-hover)]"
            >
              <MoreHorizontal size={18} />
            </button>
            {maisAberto ? (
              <>
                <button type="button" aria-label="Fechar" className="fixed inset-0 z-[79] cursor-default" onClick={() => setMaisAberto(false)} />
                <div className="absolute right-0 top-[calc(100%+6px)] z-[80] w-64 overflow-hidden rounded-2xl border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-elevated)] py-1.5 shadow-[var(--ks-shadow-overlay)]">
                  <ItemDoMenu icone={<Undo2 size={15} />} rotulo="Desfazer" onClick={() => { setMaisAberto(false); desfazerAgora(); }} desligado={!podeDesfazer} soCelular />
                  <ItemDoMenu icone={<Redo2 size={15} />} rotulo="Refazer" onClick={() => { setMaisAberto(false); refazerAgora(); }} desligado={!podeRefazer} soCelular />
                  {passagem ? (
                    <a href={passagem.enderecoPublico} target="_blank" rel="noopener noreferrer" onClick={() => setMaisAberto(false)} className="flex items-center gap-2.5 px-3.5 py-2.5 text-[13px] font-semibold text-[var(--ks-color-text-primary)] hover:bg-[var(--ks-color-bg-hover)]">
                      <ExternalLink size={15} className="text-[var(--ks-color-text-soft)]" /> Ver loja
                    </a>
                  ) : null}
                  <ItemDoMenu icone={<RotateCcw size={15} />} rotulo="Descartar as mudanças" onClick={pedirParaDescartar} desligado={!sujo} />
                  {ehPaginaExtra ? null : (
                    <ItemDoMenu
                      icone={<Wand2 size={15} />}
                      rotulo="Modelos prontos"
                      onClick={() => {
                        setMaisAberto(false);
                        setModelosAbertos(true);
                      }}
                    />
                  )}
                  <ItemDoMenu icone={<History size={15} />} rotulo="Histórico de publicações" onClick={abrirHistorico} desligado={!ultimaPublicacao} />
                  <ItemDoMenu icone={<Undo2 size={15} />} rotulo="Desfazer a última publicação" onClick={pedirParaDesfazerPublicacao} desligado={!ultimaPublicacao} />
                  <ItemDoMenu
                    icone={telaCheia ? <Minimize2 size={15} /> : <Maximize2 size={15} />}
                    rotulo={telaCheia ? "Sair da tela cheia" : "Tela cheia"}
                    onClick={() => {
                      setMaisAberto(false);
                      setTelaCheia((t) => !t);
                    }}
                  />
                </div>
              </>
            ) : null}
          </div>

          <button
            type="button"
            onClick={publicar}
            disabled={!sujo || publicando || Boolean(versaoVista)}
            className="ks-button ks-button-primary ks-button-sm disabled:opacity-45"
          >
            {publicando ? <Loader2 size={14} className="animate-spin" /> : <Check size={14} strokeWidth={3} />}
            Publicar
          </button>
        </div>

        <div className="flex min-h-0 flex-1">
          {/* Esquerda: a página como lista e os tipos de seção */}
          <aside data-editor="esquerda" className="hidden w-[284px] shrink-0 flex-col border-r border-[var(--ks-n-100)] lg:flex xl:w-[300px]">
            {entradasDaNavegacao}
            <div className="flex items-center gap-5 border-b border-[var(--ks-n-100)] px-4 pt-3">
              {(
                [
                  ["estrutura", "Seções", Layers],
                  ["adicionar", "Adicionar", Plus],
                ] as const
              ).map(([valor, rotulo, Icone]) => (
                <button
                  key={valor}
                  type="button"
                  onClick={() => setAbaDaEsquerda(valor)}
                  className={`-mb-px flex items-center gap-1.5 border-b-2 pb-2.5 text-[13px] font-semibold transition ${
                    abaDaEsquerda === valor
                      ? "border-[var(--ks-color-primary)] text-[var(--ks-color-primary)]"
                      : "border-transparent text-[var(--ks-color-text-soft)] hover:text-[var(--ks-color-text-primary)]"
                  }`}
                >
                  <Icone size={15} />
                  {rotulo}
                  {valor === "estrutura" ? (
                    <span className="rounded-full bg-[var(--ks-color-bg-inset)] px-1.5 text-[10.5px] font-bold text-[var(--ks-color-text-muted)]">{rascunho.length}</span>
                  ) : null}
                </button>
              ))}
            </div>
            <div className="min-h-0 flex-1">
              {abaDaEsquerda === "estrutura" ? (
                <EstruturaDaPagina
                  semTopo={ehPaginaExtra}
                  secoes={versaoVista ? versaoVista.secoes : rascunho}
                  somenteLeitura={Boolean(versaoVista)}
                  cliques={cliques}
                  tema={tema}
                  selecionada={selecionada}
                  aoSelecionar={(chave) => escolher(chave, true)}
                  aoReordenar={(de, para) => aplicar(mover(rascunho, de, para))}
                  aoMover={moverSecao}
                  aoAlternar={alternar}
                  aoAdicionar={() => setAbaDaEsquerda("adicionar")}
                />
              ) : (
                paleta
              )}
            </div>
          </aside>

          {/* Meio: a loja de verdade */}
          <main ref={tela} data-editor="palco" className="relative min-w-0 flex-1 overflow-hidden bg-[var(--ks-color-bg-inset)]">
            {versaoVista ? (
              <div data-editor="versao-vista" className="absolute inset-x-3 top-3 z-[5] flex flex-wrap items-center gap-2 rounded-2xl bg-[#0f766e] px-4 py-2.5 text-white shadow-[0_14px_30px_-14px_rgba(15,118,110,0.8)]">
                <History size={16} className="shrink-0" />
                <span className="min-w-0 flex-1 text-[12.5px] font-bold">
                  Vendo a versão de {versaoVista.quando} · só para ver{versaoVista.comMenu ? " (o menu e o rodapé dessa versão não aparecem aqui)" : ""}
                </span>
                <button type="button" onClick={() => voltarParaVersao(versaoVista.id)} disabled={publicando} className="rounded-full bg-white px-3 py-1.5 text-[12px] font-extrabold text-[#0f766e] hover:bg-white/90 disabled:opacity-50">
                  Voltar para esta versão
                </button>
                <button type="button" onClick={() => setVersaoVista(null)} className="rounded-full bg-white/15 px-3 py-1.5 text-[12px] font-bold text-white hover:bg-white/25">
                  Sair da prévia
                </button>
              </div>
            ) : null}
            {passagem ? (
              <div className="absolute inset-0 flex justify-center overflow-hidden pt-4">
                <div
                  className="relative shrink-0"
                  style={{ width: larguraDoAparelho * escala, height: (alturaDoQuadro + (aparelho === "computador" ? 34 : 0)) * escala }}
                >
                  <div
                    className={`absolute left-0 top-0 origin-top-left overflow-hidden bg-white ${
                      aparelho === "computador"
                        ? "rounded-xl shadow-[0_24px_60px_-20px_rgba(26,26,43,0.35)] ring-1 ring-black/5"
                        : aparelho === "tablet"
                          ? "rounded-[28px] shadow-[0_30px_70px_-24px_rgba(26,26,43,0.45)] ring-[10px] ring-[#1d1b2e]"
                          : "rounded-[36px] shadow-[0_30px_70px_-24px_rgba(26,26,43,0.45)] ring-[10px] ring-[#1d1b2e]"
                    }`}
                    style={{ width: larguraDoAparelho, transform: `scale(${escala})` }}
                  >
                    {aparelho === "computador" ? (
                      <div className="flex h-[34px] items-center gap-2 border-b border-black/5 bg-[#f6f5fb] px-3">
                        <span className="h-2.5 w-2.5 rounded-full bg-[#ff6159]" />
                        <span className="h-2.5 w-2.5 rounded-full bg-[#ffbd2e]" />
                        <span className="h-2.5 w-2.5 rounded-full bg-[#28c941]" />
                        <span className="mx-auto truncate rounded-md bg-white px-4 py-0.5 text-[11px] text-[#6f6f8e]">
                          {passagem.enderecoPublico.replace(/^https?:\/\//, "")}
                        </span>
                      </div>
                    ) : null}
                    <iframe
                      ref={quadro}
                      key={passagem.endereco}
                      src={passagem.endereco}
                      title="A loja no editor da página inicial"
                      className="block w-full border-0 bg-white"
                      style={{ height: alturaDoQuadro }}
                    />
                  </div>
                </div>
              </div>
            ) : null}
            {!lojaPronta ? (
              /* O véu deixa o mouse passar para a loja: o navegador só termina de
                 montar a página de outra origem dentro do quadro quando há
                 movimento sobre ela (sem isso a loja demorava a dizer "pronto"). */
              <div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[var(--ks-color-bg-inset)]/80 backdrop-blur-[1px]">
                {semResposta ? (
                  <div className="pointer-events-auto max-w-sm px-6 text-center">
                    <p className="text-[14px] font-extrabold text-[var(--ks-color-text-primary)]">A loja não respondeu</p>
                    <p className="mt-1 text-[12.5px] text-[var(--ks-color-text-soft)]">Confira a conexão e tente abrir de novo.</p>
                    <button
                      type="button"
                      className="ks-button ks-button-secondary ks-button-sm mt-3"
                      onClick={async () => {
                        setSemResposta(false);
                        try {
                          setLojaPronta(false);
                          setPassagem(await pedirPassagem());
                        } catch (erro) {
                          setRecado({ tom: "ruim", texto: erro instanceof Error ? erro.message : "Não deu para abrir a loja." });
                        }
                      }}
                    >
                      Abrir de novo
                    </button>
                  </div>
                ) : (
                  <span className="flex items-center gap-2 rounded-full bg-[var(--ks-color-bg-elevated)] px-4 py-2 text-[12.5px] font-semibold text-[var(--ks-color-text-secondary)] shadow-[var(--ks-shadow-soft)]">
                    <Loader2 size={15} className="animate-spin text-[var(--ks-color-primary)]" /> Abrindo a sua loja…
                  </span>
                )}
              </div>
            ) : null}
          </main>

          {/* Direita: os ajustes da seção escolhida */}
          <aside data-editor="ajustes" className="hidden w-[340px] shrink-0 border-l border-[var(--ks-n-100)] lg:block xl:w-[368px]">{painelDoHistorico || painelDaRegiao || (versaoVista ? null : ajustes) || vazio}</aside>
        </div>

        {/* Celular e tablet: a barra de baixo abre as gavetas */}
        <div className="grid grid-cols-3 gap-1 border-t border-[var(--ks-n-100)] bg-[var(--ks-color-bg-elevated)] p-1.5 lg:hidden">
          {(
            [
              ["estrutura", "Seções", Layers, false],
              ["adicionar", "Adicionar", Plus, false],
              ["ajustes", "Ajustes", Settings2, !escolhida && !painelDaRegiao && !painelDoHistorico],
            ] as const
          ).map(([valor, rotulo, Icone, desligado]) => (
            <button
              key={valor}
              type="button"
              disabled={desligado}
              onClick={() => setPainel(valor)}
              className={`flex flex-col items-center gap-0.5 rounded-xl py-2 text-[11.5px] font-bold transition disabled:opacity-35 ${
                painel === valor ? "bg-[var(--ks-brand-50)] text-[var(--ks-color-primary)]" : "text-[var(--ks-color-text-muted)]"
              }`}
            >
              <Icone size={18} />
              {rotulo}
            </button>
          ))}
        </div>
      </div>

      {/* Gavetas do celular */}
      {painel ? (
        <div className="fixed inset-0 z-[90] flex flex-col justify-end lg:hidden">
          <button type="button" aria-label="Fechar" className="absolute inset-0 bg-[rgba(12,8,24,0.42)]" onClick={() => setPainel(null)} />
          <div data-editor="gaveta" className="relative flex max-h-[82dvh] min-h-[40dvh] flex-col overflow-hidden rounded-t-[26px] bg-[var(--ks-color-bg-elevated)] shadow-[var(--ks-shadow-overlay)]">
            <div className="flex items-center justify-between px-5 pb-1 pt-3">
              <span className="mx-auto h-1.5 w-10 rounded-full bg-[var(--ks-n-200)]" />
            </div>
            <div className="flex items-center justify-between px-5 pb-2">
              <p className="text-[14px] font-extrabold text-[var(--ks-color-text-primary)]">
                {painel === "estrutura"
                  ? "Seções da página"
                  : painel === "adicionar"
                    ? "Adicionar seção"
                    : painelDoHistorico
                      ? "Histórico de publicações"
                      : regiao === "cabecalho"
                      ? "Cabeçalho e menu"
                      : regiao === "rodape"
                        ? "Rodapé"
                        : "Ajustes da seção"}
              </p>
              <button type="button" aria-label="Fechar" onClick={() => setPainel(null)} className="flex h-8 w-8 items-center justify-center rounded-lg text-[var(--ks-color-text-soft)] hover:bg-[var(--ks-color-bg-hover)]">
                <X size={17} />
              </button>
            </div>
            <div className="min-h-0 flex-1 overflow-y-auto">
              {painel === "estrutura" ? (
                <>
                {entradasDaNavegacao}
                <EstruturaDaPagina
                  semTopo={ehPaginaExtra}
                  secoes={versaoVista ? versaoVista.secoes : rascunho}
                  somenteLeitura={Boolean(versaoVista)}
                  cliques={cliques}
                  tema={tema}
                  selecionada={selecionada}
                  aoSelecionar={(chave) => {
                    escolher(chave, true);
                    setPainel("ajustes");
                  }}
                  aoReordenar={(de, para) => aplicar(mover(rascunho, de, para))}
                  aoMover={moverSecao}
                  aoAlternar={alternar}
                  aoAdicionar={() => setPainel("adicionar")}
                />
                </>
              ) : painel === "adicionar" ? (
                paleta
              ) : painelDoHistorico ? (
                painelDoHistorico
              ) : painelDaRegiao ? (
                painelDaRegiao
              ) : escolhida ? (
                ajustes
              ) : (
                vazio
              )}
            </div>
          </div>
        </div>
      ) : null}

      {recado ? (
        <div className="pointer-events-none fixed inset-x-0 bottom-20 z-[120] flex justify-center px-4 lg:bottom-8">
          <div
            role="status"
            className={`pointer-events-auto flex max-w-lg items-center gap-3 rounded-2xl px-4 py-3 text-[13px] font-semibold shadow-[var(--ks-shadow-overlay)] ${
              recado.tom === "bom"
                ? "bg-[#0f8a63] text-white"
                : recado.tom === "ruim"
                  ? "bg-[#b4233f] text-white"
                  : "bg-[var(--ks-n-900)] text-white"
            }`}
          >
            <span className="min-w-0 flex-1">{recado.texto}</span>
            <button type="button" aria-label="Fechar" onClick={() => setRecado(null)} className="rounded-md p-0.5 opacity-80 hover:opacity-100">
              <X size={15} />
            </button>
          </div>
        </div>
      ) : null}

      {modelosAbertos ? <JanelaDosModelos carregando={modeloCarregando} aoEscolher={escolherModelo} aoFechar={() => setModelosAbertos(false)} /> : null}
      {blocoParaSalvar ? (
        <JanelaDoNomeDoBloco
          sugestao={blocoParaSalvar.title || NOME_DO_MODO[blocoParaSalvar.modo]}
          aoSalvar={(nome) => salvarBloco(blocoParaSalvar, nome)}
          aoFechar={() => setBlocoParaSalvar(null)}
        />
      ) : null}
      <CaixaDeConfirmacao pedido={confirmacao} aoFechar={() => setConfirmacao(null)} />
    </ContextoDoPainel.Provider>
  );
}

/** O nome do bloco que vai para Meus blocos. */
function JanelaDoNomeDoBloco({ sugestao, aoSalvar, aoFechar }: { sugestao: string; aoSalvar: (nome: string) => Promise<void>; aoFechar: () => void }) {
  const [nome, setNome] = useState(sugestao.slice(0, 80));
  const [salvando, setSalvando] = useState(false);
  return (
    <div className="fixed inset-0 z-[96] flex items-end justify-center sm:items-center" role="dialog" aria-modal="true" aria-label="Salvar em Meus blocos" data-editor="salvar-bloco">
      <button type="button" aria-label="Fechar" className="absolute inset-0 bg-[rgba(12,8,24,0.5)]" onClick={aoFechar} />
      <form
        className="relative w-full max-w-[400px] rounded-t-[24px] bg-[var(--ks-color-bg-elevated)] p-5 shadow-[var(--ks-shadow-overlay)] sm:rounded-[22px]"
        onSubmit={async (e) => {
          e.preventDefault();
          if (!nome.trim() || salvando) return;
          setSalvando(true);
          await aoSalvar(nome);
          setSalvando(false);
        }}
      >
        <p className="text-[15px] font-extrabold text-[var(--ks-color-text-primary)]">Salvar em Meus blocos</p>
        <p className="mt-1 text-[12px] text-[var(--ks-color-text-soft)]">A seção fica guardada (com a configuração e os produtos) para pôr de novo em qualquer página da loja.</p>
        <input
          autoFocus
          value={nome}
          onChange={(e) => setNome(e.target.value.slice(0, 80))}
          aria-label="Nome do bloco"
          placeholder="Ex.: Banner da Black Friday"
          className="mt-4 h-[40px] w-full rounded-xl border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-elevated)] px-3 text-[13px] outline-none focus:border-[var(--ks-color-primary)] focus:shadow-[var(--ks-focus-ring)]"
        />
        <div className="mt-4 flex justify-end gap-2">
          <button type="button" onClick={aoFechar} className="ks-button ks-button-ghost ks-button-sm">
            Cancelar
          </button>
          <button type="submit" disabled={!nome.trim() || salvando} className="ks-button ks-button-primary ks-button-sm disabled:opacity-50">
            {salvando ? <Loader2 size={14} className="animate-spin" /> : <Check size={14} strokeWidth={3} />} Salvar bloco
          </button>
        </div>
      </form>
    </div>
  );
}

function ItemDoMenu({ icone, rotulo, onClick, desligado, soCelular }: { icone: ReactNode; rotulo: string; onClick: () => void; desligado?: boolean; soCelular?: boolean }) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={desligado}
      className={`w-full items-center gap-2.5 px-3.5 py-2.5 text-left text-[13px] font-semibold text-[var(--ks-color-text-primary)] hover:bg-[var(--ks-color-bg-hover)] disabled:opacity-40 ${soCelular ? "flex sm:hidden" : "flex"}`}
    >
      <span className="text-[var(--ks-color-text-soft)]">{icone}</span>
      {rotulo}
    </button>
  );
}
