import { resolveCompanyPublicProfileId } from "@/lib/company-context";
import { avisarLojaEEsperar } from "@/lib/loja/avisar-loja";
import {
  CABECALHO_PADRAO,
  LIMITE_DE_COLUNAS_NO_RODAPE,
  LIMITE_DE_FILHOS_NO_GRUPO,
  LIMITE_DE_ITENS_NO_MENU,
  LIMITE_DE_LINKS_NA_COLUNA,
  LIMITE_DO_SOBRE,
  RODAPE_PADRAO,
  TIPOS_DO_ITEM,
  idNovo,
  lerItem,
  navegacaoParaGravar,
  problemaDoSlugDaPagina,
  problemasDaNavegacao,
  rascunhoDaNavegacao,
  slugDePagina,
  slugLivre,
  type ItemDoMenu,
  type RascunhoDaNavegacao,
} from "@/lib/navegacao-da-loja";
import { MODOS, NOME_DO_MODO, ehModo, temaDaLoja } from "@/lib/pagina-inicial";

import type { ContextoDoAssistente } from "../contexto";
import type { Campos } from "../esquema";
import { baseDaLoja, contagem, exigir, falha, recusar } from "./comum";
import type { Ferramenta, FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/*
 * O cabeçalho, o menu, o rodapé e as páginas extras da loja pelo assistente
 * (migration 197) — o mesmo que o editor da loja faz em Catálogo > Produtos >
 * Página inicial.
 *
 * - O menu, o rodapé e as opções do cabeçalho gravam pela mesma função do
 *   editor (publicar_no_editor_da_loja), com a sessão da pessoa: entra no
 *   histórico de publicações e o "Desfazer a última publicação" do editor
 *   volta o que o assistente mudou.
 * - As páginas extras (/<slug>/pagina/<endereço>) gravam em catalog_paginas
 *   com a sessão da pessoa. As seções de uma página são trocadas inteiras
 *   pela mesma função do editor (definir_secoes_da_pagina).
 * - As seções da PÁGINA INICIAL continuam nas ferramentas de
 *   loja-estrutura.ts (criar_secao, alterar_secao...), que não enxergam as
 *   seções das páginas extras.
 */

const ESCREVER_NA_LOJA = "catalog.products.write" as const;
const LER_A_LOJA = "catalog.products.read" as const;
const SO_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

function perfilDaLoja(ctx: ContextoDoAssistente) {
  return resolveCompanyPublicProfileId(ctx.empresa, ctx.user.id);
}

async function avisar(ctx: ContextoDoAssistente) {
  const ouviu = await avisarLojaEEsperar(perfilDaLoja(ctx), new Request(`${ctx.origemDoAdmin}/api/mcp`));
  return ouviu ? {} : { observacao: "A loja pode levar até dois minutos para mostrar a mudança." };
}

type Pagina = {
  id: string;
  titulo: string;
  slug: string;
  visivel: boolean;
  noindex: boolean;
  mostrar_titulo: boolean;
  seo_titulo: string | null;
  seo_descricao: string | null;
  og_imagem: string | null;
  position: number;
  publicada_em: string | null;
};

const COLUNAS_DA_PAGINA = "id,titulo,slug,visivel,noindex,mostrar_titulo,seo_titulo,seo_descricao,og_imagem,position,publicada_em";

async function lerPaginas(ctx: ContextoDoAssistente): Promise<Pagina[]> {
  const { data, error } = await ctx.supabase
    .from("catalog_paginas")
    .select(COLUNAS_DA_PAGINA)
    .eq("user_id", perfilDaLoja(ctx))
    .order("position", { ascending: true })
    .order("created_at", { ascending: true })
    .limit(200);
  if (error) falha("ler as páginas da loja", error);
  return (data ?? []) as Pagina[];
}

/** A página pelo id ou pelo endereço (slug). */
async function acharPagina(ctx: ContextoDoAssistente, pedido: unknown): Promise<{ pagina: Pagina; todas: Pagina[] }> {
  const texto = String(pedido ?? "").trim();
  if (!texto) recusar("Diga qual página: o id ou o endereço (de ver_paginas_da_loja).");
  const todas = await lerPaginas(ctx);
  const pagina = SO_UUID.test(texto) ? todas.find((p) => p.id === texto.toLowerCase()) : todas.find((p) => p.slug === slugDePagina(texto));
  if (!pagina) recusar(`Não achei a página "${texto}" nesta loja. Use ver_paginas_da_loja para ver as páginas.`);
  return { pagina, todas };
}

function enderecoDaPagina(ctx: ContextoDoAssistente, slugDaLoja: string | null, slug: string) {
  return slugDaLoja ? `${baseDaLoja(ctx)}/${slugDaLoja}/pagina/${slug}` : null;
}

async function slugDaLoja(ctx: ContextoDoAssistente) {
  const { data } = await ctx.supabase.from("profiles").select("slug,catalog_theme").eq("id", perfilDaLoja(ctx)).maybeSingle();
  const linha = (data ?? {}) as { slug?: string | null; catalog_theme?: string | null };
  return { slug: linha.slug ?? null, tema: temaDaLoja(linha.catalog_theme) };
}

/* ========================================================== a navegação */

async function lerNavegacaoAtual(ctx: ContextoDoAssistente): Promise<RascunhoDaNavegacao> {
  const { data, error } = await ctx.supabase.from("catalog_navegacao").select("cabecalho,menu,rodape").eq("user_id", perfilDaLoja(ctx)).maybeSingle();
  if (error) falha("ler o menu e o rodapé da loja", error);
  return rascunhoDaNavegacao(data);
}

/** Os destinos dos itens são desta loja (categoria, produto, página). */
async function conferirDestinos(ctx: ContextoDoAssistente, itens: ItemDoMenu[]) {
  const todos = itens.flatMap((item) => [item, ...(item.filhos || [])]);
  const categorias = [...new Set(todos.filter((i) => i.tipo === "categoria").map((i) => String(i.categoria_id || "")))].filter(Boolean);
  const produtos = [...new Set(todos.filter((i) => i.tipo === "produto").map((i) => String(i.produto_id || "")))].filter(Boolean);
  const paginas = [...new Set(todos.filter((i) => i.tipo === "pagina").map((i) => String(i.pagina_id || "")))].filter(Boolean);
  for (const id of [...categorias, ...produtos, ...paginas]) if (!SO_UUID.test(id)) recusar(`"${id}" não é um id válido. Use os ids devolvidos pelas buscas.`);
  const [c, p, g] = await Promise.all([
    categorias.length ? ctx.supabase.from("categories").select("id,slug").in("user_id", ctx.tenantIds).in("id", categorias) : Promise.resolve({ data: [], error: null }),
    produtos.length ? ctx.supabase.from("products").select("id").in("user_id", ctx.tenantIds).in("id", produtos) : Promise.resolve({ data: [], error: null }),
    paginas.length ? ctx.supabase.from("catalog_paginas").select("id,slug").eq("user_id", perfilDaLoja(ctx)).in("id", paginas) : Promise.resolve({ data: [], error: null }),
  ]);
  if (c.error || p.error || g.error) falha("conferir os destinos do menu", c.error || p.error || g.error);
  const achadas = new Map(((c.data ?? []) as Array<{ id: string; slug: string | null }>).map((x) => [x.id, x.slug]));
  const achados = new Set(((p.data ?? []) as Array<{ id: string }>).map((x) => x.id));
  const paginasAchadas = new Map(((g.data ?? []) as Array<{ id: string; slug: string }>).map((x) => [x.id, x.slug]));
  const faltaC = categorias.find((id) => !achadas.has(id));
  if (faltaC) recusar(`A categoria ${faltaC} não é desta loja. Use listar_categorias.`);
  const faltaP = produtos.find((id) => !achados.has(id));
  if (faltaP) recusar(`O produto ${faltaP} não é desta loja. Use buscar_produtos.`);
  const faltaG = paginas.find((id) => !paginasAchadas.has(id));
  if (faltaG) recusar(`A página ${faltaG} não é desta loja. Use ver_paginas_da_loja.`);
  /* O slug guardado junto ajuda a loja se o id sumir; a loja acha pelo id. */
  const completar = (item: ItemDoMenu): ItemDoMenu => ({
    ...item,
    ...(item.tipo === "categoria" ? { categoria_slug: achadas.get(String(item.categoria_id)) || "" } : {}),
    ...(item.tipo === "pagina" ? { pagina_slug: paginasAchadas.get(String(item.pagina_id)) || "" } : {}),
    ...(item.filhos ? { filhos: item.filhos.map(completar) } : {}),
  });
  return itens.map(completar);
}

const CAMPOS_DO_LINK: Campos = {
  rotulo: { tipo: "texto", max: 60, descricao: "O nome que aparece. Vazio usa o nome da categoria, do produto ou da página." },
  tipo: {
    tipo: "opcao",
    opcoes: TIPOS_DO_ITEM.filter((t) => t !== "grupo"),
    obrigatorio: true,
    descricao: "Para onde leva: inicio (a página inicial), categoria (ou subcategoria), produto, pagina (página extra) ou url.",
  },
  categoria_id: { tipo: "id", descricao: "Com tipo=categoria: o id (de listar_categorias). Serve para subcategoria também." },
  produto_id: { tipo: "id", descricao: "Com tipo=produto: o id (de buscar_produtos)." },
  pagina_id: { tipo: "id", descricao: "Com tipo=pagina: o id (de ver_paginas_da_loja). Página escondida não aparece até ser mostrada." },
  url: { tipo: "texto", max: 500, descricao: "Com tipo=url: https://... ou um caminho da loja (/minha-loja/c/canecas)." },
  nova_aba: { tipo: "booleano", descricao: "Com tipo=url: abre em outra aba." },
};

const CAMPOS_DO_ITEM: Campos = {
  ...CAMPOS_DO_LINK,
  tipo: {
    tipo: "opcao",
    opcoes: TIPOS_DO_ITEM,
    obrigatorio: true,
    descricao: "inicio, categoria (ou subcategoria), produto, pagina (página extra), url, ou grupo (um nome com links que descem, em filhos).",
  },
  destaque: { tipo: "booleano", descricao: "O item em destaque: pílula na cor da loja." },
  filhos: { tipo: "lista", itens: CAMPOS_DO_LINK, maxItens: LIMITE_DE_FILHOS_NO_GRUPO, descricao: "Com tipo=grupo: os links que descem do grupo (sem grupo dentro de grupo)." },
};

function itensDosArgumentos(lista: unknown): ItemDoMenu[] {
  return (Array.isArray(lista) ? lista : [])
    .map((bruto, i) => lerItem({ ...(bruto as Record<string, unknown>), id: idNovo(), filhos: Array.isArray((bruto as Record<string, unknown>)?.filhos) ? ((bruto as Record<string, unknown>).filhos as unknown[]).map((f) => ({ ...(f as Record<string, unknown>), id: idNovo() })) : undefined }, false, i))
    .filter((item): item is ItemDoMenu => Boolean(item));
}

function itensEmPalavras(itens: ItemDoMenu[]): Array<Record<string, unknown>> {
  return itens.map((item) => ({
    rotulo: item.rotulo || null,
    tipo: item.tipo,
    ...(item.categoria_id ? { categoria_id: item.categoria_id } : {}),
    ...(item.produto_id ? { produto_id: item.produto_id } : {}),
    ...(item.pagina_id ? { pagina_id: item.pagina_id } : {}),
    ...(item.url ? { url: item.url } : {}),
    ...(item.destaque ? { destaque: true } : {}),
    ...(item.nova_aba ? { nova_aba: true } : {}),
    ...(item.filhos ? { filhos: itensEmPalavras(item.filhos) } : {}),
  }));
}

async function publicarNavegacao(ctx: ContextoDoAssistente, rascunho: RascunhoDaNavegacao) {
  const { error } = await ctx.supabase.rpc("publicar_no_editor_da_loja", {
    p_dono: perfilDaLoja(ctx),
    p_pagina: null,
    p_secoes: null,
    p_tema: null,
    p_navegacao: navegacaoParaGravar(rascunho),
  });
  if (error) falha("publicar o menu e o rodapé", error);
}

export const verMenuERodapeDaLoja: FerramentaDeLeitura = {
  nome: "ver_menu_e_rodape_da_loja",
  titulo: "Ver o cabeçalho, o menu e o rodapé da loja",
  descricao:
    "Mostra as opções do cabeçalho (busca, tamanho da logo, fixo ao rolar), o menu (os itens próprios, ou as categorias de sempre) e o rodapé (colunas de links, texto sobre a loja e blocos) que a loja publicou. Sem menu próprio, o menu mostra as categorias da loja sozinho; sem rodapé próprio, vale o rodapé de sempre do modelo.",
  campos: {},
  tipo: "leitura",
  permissoes: [LER_A_LOJA, ESCREVER_NA_LOJA],
  async executar(ctx) {
    exigir(ctx, LER_A_LOJA, "ver a loja");
    const [nav, paginas, loja] = await Promise.all([lerNavegacaoAtual(ctx), lerPaginas(ctx), slugDaLoja(ctx)]);
    return {
      dados: {
        modelo_da_loja: loja.tema,
        cabecalho: nav.cabecalho,
        menu: nav.menu
          ? { menu_proprio: true, mostrar_categorias: nav.menu.mostrar_categorias, itens: itensEmPalavras(nav.menu.itens) }
          : { menu_proprio: false, explicacao: "O menu mostra as categorias da loja, como sempre." },
        rodape: nav.rodape
          ? {
              rodape_proprio: true,
              sobre: nav.rodape.sobre,
              colunas: nav.rodape.colunas.map((c) => ({ titulo: c.titulo, links: itensEmPalavras(c.links) })),
              mostrar_contato: nav.rodape.mostrar_contato,
              mostrar_redes: nav.rodape.mostrar_redes,
              mostrar_pagamento: nav.rodape.mostrar_pagamento,
              mostrar_categorias: nav.rodape.mostrar_categorias,
            }
          : { rodape_proprio: false, explicacao: "O rodapé de sempre do modelo da loja." },
        paginas_extras: paginas.map((p) => ({ id: p.id, titulo: p.titulo, endereco: p.slug, visivel: p.visivel })),
      },
      resumo: "Viu o cabeçalho, o menu e o rodapé da loja",
    };
  },
};

type CargaDaNavegacao = { rascunho: RascunhoDaNavegacao; oQue: string };

export const alterarMenuDaLoja: FerramentaDeEscrita<CargaDaNavegacao> = {
  nome: "alterar_menu_da_loja",
  titulo: "Alterar o menu da loja",
  descricao:
    `Troca o menu da loja inteiro (vale em todas as páginas). Com usar_menu_proprio=true, os itens tomam o lugar da lista de categorias na faixa do menu (até ${LIMITE_DE_ITENS_NO_MENU} itens; grupo com até ${LIMITE_DE_FILHOS_NO_GRUPO} links). Com usar_menu_proprio=false a loja volta ao menu de sempre (as categorias). Leia antes com ver_menu_e_rodape_da_loja: os itens mandados substituem os de agora. Entra no histórico do editor (dá para desfazer lá).`,
  campos: {
    usar_menu_proprio: { tipo: "booleano", obrigatorio: true, descricao: "true = menu com os itens abaixo; false = o menu de sempre (categorias)." },
    mostrar_categorias: { tipo: "booleano", descricao: 'Mantém o atalho com todas as categorias ("Todas as categorias") antes dos itens. Padrão: true.' },
    itens: { tipo: "lista", itens: CAMPOS_DO_ITEM, maxItens: LIMITE_DE_ITENS_NO_MENU, descricao: "Os itens do menu, na ordem." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer no menu da loja");
    const atual = await lerNavegacaoAtual(ctx);
    let rascunho: RascunhoDaNavegacao;
    if (args.usar_menu_proprio === false) {
      rascunho = { ...atual, menu: null };
    } else {
      const itens = await conferirDestinos(ctx, itensDosArgumentos(args.itens));
      if (!itens.length && args.mostrar_categorias !== false) recusar("Mande ao menos um item (ou use usar_menu_proprio=false para voltar ao menu de sempre).");
      rascunho = { ...atual, menu: { itens, mostrar_categorias: args.mostrar_categorias !== false } };
    }
    const problema = problemasDaNavegacao(rascunho).find((p) => p.regiao === "cabecalho");
    if (problema) recusar(problema.texto);
    return {
      previa: {
        o_que_vai_acontecer: rascunho.menu ? `O menu da loja passa a ter ${contagem(rascunho.menu.itens.length, "item", "itens")} próprios.` : "O menu volta a mostrar as categorias da loja, como sempre.",
        menu: rascunho.menu ? { mostrar_categorias: rascunho.menu.mostrar_categorias, itens: itensEmPalavras(rascunho.menu.itens) } : null,
      },
      resumo: "Prévia: alterar o menu da loja",
      avisos: [],
      carga: { rascunho, oQue: "o menu" },
    };
  },
  async gravar(ctx, carga) {
    await publicarNavegacao(ctx, carga.rascunho);
    const aviso = await avisar(ctx);
    return { dados: { publicado: true, ...aviso }, resumo: "Alterou o menu da loja" };
  },
};

export const alterarRodapeDaLoja: FerramentaDeEscrita<CargaDaNavegacao> = {
  nome: "alterar_rodape_da_loja",
  titulo: "Alterar o rodapé da loja",
  descricao:
    `Troca o rodapé da loja (vale em todas as páginas): colunas de links (até ${LIMITE_DE_COLUNAS_NO_RODAPE}, com até ${LIMITE_DE_LINKS_NA_COLUNA} links cada), o texto "Sobre a loja" e os blocos de contato, redes sociais, formas de pagamento (só nos modelos Vendor e Ateliê) e categorias. Com usar_rodape_proprio=false volta o rodapé de sempre. Campos que não vierem ficam como estão. Os contatos e as redes vêm do cadastro da loja (alterar_contato_e_rodape).`,
  campos: {
    usar_rodape_proprio: { tipo: "booleano", obrigatorio: true, descricao: "true = rodapé próprio; false = o de sempre do modelo." },
    sobre: { tipo: "texto", max: LIMITE_DO_SOBRE, descricao: "O texto sobre a loja. Vazio tira." },
    colunas: {
      tipo: "lista",
      maxItens: LIMITE_DE_COLUNAS_NO_RODAPE,
      descricao: "As colunas de links, na ordem (substituem as de agora).",
      itens: {
        titulo: { tipo: "texto", max: 60, obrigatorio: true, descricao: "Título da coluna." },
        links: { tipo: "lista", itens: CAMPOS_DO_LINK, maxItens: LIMITE_DE_LINKS_NA_COLUNA, descricao: "Os links da coluna." },
      },
    },
    mostrar_contato: { tipo: "booleano", descricao: "O bloco de contato (WhatsApp, telefone, e-mail, endereço)." },
    mostrar_redes: { tipo: "booleano", descricao: "As redes sociais." },
    mostrar_pagamento: { tipo: "booleano", descricao: "As formas de pagamento (modelos Vendor e Ateliê)." },
    mostrar_categorias: { tipo: "booleano", descricao: "As cinco primeiras categorias (modelos Vitrine, Vendor e Ateliê)." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer no rodapé da loja");
    const atual = await lerNavegacaoAtual(ctx);
    let rascunho: RascunhoDaNavegacao;
    if (args.usar_rodape_proprio === false) {
      rascunho = { ...atual, rodape: null };
    } else {
      const base = atual.rodape || { ...RODAPE_PADRAO, colunas: [] };
      const colunas = Array.isArray(args.colunas)
        ? await Promise.all(
            (args.colunas as Array<Record<string, unknown>>).map(async (coluna) => ({
              id: idNovo("coluna"),
              titulo: String(coluna.titulo || "").trim(),
              links: await conferirDestinos(ctx, itensDosArgumentos(coluna.links).filter((l) => l.tipo !== "grupo")),
            }))
          )
        : base.colunas;
      rascunho = {
        ...atual,
        rodape: {
          colunas,
          sobre: typeof args.sobre === "string" ? args.sobre : base.sobre,
          mostrar_contato: typeof args.mostrar_contato === "boolean" ? args.mostrar_contato : base.mostrar_contato,
          mostrar_redes: typeof args.mostrar_redes === "boolean" ? args.mostrar_redes : base.mostrar_redes,
          mostrar_pagamento: typeof args.mostrar_pagamento === "boolean" ? args.mostrar_pagamento : base.mostrar_pagamento,
          mostrar_categorias: typeof args.mostrar_categorias === "boolean" ? args.mostrar_categorias : base.mostrar_categorias,
        },
      };
    }
    const problema = problemasDaNavegacao(rascunho).find((p) => p.regiao === "rodape");
    if (problema) recusar(problema.texto);
    const gravado = navegacaoParaGravar(rascunho).rodape;
    return {
      previa: {
        o_que_vai_acontecer: gravado
          ? `O rodapé passa a ter ${contagem(gravado.colunas.length, "coluna", "colunas")} de links${gravado.sobre ? " e o texto sobre a loja" : ""}.`
          : "A loja usa o rodapé de sempre do modelo.",
        rodape: gravado ? { ...gravado, colunas: gravado.colunas.map((c) => ({ titulo: c.titulo, links: itensEmPalavras(c.links) })) } : null,
      },
      resumo: "Prévia: alterar o rodapé da loja",
      avisos: [],
      carga: { rascunho, oQue: "o rodapé" },
    };
  },
  async gravar(ctx, carga) {
    await publicarNavegacao(ctx, carga.rascunho);
    const aviso = await avisar(ctx);
    return { dados: { publicado: true, ...aviso }, resumo: "Alterou o rodapé da loja" };
  },
};

export const alterarOpcoesDoCabecalho: FerramentaDeEscrita<CargaDaNavegacao> = {
  nome: "alterar_opcoes_do_cabecalho",
  titulo: "Alterar as opções do cabeçalho da loja",
  descricao:
    "Mostra ou tira a busca do cabeçalho, muda o tamanho da logo no computador (pequeno, normal, grande) e deixa o cabeçalho fixo ao rolar ou não. Campos que não vierem ficam como estão. (Nome, slogan e cores do cabeçalho: alterar_cabecalho_da_loja.)",
  campos: {
    mostrar_busca: { tipo: "booleano", descricao: "A caixa de busca do cabeçalho." },
    tamanho_da_logo: { tipo: "opcao", opcoes: ["pequeno", "normal", "grande"], descricao: "O tamanho da logo no computador." },
    fixo: { tipo: "booleano", descricao: "O cabeçalho acompanha a rolagem (padrão) ou sobe com a página." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer no cabeçalho da loja");
    const atual = await lerNavegacaoAtual(ctx);
    const cabecalho = {
      mostrar_busca: typeof args.mostrar_busca === "boolean" ? args.mostrar_busca : atual.cabecalho.mostrar_busca,
      tamanho_da_logo: (args.tamanho_da_logo as typeof CABECALHO_PADRAO.tamanho_da_logo) || atual.cabecalho.tamanho_da_logo,
      fixo: typeof args.fixo === "boolean" ? args.fixo : atual.cabecalho.fixo,
    };
    return {
      previa: { o_que_vai_acontecer: "As opções do cabeçalho mudam em todas as páginas da loja.", cabecalho },
      resumo: "Prévia: alterar as opções do cabeçalho",
      avisos: [],
      carga: { rascunho: { ...atual, cabecalho }, oQue: "o cabeçalho" },
    };
  },
  async gravar(ctx, carga) {
    await publicarNavegacao(ctx, carga.rascunho);
    const aviso = await avisar(ctx);
    return { dados: { publicado: true, ...aviso }, resumo: "Alterou as opções do cabeçalho da loja" };
  },
};

/* ======================================================= as páginas */

async function secoesDaPagina(ctx: ContextoDoAssistente, ids: string[]) {
  if (!ids.length) return [] as Array<Record<string, unknown>>;
  const { data, error } = await ctx.supabase
    .from("catalog_sections")
    .select("id,pagina_id,title,config,section_type,active,position,layout_width")
    .eq("user_id", perfilDaLoja(ctx))
    .in("pagina_id", ids)
    .order("position", { ascending: true })
    .limit(600);
  if (error) falha("ler as seções das páginas", error);
  return (data ?? []) as Array<Record<string, unknown>>;
}

export const verPaginasDaLoja: FerramentaDeLeitura = {
  nome: "ver_paginas_da_loja",
  titulo: "Ver as páginas extras da loja",
  descricao:
    "Lista as páginas extras da loja (Sobre nós, Trocas e devoluções...), em /<loja>/pagina/<endereço>: título, endereço, se está na loja ou escondida, SEO e as seções de cada uma (com a configuração, no formato que definir_secoes_da_pagina aceita).",
  campos: { pagina: { tipo: "texto", max: 80, descricao: "Só uma página (id ou endereço). Vazio = todas." } },
  tipo: "leitura",
  permissoes: [LER_A_LOJA, ESCREVER_NA_LOJA],
  async executar(ctx, args) {
    exigir(ctx, LER_A_LOJA, "ver a loja");
    const loja = await slugDaLoja(ctx);
    const paginas = args.pagina ? [(await acharPagina(ctx, args.pagina)).pagina] : await lerPaginas(ctx);
    const secoes = await secoesDaPagina(ctx, paginas.map((p) => p.id));
    return {
      dados: {
        paginas: paginas.map((p) => ({
          id: p.id,
          titulo: p.titulo,
          endereco: p.slug,
          link: enderecoDaPagina(ctx, loja.slug, p.slug),
          na_loja: p.visivel,
          fora_do_google: p.noindex,
          mostrar_titulo: p.mostrar_titulo,
          seo_titulo: p.seo_titulo,
          seo_descricao: p.seo_descricao,
          imagem_de_compartilhamento: p.og_imagem,
          secoes: secoes
            .filter((s) => s.pagina_id === p.id)
            .map((s) => {
              const config = (s.config && typeof s.config === "object" ? s.config : {}) as Record<string, unknown>;
              const modo = ehModo(config.mode) ? config.mode : s.section_type === "all_products" ? "all_products" : "manual_products";
              return { id: s.id, tipo: modo, nome_do_tipo: NOME_DO_MODO[modo], titulo: s.title, ativa: s.active !== false, config };
            }),
        })),
        tipos_de_secao: MODOS,
      },
      resumo: `Viu ${contagem(paginas.length, "página extra", "páginas extras")} da loja`,
    };
  },
};

type CargaDaPagina = { campos: Record<string, unknown>; id: string | null; titulo: string };

const CAMPOS_DA_PAGINA: Campos = {
  titulo: { tipo: "texto", max: 120, descricao: "O título da página (aparece no topo e no menu)." },
  endereco: { tipo: "texto", max: 80, descricao: "O endereço: /<loja>/pagina/<endereço>. Só minúsculas, números e hífen. Vazio = feito do título." },
  mostrar_titulo: { tipo: "booleano", descricao: "Mostra o título no topo da página (com a trilha Início › Título)." },
  seo_titulo: { tipo: "texto", max: 120, descricao: "O título no Google." },
  seo_descricao: { tipo: "texto", max: 320, descricao: "A descrição no Google (até 160 letras aparecem)." },
  imagem_de_compartilhamento: { tipo: "texto", max: 2000, descricao: "Endereço https da imagem ao compartilhar o link (1200 × 630)." },
  fora_do_google: { tipo: "booleano", descricao: "A página abre na loja mas não entra no Google nem no sitemap." },
};

function camposDaPagina(args: Record<string, unknown>) {
  const campos: Record<string, unknown> = {};
  if (typeof args.titulo === "string") campos.titulo = args.titulo.replace(/\s+/g, " ").trim().slice(0, 120);
  if (typeof args.mostrar_titulo === "boolean") campos.mostrar_titulo = args.mostrar_titulo;
  if (typeof args.seo_titulo === "string") campos.seo_titulo = args.seo_titulo.trim() || null;
  if (typeof args.seo_descricao === "string") campos.seo_descricao = args.seo_descricao.trim() || null;
  if (typeof args.imagem_de_compartilhamento === "string") {
    const url = args.imagem_de_compartilhamento.trim();
    if (url && !/^https:\/\//i.test(url)) recusar("A imagem de compartilhamento precisa ser um endereço https.");
    campos.og_imagem = url || null;
  }
  if (typeof args.fora_do_google === "boolean") campos.noindex = args.fora_do_google;
  return campos;
}

export const criarPagina: FerramentaDeEscrita<CargaDaPagina> = {
  nome: "criar_pagina",
  titulo: "Criar uma página extra na loja",
  descricao:
    "Cria uma página extra da loja (Sobre nós, Trocas e devoluções, Como comprar...) em /<loja>/pagina/<endereço>, com o cabeçalho e o rodapé da loja. Nasce ESCONDIDA e vazia: ponha as seções com definir_secoes_da_pagina e mostre na loja com alterar_pagina (na_loja=true). Para pôr no menu: alterar_menu_da_loja com um item tipo=pagina.",
  campos: { ...CAMPOS_DA_PAGINA, titulo: { ...CAMPOS_DA_PAGINA.titulo, obrigatorio: true } },
  tipo: "escrita",
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "criar páginas na loja");
    const todas = await lerPaginas(ctx);
    const campos = camposDaPagina(args);
    if (!campos.titulo) recusar("Dê um título para a página.");
    const existentes = todas.map((p) => p.slug);
    const slug = args.endereco ? slugDePagina(args.endereco) : slugLivre(String(campos.titulo), existentes);
    const problema = problemaDoSlugDaPagina(slug, existentes);
    if (problema) recusar(problema);
    const loja = await slugDaLoja(ctx);
    return {
      previa: { o_que_vai_acontecer: `Nasce a página "${campos.titulo}", escondida, em ${enderecoDaPagina(ctx, loja.slug, slug) || `/pagina/${slug}`}.`, ...campos, endereco: slug },
      resumo: `Prévia: criar a página "${campos.titulo}"`,
      avisos: [],
      carga: {
        campos: { ...campos, slug, visivel: false, user_id: perfilDaLoja(ctx), position: todas.length ? Math.max(...todas.map((p) => p.position)) + 1 : 0 },
        id: null,
        titulo: String(campos.titulo),
      },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.from("catalog_paginas").insert(carga.campos).select("id,slug").single();
    if (error) {
      if (/duplicate|unique/i.test(error.message || "")) recusar("Já existe uma página com esse endereço.");
      falha("criar a página", error);
    }
    const linha = data as { id: string; slug: string };
    return {
      dados: { criada: true, pagina_id: linha.id, endereco: linha.slug, na_loja: false, proximo_passo: "Ponha as seções com definir_secoes_da_pagina e mostre na loja com alterar_pagina (na_loja=true)." },
      resumo: `Criou a página "${carga.titulo}"`,
    };
  },
};

export const alterarPagina: FerramentaDeEscrita<CargaDaPagina> = {
  nome: "alterar_pagina",
  titulo: "Alterar uma página extra da loja",
  descricao:
    "Muda o título, o endereço, o SEO, a imagem de compartilhamento, o título no topo e o 'fora do Google' de uma página extra, e a mostra na loja ou esconde (na_loja). Campos que não vierem ficam como estão. Mudar o endereço quebra o link antigo (o menu acompanha sozinho).",
  campos: {
    pagina: { tipo: "texto", max: 80, obrigatorio: true, descricao: "A página: id ou endereço (de ver_paginas_da_loja)." },
    ...CAMPOS_DA_PAGINA,
    na_loja: { tipo: "booleano", descricao: "true = a página aparece na loja, no menu e no sitemap; false = escondida (rascunho)." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer nas páginas da loja");
    const { pagina, todas } = await acharPagina(ctx, args.pagina);
    const campos = camposDaPagina(args);
    if (campos.titulo === "") recusar("O título não pode ficar vazio.");
    if (typeof args.endereco === "string" && args.endereco.trim()) {
      const slug = slugDePagina(args.endereco);
      const problema = problemaDoSlugDaPagina(slug, todas.filter((p) => p.id !== pagina.id).map((p) => p.slug));
      if (problema) recusar(problema);
      if (slug !== pagina.slug) campos.slug = slug;
    }
    if (typeof args.na_loja === "boolean") campos.visivel = args.na_loja;
    if (!Object.keys(campos).length) recusar("Nada para mudar: mande ao menos um campo.");
    const avisos: string[] = [];
    if (campos.slug) avisos.push(`O endereço muda de /pagina/${pagina.slug} para /pagina/${campos.slug}: o link antigo deixa de abrir.`);
    return {
      previa: { o_que_vai_acontecer: `A página "${pagina.titulo}" muda.`, antes: { titulo: pagina.titulo, endereco: pagina.slug, na_loja: pagina.visivel }, muda: campos },
      resumo: `Prévia: alterar a página "${pagina.titulo}"`,
      avisos,
      carga: { campos: { ...campos, updated_at: new Date().toISOString() }, id: pagina.id, titulo: pagina.titulo },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.from("catalog_paginas").update(carga.campos).eq("id", carga.id).eq("user_id", perfilDaLoja(ctx)).select("id,slug,visivel");
    if (error) {
      if (/duplicate|unique/i.test(error.message || "")) recusar("Já existe uma página com esse endereço.");
      falha("alterar a página", error);
    }
    if (!(data ?? []).length) recusar("A página não existe mais.");
    const aviso = await avisar(ctx);
    const linha = (data as Array<{ slug: string; visivel: boolean }>)[0];
    return { dados: { alterada: true, endereco: linha.slug, na_loja: linha.visivel, ...aviso }, resumo: `Alterou a página "${carga.titulo}"` };
  },
};

type CargaDaDuplicacao = { id: string; titulo: string; slug: string };

export const duplicarPagina: FerramentaDeEscrita<CargaDaDuplicacao> = {
  nome: "duplicar_pagina",
  titulo: "Duplicar uma página extra",
  descricao: "Faz uma cópia de uma página extra, com as seções dela. A cópia nasce escondida.",
  campos: {
    pagina: { tipo: "texto", max: 80, obrigatorio: true, descricao: "A página: id ou endereço." },
    titulo: { tipo: "texto", max: 120, descricao: 'Título da cópia. Vazio = "<título> (cópia)".' },
  },
  tipo: "escrita",
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer nas páginas da loja");
    const { pagina, todas } = await acharPagina(ctx, args.pagina);
    const titulo = (typeof args.titulo === "string" && args.titulo.trim()) || `${pagina.titulo} (cópia)`;
    const slug = slugLivre(titulo, todas.map((p) => p.slug));
    return {
      previa: { o_que_vai_acontecer: `Nasce a página "${titulo}" (escondida), cópia de "${pagina.titulo}".`, endereco: slug },
      resumo: `Prévia: duplicar a página "${pagina.titulo}"`,
      avisos: [],
      carga: { id: pagina.id, titulo: titulo.slice(0, 120), slug },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.rpc("duplicar_pagina_da_loja", { p_dono: perfilDaLoja(ctx), p_pagina: carga.id, p_titulo: carga.titulo, p_slug: carga.slug });
    if (error) falha("duplicar a página", error);
    return { dados: { duplicada: true, pagina_id: data, endereco: carga.slug, na_loja: false }, resumo: `Duplicou a página como "${carga.titulo}"` };
  },
};

type CargaDaRemocao = { id: string; titulo: string };

export const apagarPagina: FerramentaDeEscrita<CargaDaRemocao> = {
  nome: "apagar_pagina",
  titulo: "Apagar uma página extra",
  descricao: "Apaga uma página extra da loja, com as seções dela. Não tem volta. Os links do menu e do rodapé que levavam a ela somem sozinhos. Para só tirar da loja, use alterar_pagina com na_loja=false.",
  campos: { pagina: { tipo: "texto", max: 80, obrigatorio: true, descricao: "A página: id ou endereço." } },
  tipo: "escrita",
  sobrescreve: true,
  sensivel: true,
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer nas páginas da loja");
    const { pagina } = await acharPagina(ctx, args.pagina);
    const secoes = await secoesDaPagina(ctx, [pagina.id]);
    return {
      previa: { o_que_vai_acontecer: `A página "${pagina.titulo}" (/pagina/${pagina.slug}) e ${contagem(secoes.length, "seção", "seções")} somem da loja. Não tem volta.` },
      resumo: `Prévia: apagar a página "${pagina.titulo}"`,
      avisos: pagina.visivel ? ["A página está na loja agora: o link dela vai dar página não encontrada."] : [],
      carga: { id: pagina.id, titulo: pagina.titulo },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.from("catalog_paginas").delete().eq("id", carga.id).eq("user_id", perfilDaLoja(ctx)).select("id");
    if (error) falha("apagar a página", error);
    if (!(data ?? []).length) recusar("A página não existe mais.");
    const aviso = await avisar(ctx);
    return { dados: { apagada: true, ...aviso }, resumo: `Apagou a página "${carga.titulo}"` };
  },
};

type CargaDasSecoes = { id: string; titulo: string; secoes: Array<Record<string, unknown>> };

const LIMITE_DE_SECOES_DA_PAGINA = 40;

export const definirSecoesDaPagina: FerramentaDeEscrita<CargaDasSecoes> = {
  nome: "definir_secoes_da_pagina",
  titulo: "Definir as seções de uma página extra",
  descricao:
    `Troca as seções de uma página extra inteiras (até ${LIMITE_DE_SECOES_DA_PAGINA}), na ordem: os mesmos tipos de seção da página inicial (${MODOS.join(", ")}). Cada seção leva o tipo, o título e, se quiser, a configuração em config_json (o mesmo formato que ver_paginas_da_loja e ver_estrutura_da_loja mostram: imagens, links, categoria...) e a lista de produtos (tipo manual_products). Para mudar uma seção, leia a página, ajuste a lista e mande de novo. Entra no histórico do editor (dá para desfazer lá).`,
  campos: {
    pagina: { tipo: "texto", max: 80, obrigatorio: true, descricao: "A página: id ou endereço." },
    secoes: {
      tipo: "lista",
      maxItens: LIMITE_DE_SECOES_DA_PAGINA,
      obrigatorio: true,
      descricao: "As seções da página, na ordem. Lista vazia = página sem seções.",
      itens: {
        tipo: { tipo: "opcao", opcoes: MODOS, obrigatorio: true, descricao: "O tipo da seção." },
        titulo: { tipo: "texto", max: 200, descricao: "O título da seção." },
        config_json: { tipo: "texto", max: 20000, descricao: 'A configuração da seção em JSON (ex.: {"image_url":"https://...","formato":"largo"}). O tipo vale mais que o "mode" daqui.' },
        produtos: { tipo: "textos", max: 40, maxItens: 60, descricao: "Com tipo=manual_products: os ids dos produtos, na ordem." },
        ativa: { tipo: "booleano", descricao: "false = guardada mas escondida. Padrão: true." },
        largura: { tipo: "opcao", opcoes: ["full", "contained"], descricao: "full = a largura toda; contained = no centro." },
      },
    },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: [ESCREVER_NA_LOJA],
  async preparar(ctx, args) {
    exigir(ctx, ESCREVER_NA_LOJA, "mexer nas páginas da loja");
    const { pagina } = await acharPagina(ctx, args.pagina);
    const pedidas = (Array.isArray(args.secoes) ? args.secoes : []) as Array<Record<string, unknown>>;
    if (pedidas.filter((s) => s.tipo === "all_products" && s.ativa !== false).length > 1) recusar('Só pode haver uma seção "all_products" (Todos os produtos) ligada.');
    const todosOsProdutos = [...new Set(pedidas.flatMap((s) => (Array.isArray(s.produtos) ? (s.produtos as string[]) : [])))];
    for (const id of todosOsProdutos) if (!SO_UUID.test(id)) recusar(`"${id}" não é um id de produto. Use buscar_produtos.`);
    if (todosOsProdutos.length) {
      const { data, error } = await ctx.supabase.from("products").select("id").in("user_id", ctx.tenantIds).in("id", todosOsProdutos);
      if (error) falha("conferir os produtos", error);
      const achados = new Set(((data ?? []) as Array<{ id: string }>).map((p) => p.id));
      const falta = todosOsProdutos.find((id) => !achados.has(id));
      if (falta) recusar(`O produto ${falta} não é desta loja. Use buscar_produtos.`);
    }
    const secoes = pedidas.map((s, i) => {
      const modo = String(s.tipo);
      if (!ehModo(modo)) recusar(`secoes[${i + 1}]: tipo desconhecido.`);
      let config: Record<string, unknown> = {};
      if (typeof s.config_json === "string" && s.config_json.trim()) {
        try {
          const lido = JSON.parse(s.config_json);
          if (!lido || typeof lido !== "object" || Array.isArray(lido)) throw new Error("não é objeto");
          config = lido as Record<string, unknown>;
        } catch {
          recusar(`secoes[${i + 1}]: config_json não é um JSON de objeto válido.`);
        }
      }
      for (const valor of JSON.stringify(config).match(/"(?:url|link_url|image_url|imagem|imagem_celular)"\s*:\s*"([^"]*)"/g) || []) {
        if (/javascript:|data:|vbscript:/i.test(valor)) recusar(`secoes[${i + 1}]: um endereço da configuração não pode virar link.`);
      }
      return {
        id: null,
        title: String(s.titulo ?? NOME_DO_MODO[modo]).slice(0, 200),
        background_color: "#ffffff",
        text_color: "#111827",
        placement: "above",
        layout_width: s.largura === "contained" ? "contained" : "full",
        title_align: "left",
        title_size: "lg",
        section_type: modo === "all_products" ? "all_products" : "manual_products",
        config: { ...config, mode: modo },
        active: s.ativa !== false,
        ...(modo === "manual_products" ? { produtos: Array.isArray(s.produtos) ? s.produtos : [] } : {}),
      };
    });
    return {
      previa: {
        o_que_vai_acontecer: `As seções da página "${pagina.titulo}" passam a ser ${contagem(secoes.length, "seção", "seções")}, nesta ordem (as de agora saem).`,
        secoes: secoes.map((s) => ({ tipo: NOME_DO_MODO[(s.config as { mode: (typeof MODOS)[number] }).mode], titulo: s.title, ativa: s.active })),
        na_loja: pagina.visivel,
      },
      resumo: `Prévia: definir as seções da página "${pagina.titulo}"`,
      avisos: pagina.visivel ? [] : ["A página está escondida: as seções só aparecem na loja depois de alterar_pagina com na_loja=true."],
      carga: { id: pagina.id, titulo: pagina.titulo, secoes },
    };
  },
  async gravar(ctx, carga) {
    const { error } = await ctx.supabase.rpc("publicar_no_editor_da_loja", {
      p_dono: perfilDaLoja(ctx),
      p_pagina: carga.id,
      p_secoes: carga.secoes,
      p_tema: null,
      p_navegacao: null,
    });
    if (error) falha("gravar as seções da página", error);
    const aviso = await avisar(ctx);
    return { dados: { publicado: true, secoes: carga.secoes.length, ...aviso }, resumo: `Definiu as seções da página "${carga.titulo}"` };
  },
};

export const FERRAMENTAS_DO_MENU_E_DAS_PAGINAS: Ferramenta[] = [
  verMenuERodapeDaLoja,
  alterarMenuDaLoja,
  alterarRodapeDaLoja,
  alterarOpcoesDoCabecalho,
  verPaginasDaLoja,
  criarPagina,
  alterarPagina,
  duplicarPagina,
  apagarPagina,
  definirSecoesDaPagina,
] as Ferramenta[];
