import * as XLSX from "xlsx";

import { validateDocument } from "@/lib/document-utils";
import { codigoIbge } from "@/lib/fiscal/servidor/consultas";

import { COMO_ENTREGAR_O_ARQUIVO, entregarArquivo } from "../arquivos";
import type { ContextoDoAssistente } from "../contexto";
import type { Campos } from "../esquema";
import { contagem, dataBr, exigir, falha, hojeEmBrasilia, inicioDoDia, limparBusca, recusar, somarDias, todasAsLinhas } from "./comum";
import {
  celulaDePlanilha,
  documentoFormatado,
  emailValido,
  etiquetasLimpas,
  marcasDoCliente,
  montarCsv,
  soDigitos,
  whatsappNoPadrao,
} from "./operacao-regras";
import type { FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/*
 * Clientes em quantidade: cadastro em lote, atualização de um cadastro e a
 * planilha da carteira. As regras de cada campo são as da tela de cliente
 * (components/clients/client-form-modal.tsx) e as de criar_cliente.
 */

const CONTRIBUINTE = ["contribuinte", "isento", "nao_contribuinte"] as const;

/* Os campos de um cadastro, iguais no lote e na atualização. */
const CAMPOS_DO_CADASTRO: Campos = {
  nome: { tipo: "texto", descricao: "Nome da pessoa (ou do contato, se for empresa).", min: 2, max: 120 },
  empresa: { tipo: "texto", descricao: "Nome da empresa do cliente.", max: 120 },
  tipo: { tipo: "opcao", descricao: "pessoa (CPF) ou empresa (CNPJ). Sem isto, decide pelo documento.", opcoes: ["pessoa", "empresa"] },
  documento: { tipo: "texto", descricao: "CPF ou CNPJ, com ou sem pontuação.", max: 20 },
  email: { tipo: "texto", descricao: "E-mail.", max: 120 },
  telefone: { tipo: "texto", descricao: "Telefone, com DDD.", max: 20 },
  whatsapp: { tipo: "texto", descricao: "Celular com WhatsApp, com DDD. Ex.: 81999990000.", max: 20 },
  cep: { tipo: "texto", descricao: "CEP.", max: 10 },
  rua: { tipo: "texto", descricao: "Rua, avenida.", max: 160 },
  numero: { tipo: "texto", descricao: "Número do endereço.", max: 20 },
  complemento: { tipo: "texto", descricao: "Complemento.", max: 80 },
  bairro: { tipo: "texto", descricao: "Bairro.", max: 80 },
  cidade: { tipo: "texto", descricao: "Cidade.", max: 80 },
  estado: { tipo: "texto", descricao: "Sigla do estado, duas letras. Ex.: PE.", min: 2, max: 2 },
  codigo_ibge: { tipo: "texto", descricao: "Código IBGE da cidade (7 números). Sem isto, é achado pela cidade e estado.", max: 7 },
  contribuinte_icms: {
    tipo: "opcao",
    descricao: "Só para empresa: contribuinte (tem inscrição estadual), isento ou nao_contribuinte. Vai na nota fiscal.",
    opcoes: CONTRIBUINTE,
  },
  inscricao_estadual: { tipo: "texto", descricao: "Inscrição estadual (obrigatória se contribuinte). Pode ser ISENTO.", max: 20 },
  observacoes: { tipo: "texto", descricao: "Anotação interna sobre o cliente.", max: 2000 },
  etiquetas: { tipo: "textos", descricao: "Etiquetas do cliente (substituem as atuais). Ex.: [\"VIP\", \"Atacado\"].", maxItens: 30, max: 40 },
};

type Cadastro = {
  name: string;
  company_name: string | null;
  type: "pf" | "pj";
  document: string | null;
  email: string | null;
  phone: string | null;
  whatsapp: string | null;
  address_zip: string | null;
  address_street: string | null;
  address_number: string | null;
  address_complement: string | null;
  address_neighborhood: string | null;
  address_city: string | null;
  address_state: string | null;
  address_ibge: string | null;
  inscricao_estadual: string | null;
  contribuinte_icms: string | null;
  notes: string | null;
  tags: string[];
};

const COLUNAS_DO_CADASTRO =
  "id,name,company_name,type,document,email,phone,whatsapp,address_zip,address_street,address_number,address_complement,address_neighborhood,address_city,address_state,address_ibge,inscricao_estadual,contribuinte_icms,notes,tags,active";

type ClienteGravado = Cadastro & { id: string; active: boolean | null };

const vazioVira = (valor: unknown) => {
  const texto = typeof valor === "string" ? valor.trim() : valor === undefined || valor === null ? "" : String(valor);
  return texto || null;
};

/**
 * Monta o cadastro a partir do que veio, por cima de um cadastro existente
 * (na atualização) ou do zero (no lote). Devolve o erro em português, ou o
 * cadastro pronto para gravar, com as mesmas regras da tela.
 */
function montarCadastro(args: Record<string, unknown>, base: Cadastro | null, apagar: Set<string>): { ok: true; cadastro: Cadastro } | { ok: false; erro: string } {
  const pega = (campo: string, atual: string | null) => (apagar.has(campo) ? null : args[campo] !== undefined ? vazioVira(args[campo]) : atual);

  const name = (args.nome !== undefined ? vazioVira(args.nome) : base?.name) ?? null;
  if (!name || name.length < 2) return { ok: false, erro: "Falta o nome do cliente." };

  const documentoBruto = apagar.has("documento") ? "" : args.documento !== undefined ? soDigitos(args.documento) : soDigitos(base?.document);
  const type: "pf" | "pj" =
    args.tipo === "empresa" ? "pj" : args.tipo === "pessoa" ? "pf" : args.documento !== undefined && documentoBruto ? (documentoBruto.length === 14 ? "pj" : "pf") : (base?.type ?? (documentoBruto.length === 14 ? "pj" : "pf"));
  /* Só confere o documento que chegou agora (ou o tipo que mudou): cadastro antigo com documento torto não trava outra mudança. */
  if (documentoBruto && (args.documento !== undefined || args.tipo !== undefined || !base) && !validateDocument(documentoBruto, type)) {
    return { ok: false, erro: `O ${type === "pf" ? "CPF" : "CNPJ"} informado não é válido.` };
  }

  const email = (pega("email", base?.email ?? null) ?? "").toLowerCase();
  if (email && !emailValido(email)) return { ok: false, erro: "O e-mail informado não é válido." };

  let whatsapp = base?.whatsapp ?? null;
  if (apagar.has("whatsapp")) whatsapp = null;
  else if (args.whatsapp !== undefined) {
    const lido = whatsappNoPadrao(args.whatsapp);
    if (lido === "invalido") return { ok: false, erro: "O WhatsApp precisa ter DDD e número, com 10 ou 11 dígitos. Ex.: 81999990000." };
    whatsapp = lido;
  }

  const estado = (pega("estado", base?.address_state ?? null) ?? "").toUpperCase();
  if (estado && !/^[A-Z]{2}$/.test(estado)) return { ok: false, erro: "O estado precisa ser a sigla com duas letras. Ex.: PE." };

  const cepDigitos = apagar.has("cep") ? "" : args.cep !== undefined ? soDigitos(args.cep) : soDigitos(base?.address_zip);
  if (cepDigitos && cepDigitos.length !== 8 && args.cep !== undefined) return { ok: false, erro: "O CEP precisa ter 8 números." };

  /* Cidade ou estado mudou sem código novo: o código velho apontaria para a cidade antiga. */
  const lugarMudou = args.cidade !== undefined || args.estado !== undefined || apagar.has("cidade") || apagar.has("estado");
  let ibge = apagar.has("codigo_ibge") ? null : args.codigo_ibge !== undefined ? soDigitos(args.codigo_ibge) : lugarMudou ? null : (base?.address_ibge ?? null);
  if (ibge && ibge.length !== 7) return { ok: false, erro: "O código IBGE da cidade tem 7 números." };
  if (!ibge) ibge = null;

  /* Inscrição estadual e situação no ICMS: só empresa; contribuinte exige a inscrição. */
  let contribuinte = apagar.has("contribuinte_icms") ? null : ((args.contribuinte_icms as string | undefined) ?? base?.contribuinte_icms ?? null);
  const ieBruta = apagar.has("inscricao_estadual") ? "" : args.inscricao_estadual !== undefined ? String(args.inscricao_estadual) : String(base?.inscricao_estadual ?? "");
  const ie = ieBruta.trim().toUpperCase() === "ISENTO" ? "ISENTO" : soDigitos(ieBruta);
  if (type === "pf") contribuinte = null;
  if (type === "pj" && contribuinte === "contribuinte" && !ie) {
    return { ok: false, erro: "Informe a inscrição estadual do cliente contribuinte de ICMS." };
  }

  const tags = apagar.has("etiquetas") ? [] : args.etiquetas !== undefined ? etiquetasLimpas(args.etiquetas) : (base?.tags ?? []);

  return {
    ok: true,
    cadastro: {
      name,
      company_name: pega("empresa", base?.company_name ?? null),
      type,
      /* O que não veio fica exatamente como estava gravado (com ou sem pontuação). */
      document: !documentoBruto ? null : args.documento === undefined && base?.document ? base.document : documentoFormatado(documentoBruto),
      email: email || null,
      phone: pega("telefone", base?.phone ?? null),
      whatsapp,
      address_zip: !cepDigitos ? null : args.cep === undefined && base?.address_zip ? base.address_zip : cepDigitos.replace(/(\d{5})(\d{3})/, "$1-$2"),
      address_street: pega("rua", base?.address_street ?? null),
      address_number: pega("numero", base?.address_number ?? null),
      address_complement: pega("complemento", base?.address_complement ?? null),
      address_neighborhood: pega("bairro", base?.address_neighborhood ?? null),
      address_city: pega("cidade", base?.address_city ?? null),
      address_state: estado || null,
      address_ibge: ibge,
      inscricao_estadual: type === "pj" && contribuinte === "contribuinte" && ie ? ie : null,
      contribuinte_icms: type === "pj" && contribuinte ? contribuinte : null,
      notes: pega("observacoes", base?.notes ?? null),
      tags,
    },
  };
}

/** O código IBGE que a nota fiscal pede, como a tela resolve: pelo CEP ou pela cidade. Sem resposta, fica vazio. */
async function completarIbge(cadastro: Cadastro, usarCep: boolean) {
  if (cadastro.address_ibge || !cadastro.address_city || !cadastro.address_state) return;
  try {
    const achado = await codigoIbge({ cep: usarCep ? cadastro.address_zip : null, cidade: cadastro.address_city, uf: cadastro.address_state });
    const codigo = soDigitos(achado?.ibge);
    if (codigo.length === 7) cadastro.address_ibge = codigo;
  } catch {
    /* Não trava o cadastro: a pessoa completa depois, como na tela. */
  }
}

function resumoDoCadastro(c: Cadastro) {
  return {
    nome: c.name,
    empresa: c.company_name,
    tipo: c.type === "pj" ? "empresa" : "pessoa",
    documento: c.document,
    email: c.email,
    telefone: c.phone,
    whatsapp: c.whatsapp,
    endereco: [c.address_street, c.address_number, c.address_complement, c.address_neighborhood].filter(Boolean).join(", ") || null,
    cidade: [c.address_city, c.address_state].filter(Boolean).join(" / ") || null,
    cep: c.address_zip,
    ...(c.type === "pj" ? { contribuinte_icms: c.contribuinte_icms, inscricao_estadual: c.inscricao_estadual } : {}),
    etiquetas: c.tags,
    observacoes: c.notes,
  };
}

type Existente = { id: string; name: string | null; company_name: string | null; document: string | null; email: string | null; whatsapp: string | null; phone: string | null };

async function clientesExistentes(ctx: ContextoDoAssistente) {
  const { linhas } = await todasAsLinhas<Existente>(
    (de, ate) =>
      ctx.supabase
        .from("clients")
        .select("id,name,company_name,document,email,whatsapp,phone")
        .in("user_id", ctx.tenantIds)
        .not("is_deleted", "is", true)
        .not("is_system_placeholder", "is", true)
        .order("id")
        .range(de, ate),
    "conferir os clientes já cadastrados",
    50_000
  );
  return linhas;
}

/* ------------------------------------------------------------ lote */

type CargaDoLote = { cadastros: Cadastro[] };

const NOME_DA_MARCA = { documento: "mesmo CPF/CNPJ", email: "mesmo e-mail", telefone: "mesmo telefone" } as const;

export const criarClientesEmLote: FerramentaDeEscrita<CargaDoLote> = {
  nome: "criar_clientes_em_lote",
  titulo: "Cadastrar vários clientes",
  descricao:
    "Cadastra até 100 clientes de uma vez (de uma planilha, de uma lista). Cada um passa pelas mesmas conferências de criar_cliente. Quem já existe (mesmo CPF/CNPJ, e-mail ou telefone) ou se repete na própria lista é pulado, e a resposta diz quem entrou e quem ficou de fora, e por quê.",
  campos: {
    clientes: { tipo: "lista", descricao: "Os clientes a cadastrar. Só nome é obrigatório.", itens: { ...CAMPOS_DO_CADASTRO, nome: { ...CAMPOS_DO_CADASTRO.nome, obrigatorio: true } }, minItens: 1, maxItens: 100 },
    cadastrar_parecidos: {
      tipo: "booleano",
      descricao: "Cadastrar mesmo quem tem o e-mail ou o telefone de alguém que já existe. O padrão é não (pula). Documento repetido é sempre pulado.",
    },
  },
  tipo: "escrita",
  permissoes: ["clients.list.write"],
  async preparar(ctx, args) {
    exigir(ctx, "clients.list.write", "cadastrar clientes");
    const lista = args.clientes as Record<string, unknown>[];
    const cadastrarParecidos = args.cadastrar_parecidos === true;

    const existentes = await clientesExistentes(ctx);
    const conhecidas = new Map<string, Existente>();
    for (const cliente of existentes) for (const marca of marcasDoCliente(cliente)) if (!conhecidas.has(marca.valor)) conhecidas.set(marca.valor, cliente);
    const naLista = new Map<string, number>();

    const cadastros: Cadastro[] = [];
    const pulados: Array<{ linha: number; nome: string; motivo: string }> = [];
    for (let i = 0; i < lista.length; i += 1) {
      const linha = i + 1;
      const montado = montarCadastro(lista[i], null, new Set());
      const nome = String(lista[i].empresa || lista[i].nome || "").trim();
      if (!montado.ok) {
        pulados.push({ linha, nome, motivo: montado.erro });
        continue;
      }
      const marcas = marcasDoCliente(montado.cadastro).filter((m) => m.tipo === "documento" || !cadastrarParecidos);
      const jaExiste = marcas.map((m) => ({ m, quem: conhecidas.get(m.valor) })).find((x) => x.quem);
      if (jaExiste?.quem) {
        pulados.push({
          linha,
          nome,
          motivo: `Já cadastrado (${NOME_DA_MARCA[jaExiste.m.tipo]}): ${jaExiste.quem.company_name || jaExiste.quem.name} (id ${jaExiste.quem.id}).`,
        });
        continue;
      }
      const repetido = marcas.find((m) => naLista.has(m.valor));
      if (repetido) {
        pulados.push({ linha, nome, motivo: `Repete a linha ${naLista.get(repetido.valor)} desta lista (${NOME_DA_MARCA[repetido.tipo]}).` });
        continue;
      }
      for (const m of marcas) naLista.set(m.valor, linha);
      cadastros.push(montado.cadastro);
    }

    /* O código IBGE pela cidade: a lista de cidades de cada estado fica guardada, então é rápido. */
    for (const cadastro of cadastros) await completarIbge(cadastro, false);

    return {
      previa: {
        o_que_vai_acontecer: cadastros.length
          ? `${contagem(cadastros.length, "cliente novo será cadastrado", "clientes novos serão cadastrados")}.`
          : "Nenhum cliente será cadastrado: todos foram pulados.",
        vao_entrar: cadastros.map((c) => ({ nome: c.company_name || c.name, documento: c.document, cidade: [c.address_city, c.address_state].filter(Boolean).join(" / ") || null })),
        pulados,
      },
      resumo: `Prévia do cadastro em lote: ${cadastros.length} novos, ${pulados.length} pulados`,
      avisos: pulados.length ? [`${contagem(pulados.length, "linha ficou de fora", "linhas ficaram de fora")}; veja "pulados".`] : [],
      carga: { cadastros },
    };
  },
  async gravar(ctx, carga) {
    if (!carga.cadastros.length) {
      return { dados: { criados: 0, clientes: [] }, resumo: "Cadastro em lote sem nenhum cliente novo" };
    }
    const agora = new Date().toISOString();
    const { data, error } = await ctx.supabase
      .from("clients")
      .insert(carga.cadastros.map((c) => ({ ...c, active: true, whatsapp_notify: true, user_id: ctx.donoDosDados, created_at: agora, updated_at: agora })))
      .select("id,name,company_name");
    if (error) falha("cadastrar os clientes", error);
    const criados = ((data ?? []) as Array<{ id: string; name: string | null; company_name: string | null }>).map((c) => ({ cliente_id: c.id, nome: c.company_name || c.name }));
    return {
      dados: { criados: criados.length, clientes: criados },
      resumo: `Cadastrou ${contagem(criados.length, "cliente", "clientes")} em lote`,
    };
  },
};

/* ------------------------------------------------------------ atualizar */

type CargaDaAtualizacao = { id: string; cadastro: Cadastro & { active: boolean } };

const APAGAVEIS_DO_CLIENTE = [
  "empresa",
  "documento",
  "email",
  "telefone",
  "whatsapp",
  "cep",
  "rua",
  "numero",
  "complemento",
  "bairro",
  "cidade",
  "estado",
  "codigo_ibge",
  "contribuinte_icms",
  "inscricao_estadual",
  "observacoes",
  "etiquetas",
];

export const atualizarCliente: FerramentaDeEscrita<CargaDaAtualizacao> = {
  nome: "atualizar_cliente",
  titulo: "Atualizar o cadastro de um cliente",
  descricao:
    "Muda o cadastro de um cliente: contato, endereço, documento, dados fiscais (inscrição estadual, contribuinte de ICMS, código IBGE da cidade), observações, etiquetas, ou se está ativo. Mande só o que muda; para esvaziar um campo, use apagar. Ache o cliente com buscar_clientes.",
  campos: {
    cliente_id: { tipo: "id", descricao: "Id do cliente (de buscar_clientes).", obrigatorio: true },
    ...CAMPOS_DO_CADASTRO,
    ativo: { tipo: "booleano", descricao: "false deixa o cliente inativo (some das escolhas); true reativa." },
    apagar: { tipo: "textos", descricao: `Campos a esvaziar: ${APAGAVEIS_DO_CLIENTE.join(", ")}.`, maxItens: 17, max: 30 },
  },
  tipo: "escrita",
  sobrescreve: true,
  /* Contato e documento são por onde a nota, a cobrança e o WhatsApp chegam
     ao cliente: trocar ou apagar passa pela aprovação da pessoa. */
  sensivel: (args) => {
    const delicados = ["email", "whatsapp", "telefone", "documento", "tipo"];
    const apagar = ((args.apagar as string[] | undefined) ?? []).map((n) => String(n).trim().toLowerCase());
    return delicados.some((campo) => args[campo] !== undefined || apagar.includes(campo));
  },
  permissoes: ["clients.list.write"],
  async preparar(ctx, args) {
    exigir(ctx, "clients.list.write", "alterar clientes");
    const { data, error } = await ctx.supabase
      .from("clients")
      .select(`${COLUNAS_DO_CADASTRO},is_deleted,is_system_placeholder`)
      .eq("id", String(args.cliente_id))
      .in("user_id", ctx.tenantIds)
      .maybeSingle();
    if (error) falha("ler o cliente", error);
    const atual = data as (ClienteGravado & { is_deleted?: boolean | null; is_system_placeholder?: boolean | null }) | null;
    if (!atual || atual.is_deleted) recusar("Cliente não encontrado. Use buscar_clientes para achar o id certo.");
    if (atual.is_system_placeholder) recusar("Este é o cliente padrão do sistema (venda sem cliente) e não se edita.");

    const apagar = new Set(((args.apagar as string[] | undefined) ?? []).map((n) => n.trim().toLowerCase()));
    for (const nome of apagar) {
      if (!APAGAVEIS_DO_CLIENTE.includes(nome)) recusar(`Não dá para apagar "${nome}". Use um destes: ${APAGAVEIS_DO_CLIENTE.join(", ")}.`);
    }
    const base: Cadastro = { ...atual, type: atual.type === "pj" ? "pj" : "pf", tags: Array.isArray(atual.tags) ? atual.tags : [] };
    const montado = montarCadastro(args, base, apagar);
    if (!montado.ok) recusar(montado.erro);
    const cadastro = montado.cadastro;

    const mudou = (campo: keyof Cadastro) => JSON.stringify(cadastro[campo] ?? null) !== JSON.stringify(base[campo] ?? null);
    if (mudou("address_city") || mudou("address_state") || mudou("address_zip")) await completarIbge(cadastro, true);

    /* Documento novo não pode ser de outro cliente da empresa. */
    const digitos = soDigitos(cadastro.document);
    if (digitos && digitos !== soDigitos(base.document)) {
      const { data: repetidos } = await ctx.supabase
        .from("clients")
        .select("id,name,company_name")
        .in("user_id", ctx.tenantIds)
        .in("document", [digitos, documentoFormatado(digitos)])
        .neq("id", atual.id)
        .not("is_deleted", "is", true)
        .limit(1);
      const repetido = (repetidos ?? [])[0] as { id: string; name: string | null; company_name: string | null } | undefined;
      if (repetido) recusar(`Já existe outro cliente com este documento: ${repetido.company_name || repetido.name} (id ${repetido.id}).`);
    }

    const ativo = args.ativo !== undefined ? args.ativo === true : atual.active !== false;
    const campos = (Object.keys(cadastro) as Array<keyof Cadastro>).filter(mudou);
    if (!campos.length && ativo === (atual.active !== false)) recusar("Nada muda: os dados mandados são iguais ao cadastro atual.");

    const antes = resumoDoCadastro(base);
    const depois = resumoDoCadastro(cadastro);
    const mudancas: Record<string, { antes: unknown; depois: unknown }> = {};
    for (const chave of Object.keys(depois) as Array<keyof typeof depois>) {
      if (JSON.stringify(antes[chave] ?? null) !== JSON.stringify(depois[chave] ?? null)) mudancas[chave] = { antes: antes[chave] ?? null, depois: depois[chave] ?? null };
    }
    if (mudou("address_ibge")) mudancas.codigo_ibge = { antes: base.address_ibge, depois: cadastro.address_ibge };
    if (ativo !== (atual.active !== false)) mudancas.ativo = { antes: atual.active !== false, depois: ativo };

    const avisos: string[] = [];
    if (cadastro.address_city && cadastro.address_state && !cadastro.address_ibge) {
      avisos.push("Não achei o código IBGE desta cidade. Ele é preciso para a nota fiscal; confira o nome da cidade ou mande codigo_ibge.");
    }
    return {
      previa: { o_que_vai_acontecer: `O cadastro de ${base.company_name || base.name} será atualizado.`, mudancas },
      resumo: `Prévia da atualização do cliente ${base.company_name || base.name}`,
      avisos,
      carga: { id: atual.id, cadastro: { ...cadastro, active: ativo } },
    };
  },
  async gravar(ctx, carga) {
    const { error } = await ctx.supabase
      .from("clients")
      .update({ ...carga.cadastro, updated_at: new Date().toISOString() })
      .eq("id", carga.id)
      .in("user_id", ctx.tenantIds);
    if (error) falha("atualizar o cliente", error);
    const nome = carga.cadastro.company_name || carga.cadastro.name;
    return { dados: { atualizado: true, cliente_id: carga.id, nome }, resumo: `Atualizou o cadastro do cliente ${nome}` };
  },
};

/* ------------------------------------------------------------ exportar */

export const exportarClientes: FerramentaDeLeitura = {
  nome: "exportar_clientes",
  titulo: "Exportar os clientes",
  descricao:
    "Gera uma planilha (Excel ou CSV) com os clientes da empresa, com contato, endereço, documento, dados fiscais e etiquetas, e devolve um link para baixar (vale 24 horas). Dá para filtrar por busca, cidade, estado, etiqueta, tipo, ativos e data de cadastro.",
  campos: {
    formato: { tipo: "opcao", descricao: "xlsx (Excel, padrão) ou csv.", opcoes: ["xlsx", "csv"] },
    busca: { tipo: "texto", descricao: "Nome, empresa ou e-mail.", max: 80 },
    cidade: { tipo: "texto", descricao: "Só desta cidade.", max: 80 },
    estado: { tipo: "texto", descricao: "Só deste estado (sigla).", min: 2, max: 2 },
    etiqueta: { tipo: "texto", descricao: "Só quem tem esta etiqueta.", max: 40 },
    tipo: { tipo: "opcao", descricao: "pessoa ou empresa.", opcoes: ["pessoa", "empresa"] },
    situacao: { tipo: "opcao", descricao: "ativos (padrão), inativos ou todos.", opcoes: ["ativos", "inativos", "todos"] },
    cadastrados_desde: { tipo: "data", descricao: "Cadastrados a partir deste dia." },
    cadastrados_ate: { tipo: "data", descricao: "Cadastrados até este dia." },
  },
  tipo: "leitura",
  permissoes: ["clients.list.read"],
  async executar(ctx, args) {
    exigir(ctx, "clients.list.read", "ver os clientes");
    const busca = limparBusca(args.busca);
    const situacao = (args.situacao as string | undefined) ?? "ativos";

    const { linhas, cortado } = await todasAsLinhas<ClienteGravado & { created_at: string | null }>(
      (de, ate) => {
        let consulta = ctx.supabase
          .from("clients")
          .select(`${COLUNAS_DO_CADASTRO},created_at`)
          .in("user_id", ctx.tenantIds)
          .not("is_deleted", "is", true)
          .not("is_system_placeholder", "is", true);
        if (busca) consulta = consulta.or(`name.ilike.%${busca}%,company_name.ilike.%${busca}%,email.ilike.%${busca}%`);
        if (args.cidade) consulta = consulta.ilike("address_city", limparBusca(args.cidade));
        if (args.estado) consulta = consulta.eq("address_state", String(args.estado).toUpperCase());
        if (args.etiqueta) consulta = consulta.contains("tags", [String(args.etiqueta)]);
        if (args.tipo) consulta = consulta.eq("type", args.tipo === "empresa" ? "pj" : "pf");
        if (situacao === "ativos") consulta = consulta.not("active", "is", false);
        if (situacao === "inativos") consulta = consulta.eq("active", false);
        if (args.cadastrados_desde) consulta = consulta.gte("created_at", inicioDoDia(String(args.cadastrados_desde)));
        if (args.cadastrados_ate) consulta = consulta.lt("created_at", inicioDoDia(somarDias(String(args.cadastrados_ate), 1)));
        return consulta.order("name", { ascending: true }).order("id").range(de, ate);
      },
      "ler os clientes",
      20_000
    );
    if (!linhas.length) recusar("Nenhum cliente com esses filtros. Nada foi exportado.");

    const cabecalho = [
      "Nome",
      "Empresa",
      "Tipo",
      "CPF/CNPJ",
      "E-mail",
      "Telefone",
      "WhatsApp",
      "CEP",
      "Endereço",
      "Número",
      "Complemento",
      "Bairro",
      "Cidade",
      "UF",
      "Código IBGE",
      "Contribuinte ICMS",
      "Inscrição estadual",
      "Etiquetas",
      "Observações",
      "Ativo",
      "Cadastrado em",
      "Id",
    ];
    const ICMS: Record<string, string> = { contribuinte: "Contribuinte", isento: "Isento", nao_contribuinte: "Não contribuinte" };
    const tabela = linhas.map((c) => [
      c.name,
      c.company_name,
      c.type === "pj" ? "Empresa" : "Pessoa",
      c.document,
      c.email,
      c.phone,
      c.whatsapp,
      c.address_zip,
      c.address_street,
      c.address_number,
      c.address_complement,
      c.address_neighborhood,
      c.address_city,
      c.address_state,
      c.address_ibge,
      ICMS[c.contribuinte_icms ?? ""] ?? "",
      c.inscricao_estadual,
      Array.isArray(c.tags) ? c.tags.join(", ") : "",
      c.notes,
      c.active === false ? "Não" : "Sim",
      c.created_at ? dataBr(c.created_at) : "",
      c.id,
    ]);

    const formato = (args.formato as string | undefined) ?? "xlsx";
    const nome = `clientes-${hojeEmBrasilia()}`;
    let arquivo;
    if (formato === "csv") {
      arquivo = await entregarArquivo(ctx, { conteudo: montarCsv(cabecalho, tabela), nome: `${nome}.csv`, tipo: "text/csv; charset=utf-8" });
    } else {
      const planilha = XLSX.utils.aoa_to_sheet([cabecalho, ...tabela.map((linha) => linha.map(celulaDePlanilha))]);
      const livro = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(livro, planilha, "Clientes");
      const conteudo = XLSX.write(livro, { type: "buffer", bookType: "xlsx" }) as Buffer;
      arquivo = await entregarArquivo(ctx, { conteudo, nome: `${nome}.xlsx`, tipo: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
    }

    return {
      dados: {
        ...arquivo,
        clientes: linhas.length,
        ...(cortado ? { aviso: "A planilha parou em 20.000 clientes. Use filtros para exportar o resto." } : {}),
        como_entregar: COMO_ENTREGAR_O_ARQUIVO,
      },
      resumo: `Exportou ${contagem(linhas.length, "cliente", "clientes")} (${formato.toUpperCase()})`,
    };
  },
};

export const FERRAMENTAS_DE_CLIENTES_EM_QUANTIDADE = [criarClientesEmLote, atualizarCliente, exportarClientes];
