import type { CompanyPermission } from "@/lib/company-context";
import {
  ENTIDADES,
  LIMITES,
  ROTULO_DA_ENTIDADE,
  ROTULO_DO_TIPO,
  TABELA_DA_ENTIDADE,
  TIPOS,
  camposParaLer,
  chaveDoRotulo,
  chaveUnica,
  conferirDefinicao,
  conferirFiltro,
  conferirValores,
  formatarValor,
  semAcento,
  valorCombina,
  valoresDaLinha,
  type DefinicaoDeCampo,
  type EntidadeDoCampo,
  type FiltroDoCampo,
  type TipoDoCampo,
  type ValorDeCampo,
} from "@/lib/campos-proprios";
import { lerDefinicoes } from "@/lib/campos-proprios-do-banco";

import type { ContextoDoAssistente } from "../contexto";
import { AVISO_DE_DADOS, SITUACAO_DO_ORCAMENTO, SITUACAO_DO_PEDIDO, contagem, dinheiro, exigir, falha, limparBusca, nomeDoCliente, recusar, todasAsLinhas, um } from "./comum";
import type { Ferramenta, FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/*
  Campos próprios pelo assistente (migration 187): os campos a mais que a
  empresa criou para cliente, orçamento e pedido. Listar, criar e alterar os
  campos; preencher os valores num cadastro; e procurar cadastros pelo valor
  ("aniversariantes do mês", "pedidos com entrega técnica").

  As regras de cada tipo moram em lib/campos-proprios.ts, as mesmas do painel.
  Nada aqui apaga: desligar um campo esconde, e o valor fica.
*/

const PERMISSAO_DE_LER: Record<EntidadeDoCampo, CompanyPermission> = {
  cliente: "clients.list.read",
  orcamento: "quotes.read",
  pedido: "orders.read",
};
const PERMISSAO_DE_GRAVAR: Record<EntidadeDoCampo, CompanyPermission> = {
  cliente: "clients.list.write",
  orcamento: "quotes.write",
  pedido: "orders.write",
};
const O_QUE: Record<EntidadeDoCampo, string> = { cliente: "os clientes", orcamento: "os orçamentos", pedido: "os pedidos" };

const CAMPO_ENTIDADE = {
  tipo: "opcao",
  descricao: "De qual cadastro: cliente, orcamento ou pedido.",
  opcoes: ENTIDADES,
  obrigatorio: true,
} as const;

function lerEntidade(valor: unknown): EntidadeDoCampo {
  return valor as EntidadeDoCampo;
}

async function definicoesDa(ctx: ContextoDoAssistente, entidade?: EntidadeDoCampo) {
  const { definicoes, erro } = await lerDefinicoes(ctx.supabase, entidade);
  if (erro) falha("ler os campos próprios", { message: erro });
  return definicoes;
}

const comparavel = (texto: string) => semAcento(texto).toLowerCase().replace(/\s+/g, " ").trim();

/** O campo pela chave ("quem_indicou") ou pelo nome ("Quem indicou"). */
function acharCampo(definicoes: DefinicaoDeCampo[], texto: string, entidade: EntidadeDoCampo): DefinicaoDeCampo {
  const procurado = String(texto ?? "").trim();
  const achado =
    definicoes.find((def) => def.chave === procurado) ??
    definicoes.find((def) => comparavel(def.rotulo) === comparavel(procurado)) ??
    definicoes.find((def) => def.chave === chaveDoRotulo(procurado));
  if (!achado) {
    const nomes = definicoes.map((def) => `${def.rotulo} (${def.chave})`).join(", ");
    recusar(
      definicoes.length
        ? `Não existe o campo "${procurado}" em ${ROTULO_DA_ENTIDADE[entidade].toLowerCase()}. Campos existentes: ${nomes}.`
        : `${ROTULO_DA_ENTIDADE[entidade]} ainda não tem campos próprios. Crie com criar_campo_proprio.`
    );
  }
  return achado;
}

function definicaoParaLer(def: DefinicaoDeCampo) {
  return {
    chave: def.chave,
    nome: def.rotulo,
    tipo: def.tipo,
    tipo_por_extenso: ROTULO_DO_TIPO[def.tipo],
    ...(def.tipo === "opcoes" ? { opcoes: def.opcoes } : {}),
    obrigatorio: def.obrigatorio,
    ligado: def.ativo,
    ...(def.ajuda ? { explicacao: def.ajuda } : {}),
  };
}

const COMO_MANDAR_VALOR =
  "Valores como texto: data em AAAA-MM-DD ou DD/MM/AAAA; sim_nao como \"sim\" ou \"não\"; numero e moeda como \"1500,50\"; opcoes com uma das opções do campo.";

/* -------------------------------------------------------------- o registro */

type Registro = { id: string; nome: string; valores: ReturnType<typeof valoresDaLinha> };

const ARGUMENTOS_DO_REGISTRO = {
  registro_id: { tipo: "id", descricao: "Id do cliente, orçamento ou pedido (de buscar_clientes, listar_orcamentos, listar_pedidos)." },
  numero: { tipo: "inteiro", descricao: "Número do orçamento ou do pedido, como aparece no painel.", min: 1 },
  cliente: { tipo: "texto", descricao: "Para cliente: nome, empresa ou e-mail. Precisa achar um cliente só.", max: 80 },
} as const;

async function acharRegistro(ctx: ContextoDoAssistente, entidade: EntidadeDoCampo, args: Record<string, unknown>): Promise<Registro> {
  const tabela = TABELA_DA_ENTIDADE[entidade];
  if (entidade === "cliente") {
    let consulta = ctx.supabase
      .from("clients")
      .select("id,name,company_name,campos_proprios")
      .in("user_id", ctx.tenantIds)
      .not("is_deleted", "is", true)
      .not("is_system_placeholder", "is", true)
      .limit(6);
    if (args.registro_id) consulta = consulta.eq("id", String(args.registro_id));
    else {
      const busca = limparBusca(args.cliente);
      if (!busca) recusar("Informe registro_id (o id do cliente) ou cliente (o nome).");
      consulta = consulta.or(`name.ilike.%${busca}%,company_name.ilike.%${busca}%,email.ilike.%${busca}%`);
    }
    const { data, error } = await consulta;
    if (error) falha("achar o cliente", error);
    const lista = (data ?? []) as Array<{ id: string; name: string | null; company_name: string | null; campos_proprios: unknown }>;
    if (!lista.length) recusar("Cliente não encontrado. Use buscar_clientes para achar o id certo.");
    if (lista.length > 1) {
      recusar(
        `Mais de um cliente com esse nome: ${lista
          .slice(0, 5)
          .map((c) => `${nomeDoCliente(c)} (id ${c.id})`)
          .join("; ")}. Mande o registro_id do cliente certo.`
      );
    }
    const cliente = lista[0];
    return { id: cliente.id, nome: nomeDoCliente(cliente) ?? "cliente", valores: valoresDaLinha(cliente.campos_proprios) };
  }

  if (!args.registro_id && !args.numero) recusar(`Informe registro_id ou numero do ${entidade === "pedido" ? "pedido" : "orçamento"}.`);
  let consulta = ctx.supabase.from(tabela).select("id,number,campos_proprios").in("user_id", ctx.tenantIds).order("created_at", { ascending: false }).limit(1);
  consulta = args.registro_id ? consulta.eq("id", String(args.registro_id)) : consulta.eq("number", Number(args.numero));
  const { data, error } = await consulta;
  if (error) falha(`achar o ${entidade === "pedido" ? "pedido" : "orçamento"}`, error);
  const linha = ((data ?? []) as Array<{ id: string; number: number | null; campos_proprios: unknown }>)[0];
  if (!linha) recusar(`${entidade === "pedido" ? "Pedido" : "Orçamento"} não encontrado. Use ${entidade === "pedido" ? "listar_pedidos" : "listar_orcamentos"} para achar o certo.`);
  return {
    id: linha.id,
    nome: `${entidade === "pedido" ? "pedido" : "orçamento"} #${linha.number ?? "?"}`,
    valores: valoresDaLinha(linha.campos_proprios),
  };
}

/* ---------------------------------------------------------------- listar */

export const listarCamposProprios: FerramentaDeLeitura = {
  nome: "listar_campos_proprios",
  titulo: "Ver os campos próprios",
  descricao:
    "Lista os campos a mais que a empresa criou para cliente, orçamento e pedido (ex.: Aniversário, Quem indicou, Nome do evento), com a chave, o tipo e as opções. Use antes de preencher_campos ou buscar_por_campo. Sem entidade, lista os três.",
  campos: {
    entidade: { tipo: "opcao", descricao: "cliente, orcamento ou pedido. Sem isto, todos.", opcoes: ENTIDADES },
    incluir_desligados: { tipo: "booleano", descricao: "Mostrar também os campos desligados. O padrão é não." },
  },
  tipo: "leitura",
  permissoes: [],
  async executar(ctx, args) {
    const entidade = args.entidade ? lerEntidade(args.entidade) : undefined;
    const todos = await definicoesDa(ctx, entidade);
    const lista = args.incluir_desligados === true ? todos : todos.filter((def) => def.ativo);
    const porEntidade: Record<string, unknown[]> = {};
    for (const e of entidade ? [entidade] : ENTIDADES) porEntidade[e] = lista.filter((def) => def.entidade === e).map(definicaoParaLer);
    return {
      dados: {
        campos: porEntidade,
        tipos_possiveis: Object.fromEntries(TIPOS.map((tipo) => [tipo, ROTULO_DO_TIPO[tipo]])),
        como_preencher: COMO_MANDAR_VALOR,
      },
      resumo: `Consultou os campos próprios${entidade ? ` de ${ROTULO_DA_ENTIDADE[entidade].toLowerCase()}` : ""} (${contagem(lista.length, "campo", "campos")})`,
    };
  },
};

/* ------------------------------------------------------------------ criar */

type CargaDoCampoNovo = {
  entidade: EntidadeDoCampo;
  chave: string;
  rotulo: string;
  tipo: TipoDoCampo;
  opcoes: string[];
  obrigatorio: boolean;
  ajuda: string | null;
  posicao: number;
};

export const criarCampoProprio: FerramentaDeEscrita<CargaDoCampoNovo> = {
  nome: "criar_campo_proprio",
  titulo: "Criar um campo próprio",
  descricao:
    "Cria um campo a mais no cadastro de cliente, no orçamento ou no pedido. Ele passa a aparecer nos formulários do painel. Tipos: texto, texto_longo, numero, moeda, data, sim_nao, opcoes (com a lista de opções).",
  campos: {
    entidade: CAMPO_ENTIDADE,
    nome: { tipo: "texto", descricao: "Nome do campo como a equipe vai ver. Ex.: Quem indicou.", obrigatorio: true, max: LIMITES.rotulo },
    tipo: { tipo: "opcao", descricao: "O tipo do valor. Não muda depois de criado.", opcoes: TIPOS, obrigatorio: true },
    opcoes: { tipo: "textos", descricao: "Para tipo opcoes: a lista de opções (pelo menos 2).", max: LIMITES.opcao, maxItens: LIMITES.opcoes },
    obrigatorio: { tipo: "booleano", descricao: "Se o painel exige o campo para salvar o cadastro. O padrão é não." },
    explicacao: { tipo: "texto", descricao: "Frase curta que aparece embaixo do campo.", max: LIMITES.ajuda },
  },
  tipo: "escrita",
  permissoes: ["company.settings"],
  async preparar(ctx, args) {
    exigir(ctx, "company.settings", "criar campos próprios");
    const entidade = lerEntidade(args.entidade);
    const conferido = conferirDefinicao({ rotulo: args.nome, tipo: args.tipo, opcoes: args.opcoes, obrigatorio: args.obrigatorio, ajuda: args.explicacao });
    if (!conferido.ok) recusar(conferido.erro);
    const existentes = await definicoesDa(ctx, entidade);
    const repetido = existentes.find((def) => comparavel(def.rotulo) === comparavel(conferido.definicao.rotulo));
    if (repetido) {
      recusar(
        `Já existe o campo "${repetido.rotulo}" (chave ${repetido.chave}) em ${ROTULO_DA_ENTIDADE[entidade].toLowerCase()}.${repetido.ativo ? "" : " Ele está desligado: use alterar_campo_proprio com ligado=true."}`
      );
    }
    if (existentes.length >= LIMITES.camposPorEntidade) recusar(`Cada cadastro aceita até ${LIMITES.camposPorEntidade} campos.`);
    const carga: CargaDoCampoNovo = {
      entidade,
      chave: chaveUnica(conferido.definicao.rotulo, existentes.map((def) => def.chave)),
      ...conferido.definicao,
      posicao: existentes.length ? Math.max(...existentes.map((def) => def.posicao)) + 1 : 0,
    };
    return {
      previa: {
        o_que_vai_acontecer: `Um campo novo vai aparecer no ${ROTULO_DA_ENTIDADE[entidade].toLowerCase()}.`,
        campo: { nome: carga.rotulo, chave: carga.chave, tipo: ROTULO_DO_TIPO[carga.tipo], opcoes: carga.opcoes.length ? carga.opcoes : undefined, obrigatorio: carga.obrigatorio },
      },
      resumo: `Prévia do campo "${carga.rotulo}" em ${ROTULO_DA_ENTIDADE[entidade].toLowerCase()}`,
      avisos: [],
      carga,
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase
      .from("campos_proprios")
      .insert({
        company_id: ctx.donoDosDados,
        entidade: carga.entidade,
        chave: carga.chave,
        rotulo: carga.rotulo,
        tipo: carga.tipo,
        opcoes: carga.opcoes,
        obrigatorio: carga.obrigatorio,
        ajuda: carga.ajuda,
        posicao: carga.posicao,
      })
      .select("id")
      .maybeSingle();
    if (error || !data) falha("criar o campo", error);
    return {
      dados: { criado: true, entidade: carga.entidade, chave: carga.chave, nome: carga.rotulo, tipo: carga.tipo },
      resumo: `Criou o campo "${carga.rotulo}" em ${ROTULO_DA_ENTIDADE[carga.entidade].toLowerCase()}`,
    };
  },
};

/* ---------------------------------------------------------------- alterar */

type CargaDaAlteracao = { id: string; rotuloAntes: string; entidade: EntidadeDoCampo; mudancas: Record<string, unknown> };

export const alterarCampoProprio: FerramentaDeEscrita<CargaDaAlteracao> = {
  nome: "alterar_campo_proprio",
  titulo: "Alterar um campo próprio",
  descricao:
    "Muda um campo próprio: o nome, as opções (a lista nova substitui a antiga), se é obrigatório, a explicação, ou liga/desliga. Desligar esconde o campo dos formulários sem apagar o que já foi preenchido. O tipo não muda.",
  campos: {
    entidade: CAMPO_ENTIDADE,
    campo: { tipo: "texto", descricao: "Chave ou nome atual do campo (de listar_campos_proprios).", obrigatorio: true, max: 80 },
    novo_nome: { tipo: "texto", descricao: "Nome novo. A chave continua a mesma.", max: LIMITES.rotulo },
    opcoes: { tipo: "textos", descricao: "Para tipo opcoes: a lista completa nova.", max: LIMITES.opcao, maxItens: LIMITES.opcoes },
    obrigatorio: { tipo: "booleano", descricao: "Exigir ou não o campo no painel." },
    explicacao: { tipo: "texto", descricao: "Frase curta embaixo do campo. Mande um espaço para tirar.", max: LIMITES.ajuda },
    ligado: { tipo: "booleano", descricao: "true liga, false desliga." },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["company.settings"],
  async preparar(ctx, args) {
    exigir(ctx, "company.settings", "alterar campos próprios");
    const entidade = lerEntidade(args.entidade);
    const definicoes = await definicoesDa(ctx, entidade);
    const campo = acharCampo(definicoes, String(args.campo), entidade);
    const conferido = conferirDefinicao({
      rotulo: args.novo_nome ?? campo.rotulo,
      tipo: campo.tipo,
      opcoes: args.opcoes ?? campo.opcoes,
      obrigatorio: args.obrigatorio ?? campo.obrigatorio,
      ajuda: args.explicacao !== undefined ? args.explicacao : campo.ajuda ?? "",
    });
    if (!conferido.ok) recusar(conferido.erro);
    if (args.novo_nome) {
      const repetido = definicoes.find((def) => def.id !== campo.id && comparavel(def.rotulo) === comparavel(conferido.definicao.rotulo));
      if (repetido) recusar(`Já existe outro campo chamado "${repetido.rotulo}".`);
    }

    const mudancas: Record<string, unknown> = {};
    const antes: Record<string, unknown> = {};
    if (conferido.definicao.rotulo !== campo.rotulo) {
      mudancas.rotulo = conferido.definicao.rotulo;
      antes.nome = campo.rotulo;
    }
    if (campo.tipo === "opcoes" && JSON.stringify(conferido.definicao.opcoes) !== JSON.stringify(campo.opcoes)) {
      mudancas.opcoes = conferido.definicao.opcoes;
      antes.opcoes = campo.opcoes;
    }
    if (conferido.definicao.obrigatorio !== campo.obrigatorio) {
      mudancas.obrigatorio = conferido.definicao.obrigatorio;
      antes.obrigatorio = campo.obrigatorio;
    }
    if (conferido.definicao.ajuda !== campo.ajuda) {
      mudancas.ajuda = conferido.definicao.ajuda;
      antes.explicacao = campo.ajuda;
    }
    if (typeof args.ligado === "boolean" && args.ligado !== campo.ativo) {
      mudancas.ativo = args.ligado;
      antes.ligado = campo.ativo;
    }
    if (!Object.keys(mudancas).length) recusar("Nada para mudar: os valores mandados são iguais aos de agora.");

    const avisos: string[] = [];
    if (mudancas.opcoes) avisos.push("Cadastros que já têm uma opção retirada continuam com ela até alguém mudar.");
    return {
      previa: { o_que_vai_acontecer: `O campo "${campo.rotulo}" vai mudar.`, antes, depois: mudancas },
      resumo: `Prévia da mudança no campo "${campo.rotulo}"`,
      avisos,
      carga: { id: campo.id, rotuloAntes: campo.rotulo, entidade, mudancas },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.from("campos_proprios").update(carga.mudancas).eq("id", carga.id).select("id").maybeSingle();
    if (error || !data) falha("alterar o campo", error);
    return {
      dados: { alterado: true, campo: carga.rotuloAntes, mudancas: carga.mudancas },
      resumo: `Alterou o campo "${carga.rotuloAntes}" em ${ROTULO_DA_ENTIDADE[carga.entidade].toLowerCase()}`,
    };
  },
};

/* -------------------------------------------------------------- preencher */

type CargaDoPreenchimento = {
  entidade: EntidadeDoCampo;
  registroId: string;
  nome: string;
  /* chave -> valor conferido; null = apagar o valor. */
  mudancas: Record<string, ValorDeCampo | null>;
  rotulos: Record<string, string>;
};

export const preencherCampos: FerramentaDeEscrita<CargaDoPreenchimento> = {
  nome: "preencher_campos",
  titulo: "Preencher campos próprios",
  descricao: `Preenche ou muda os campos próprios de um cliente, orçamento ou pedido. Ache o registro pelo id, pelo número (orçamento e pedido) ou pelo nome (cliente). Campo sem valor apaga o que estava. ${COMO_MANDAR_VALOR}`,
  campos: {
    entidade: CAMPO_ENTIDADE,
    ...ARGUMENTOS_DO_REGISTRO,
    valores: {
      tipo: "lista",
      descricao: "Os campos a preencher.",
      obrigatorio: true,
      minItens: 1,
      maxItens: LIMITES.camposPorEntidade,
      itens: {
        campo: { tipo: "texto", descricao: "Chave ou nome do campo.", obrigatorio: true, max: 80 },
        valor: { tipo: "texto", descricao: "O valor, como texto. Vazio apaga.", max: LIMITES.textoLongo },
      },
    },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["clients.list.write", "quotes.write", "orders.write"],
  async preparar(ctx, args) {
    const entidade = lerEntidade(args.entidade);
    exigir(ctx, PERMISSAO_DE_GRAVAR[entidade], `editar ${O_QUE[entidade]}`);
    const definicoes = await definicoesDa(ctx, entidade);
    const registro = await acharRegistro(ctx, entidade, args);

    const novos: Record<string, unknown> = {};
    const rotulos: Record<string, string> = {};
    for (const item of (args.valores ?? []) as Array<{ campo: string; valor?: unknown }>) {
      const def = acharCampo(definicoes, item.campo, entidade);
      if (!def.ativo) recusar(`O campo "${def.rotulo}" está desligado. Ligue com alterar_campo_proprio antes de preencher.`);
      novos[def.chave] = item.valor ?? null;
      rotulos[def.chave] = def.rotulo;
    }
    const conferido = conferirValores(definicoes, novos, { atuais: registro.valores });
    if (!conferido.ok) recusar(Object.values(conferido.erros).join(" "));

    const mudancas: Record<string, ValorDeCampo | null> = {};
    const depois: Record<string, string> = {};
    const antes: Record<string, string> = {};
    for (const chave of Object.keys(novos)) {
      const def = definicoes.find((d) => d.chave === chave) as DefinicaoDeCampo;
      const valor = conferido.valores[chave] ?? null;
      if ((registro.valores[chave] ?? null) === valor) continue;
      mudancas[chave] = valor;
      antes[def.rotulo] = formatarValor(def, registro.valores[chave]) || "(vazio)";
      depois[def.rotulo] = formatarValor(def, valor) || "(vazio)";
    }
    if (!Object.keys(mudancas).length) recusar("Nada para mudar: os valores já estão assim.");

    return {
      previa: { o_que_vai_acontecer: `Os campos de ${registro.nome} vão mudar.`, antes, depois },
      resumo: `Prévia dos campos de ${registro.nome}`,
      avisos: [],
      carga: { entidade, registroId: registro.id, nome: registro.nome, mudancas, rotulos },
    };
  },
  async gravar(ctx, carga) {
    const tabela = TABELA_DA_ENTIDADE[carga.entidade];
    /* Relê antes de gravar: o que outra pessoa mudou entre a prévia e agora fica. */
    const { data, error } = await ctx.supabase.from(tabela).select("campos_proprios").eq("id", carga.registroId).in("user_id", ctx.tenantIds).maybeSingle();
    if (error || !data) falha("ler o cadastro", error);
    const valores = valoresDaLinha((data as { campos_proprios?: unknown }).campos_proprios);
    for (const [chave, valor] of Object.entries(carga.mudancas)) {
      if (valor === null) delete valores[chave];
      else valores[chave] = valor;
    }
    const { error: erroAoGravar } = await ctx.supabase.from(tabela).update({ campos_proprios: valores }).eq("id", carga.registroId).in("user_id", ctx.tenantIds);
    if (erroAoGravar) falha("gravar os campos", erroAoGravar);
    return {
      dados: { gravado: true, registro: carga.nome, registro_id: carga.registroId, campos: Object.keys(carga.mudancas).map((chave) => carga.rotulos[chave] ?? chave) },
      resumo: `Preencheu ${contagem(Object.keys(carga.mudancas).length, "campo", "campos")} de ${carga.nome}`,
    };
  },
};

/* -------------------------------------------------------- ver de um registro */

export const verCamposDoRegistro: FerramentaDeLeitura = {
  nome: "ver_campos_proprios",
  titulo: "Ver os campos próprios de um cadastro",
  descricao: "Mostra os campos próprios preenchidos (e os vazios) de um cliente, orçamento ou pedido. Ache pelo id, pelo número ou pelo nome do cliente.",
  campos: { entidade: CAMPO_ENTIDADE, ...ARGUMENTOS_DO_REGISTRO },
  tipo: "leitura",
  permissoes: ["clients.list.read", "quotes.read", "orders.read"],
  async executar(ctx, args) {
    const entidade = lerEntidade(args.entidade);
    exigir(ctx, PERMISSAO_DE_LER[entidade], `ver ${O_QUE[entidade]}`);
    const [definicoes, registro] = await Promise.all([definicoesDa(ctx, entidade), acharRegistro(ctx, entidade, args)]);
    const campos = camposParaLer(definicoes, registro.valores, { incluirVazios: true }).map((c) => ({ campo: c.campo, chave: c.chave, tipo: c.tipo, valor: c.valor, como_aparece: c.texto || null }));
    return {
      dados: { _observacao: AVISO_DE_DADOS, registro: registro.nome, registro_id: registro.id, campos },
      resumo: `Consultou os campos próprios de ${registro.nome}`,
    };
  },
};

/* ------------------------------------------------------------------ buscar */

const TETO_DA_BUSCA = 5000;

const COLUNAS_DA_BUSCA: Record<EntidadeDoCampo, string> = {
  cliente: "id,name,company_name,whatsapp,email,campos_proprios",
  orcamento: "id,number,title,status,total,created_at,campos_proprios,clients(id,name,company_name)",
  pedido: "id,number,status,total_amount,delivery_date,created_at,campos_proprios,clients(id,name,company_name)",
};

type ClienteEmbutido = { id?: string | null; name?: string | null; company_name?: string | null };
type LinhaDaBusca = {
  id: string;
  name?: string | null;
  company_name?: string | null;
  whatsapp?: string | null;
  email?: string | null;
  number?: number | null;
  title?: string | null;
  status?: string | null;
  total?: number | null;
  total_amount?: number | null;
  delivery_date?: string | null;
  created_at?: string | null;
  campos_proprios?: unknown;
  clients?: ClienteEmbutido | ClienteEmbutido[] | null;
};

function linhaAchada(entidade: EntidadeDoCampo, linha: LinhaDaBusca, definicoes: DefinicaoDeCampo[], def: DefinicaoDeCampo) {
  const valores = valoresDaLinha(linha.campos_proprios);
  const base = {
    id: linha.id,
    [def.rotulo]: formatarValor(def, valores[def.chave]) || null,
    outros_campos: Object.fromEntries(camposParaLer(definicoes, valores).filter((c) => c.chave !== def.chave).map((c) => [c.campo, c.texto])),
  };
  if (entidade === "cliente") {
    return { ...base, nome: nomeDoCliente(linha), whatsapp: linha.whatsapp || null, email: linha.email || null };
  }
  const cliente = nomeDoCliente(um(linha.clients));
  if (entidade === "orcamento") {
    return { ...base, numero: linha.number, titulo: linha.title || null, cliente, situacao: SITUACAO_DO_ORCAMENTO[linha.status ?? ""] ?? linha.status, total: dinheiro(linha.total) };
  }
  return { ...base, numero: linha.number, cliente, situacao: SITUACAO_DO_PEDIDO[linha.status ?? ""] ?? linha.status, total: dinheiro(linha.total_amount), entrega: linha.delivery_date ?? null };
}

export const buscarPorCampo: FerramentaDeLeitura = {
  nome: "buscar_por_campo",
  titulo: "Buscar pelos campos próprios",
  descricao:
    "Lista clientes, orçamentos ou pedidos pelo valor de um campo próprio: igual a um valor, contendo um texto, numa faixa (de/até) de número, valor ou data, ou pelo mês/dia de uma data sem olhar o ano (aniversariantes do mês: campo de data com mes). Sem condição, lista quem tem o campo preenchido; preenchido=false lista quem não tem.",
  campos: {
    entidade: CAMPO_ENTIDADE,
    campo: { tipo: "texto", descricao: "Chave ou nome do campo.", obrigatorio: true, max: 80 },
    igual: { tipo: "texto", descricao: "Valor exato (sem diferença de maiúscula e acento). Sim/não: \"sim\" ou \"não\".", max: 500 },
    contem: { tipo: "texto", descricao: "Trecho de texto, para campos de texto ou opções.", max: 200 },
    de: { tipo: "texto", descricao: "A partir de (número, valor ou data AAAA-MM-DD).", max: 40 },
    ate: { tipo: "texto", descricao: "Até (número, valor ou data AAAA-MM-DD).", max: 40 },
    mes: { tipo: "inteiro", descricao: "Para campo de data: o mês (1 a 12), de qualquer ano.", min: 1, max: 12 },
    dia: { tipo: "inteiro", descricao: "Para campo de data, junto com mes: o dia do mês.", min: 1, max: 31 },
    preenchido: { tipo: "booleano", descricao: "true: só quem tem o campo preenchido. false: só quem não tem." },
    limite: { tipo: "inteiro", descricao: "Quantos devolver. O padrão é 50.", min: 1, max: 200 },
  },
  tipo: "leitura",
  permissoes: ["clients.list.read", "quotes.read", "orders.read"],
  async executar(ctx, args) {
    const entidade = lerEntidade(args.entidade);
    exigir(ctx, PERMISSAO_DE_LER[entidade], `ver ${O_QUE[entidade]}`);
    const definicoes = await definicoesDa(ctx, entidade);
    const def = acharCampo(definicoes, String(args.campo), entidade);
    const filtroBruto: FiltroDoCampo = {
      igual: args.igual,
      contem: args.contem as string | undefined,
      de: args.de,
      ate: args.ate,
      mes: args.mes as number | undefined,
      dia: args.dia as number | undefined,
      preenchido: args.preenchido as boolean | undefined,
    };
    const conferido = conferirFiltro(def, filtroBruto);
    if (!conferido.ok) recusar(conferido.erro);
    const filtro = conferido.filtro;
    const limite = (args.limite as number | undefined) ?? 50;
    const tabela = TABELA_DA_ENTIDADE[entidade];
    /* A chave só tem letras minúsculas, números e "_" (conferido na tabela), então entra no filtro sem risco. */
    const caminho = `campos_proprios->>${def.chave}`;

    const { linhas, cortado } = await todasAsLinhas<LinhaDaBusca>((de, ate) => {
      let consulta = ctx.supabase.from(tabela).select(COLUNAS_DA_BUSCA[entidade]).in("user_id", ctx.tenantIds);
      if (entidade === "cliente") consulta = consulta.not("is_deleted", "is", true).not("is_system_placeholder", "is", true);
      consulta = filtro.preenchido === false ? consulta.is(caminho, null) : consulta.not(caminho, "is", null);
      return consulta.order("created_at", { ascending: false }).range(de, ate);
    }, "buscar pelos campos", TETO_DA_BUSCA);

    const achadas = linhas.filter((linha) => valorCombina(def, valoresDaLinha(linha.campos_proprios)[def.chave], filtro));
    /* Aniversariantes: na ordem do dia do mês. */
    if (def.tipo === "data" && filtro.mes !== undefined) {
      const dia = (linha: LinhaDaBusca) => String(valoresDaLinha(linha.campos_proprios)[def.chave] ?? "").slice(5, 10);
      achadas.sort((a, b) => dia(a).localeCompare(dia(b)));
    }
    const resultado = achadas.slice(0, limite).map((linha) => linhaAchada(entidade, linha, definicoes, def));

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        campo: definicaoParaLer(def),
        encontrados: achadas.length,
        itens: resultado,
        ...(achadas.length > limite ? { mostrando: limite } : {}),
        ...(cortado ? { aviso: `A busca olhou só os ${TETO_DA_BUSCA} cadastros mais recentes com o campo preenchido.` } : {}),
      },
      resumo: `Buscou ${O_QUE[entidade]} pelo campo "${def.rotulo}" (${contagem(achadas.length, "encontrado", "encontrados")})`,
    };
  },
};

export const FERRAMENTAS_DE_CAMPOS_PROPRIOS: Ferramenta[] = [
  listarCamposProprios,
  verCamposDoRegistro,
  buscarPorCampo,
  criarCampoProprio,
  alterarCampoProprio,
  preencherCampos,
] as Ferramenta[];
