import { resolveCompanyPublicProfileId, type CompanyPermission } from "@/lib/company-context";
import { avisarConversa } from "@/lib/chats/aviso";
import { avisarLojaEEsperar } from "@/lib/loja/avisar-loja";

import { recusaDeContatos } from "../contatos";
import type { ContextoDoAssistente } from "../contexto";
import type { Campos } from "../esquema";
import { AVISO_DE_DADOS, baseDaLoja, contagem, dataBr, exigir, falha, pode, recusar, texto } from "./comum";
import {
  ajustarCorte,
  ajustarEfeitoDaLogo,
  CABECALHO_DA_LOJA,
  camposDoGrupo,
  CHAT_DA_LOJA,
  colunasDaModeracao,
  comoDesfazer,
  CONTATO_E_RODAPE,
  CORTE_DE_PRODUCAO,
  contatosNosTextos,
  FILTROS_DE_AVALIACAO,
  lerGrupo,
  montarMudanca,
  montarPonto,
  motivosSensiveis,
  pedidoDoGrupo,
  pontoLegivel,
  REGRAS_DA_LOJA,
  SEO_DA_LOJA,
  SITUACAO_DA_AVALIACAO,
  SITUACAO_DA_LOJA,
  type Grupo,
  type Mudanca,
  type PontoDeRetirada,
} from "./loja-regras-e-contato-regras";
import type { Ferramenta, FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/**
 * As regras da loja e o contato pelo assistente: como a loja vende (preço,
 * modo de compra, acesso, cadastro, pedido mínimo, estoque, situação), o
 * cabeçalho, o contato e o rodapé, o resto do SEO, o corte de produção, o
 * chat, os pontos de retirada e a moderação das avaliações.
 *
 * Tudo espelha a tela Catálogo (abas Personalização, Regras, Logística e
 * SEO), Chats e Avaliações: as mesmas colunas, os mesmos valores e a mesma
 * permissão. O perfil é o do dono da empresa (o que a loja mostra), gravado
 * com a sessão da pessoa, como a tela. Só o chat e as avaliações vão pela
 * chave de serviço, como as rotas do painel, sempre com o filtro da empresa.
 *
 * As regras sem banco ficam em ./loja-regras-e-contato-regras.ts.
 */

const PERMISSAO_DA_LOJA: CompanyPermission = "catalog.store.write";

const TRAVADO =
  "O banco não deixou gravar: as configurações da loja moram no perfil do dono da empresa, e para mudá-las a pessoa precisa também da permissão de configurações da empresa. Quem libera é o dono, no painel, em Funcionários.";

const ONDE_NO_PAINEL = "em Catálogo › Personalização";

/* O perfil que a loja mostra: o do dono (funcionário tem perfil próprio, vazio). */
function perfilDaLoja(ctx: ContextoDoAssistente) {
  return resolveCompanyPublicProfileId(ctx.empresa, ctx.user.id);
}

const TODOS_OS_GRUPOS: Grupo[] = [REGRAS_DA_LOJA, CABECALHO_DA_LOJA, CONTATO_E_RODAPE, SEO_DA_LOJA, CORTE_DE_PRODUCAO, CHAT_DA_LOJA];

const COLUNAS_LIDAS = [
  "id",
  "slug",
  "catalog_logo_effect_enabled",
  ...new Set(TODOS_OS_GRUPOS.flatMap((grupo) => Object.values(grupo).map((campo) => campo.coluna))),
].join(",");

async function lerPerfil(ctx: ContextoDoAssistente): Promise<Record<string, unknown>> {
  const { data, error } = await ctx.supabase.from("profiles").select(COLUNAS_LIDAS).eq("id", perfilDaLoja(ctx)).maybeSingle();
  if (error) falha("ler as configurações da loja", error);
  if (!data) recusar("A loja desta empresa ainda não foi criada. Ela é criada no painel, em Catálogo.");
  return data as unknown as Record<string, unknown>;
}

function linkDaLoja(ctx: ContextoDoAssistente, perfil: Record<string, unknown>) {
  return perfil.slug ? `${baseDaLoja(ctx)}/${String(perfil.slug)}` : null;
}

/* Avisa a loja (que guarda o perfil por dois minutos) e diz se ela ouviu. */
async function avisarALoja(perfilId: string) {
  const ouviu = await avisarLojaEEsperar(perfilId);
  return ouviu ? {} : { observacao: "A loja pode levar até dois minutos para mostrar a mudança." };
}

/* ============================================================== leitura */

export const verRegrasEContatoDaLoja: FerramentaDeLeitura = {
  nome: "ver_regras_e_contato_da_loja",
  titulo: "Ver as regras e o contato da loja",
  descricao:
    "Mostra como a loja virtual vende e se apresenta, com o significado de cada opção: quem vê o preço, como o cliente fecha (carrinho, orçamento, WhatsApp, só vitrine), quem pode entrar e o cadastro, pedido mínimo, estoque e esgotados, avaliações, SKU, prazo, se a loja está aberta, em pausa ou fechada; o cabeçalho (nome, slogan, efeito da logo); o contato e o rodapé (WhatsApp, telefone, endereço, CNPJ, redes sociais); palavras-chave, usuário no X e se aparece no Google; o corte de produção; o chat da loja; e os pontos de retirada. Use antes de alterar qualquer um deles.",
  campos: {},
  tipo: "leitura",
  permissoes: ["catalog.store.read", "catalog.store.write"],
  async executar(ctx) {
    if (!pode(ctx, "catalog.store.read") && !pode(ctx, "catalog.store.write")) recusar("Esta pessoa não tem permissão para ver as configurações da loja.");
    const perfil = await lerPerfil(ctx);

    const { data: pontos, error: erroDosPontos } = await ctx.supabase
      .from("catalog_pickup_locations")
      .select("id,name,price,ready_in_days,zip,street,number,complement,neighborhood,city,state,instructions,source,position,active")
      .eq("user_id", String(perfil.id))
      .order("position", { ascending: true })
      .limit(100);

    let avaliacoesPendentes: number | null = null;
    if (pode(ctx, "catalog.read")) {
      const { count } = await ctx.admin
        .from("product_reviews")
        .select("id", { count: "exact", head: true })
        .in("catalog_user_id", ctx.tenantIds)
        .eq("status", "pending");
      avaliacoesPendentes = count ?? null;
    }

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        link_da_loja: linkDaLoja(ctx, perfil),
        regras_da_loja: lerGrupo(REGRAS_DA_LOJA, perfil),
        cabecalho: lerGrupo(CABECALHO_DA_LOJA, perfil),
        contato_e_rodape: lerGrupo(CONTATO_E_RODAPE, perfil),
        seo: lerGrupo(SEO_DA_LOJA, perfil),
        corte_de_producao: lerGrupo(CORTE_DE_PRODUCAO, perfil),
        ...(pode(ctx, "chats.read") ? { chat_da_loja: lerGrupo(CHAT_DA_LOJA, perfil) } : {}),
        pontos_de_retirada: erroDosPontos
          ? "Não foi possível ler os pontos de retirada (falta a permissão de catálogo?)."
          : ((pontos ?? []) as Array<PontoDeRetirada & { id: string; source: string }>).map(pontoLegivel),
        ...(avaliacoesPendentes !== null ? { avaliacoes_esperando_moderacao: avaliacoesPendentes } : {}),
        como_mudar: {
          regras: "alterar_regras_da_loja (ou abrir_ou_fechar_a_loja só para a situação)",
          cabecalho: "alterar_cabecalho_da_loja (cores, nome, slogan, modelo e fontes ficam em alterar_aparencia_da_loja)",
          contato_e_rodape: "alterar_contato_e_rodape (a cor do rodapé fica em alterar_aparencia_da_loja)",
          seo: "alterar_seo_da_loja (título e descrição do Google ficam em alterar_aparencia_da_loja)",
          corte_de_producao: "alterar_corte_de_producao",
          chat: "alterar_chat_da_loja",
          pontos_de_retirada: "criar_ponto_de_retirada, alterar_ponto_de_retirada, remover_ponto_de_retirada",
          avaliacoes: "listar_avaliacoes e moderar_avaliacao",
          mensagens_do_whatsapp: "A dúvida sobre produto e o cabeçalho e o rodapé do pedido da loja: ver_personalizacoes e alterar_mensagem_do_whatsapp.",
        },
      },
      resumo: "Consultou as regras e o contato da loja",
    };
  },
};

/* ==================================================== escritas no perfil */

type CargaDoPerfil = {
  perfilId: string;
  colunas: Record<string, string | number | boolean | null>;
  antes: Record<string, string | number | boolean | null>;
  linhas: Mudanca["linhas"];
  slug: string | null;
};

type Definicao = {
  nome: string;
  titulo: string;
  /* A linha do registro de atividade, no passado. */
  feito: string;
  descricao: string;
  grupo: Grupo;
  permissao: CompanyPermission;
  oQue: string;
  /* O que muda junto (efeito da logo, corte), depois de montar. */
  ajustar?: (mudanca: Mudanca, atual: Record<string, unknown>) => { ok: true } | { ok: false; erro: string };
  /* Grava pela chave de serviço com o filtro da empresa (o chat, como a rota do painel). */
  pelaChaveDeServico?: boolean;
  depoisDeGravar?: (ctx: ContextoDoAssistente, carga: CargaDoPerfil) => Promise<void>;
};

function ferramentaDoPerfil(definicao: Definicao): FerramentaDeEscrita<CargaDoPerfil> {
  const campos: Campos = camposDoGrupo(definicao.grupo);
  return {
    nome: definicao.nome,
    titulo: definicao.titulo,
    descricao: definicao.descricao,
    campos,
    tipo: "escrita",
    sobrescreve: true,
    permissoes: [definicao.permissao],
    async preparar(ctx, args) {
      exigir(ctx, definicao.permissao, definicao.oQue);
      const atual = await lerPerfil(ctx);
      const { valores, voltar } = pedidoDoGrupo(definicao.grupo, args);
      if (!Object.keys(valores).length && !voltar.length) recusar("Mande ao menos um campo para mudar.");
      const montado = montarMudanca(definicao.grupo, valores, voltar, atual);
      if (!montado.ok) recusar(montado.erro);
      const { mudanca } = montado;
      if (definicao.ajustar) {
        const ajustado = definicao.ajustar(mudanca, atual);
        if (!ajustado.ok) recusar(ajustado.erro);
      }
      /* Textos que o cliente lê: o assistente não escreve link, telefone,
         e-mail ou Pix que já não estavam lá (contra instrução escondida em
         texto de cliente). Telefone, endereço e redes são campos próprios. */
      const contatos = contatosNosTextos(definicao.grupo, mudanca);
      if (contatos.length) recusar(recusaDeContatos(contatos, ONDE_NO_PAINEL));

      const motivos = motivosSensiveis(mudanca.colunas, atual);
      return {
        previa: {
          o_que_vai_acontecer: "A loja muda assim que gravar, para todos os clientes.",
          mudancas: mudanca.linhas,
          ...(motivos.length ? { atencao: motivos } : {}),
          link_da_loja: linkDaLoja(ctx, atual),
        },
        resumo: `Prévia: ${definicao.titulo.toLowerCase()} (${mudanca.linhas.map((linha) => linha.campo).join(", ")})`,
        avisos: mudanca.avisos,
        sensivel: motivos.length > 0,
        carga: {
          perfilId: String(atual.id),
          colunas: mudanca.colunas,
          antes: mudanca.antes,
          linhas: mudanca.linhas,
          slug: (atual.slug as string | null) ?? null,
        },
      };
    },
    async gravar(ctx, carga) {
      const cliente = definicao.pelaChaveDeServico ? ctx.admin : ctx.supabase;
      let consulta = cliente.from("profiles").update({ ...carga.colunas, updated_at: new Date().toISOString() }).eq("id", carga.perfilId);
      /* Pela chave de serviço, a trava é o filtro: só o perfil desta empresa. */
      if (definicao.pelaChaveDeServico) consulta = consulta.in("id", ctx.tenantIds);
      const { data, error } = await consulta.select("id");
      if (error) falha(definicao.oQue, error);
      if (!(data ?? []).length) recusar(TRAVADO);
      const aviso = await avisarALoja(carga.perfilId);
      if (definicao.depoisDeGravar) await definicao.depoisDeGravar(ctx, carga);
      return {
        dados: {
          alterado: true,
          mudancas: carga.linhas,
          para_desfazer: { comando: definicao.nome, argumentos: comoDesfazer(definicao.grupo, carga.antes) },
          ...aviso,
        },
        resumo: `${definicao.feito} (${carga.linhas.map((linha) => linha.campo).join(", ")})`,
      };
    },
  };
}

const SENSIVEL_NA_PREVIA =
  "Fechar ou pausar a loja, deixá-la só para clientes logados, mudar quem vê o preço ou como o cliente fecha, e fechar o cadastro pedem a aprovação da pessoa: a primeira chamada devolve uma prévia com código; depois de ela aprovar, chame de novo com os mesmos dados e o código em `confirmacao`.";

export const alterarRegrasDaLoja = ferramentaDoPerfil({
  nome: "alterar_regras_da_loja",
  titulo: "Alterar as regras da loja",
  feito: "Alterou as regras da loja",
  descricao: `Muda como a loja virtual vende (aba Regras do Catálogo): quem vê o preço, o texto no lugar dele, como o cliente fecha (carrinho, orçamento, WhatsApp, só vitrine), os textos dos botões, quem pode entrar, o cadastro, pedido mínimo, estoque, esgotados, avaliações, SKU, prazo e a situação da loja. Mande só o que muda, com os nomes das opções (veja ver_regras_e_contato_da_loja). Para apagar um texto ou um mínimo, ponha o nome em voltar_ao_padrao. ${SENSIVEL_NA_PREVIA} A resposta traz o que era antes e os argumentos para desfazer.`,
  grupo: REGRAS_DA_LOJA,
  permissao: PERMISSAO_DA_LOJA,
  oQue: "mudar as regras da loja",
});

export const abrirOuFecharALoja = ferramentaDoPerfil({
  nome: "abrir_ou_fechar_a_loja",
  titulo: "Abrir, pausar ou fechar a loja",
  feito: "Mudou a situação da loja",
  descricao: `Abre a loja, põe em pausa (aparece, mas ninguém fecha pedido) ou fecha (página 'em breve'), com um recado para o cliente e a previsão de volta. A equipe continua entrando na loja pausada ou fechada. Pausar e fechar pedem a aprovação da pessoa: a primeira chamada devolve uma prévia com código; depois de ela aprovar, chame de novo com os mesmos dados e o código em \`confirmacao\`. Para tirar o recado ou a data, use voltar_ao_padrao.`,
  grupo: SITUACAO_DA_LOJA,
  permissao: PERMISSAO_DA_LOJA,
  oQue: "abrir ou fechar a loja",
});

export const alterarCabecalhoDaLoja = ferramentaDoPerfil({
  nome: "alterar_cabecalho_da_loja",
  titulo: "Alterar o cabeçalho da loja",
  feito: "Alterou o cabeçalho da loja",
  descricao:
    "Muda o cabeçalho da loja virtual: mostrar ou não o nome e o slogan, o efeito animado da logo (sem efeito, rotação 3D, pulse premium, brilho ritmado) com velocidade (50 a 200%) e intensidade (40 a 180%), e a listagem dos produtos no modelo Vitrine (mosaico ou listagem normal). Escolher um efeito liga; 'sem efeito' desliga e volta velocidade e intensidade a 100%. Cores, nome, slogan e fontes ficam em alterar_aparencia_da_loja. A resposta traz os argumentos para desfazer.",
  grupo: CABECALHO_DA_LOJA,
  permissao: PERMISSAO_DA_LOJA,
  oQue: "mudar o cabeçalho da loja",
  ajustar: ajustarEfeitoDaLogo,
});

export const alterarContatoERodape = ferramentaDoPerfil({
  nome: "alterar_contato_e_rodape",
  titulo: "Alterar o contato e o rodapé da loja",
  feito: "Alterou o contato e o rodapé da loja",
  descricao:
    "Muda o contato e o rodapé da loja virtual: WhatsApp do catálogo (rodapé, ficha do produto, pedido) e o botão flutuante, telefone, WhatsApp da empresa, endereço, mostrar CPF/CNPJ, e as redes sociais (Instagram, Facebook, X, LinkedIn, YouTube). Telefones com DDD (gravados como (11) 98765-4321). Rede social: link https do site da rede ou o @ (vira o link do perfil; o LinkedIn só aceita link). A resposta mostra exatamente o que foi gravado. Para tirar um campo, ponha o nome em voltar_ao_padrao. A cor do rodapé fica em alterar_aparencia_da_loja.",
  grupo: CONTATO_E_RODAPE,
  permissao: PERMISSAO_DA_LOJA,
  oQue: "mudar o contato e o rodapé da loja",
});

export const alterarSeoDaLoja = ferramentaDoPerfil({
  nome: "alterar_seo_da_loja",
  titulo: "Alterar o SEO da loja",
  feito: "Alterou o SEO da loja",
  descricao:
    "Muda as palavras-chave da loja, o usuário no X (aparece no cartão do link compartilhado) e se a loja aparece nas buscas do Google. Tirar a loja do Google (aparecer_no_google=falso) pede a aprovação da pessoa: a primeira chamada devolve uma prévia com código; depois de ela aprovar, chame de novo com os mesmos dados e o código em `confirmacao`. Título e descrição do Google ficam em alterar_aparencia_da_loja.",
  grupo: SEO_DA_LOJA,
  permissao: PERMISSAO_DA_LOJA,
  oQue: "mudar o SEO da loja",
});

export const alterarCorteDeProducao = ferramentaDoPerfil({
  nome: "alterar_corte_de_producao",
  titulo: "Alterar o corte de produção",
  feito: "Alterou o corte de produção",
  descricao:
    "Liga, desliga ou muda a hora do corte de produção: a loja mostra 'entra em produção hoje se finalizado até HH:MM', com o relógio correndo. Depois da hora, a produção começa no próximo dia útil. Ligar pede a hora (ou usa a que já estava); desligar apaga a hora, como a tela.",
  grupo: CORTE_DE_PRODUCAO,
  permissao: PERMISSAO_DA_LOJA,
  oQue: "mudar o corte de produção",
  ajustar: ajustarCorte,
});

export const alterarChatDaLoja = ferramentaDoPerfil({
  nome: "alterar_chat_da_loja",
  titulo: "Alterar o chat da loja",
  feito: "Alterou o chat da loja",
  descricao: `Liga ou desliga o chat da loja virtual (o balão do Ktalogu Chats), marca se tem alguém atendendo agora (online) e muda a saudação (até 400 letras) e se ela aparece. Offline o cliente escreve do mesmo jeito e a loja responde quando voltar.`,
  grupo: CHAT_DA_LOJA,
  permissao: "chats.write",
  oQue: "mudar o chat da loja",
  pelaChaveDeServico: true,
  async depoisDeGravar(_ctx, carga) {
    /* Quem está com a loja aberta vê a bolinha mudar na hora (como a rota do painel). */
    if (carga.slug && typeof carga.colunas.store_chat_online === "boolean") {
      await avisarConversa([`loja-${carga.slug}`], {
        threadId: `presenca-${carga.slug}`,
        autor: "system",
        resumo: carga.colunas.store_chat_online ? "online" : "offline",
      });
    }
  },
});

/* ==================================================== pontos de retirada */

const COLUNAS_DO_PONTO = "id,name,price,ready_in_days,zip,street,number,complement,neighborhood,city,state,instructions,source,position,active";

const CAMPOS_DO_PONTO: Campos = {
  nome: { tipo: "texto", descricao: "O nome que o cliente vê na loja (ex.: Retirada no centro).", max: 80 },
  cep: { tipo: "texto", descricao: "CEP, 8 dígitos.", max: 9 },
  rua: { tipo: "texto", descricao: "Rua.", max: 160 },
  numero: { tipo: "texto", descricao: "Número.", max: 20 },
  complemento: { tipo: "texto", descricao: "Complemento.", max: 120 },
  bairro: { tipo: "texto", descricao: "Bairro.", max: 120 },
  cidade: { tipo: "texto", descricao: "Cidade.", max: 120 },
  uf: { tipo: "texto", descricao: "Estado, a sigla com duas letras (ex.: SP).", max: 2 },
  preco: { tipo: "numero", descricao: "Quanto o cliente paga pela retirada, em reais. 0 = grátis.", min: 0, max: 100_000 },
  pronto_em_dias: { tipo: "inteiro", descricao: "Em quantos dias o pedido fica pronto para retirar. 0 = no mesmo dia.", min: 0, max: 365 },
  instrucoes: { tipo: "texto", descricao: "Instruções para o cliente (horário, onde chamar).", max: 500 },
  ativo: { tipo: "booleano", descricao: "Aparece como opção na loja. Falso para pausar sem apagar." },
};

type LinhaDoPonto = PontoDeRetirada & { id: string; source: string; position: number };

async function acharPonto(ctx: ContextoDoAssistente, id: string): Promise<LinhaDoPonto> {
  const { data, error } = await ctx.supabase
    .from("catalog_pickup_locations")
    .select(COLUNAS_DO_PONTO)
    .eq("id", id)
    .eq("user_id", perfilDaLoja(ctx))
    .maybeSingle();
  if (error) falha("ler o ponto de retirada", error);
  if (!data) recusar("Ponto de retirada não encontrado nesta loja. Veja os pontos (com o id) em ver_regras_e_contato_da_loja.");
  return data as unknown as LinhaDoPonto;
}

function sobPermissaoDoCatalogo(ctx: ContextoDoAssistente) {
  exigir(ctx, PERMISSAO_DA_LOJA, "mexer nos pontos de retirada da loja");
}

type CargaDoPonto = { id: string | null; ponto: PontoDeRetirada; antes: Record<string, unknown> | null };

function camposDoPonto(ponto: PontoDeRetirada) {
  return {
    name: ponto.name,
    price: ponto.price,
    ready_in_days: ponto.ready_in_days,
    zip: ponto.zip,
    street: ponto.street,
    number: ponto.number,
    complement: ponto.complement,
    neighborhood: ponto.neighborhood,
    city: ponto.city,
    state: ponto.state,
    instructions: ponto.instructions,
    active: ponto.active,
  };
}

export const criarPontoDeRetirada: FerramentaDeEscrita<CargaDoPonto> = {
  nome: "criar_ponto_de_retirada",
  titulo: "Criar um ponto de retirada",
  descricao:
    "Cadastra um ponto de retirada (balcão) da loja: o cliente escolhe retirar ali em vez de receber pelo frete. Precisa de nome, rua e cidade; o resto é opcional. O preço sai daqui, nunca do navegador do cliente.",
  campos: {
    ...CAMPOS_DO_PONTO,
    nome: { ...CAMPOS_DO_PONTO.nome, obrigatorio: true },
    rua: { ...CAMPOS_DO_PONTO.rua, obrigatorio: true },
    cidade: { ...CAMPOS_DO_PONTO.cidade, obrigatorio: true },
  },
  tipo: "escrita",
  permissoes: [PERMISSAO_DA_LOJA],
  async preparar(ctx, args) {
    sobPermissaoDoCatalogo(ctx);
    const montado = montarPonto(args, null);
    if (!montado.ok) recusar(montado.erro);
    if (montado.contatos.length) recusar(recusaDeContatos(montado.contatos, "em Catálogo › Logística"));
    return {
      previa: { o_que_vai_acontecer: "Um ponto de retirada novo aparece na loja.", ponto: pontoLegivel(montado.ponto) },
      resumo: `Prévia do ponto de retirada ${montado.ponto.name}`,
      avisos: [],
      carga: { id: null, ponto: montado.ponto, antes: null },
    };
  },
  async gravar(ctx, carga) {
    const dono = perfilDaLoja(ctx);
    const { data: ultimos } = await ctx.supabase
      .from("catalog_pickup_locations")
      .select("position")
      .eq("user_id", dono)
      .order("position", { ascending: false })
      .limit(1);
    const posicao = (Number((ultimos as Array<{ position: number }> | null)?.[0]?.position) || 0) + 1;
    const { data, error } = await ctx.supabase
      .from("catalog_pickup_locations")
      .insert({ ...camposDoPonto(carga.ponto), user_id: dono, source: "custom", position: posicao })
      .select("id")
      .single();
    if (error || !data) falha("criar o ponto de retirada", error);
    const aviso = await avisarALoja(dono);
    return {
      dados: { criado: true, ponto: pontoLegivel({ ...carga.ponto, id: (data as { id: string }).id }), ...aviso },
      resumo: `Criou o ponto de retirada ${carga.ponto.name}`,
    };
  },
};

export const alterarPontoDeRetirada: FerramentaDeEscrita<CargaDoPonto> = {
  nome: "alterar_ponto_de_retirada",
  titulo: "Alterar um ponto de retirada",
  descricao:
    "Muda um ponto de retirada da loja: nome, endereço, preço, prazo, instruções, ou ativo=falso para pausar sem apagar. Mande o id (de ver_regras_e_contato_da_loja) e só o que muda. A resposta traz como era antes.",
  campos: { ponto_id: { tipo: "id", descricao: "O id do ponto, de ver_regras_e_contato_da_loja.", obrigatorio: true }, ...CAMPOS_DO_PONTO },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: [PERMISSAO_DA_LOJA],
  async preparar(ctx, args) {
    sobPermissaoDoCatalogo(ctx);
    const atual = await acharPonto(ctx, String(args.ponto_id));
    const montado = montarPonto(args, atual);
    if (!montado.ok) recusar(montado.erro);
    if (montado.contatos.length) recusar(recusaDeContatos(montado.contatos, "em Catálogo › Logística"));
    const antes = camposDoPonto(atual);
    const depois = camposDoPonto(montado.ponto);
    if (JSON.stringify(antes) === JSON.stringify(depois)) recusar("Nada muda: o ponto de retirada já está assim.");
    return {
      previa: { o_que_vai_acontecer: "O ponto de retirada muda na loja.", antes: pontoLegivel(atual), depois: pontoLegivel({ ...montado.ponto, id: atual.id, source: atual.source }) },
      resumo: `Prévia da mudança no ponto de retirada ${atual.name}`,
      avisos: [],
      carga: { id: atual.id, ponto: montado.ponto, antes },
    };
  },
  async gravar(ctx, carga) {
    const dono = perfilDaLoja(ctx);
    const { data, error } = await ctx.supabase
      .from("catalog_pickup_locations")
      .update(camposDoPonto(carga.ponto))
      .eq("id", carga.id as string)
      .eq("user_id", dono)
      .select("id");
    if (error) falha("alterar o ponto de retirada", error);
    if (!(data ?? []).length) recusar("O banco não deixou alterar o ponto de retirada (ele foi removido, ou falta a permissão de catálogo).");
    const aviso = await avisarALoja(dono);
    return {
      dados: { alterado: true, ponto: pontoLegivel({ ...carga.ponto, id: carga.id as string }), como_era: carga.antes, ...aviso },
      resumo: `Alterou o ponto de retirada ${carga.ponto.name}`,
    };
  },
};

type CargaDaRemocao = { id: string; nome: string; antes: Record<string, unknown> };

export const removerPontoDeRetirada: FerramentaDeEscrita<CargaDaRemocao> = {
  nome: "remover_ponto_de_retirada",
  titulo: "Remover um ponto de retirada",
  descricao:
    "Apaga um ponto de retirada da loja. Não tem volta (a resposta traz os dados para recriar). Para só pausar, use alterar_ponto_de_retirada com ativo=falso. Sempre com a aprovação da pessoa: a primeira chamada devolve uma prévia com código; depois de ela aprovar, chame de novo com os mesmos dados e o código em `confirmacao`.",
  campos: { ponto_id: { tipo: "id", descricao: "O id do ponto, de ver_regras_e_contato_da_loja.", obrigatorio: true } },
  tipo: "escrita",
  sobrescreve: true,
  sensivel: true,
  permissoes: [PERMISSAO_DA_LOJA],
  async preparar(ctx, args) {
    sobPermissaoDoCatalogo(ctx);
    const atual = await acharPonto(ctx, String(args.ponto_id));
    return {
      previa: { o_que_vai_acontecer: "O ponto de retirada some da loja. Não tem volta.", ponto: pontoLegivel(atual) },
      resumo: `Prévia: remover o ponto de retirada ${atual.name}`,
      avisos: [],
      carga: { id: atual.id, nome: atual.name, antes: camposDoPonto(atual) },
    };
  },
  async gravar(ctx, carga) {
    const dono = perfilDaLoja(ctx);
    const { data, error } = await ctx.supabase.from("catalog_pickup_locations").delete().eq("id", carga.id).eq("user_id", dono).select("id");
    if (error) falha("remover o ponto de retirada", error);
    if (!(data ?? []).length) recusar("O banco não deixou remover o ponto de retirada (ele já foi removido, ou falta a permissão de catálogo).");
    const aviso = await avisarALoja(dono);
    return {
      dados: { removido: true, nome: carga.nome, para_recriar: carga.antes, ...aviso },
      resumo: `Removeu o ponto de retirada ${carga.nome}`,
    };
  },
};

/* ============================================================ avaliações */

type LinhaDaAvaliacao = {
  id: string;
  catalog_user_id: string;
  product_id: string;
  client_id: string;
  rating: number;
  comment: string | null;
  status: string;
  created_at: string;
  moderated_at: string | null;
};

export const listarAvaliacoes: FerramentaDeLeitura = {
  nome: "listar_avaliacoes",
  titulo: "Listar as avaliações dos produtos",
  descricao:
    "Lista as avaliações que os clientes deixaram nos produtos da loja (nota de 1 a 5 e comentário), das mais novas para as mais antigas, com a situação: pendente (esperando a loja aprovar), aprovada (aparece na loja) ou recusada. Filtre por situação. Para aprovar ou recusar, use moderar_avaliacao com os ids.",
  campos: {
    situacao: { tipo: "opcao", descricao: "Quais avaliações. Padrão: pendentes.", opcoes: Object.keys(FILTROS_DE_AVALIACAO) },
    limite: { tipo: "inteiro", descricao: "Quantas, no máximo. Padrão 30.", min: 1, max: 100 },
  },
  tipo: "leitura",
  permissoes: ["catalog.read"],
  async executar(ctx, args) {
    exigir(ctx, "catalog.read", "ver as avaliações da loja");
    const filtro = FILTROS_DE_AVALIACAO[String(args.situacao ?? "pendentes")] ?? null;
    let consulta = ctx.admin
      .from("product_reviews")
      .select("id,catalog_user_id,product_id,client_id,rating,comment,status,created_at,moderated_at")
      .in("catalog_user_id", ctx.tenantIds)
      .order("created_at", { ascending: false })
      .limit(Number(args.limite ?? 30));
    if (filtro) consulta = consulta.eq("status", filtro);
    const { data, error } = await consulta;
    if (error) falha("ler as avaliações", error);
    const linhas = (data ?? []) as LinhaDaAvaliacao[];

    const produtos = [...new Set(linhas.map((linha) => linha.product_id).filter(Boolean))];
    const clientes = [...new Set(linhas.map((linha) => linha.client_id).filter(Boolean))];
    const [{ data: dosProdutos }, { data: dosClientes }] = await Promise.all([
      produtos.length ? ctx.admin.from("products").select("id,name").in("id", produtos).in("user_id", ctx.tenantIds) : Promise.resolve({ data: [] }),
      clientes.length ? ctx.admin.from("clients").select("id,name").in("id", clientes).in("user_id", ctx.tenantIds) : Promise.resolve({ data: [] }),
    ]);
    const nomeDoProduto = new Map(((dosProdutos ?? []) as Array<{ id: string; name: string | null }>).map((p) => [p.id, p.name]));
    const nomeDoCliente = new Map(((dosClientes ?? []) as Array<{ id: string; name: string | null }>).map((c) => [c.id, c.name]));

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        situacao: String(args.situacao ?? "pendentes"),
        quantas: linhas.length,
        avaliacoes: linhas.map((linha) => ({
          id: linha.id,
          produto: texto(nomeDoProduto.get(linha.product_id) ?? null, 120) ?? "Produto",
          cliente: texto(nomeDoCliente.get(linha.client_id) ?? null, 80) ?? "Cliente",
          nota: linha.rating,
          comentario: texto(linha.comment, 1000),
          situacao: SITUACAO_DA_AVALIACAO[linha.status] ?? linha.status,
          quando: dataBr(linha.created_at),
        })),
      },
      resumo: `Consultou as avaliações (${contagem(linhas.length, "avaliação", "avaliações")})`,
    };
  },
};

type CargaDaModeracao = { ids: string[]; acao: "aprovar" | "recusar" };

const SO_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

export const moderarAvaliacao: FerramentaDeEscrita<CargaDaModeracao> = {
  nome: "moderar_avaliacao",
  titulo: "Aprovar ou recusar avaliações",
  descricao:
    "Aprova (passa a aparecer na página do produto) ou recusa (não aparece) avaliações de clientes, até 50 de uma vez. Pegue os ids em listar_avaliacoes. O comentário do cliente não é alterado.",
  campos: {
    ids: { tipo: "textos", descricao: "Os ids das avaliações, de listar_avaliacoes.", obrigatorio: true, max: 36, minItens: 1, maxItens: 50 },
    acao: { tipo: "opcao", descricao: "aprovar ou recusar.", obrigatorio: true, opcoes: ["aprovar", "recusar"] },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["catalog.reviews.write"],
  async preparar(ctx, args) {
    exigir(ctx, "catalog.reviews.write", "moderar as avaliações da loja");
    const ids = [...new Set((args.ids as string[]).map((id) => id.trim().toLowerCase()))].sort();
    const invalido = ids.find((id) => !SO_UUID.test(id));
    if (invalido) recusar(`"${invalido}" não é um id de avaliação. Use os ids de listar_avaliacoes.`);
    const acao = args.acao as "aprovar" | "recusar";

    const { data, error } = await ctx.admin
      .from("product_reviews")
      .select("id,status,rating")
      .in("id", ids)
      .in("catalog_user_id", ctx.tenantIds);
    if (error) falha("ler as avaliações", error);
    const achadas = (data ?? []) as Array<{ id: string; status: string; rating: number }>;
    const faltam = ids.filter((id) => !achadas.some((linha) => linha.id === id));
    if (faltam.length) recusar(`Não achei nesta loja: ${faltam.slice(0, 5).join(", ")}${faltam.length > 5 ? "…" : ""}. Use os ids de listar_avaliacoes.`);
    const alvo = acao === "aprovar" ? "approved" : "rejected";
    const jaEstao = achadas.filter((linha) => linha.status === alvo).length;
    return {
      previa: {
        o_que_vai_acontecer:
          acao === "aprovar"
            ? `${contagem(ids.length, "avaliação passa", "avaliações passam")} a aparecer na página do produto.`
            : `${contagem(ids.length, "avaliação deixa", "avaliações deixam")} de aparecer na loja.`,
        ...(jaEstao ? { observacao: `${contagem(jaEstao, "já estava", "já estavam")} assim.` } : {}),
      },
      resumo: `Prévia: ${acao} ${contagem(ids.length, "avaliação", "avaliações")}`,
      avisos: [],
      carga: { ids, acao },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.admin
      .from("product_reviews")
      .update(colunasDaModeracao(carga.acao, new Date().toISOString()))
      .in("id", carga.ids)
      .in("catalog_user_id", ctx.tenantIds)
      .select("id,catalog_user_id");
    if (error) falha("moderar as avaliações", error);
    const feitas = (data ?? []) as Array<{ id: string; catalog_user_id: string }>;
    /* A página do produto guarda as avaliações aprovadas por dois minutos. */
    const lojas = [...new Set(feitas.map((linha) => linha.catalog_user_id))];
    let aviso = {};
    for (const loja of lojas) aviso = { ...aviso, ...(await avisarALoja(loja)) };
    return {
      dados: { feito: true, acao: carga.acao, quantas: feitas.length, ...aviso },
      resumo: `${carga.acao === "aprovar" ? "Aprovou" : "Recusou"} ${contagem(feitas.length, "avaliação", "avaliações")}`,
    };
  },
};

export const FERRAMENTAS_DE_REGRAS_E_CONTATO_DA_LOJA: Ferramenta[] = [
  verRegrasEContatoDaLoja,
  alterarRegrasDaLoja,
  abrirOuFecharALoja,
  alterarCabecalhoDaLoja,
  alterarContatoERodape,
  alterarSeoDaLoja,
  alterarCorteDeProducao,
  alterarChatDaLoja,
  criarPontoDeRetirada,
  alterarPontoDeRetirada,
  removerPontoDeRetirada,
  listarAvaliacoes,
  moderarAvaliacao,
] as Ferramenta[];
