import { createPaymentLink } from "@/lib/api-v1/services/payment-links-service";
import { contatosNovos, recusaDeContatos } from "../contatos";
import { getBankAccountDependencies } from "@/lib/bank-account-admin";
import { canManageFinancialCritical } from "@/lib/company-context";
import {
  baixarContaAPagar,
  criarContaAPagar,
  getAccountsPayableByIdWithFallback,
  insertSupplierActivity,
  updateAccountsPayableWithFallback,
} from "@/lib/financeiro/contas-a-pagar";
import { COLUNAS_DA_CONTA_A_RECEBER, criarContaAReceber, registrarRecebimento } from "@/lib/financeiro/contas-a-receber";
import { registrarPagamentoDoPedido } from "@/lib/financeiro/pagamento-do-pedido";
import { transferirEntreContas } from "@/lib/financeiro/transferencia";
import { isCashAccountType } from "@/lib/liquidation-accounts";

import type { ContextoDoAssistente } from "../contexto";
import { dataBr, dinheiro, exigir, falha, hojeEmBrasilia, nomeDoCliente, reais, recusar, texto, um } from "./comum";
import { FORMAS, centavos, formaNoBanco, nomeDaForma, nomeDoTipoDeConta, type FormaDePagamento } from "./financeiro-completo-apoio";
import {
  empresaDe,
  escolherCategoria,
  escolherConta,
  escolherFornecedor,
  nomeDaConta,
  podeVerValores,
  saldosDasContas,
} from "./financeiro-completo-comum";
import { LEITURAS_DE_FINANCEIRO, contextoDosLinks, erroDoServicoDeLinks } from "./financeiro-completo-leituras";
import type { Ferramenta, FerramentaDeEscrita } from "./tipos";

/* As escritas do financeiro. Toda gravação passa pela mesma função que a
   tela usa (lib/financeiro/*): as conferências de saldo, de conta compatível
   com a forma de pagamento e o lançamento no fluxo de caixa são as mesmas.

   Mexer em dinheiro (baixa, recebimento, pagamento de pedido, transferência),
   cancelar ou desfazer é sensível: sempre volta uma prévia com código antes.
   Criar ou ajustar uma conta que ainda não foi paga é só um registro e grava
   direto. */

const FORMA_DESCRICAO = "Como foi pago: pix, dinheiro (em espécie, entra/sai do caixa físico), cartao_de_credito, cartao_de_debito ou transferencia.";

function exigirValores(ctx: ContextoDoAssistente) {
  if (!podeVerValores(ctx)) exigir(ctx, "financial.amounts.see", "ver os valores do financeiro");
}

function formaObrigatoria(valor: unknown): FormaDePagamento {
  const forma = formaNoBanco(valor);
  if (!forma) recusar(`Forma de pagamento inválida. ${FORMA_DESCRICAO}`);
  return forma;
}

function erroDaGravacao(corpo: Record<string, unknown>, oQue: string, status: number): never {
  const mensagem = typeof corpo.error === "string" ? corpo.error : "";
  if (status < 500 && mensagem) recusar(mensagem);
  return falha(oQue, { message: String(corpo.message ?? mensagem) });
}

/* ----------------------------------------------------------- contas a pagar */

type ContaAPagar = Record<string, unknown> & {
  id: string;
  description?: string | null;
  amount?: number | null;
  due_date?: string | null;
  status?: string | null;
  bank_account_id?: string | null;
  category_id?: string | null;
  supplier_id?: string | null;
  supplier_name?: string | null;
  is_recurring?: boolean | null;
};

async function lerContaAPagar(ctx: ContextoDoAssistente, id: unknown): Promise<ContaAPagar> {
  const { data, error } = await getAccountsPayableByIdWithFallback(ctx.admin, empresaDe(ctx), String(id));
  if (error) falha("ler a conta a pagar", { message: error.message ?? undefined });
  if (!data) recusar("Conta a pagar não encontrada. Use listar_contas_a_pagar para achar o id certo.");
  return data as ContaAPagar;
}

function contaAindaAberta(conta: ContaAPagar) {
  if (conta.status === "PAID") recusar("Esta conta já está paga.");
  if (conta.status === "CANCELED") recusar("Esta conta está cancelada.");
}

type CargaDaContaAPagar = { body: Record<string, unknown>; descricao: string; valor: number; vencimento: string };

export const criarContaAPagarFerramenta: FerramentaDeEscrita<CargaDaContaAPagar> = {
  nome: "criar_conta_a_pagar",
  titulo: "Lançar conta a pagar",
  descricao:
    "Lança uma conta a pagar no financeiro (aluguel, energia, compra de fornecedor, imposto…), para pagar depois. Não mexe em saldo: só registra a dívida com vencimento. Aceita o nome da conta de onde vai sair, da categoria e do fornecedor (não precisa do id). Para registrar uma conta que já foi paga, lance e depois use dar_baixa_conta_a_pagar.",
  campos: {
    descricao: { tipo: "texto", descricao: "O que é a conta. Ex.: Aluguel de outubro.", obrigatorio: true, max: 200 },
    valor: { tipo: "numero", descricao: "Valor em reais.", obrigatorio: true, min: 0.01, max: 100_000_000 },
    vencimento: { tipo: "data", descricao: "Dia do vencimento.", obrigatorio: true },
    conta: { tipo: "texto", descricao: "Nome ou id da conta de onde o dinheiro vai sair. Pode ficar vazio se a empresa só tiver uma conta.", max: 120 },
    categoria: { tipo: "texto", descricao: "Nome da categoria (veja listar_categorias_financeiras).", max: 80 },
    fornecedor: { tipo: "texto", descricao: "Nome do fornecedor cadastrado, se for conta de fornecedor.", max: 120 },
    forma_de_pagamento: { tipo: "opcao", descricao: "Como vai ser paga, se já souber.", opcoes: FORMAS },
    recorrente: { tipo: "booleano", descricao: "Repete todo mês (aluguel, internet). Ao pagar, a do mês seguinte é criada sozinha." },
  },
  tipo: "escrita",
  permissoes: ["financial.payables.write"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.payables.write", "lançar contas a pagar");
    const forma = args.forma_de_pagamento ? formaNoBanco(args.forma_de_pagamento) : null;
    const [conta, categoria, fornecedor] = await Promise.all([
      escolherConta(ctx, { dito: args.conta, forma, papel: "de qual conta o dinheiro vai sair" }),
      escolherCategoria(ctx, args.categoria),
      escolherFornecedor(ctx, args.fornecedor),
    ]);
    const descricao = String(args.descricao).trim();
    const valor = dinheiro(args.valor);
    const vencimento = String(args.vencimento);
    const recorrente = args.recorrente === true;
    const avisos: string[] = [];
    if (vencimento < hojeEmBrasilia()) avisos.push(`O vencimento (${dataBr(vencimento)}) já passou: a conta entra como vencida.`);

    return {
      previa: {
        o_que_vai_acontecer: "Uma conta a pagar nova será lançada no financeiro. Nenhum dinheiro sai agora.",
        descricao,
        valor: reais(valor),
        vencimento: dataBr(vencimento),
        sai_da_conta: nomeDaConta(conta),
        categoria: categoria?.name ?? "sem categoria",
        fornecedor: fornecedor?.name ?? null,
        forma_de_pagamento: forma ? nomeDaForma(forma) : "a definir",
        recorrente: recorrente ? "sim, todo mês" : "não",
      },
      resumo: `Prévia de conta a pagar: ${descricao}, ${reais(valor)} para ${dataBr(vencimento)}`,
      avisos,
      carga: {
        body: {
          description: descricao,
          amount: valor,
          due_date: vencimento,
          bank_account_id: conta?.id ?? "",
          category_id: categoria?.id ?? null,
          supplier_id: fornecedor?.id ?? null,
          is_recurring: recorrente,
          recurrence_rule: recorrente ? "Mensal" : null,
          payment_method: forma,
        },
        descricao,
        valor,
        vencimento,
      },
    };
  },
  async gravar(ctx, carga) {
    const resultado = await criarContaAPagar({ supabase: ctx.supabase, admin: ctx.admin, companyId: empresaDe(ctx), userId: ctx.user.id, body: carga.body });
    if (!resultado.ok) erroDaGravacao(resultado.corpo, "lançar a conta a pagar", resultado.status);
    return {
      dados: { criada: true, conta_a_pagar_id: resultado.account.id, descricao: carga.descricao, valor: carga.valor, vencimento: carga.vencimento },
      resumo: `Lançou a conta a pagar "${carga.descricao}" de ${reais(carga.valor)} para ${dataBr(carga.vencimento)}`,
    };
  },
};

type CargaDaEdicaoAPagar = { id: string; patch: Record<string, unknown>; supplierId: string | null; descricao: string };

export const editarContaAPagar: FerramentaDeEscrita<CargaDaEdicaoAPagar> = {
  nome: "editar_conta_a_pagar",
  titulo: "Corrigir conta a pagar",
  descricao:
    "Corrige uma conta a pagar que ainda não foi paga: descrição, valor, vencimento, categoria, conta de onde sai ou fornecedor. Mande só o que muda. Conta já paga ou cancelada não se edita (para desfazer um pagamento, use desfazer_pagamento_conta_a_pagar).",
  campos: {
    conta_a_pagar_id: { tipo: "id", descricao: "Id da conta (de listar_contas_a_pagar).", obrigatorio: true },
    descricao: { tipo: "texto", descricao: "Nova descrição.", max: 200 },
    valor: { tipo: "numero", descricao: "Novo valor em reais.", min: 0.01, max: 100_000_000 },
    vencimento: { tipo: "data", descricao: "Novo vencimento." },
    categoria: { tipo: "texto", descricao: "Nova categoria (nome).", max: 80 },
    conta: { tipo: "texto", descricao: "Nova conta de onde vai sair (nome ou id).", max: 120 },
    fornecedor: { tipo: "texto", descricao: "Novo fornecedor (nome cadastrado).", max: 120 },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["financial.payables.write"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.payables.write", "corrigir contas a pagar");
    const atual = await lerContaAPagar(ctx, args.conta_a_pagar_id);
    contaAindaAberta(atual);
    const verValores = podeVerValores(ctx);

    const patch: Record<string, unknown> = {};
    const mudancas: Record<string, { antes: unknown; depois: unknown }> = {};
    if (args.descricao !== undefined) {
      patch.description = String(args.descricao).trim();
      mudancas.descricao = { antes: atual.description, depois: patch.description };
    }
    if (args.valor !== undefined) {
      exigirValores(ctx);
      patch.amount = dinheiro(args.valor);
      mudancas.valor = { antes: reais(atual.amount), depois: reais(patch.amount) };
    }
    if (args.vencimento !== undefined) {
      patch.due_date = String(args.vencimento);
      mudancas.vencimento = { antes: atual.due_date ? dataBr(atual.due_date) : null, depois: dataBr(String(args.vencimento)) };
      /* Uma conta marcada como vencida que ganhou vencimento novo, no futuro, volta a ser só pendente. */
      if (atual.status === "OVERDUE" && String(args.vencimento) >= hojeEmBrasilia()) patch.status = "PENDING";
    }
    if (args.categoria !== undefined) {
      const categoria = await escolherCategoria(ctx, args.categoria);
      patch.category_id = categoria?.id ?? null;
      mudancas.categoria = { antes: null, depois: categoria?.name ?? "sem categoria" };
    }
    if (args.conta !== undefined) {
      const conta = await escolherConta(ctx, { dito: args.conta, papel: "de qual conta o dinheiro vai sair" });
      patch.bank_account_id = conta?.id;
      mudancas.conta = { antes: null, depois: nomeDaConta(conta) };
    }
    let supplierId = typeof atual.supplier_id === "string" ? atual.supplier_id : null;
    if (args.fornecedor !== undefined) {
      const fornecedor = await escolherFornecedor(ctx, args.fornecedor);
      patch.supplier_id = fornecedor?.id ?? null;
      patch.supplier_name = fornecedor?.name ?? null;
      supplierId = fornecedor?.id ?? supplierId;
      mudancas.fornecedor = { antes: atual.supplier_name ?? null, depois: fornecedor?.name ?? null };
    }
    if (!Object.keys(mudancas).length) recusar("Nada para mudar. Mande ao menos um campo: descricao, valor, vencimento, categoria, conta ou fornecedor.");

    const descricao = String(patch.description ?? atual.description ?? "Conta a pagar");
    return {
      previa: {
        o_que_vai_acontecer: "A conta a pagar será corrigida. Nenhum dinheiro se move.",
        conta_a_pagar: descricao,
        valor_atual: verValores ? reais(atual.amount) : null,
        mudancas,
      },
      resumo: `Prévia de correção da conta a pagar "${descricao}"`,
      avisos: [],
      carga: { id: atual.id, patch, supplierId, descricao },
    };
  },
  async gravar(ctx, carga) {
    /* Pode ter sido paga ou cancelada entre a prévia e a gravação. */
    contaAindaAberta(await lerContaAPagar(ctx, carga.id));
    const { data, error } = await updateAccountsPayableWithFallback(ctx.admin, empresaDe(ctx), carga.id, carga.patch);
    if (error) falha("corrigir a conta a pagar", { message: error.message ?? undefined });
    if (!data) recusar("Conta a pagar não encontrada.");
    await insertSupplierActivity(ctx.admin, empresaDe(ctx), carga.supplierId, ctx.user.id, carga.id, "payable.updated", `Conta a pagar atualizada: ${carga.descricao}.`, carga.patch);
    return {
      dados: { corrigida: true, conta_a_pagar_id: carga.id, campos: Object.keys(carga.patch) },
      resumo: `Corrigiu a conta a pagar "${carga.descricao}"`,
    };
  },
};

type CargaDaBaixa = { id: string; payload: Record<string, unknown>; descricao: string; valor: number; conta: string | null; dia: string; forma: string };

export const darBaixaContaAPagar: FerramentaDeEscrita<CargaDaBaixa> = {
  nome: "dar_baixa_conta_a_pagar",
  titulo: "Pagar conta (dar baixa)",
  descricao:
    "Registra que uma conta a pagar foi paga: a conta vira paga e a saída entra no fluxo de caixa da conta escolhida (o saldo dela diminui). Sem conta, usa a conta da própria despesa; sem valor, o valor dela; sem data, hoje. Se a conta for mensal, a do mês seguinte é criada. Sempre mostra uma prévia e pede confirmação.",
  campos: {
    conta_a_pagar_id: { tipo: "id", descricao: "Id da conta a pagar (de listar_contas_a_pagar).", obrigatorio: true },
    forma_de_pagamento: { tipo: "opcao", descricao: FORMA_DESCRICAO, opcoes: FORMAS, obrigatorio: true },
    conta: { tipo: "texto", descricao: "Nome ou id da conta (ou caixa físico) de onde o dinheiro saiu.", max: 120 },
    valor: { tipo: "numero", descricao: "Valor pago, se diferente do valor da conta (juros, desconto).", min: 0.01, max: 100_000_000 },
    data: { tipo: "data", descricao: "Dia em que foi pago. O padrão é hoje." },
  },
  tipo: "escrita",
  sensivel: true,
  permissoes: ["financial.payables.settle"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.payables.settle", "pagar contas");
    exigirValores(ctx);
    const atual = await lerContaAPagar(ctx, args.conta_a_pagar_id);
    contaAindaAberta(atual);
    const forma = formaObrigatoria(args.forma_de_pagamento);
    const conta = await escolherConta(ctx, { dito: args.conta, forma, padraoId: atual.bank_account_id ?? null, papel: "de qual conta o dinheiro saiu" });
    const valorDaConta = dinheiro(atual.amount);
    const valor = args.valor !== undefined ? dinheiro(args.valor) : valorDaConta;
    const dia = (args.data as string | undefined) ?? hojeEmBrasilia();
    const descricao = String(atual.description || "Conta a pagar");

    const avisos: string[] = [];
    if (centavos(valor) !== centavos(valorDaConta)) avisos.push(`O valor pago (${reais(valor)}) é diferente do valor da conta (${reais(valorDaConta)}); a conta fica registrada com o valor pago.`);
    if (dia > hojeEmBrasilia()) avisos.push(`A data do pagamento (${dataBr(dia)}) está no futuro.`);
    if (atual.is_recurring) avisos.push("É uma conta mensal: a do mês seguinte será criada.");
    const saldos = await saldosDasContas(ctx, conta ? [conta] : []);
    const saldoAntes = conta ? saldos.get(conta.id) ?? 0 : 0;
    if (conta && saldoAntes < valor) avisos.push(`${nomeDaConta(conta)} tem ${reais(saldoAntes)}; depois do pagamento fica negativa.`);

    return {
      previa: {
        o_que_vai_acontecer: "A conta será marcada como paga e a saída entra no fluxo de caixa.",
        conta_a_pagar: descricao,
        fornecedor: atual.supplier_name ?? null,
        vencimento: atual.due_date ? dataBr(atual.due_date) : null,
        valor_pago: reais(valor),
        forma: nomeDaForma(forma),
        sai_de: nomeDaConta(conta),
        saldo_da_conta_antes: reais(saldoAntes),
        saldo_da_conta_depois: reais(saldoAntes - valor),
        dia_do_pagamento: dataBr(dia),
      },
      resumo: `Prévia de pagamento: ${descricao}, ${reais(valor)}`,
      avisos,
      carga: {
        id: atual.id,
        payload: { payment_date: dia, bank_account_id: conta?.id, amount: valor, payment_method: forma },
        descricao,
        valor,
        conta: nomeDaConta(conta),
        dia,
        forma,
      },
    };
  },
  async gravar(ctx, carga) {
    const existing = await lerContaAPagar(ctx, carga.id);
    contaAindaAberta(existing);
    const resultado = await baixarContaAPagar({
      supabase: ctx.supabase,
      admin: ctx.admin,
      companyId: empresaDe(ctx),
      userId: ctx.user.id,
      accountId: carga.id,
      existing,
      payload: carga.payload,
    });
    if (!resultado.ok) erroDaGravacao(resultado.corpo, "dar baixa na conta a pagar", resultado.status);
    return {
      dados: { paga: true, conta_a_pagar_id: carga.id, valor: carga.valor, saiu_de: carga.conta, dia: carga.dia, forma: nomeDaForma(carga.forma) },
      resumo: `Pagou a conta "${carga.descricao}": ${reais(carga.valor)} saindo de ${carga.conta} em ${dataBr(carga.dia)}`,
    };
  },
};

type CargaDoDesfazer = { id: string; motivo: string; descricao: string };

export const desfazerPagamentoContaAPagar: FerramentaDeEscrita<CargaDoDesfazer> = {
  nome: "desfazer_pagamento_conta_a_pagar",
  titulo: "Desfazer pagamento de conta",
  descricao:
    "Desfaz o pagamento de uma conta a pagar que foi baixada por engano: a conta volta a ficar em aberto e entra um lançamento de estorno no fluxo de caixa (o dinheiro volta para a conta; nada é apagado). Sempre mostra uma prévia e pede confirmação.",
  campos: {
    conta_a_pagar_id: { tipo: "id", descricao: "Id da conta paga (de listar_contas_a_pagar com situacao pagas).", obrigatorio: true },
    motivo: { tipo: "texto", descricao: "Por que o pagamento está sendo desfeito.", obrigatorio: true, max: 300 },
  },
  tipo: "escrita",
  sensivel: true,
  sobrescreve: true,
  permissoes: ["financial.payables.settle"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.payables.settle", "desfazer pagamentos de contas");
    const atual = await lerContaAPagar(ctx, args.conta_a_pagar_id);
    if (atual.status !== "PAID") recusar("Esta conta não está paga; não há pagamento para desfazer.");
    const descricao = String(atual.description || "Conta a pagar");
    const motivo = String(args.motivo).trim();
    return {
      previa: {
        o_que_vai_acontecer: "O pagamento será estornado: a conta volta a ficar em aberto e o valor volta para a conta de onde saiu.",
        conta_a_pagar: descricao,
        valor: podeVerValores(ctx) ? reais(atual.amount) : null,
        paga_em: typeof atual.payment_date === "string" ? dataBr(atual.payment_date) : null,
        motivo,
      },
      resumo: `Prévia de estorno do pagamento de "${descricao}"`,
      avisos: [],
      carga: { id: atual.id, motivo, descricao },
    };
  },
  async gravar(ctx, carga) {
    const { error } = await ctx.supabase.rpc("reverse_payable_with_cash_flow", { p_payable_id: carga.id, p_reason: carga.motivo });
    /* "raise exception" da função (P0001) é recusa de regra — sem permissão,
       conta não encontrada — e a pessoa precisa ler; o resto é falha nossa. */
    if (error?.code === "P0001" && error.message) recusar(`Não foi possível desfazer o pagamento: ${error.message}`);
    if (error) falha("desfazer o pagamento", error);
    return { dados: { desfeito: true, conta_a_pagar_id: carga.id }, resumo: `Desfez o pagamento da conta "${carga.descricao}" (${carga.motivo})` };
  },
};

type CargaDoCancelamento = { id: string; descricao: string };

export const cancelarContaAPagar: FerramentaDeEscrita<CargaDoCancelamento> = {
  nome: "cancelar_conta_a_pagar",
  titulo: "Cancelar conta a pagar",
  descricao:
    "Cancela uma conta a pagar em aberto que não vai mais ser paga (lançada por engano, negociada). Ela some das contas em aberto, mas continua no histórico. Conta já paga não se cancela: desfaça o pagamento antes. Sempre mostra uma prévia e pede confirmação.",
  campos: {
    conta_a_pagar_id: { tipo: "id", descricao: "Id da conta (de listar_contas_a_pagar).", obrigatorio: true },
  },
  tipo: "escrita",
  sensivel: true,
  sobrescreve: true,
  permissoes: ["financial.payables.settle"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.payables.settle", "cancelar contas a pagar");
    const atual = await lerContaAPagar(ctx, args.conta_a_pagar_id);
    contaAindaAberta(atual);
    const descricao = String(atual.description || "Conta a pagar");
    return {
      previa: {
        o_que_vai_acontecer: "A conta a pagar será cancelada. Nenhum dinheiro se move.",
        conta_a_pagar: descricao,
        valor: podeVerValores(ctx) ? reais(atual.amount) : null,
        vencimento: atual.due_date ? dataBr(atual.due_date) : null,
        fornecedor: atual.supplier_name ?? null,
      },
      resumo: `Prévia de cancelamento da conta a pagar "${descricao}"`,
      avisos: [],
      carga: { id: atual.id, descricao },
    };
  },
  async gravar(ctx, carga) {
    /* Como o cancelar da tela, com uma diferença: a conta que o sistema já
       marcou como vencida também pode ser cancelada. */
    const { data, error } = await ctx.admin
      .from("accounts_payable")
      .update({ status: "CANCELED", payment_date: null })
      .eq("id", carga.id)
      .eq("company_id", empresaDe(ctx))
      .in("status", ["PENDING", "OVERDUE"])
      .select("id")
      .maybeSingle();
    if (error) falha("cancelar a conta a pagar", error);
    if (!data) recusar("A conta não está mais em aberto; nada foi cancelado.");
    return { dados: { cancelada: true, conta_a_pagar_id: carga.id }, resumo: `Cancelou a conta a pagar "${carga.descricao}"` };
  },
};

/* --------------------------------------------------------- contas a receber */

type ContaAReceber = {
  id: string;
  description: string | null;
  client_name: string | null;
  client_company: string | null;
  total_amount: number | null;
  paid_amount: number | null;
  due_date: string | null;
  status: string | null;
  bank_account_id: string | null;
  category_id: string | null;
};

async function lerContaAReceber(ctx: ContextoDoAssistente, id: unknown): Promise<ContaAReceber> {
  const { data, error } = await ctx.admin
    .from("accounts_receivable")
    .select(COLUNAS_DA_CONTA_A_RECEBER)
    .eq("id", String(id))
    .eq("company_id", empresaDe(ctx))
    .maybeSingle();
  if (error) falha("ler a conta a receber", error);
  if (!data) recusar("Conta a receber não encontrada. Use listar_contas_a_receber para achar o id certo (pedidos usam registrar_pagamento_do_pedido).");
  return data as unknown as ContaAReceber;
}

async function clienteDoCadastro(ctx: ContextoDoAssistente, clienteId: unknown) {
  const { data } = await ctx.supabase
    .from("clients")
    .select("id,name,company_name")
    .eq("id", String(clienteId))
    .in("user_id", ctx.tenantIds)
    .maybeSingle();
  if (!data) recusar("Cliente não encontrado. Use buscar_clientes para achar o id certo.");
  return data as { id: string; name: string | null; company_name: string | null };
}

type CargaDaContaAReceber = { body: Record<string, unknown>; descricao: string; valor: number; vencimento: string };

export const criarContaAReceberFerramenta: FerramentaDeEscrita<CargaDaContaAReceber> = {
  nome: "criar_conta_a_receber",
  titulo: "Lançar conta a receber",
  descricao:
    "Lança um título a receber avulso no financeiro (um valor que alguém vai pagar e que não é de pedido: serviço combinado, aluguel de equipamento, reembolso). Não mexe em saldo. Para cobrar um pedido, não precisa disto: o saldo do pedido já aparece em listar_contas_a_receber.",
  campos: {
    descricao: { tipo: "texto", descricao: "Do que é o valor.", obrigatorio: true, max: 200 },
    valor: { tipo: "numero", descricao: "Valor em reais.", obrigatorio: true, min: 0.01, max: 100_000_000 },
    vencimento: { tipo: "data", descricao: "Dia em que deve ser pago.", obrigatorio: true },
    cliente_id: { tipo: "id", descricao: "Id do cliente cadastrado (de buscar_clientes)." },
    cliente: { tipo: "texto", descricao: "Nome de quem vai pagar, se não for um cliente cadastrado.", max: 160 },
    conta: { tipo: "texto", descricao: "Conta onde se espera receber (nome ou id). Opcional.", max: 120 },
    categoria: { tipo: "texto", descricao: "Nome da categoria. Opcional.", max: 80 },
    observacoes: { tipo: "texto", descricao: "Observações internas.", max: 1000 },
  },
  tipo: "escrita",
  permissoes: ["financial.receivables.write"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.receivables.write", "lançar contas a receber");
    const cadastro = args.cliente_id ? await clienteDoCadastro(ctx, args.cliente_id) : null;
    const [conta, categoria] = await Promise.all([
      args.conta ? escolherConta(ctx, { dito: args.conta, papel: "a conta onde se espera receber" }) : Promise.resolve(null),
      escolherCategoria(ctx, args.categoria),
    ]);
    const descricao = String(args.descricao).trim();
    const valor = dinheiro(args.valor);
    const vencimento = String(args.vencimento);
    const nome = cadastro?.name ?? (args.cliente ? String(args.cliente).trim() : null);
    const empresa = cadastro?.company_name ?? null;
    const observacoes = args.observacoes ? String(args.observacoes).trim() : null;
    const avisos: string[] = [];
    if (vencimento < hojeEmBrasilia()) avisos.push(`O vencimento (${dataBr(vencimento)}) já passou: o título entra como vencido.`);

    return {
      previa: {
        o_que_vai_acontecer: "Um título a receber novo será lançado no financeiro. Nenhum dinheiro entra agora.",
        descricao,
        cliente: nomeDoCliente({ name: nome, company_name: empresa }),
        valor: reais(valor),
        vencimento: dataBr(vencimento),
        conta: conta ? nomeDaConta(conta) : null,
        categoria: categoria?.name ?? "sem categoria",
        observacoes,
      },
      resumo: `Prévia de conta a receber: ${descricao}, ${reais(valor)} para ${dataBr(vencimento)}`,
      avisos,
      carga: {
        body: {
          description: descricao,
          total_amount: valor,
          due_date: vencimento,
          client_name: nome,
          client_company: empresa,
          bank_account_id: conta?.id ?? null,
          category_id: categoria?.id ?? null,
          notes: observacoes,
        },
        descricao,
        valor,
        vencimento,
      },
    };
  },
  async gravar(ctx, carga) {
    const resultado = await criarContaAReceber({ admin: ctx.admin, companyId: empresaDe(ctx), userId: ctx.user.id, body: carga.body });
    if (!resultado.ok) erroDaGravacao(resultado.corpo, "lançar a conta a receber", resultado.status);
    return {
      dados: { criada: true, conta_a_receber_id: resultado.account?.id ?? null, descricao: carga.descricao, valor: carga.valor, vencimento: carga.vencimento },
      resumo: `Lançou a conta a receber "${carga.descricao}" de ${reais(carga.valor)} para ${dataBr(carga.vencimento)}`,
    };
  },
};

/* Título recebido por inteiro ou cancelado não se corrige: o caixa já conta com ele. */
function tituloAindaEditavel(atual: ContaAReceber) {
  if (atual.status === "CANCELED") recusar("Este título está cancelado.");
  if (atual.status === "PAID") recusar("Este título já foi recebido por inteiro e não se corrige. Para mudar, desfaça o recebimento no painel, em Financeiro › Contas a receber.");
}

type CargaDaEdicaoAReceber = { id: string; patch: Record<string, unknown>; sincronizar: boolean; descricao: string };

export const editarContaAReceber: FerramentaDeEscrita<CargaDaEdicaoAReceber> = {
  nome: "editar_conta_a_receber",
  titulo: "Corrigir conta a receber",
  descricao:
    "Corrige um título a receber avulso: descrição, valor, vencimento, cliente, conta, categoria ou observações. Mande só o que muda. O valor não pode ficar menor que o já recebido.",
  campos: {
    conta_a_receber_id: { tipo: "id", descricao: "Id do título (de listar_contas_a_receber).", obrigatorio: true },
    descricao: { tipo: "texto", descricao: "Nova descrição.", max: 200 },
    valor: { tipo: "numero", descricao: "Novo valor total em reais.", min: 0.01, max: 100_000_000 },
    vencimento: { tipo: "data", descricao: "Novo vencimento." },
    cliente: { tipo: "texto", descricao: "Novo nome de quem paga.", max: 160 },
    conta: { tipo: "texto", descricao: "Nova conta onde se espera receber (nome ou id).", max: 120 },
    categoria: { tipo: "texto", descricao: "Nova categoria (nome).", max: 80 },
    observacoes: { tipo: "texto", descricao: "Novas observações.", max: 1000 },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["financial.receivables.write"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.receivables.write", "corrigir contas a receber");
    const atual = await lerContaAReceber(ctx, args.conta_a_receber_id);
    tituloAindaEditavel(atual);
    const patch: Record<string, unknown> = {};
    const mudancas: Record<string, { antes: unknown; depois: unknown }> = {};
    if (args.descricao !== undefined) {
      patch.description = String(args.descricao).trim();
      mudancas.descricao = { antes: atual.description, depois: patch.description };
    }
    if (args.valor !== undefined) {
      exigirValores(ctx);
      const valor = dinheiro(args.valor);
      if (valor + 0.01 < Number(atual.paid_amount ?? 0)) recusar(`O valor não pode ficar menor que o já recebido (${reais(atual.paid_amount)}).`);
      patch.total_amount = valor;
      mudancas.valor = { antes: reais(atual.total_amount), depois: reais(valor) };
    }
    if (args.vencimento !== undefined) {
      patch.due_date = String(args.vencimento);
      mudancas.vencimento = { antes: atual.due_date ? dataBr(atual.due_date) : null, depois: dataBr(String(args.vencimento)) };
    }
    if (args.cliente !== undefined) {
      patch.client_name = String(args.cliente).trim() || null;
      mudancas.cliente = { antes: atual.client_name, depois: patch.client_name };
    }
    if (args.conta !== undefined) {
      const conta = await escolherConta(ctx, { dito: args.conta, papel: "a conta onde se espera receber" });
      patch.bank_account_id = conta?.id ?? null;
      mudancas.conta = { antes: null, depois: nomeDaConta(conta) };
    }
    if (args.categoria !== undefined) {
      const categoria = await escolherCategoria(ctx, args.categoria);
      patch.category_id = categoria?.id ?? null;
      mudancas.categoria = { antes: null, depois: categoria?.name ?? "sem categoria" };
    }
    if (args.observacoes !== undefined) {
      patch.notes = String(args.observacoes).trim() || null;
      mudancas.observacoes = { antes: null, depois: patch.notes };
    }
    if (!Object.keys(mudancas).length) recusar("Nada para mudar. Mande ao menos um campo.");
    const descricao = String(patch.description ?? atual.description ?? "Conta a receber");
    return {
      previa: { o_que_vai_acontecer: "O título a receber será corrigido. Nenhum dinheiro se move.", titulo: descricao, mudancas },
      resumo: `Prévia de correção da conta a receber "${descricao}"`,
      avisos: [],
      carga: { id: atual.id, patch, sincronizar: patch.total_amount !== undefined || patch.due_date !== undefined, descricao },
    };
  },
  async gravar(ctx, carga) {
    /* Pode ter sido recebido ou cancelado entre a prévia e a gravação. */
    tituloAindaEditavel(await lerContaAReceber(ctx, carga.id));
    /* O mesmo que o PATCH de /api/accounts-receivable. */
    const { data, error } = await ctx.admin
      .from("accounts_receivable")
      .update({ ...carga.patch, updated_at: new Date().toISOString() })
      .eq("id", carga.id)
      .eq("company_id", empresaDe(ctx))
      .not("status", "in", "(PAID,CANCELED)")
      .select("id")
      .maybeSingle();
    if (error) falha("corrigir a conta a receber", error);
    if (!data) recusar("Este título já foi recebido ou cancelado; nada foi corrigido.");
    if (carga.sincronizar) {
      const { error: erroDoResumo } = await ctx.supabase.rpc("sync_receivable_summary", { p_receivable_id: carga.id });
      if (erroDoResumo) falha("atualizar a situação do título", erroDoResumo);
    }
    return { dados: { corrigida: true, conta_a_receber_id: carga.id, campos: Object.keys(carga.patch) }, resumo: `Corrigiu a conta a receber "${carga.descricao}"` };
  },
};

type CargaDoRecebimento = { id: string; body: Record<string, unknown>; descricao: string; valor: number; conta: string | null; dia: string; forma: string };

export const receberContaAReceber: FerramentaDeEscrita<CargaDoRecebimento> = {
  nome: "receber_conta_a_receber",
  titulo: "Receber título (dar baixa)",
  descricao:
    "Registra o recebimento de um título a receber avulso, inteiro ou em parte: o valor entra no fluxo de caixa da conta escolhida (o saldo dela aumenta) e o título fica recebido ou recebido em parte. Sem valor, recebe o que falta; sem data, hoje. Para pedido, use registrar_pagamento_do_pedido. Sempre mostra uma prévia e pede confirmação.",
  campos: {
    conta_a_receber_id: { tipo: "id", descricao: "Id do título (de listar_contas_a_receber).", obrigatorio: true },
    forma_de_pagamento: { tipo: "opcao", descricao: FORMA_DESCRICAO, opcoes: FORMAS, obrigatorio: true },
    conta: { tipo: "texto", descricao: "Nome ou id da conta (ou caixa físico) onde o dinheiro entrou.", max: 120 },
    valor: { tipo: "numero", descricao: "Quanto foi recebido. O padrão é o que falta.", min: 0.01, max: 100_000_000 },
    data: { tipo: "data", descricao: "Dia do recebimento. O padrão é hoje." },
    observacoes: { tipo: "texto", descricao: "Observação do recebimento.", max: 500 },
  },
  tipo: "escrita",
  sensivel: true,
  permissoes: ["financial.receivables.settle"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.receivables.settle", "registrar recebimentos");
    exigirValores(ctx);
    const atual = await lerContaAReceber(ctx, args.conta_a_receber_id);
    if (atual.status === "PAID") recusar("Este título já está recebido.");
    if (atual.status === "CANCELED") recusar("Este título está cancelado.");
    const falta = dinheiro(Math.max(0, Number(atual.total_amount || 0) - Number(atual.paid_amount || 0)));
    if (falta <= 0) recusar("Este título não tem saldo a receber.");
    const valor = args.valor !== undefined ? dinheiro(args.valor) : falta;
    if (centavos(valor) > centavos(falta)) recusar(`O valor (${reais(valor)}) é maior que o que falta receber (${reais(falta)}).`);
    const forma = formaObrigatoria(args.forma_de_pagamento);
    const conta = await escolherConta(ctx, { dito: args.conta, forma, padraoId: atual.bank_account_id, papel: "em qual conta o dinheiro entrou" });
    const dia = (args.data as string | undefined) ?? hojeEmBrasilia();
    const descricao = String(atual.description || "Conta a receber");
    const observacoes = args.observacoes ? String(args.observacoes).trim() : null;
    const avisos: string[] = [];
    if (dia > hojeEmBrasilia()) avisos.push(`A data do recebimento (${dataBr(dia)}) está no futuro.`);
    if (centavos(valor) < centavos(falta)) avisos.push(`Recebimento em parte: depois dele ainda faltam ${reais(falta - valor)}.`);

    return {
      previa: {
        o_que_vai_acontecer: "O recebimento será registrado e o valor entra no fluxo de caixa.",
        titulo: descricao,
        cliente: atual.client_company || atual.client_name || null,
        falta_receber: reais(falta),
        valor_recebido: reais(valor),
        forma: nomeDaForma(forma),
        entra_em: nomeDaConta(conta),
        dia: dataBr(dia),
        observacoes,
      },
      resumo: `Prévia de recebimento: ${descricao}, ${reais(valor)}`,
      avisos,
      carga: {
        id: atual.id,
        body: { amount: valor, received_at: dia, bank_account_id: conta?.id, payment_method: forma, notes: observacoes },
        descricao,
        valor,
        conta: nomeDaConta(conta),
        dia,
        forma,
      },
    };
  },
  async gravar(ctx, carga) {
    const resultado = await registrarRecebimento({
      supabase: ctx.supabase,
      admin: ctx.admin,
      companyId: empresaDe(ctx),
      userId: ctx.user.id,
      receivableId: carga.id,
      body: carga.body,
    });
    if (!resultado.ok) erroDaGravacao(resultado.corpo, "registrar o recebimento", resultado.status);
    const conta = resultado.account as { total_amount?: number; paid_amount?: number; status?: string } | null;
    return {
      dados: {
        recebido: true,
        conta_a_receber_id: carga.id,
        valor: carga.valor,
        entrou_em: carga.conta,
        dia: carga.dia,
        ...(conta ? { falta_receber: dinheiro(Math.max(0, Number(conta.total_amount || 0) - Number(conta.paid_amount || 0))) } : {}),
      },
      resumo: `Recebeu ${reais(carga.valor)} do título "${carga.descricao}" em ${carga.conta} (${nomeDaForma(carga.forma)})`,
    };
  },
};

export const cancelarContaAReceber: FerramentaDeEscrita<CargaDoCancelamento> = {
  nome: "cancelar_conta_a_receber",
  titulo: "Cancelar conta a receber",
  descricao:
    "Cancela um título a receber avulso que não vai mais ser pago. Só vale para título sem nenhum recebimento. Sempre mostra uma prévia e pede confirmação.",
  campos: {
    conta_a_receber_id: { tipo: "id", descricao: "Id do título (de listar_contas_a_receber).", obrigatorio: true },
  },
  tipo: "escrita",
  sensivel: true,
  sobrescreve: true,
  permissoes: ["financial.receivables.write"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.receivables.write", "cancelar contas a receber");
    const atual = await lerContaAReceber(ctx, args.conta_a_receber_id);
    if (atual.status === "CANCELED") recusar("Este título já está cancelado.");
    if (Number(atual.paid_amount ?? 0) > 0) recusar("Este título já tem recebimento e não pode ser cancelado.");
    const descricao = String(atual.description || "Conta a receber");
    return {
      previa: {
        o_que_vai_acontecer: "O título a receber será cancelado. Nenhum dinheiro se move.",
        titulo: descricao,
        cliente: atual.client_company || atual.client_name || null,
        valor: podeVerValores(ctx) ? reais(atual.total_amount) : null,
        vencimento: atual.due_date ? dataBr(atual.due_date) : null,
      },
      resumo: `Prévia de cancelamento da conta a receber "${descricao}"`,
      avisos: [],
      carga: { id: atual.id, descricao },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.admin
      .from("accounts_receivable")
      .update({ status: "CANCELED", updated_at: new Date().toISOString() })
      .eq("id", carga.id)
      .eq("company_id", empresaDe(ctx))
      .neq("status", "CANCELED")
      .or("paid_amount.is.null,paid_amount.eq.0")
      .select("id")
      .maybeSingle();
    if (error) falha("cancelar a conta a receber", error);
    if (!data) recusar("O título mudou (recebeu algum valor ou já foi cancelado); nada foi cancelado.");
    return { dados: { cancelada: true, conta_a_receber_id: carga.id }, resumo: `Cancelou a conta a receber "${carga.descricao}"` };
  },
};

/* ------------------------------------------------------- pagamento de pedido */

type CargaDoPagamento = {
  pedidoId: string;
  numero: number | null;
  valor: number;
  forma: FormaDePagamento;
  dia: string;
  contaId: string | null;
  conta: string | null;
  observacoes: string | null;
};

export const registrarPagamentoDoPedidoFerramenta: FerramentaDeEscrita<CargaDoPagamento> = {
  nome: "registrar_pagamento_do_pedido",
  titulo: "Registrar pagamento de pedido",
  descricao:
    "Registra um pagamento recebido de um pedido (sinal, parcela ou o restante): o valor abate o saldo do pedido e entra no fluxo de caixa da conta escolhida. Aceita o número do pedido. Sem valor, registra o que falta pagar; sem data, hoje. Não aceita valor maior que o saldo do pedido. Sempre mostra uma prévia e pede confirmação.",
  campos: {
    pedido_id: { tipo: "id", descricao: "Id do pedido." },
    numero: { tipo: "inteiro", descricao: "Número do pedido (o que aparece como #123).", min: 1 },
    forma_de_pagamento: { tipo: "opcao", descricao: FORMA_DESCRICAO, opcoes: FORMAS, obrigatorio: true },
    conta: { tipo: "texto", descricao: "Nome ou id da conta (ou caixa físico) onde o dinheiro entrou.", max: 120 },
    valor: { tipo: "numero", descricao: "Quanto foi pago. O padrão é o saldo do pedido.", min: 0.01, max: 100_000_000 },
    data: { tipo: "data", descricao: "Dia do pagamento. O padrão é hoje." },
    observacoes: { tipo: "texto", descricao: "Observação do pagamento.", max: 500 },
  },
  tipo: "escrita",
  sensivel: true,
  permissoes: ["financial.receivables.settle"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.receivables.settle", "registrar pagamentos de pedidos");
    exigirValores(ctx);
    if (!args.pedido_id && !args.numero) recusar("Informe pedido_id ou numero.");
    let consulta = ctx.supabase
      .from("orders")
      .select("id,number,status,total_amount,paid_amount,clients(name,company_name)")
      .in("user_id", ctx.tenantIds)
      .order("created_at", { ascending: false })
      .limit(1);
    consulta = args.pedido_id ? consulta.eq("id", String(args.pedido_id)) : consulta.eq("number", Number(args.numero));
    const { data, error } = await consulta;
    if (error) falha("ler o pedido", error);
    const pedido = ((data ?? []) as Array<{
      id: string;
      number: number | null;
      status: string | null;
      total_amount: number | null;
      paid_amount: number | null;
      clients?: { name?: string | null; company_name?: string | null } | Array<{ name?: string | null; company_name?: string | null }> | null;
    }>)[0];
    if (!pedido) recusar("Pedido não encontrado. Use listar_pedidos para achar o número certo.");
    if (pedido.status === "canceled") recusar("Este pedido está cancelado; não recebe pagamento.");
    const falta = dinheiro(Number(pedido.total_amount || 0) - Number(pedido.paid_amount || 0));
    if (falta <= 0) recusar(`O pedido #${pedido.number} já está pago.`);
    const valor = args.valor !== undefined ? dinheiro(args.valor) : falta;
    if (centavos(valor) > centavos(falta)) recusar(`O valor (${reais(valor)}) é maior que o saldo do pedido (${reais(falta)}).`);
    const forma = formaObrigatoria(args.forma_de_pagamento);
    const conta = await escolherConta(ctx, { dito: args.conta, forma, papel: "em qual conta o dinheiro entrou", podeFicarSem: true });
    const dia = (args.data as string | undefined) ?? hojeEmBrasilia();
    const observacoes = args.observacoes ? String(args.observacoes).trim() : null;
    const avisos: string[] = [];
    if (dia > hojeEmBrasilia()) avisos.push(`A data do pagamento (${dataBr(dia)}) está no futuro.`);
    if (centavos(valor) < centavos(falta)) avisos.push(`Pagamento em parte: depois dele o pedido ainda deve ${reais(falta - valor)}.`);

    return {
      previa: {
        o_que_vai_acontecer: "O pagamento será registrado no pedido e o valor entra no fluxo de caixa.",
        pedido: `#${pedido.number}`,
        cliente: nomeDoCliente(um(pedido.clients) ?? null),
        total_do_pedido: reais(pedido.total_amount),
        ja_pago: reais(pedido.paid_amount),
        valor_deste_pagamento: reais(valor),
        forma: nomeDaForma(forma),
        entra_em: conta ? nomeDaConta(conta) : "sem conta (a empresa não tem conta cadastrada)",
        dia: dataBr(dia),
        observacoes,
      },
      resumo: `Prévia de pagamento do pedido #${pedido.number}: ${reais(valor)}`,
      avisos,
      carga: { pedidoId: pedido.id, numero: pedido.number, valor, forma, dia, contaId: conta?.id ?? null, conta: nomeDaConta(conta), observacoes },
    };
  },
  async gravar(ctx, carga) {
    const resultado = await registrarPagamentoDoPedido(
      ctx.supabase,
      { userId: ctx.user.id, tenantIds: ctx.tenantIds },
      {
        orderId: carga.pedidoId,
        amount: carga.valor,
        paymentMethod: carga.forma,
        paymentDateTime: null,
        paymentDate: carga.dia,
        /* Meio-dia no horário de Brasília, como a tela grava quem está no Brasil. */
        tzOffsetMinutesRaw: "180",
        notes: carga.observacoes,
        bankAccountIdRaw: carga.contaId,
      }
    );
    if ("error" in resultado) recusar(resultado.error);
    const { data } = await ctx.supabase.from("orders").select("total_amount,paid_amount,financial_status").eq("id", carga.pedidoId).maybeSingle();
    const pedido = data as { total_amount: number | null; paid_amount: number | null } | null;
    return {
      dados: {
        registrado: true,
        pedido: carga.numero,
        valor: resultado.valor,
        entrou_em: carga.conta,
        dia: carga.dia,
        ...(pedido ? { saldo_do_pedido: dinheiro(Math.max(0, Number(pedido.total_amount || 0) - Number(pedido.paid_amount || 0))) } : {}),
      },
      resumo: `Registrou pagamento de ${reais(resultado.valor)} no pedido #${carga.numero} (${nomeDaForma(carga.forma)}${carga.conta ? `, em ${carga.conta}` : ""})`,
    };
  },
};

/* ------------------------------------------------------------- transferência */

type CargaDaTransferencia = { origemId: string; destinoId: string; origem: string; destino: string; valor: number; dia: string; motivo: string };

export const transferirEntreContasFerramenta: FerramentaDeEscrita<CargaDaTransferencia> = {
  nome: "transferir_entre_contas",
  titulo: "Transferir entre contas",
  descricao:
    "Transfere dinheiro entre duas contas da própria empresa (ex.: do caixa físico para o banco, de uma conta para outra). Lança a saída numa e a entrada na outra, com o motivo. O caixa físico não pode ficar negativo. Só o dono ou um administrador da empresa pode. Sempre mostra uma prévia e pede confirmação.",
  campos: {
    origem: { tipo: "texto", descricao: "Nome ou id da conta de onde o dinheiro sai.", obrigatorio: true, max: 120 },
    destino: { tipo: "texto", descricao: "Nome ou id da conta para onde vai.", obrigatorio: true, max: 120 },
    valor: { tipo: "numero", descricao: "Valor em reais.", obrigatorio: true, min: 0.01, max: 100_000_000 },
    motivo: { tipo: "texto", descricao: "Motivo da transferência. Ex.: depósito do caixa do dia.", obrigatorio: true, max: 300 },
    data: { tipo: "data", descricao: "Dia da transferência. O padrão é hoje." },
  },
  tipo: "escrita",
  sensivel: true,
  permissoes: ["financial.bank.write"],
  async preparar(ctx, args) {
    if (!canManageFinancialCritical(ctx.empresa, ctx.user.id)) {
      recusar("Só o dono ou um administrador da empresa pode transferir entre contas.");
    }
    /* A prévia mostra os saldos das duas contas. */
    exigirValores(ctx);
    const origem = await escolherConta(ctx, { dito: args.origem, papel: "a conta de origem" });
    const destino = await escolherConta(ctx, { dito: args.destino, papel: "a conta de destino" });
    if (!origem || !destino) recusar("Informe a conta de origem e a de destino.");
    if (origem.id === destino.id) recusar("Origem e destino precisam ser contas diferentes.");
    const valor = dinheiro(args.valor);
    const dia = (args.data as string | undefined) ?? hojeEmBrasilia();
    const motivo = String(args.motivo).trim();

    const saldos = await saldosDasContas(ctx, [origem, destino]);
    const saldoDaOrigem = saldos.get(origem.id) ?? 0;
    const saldoDoDestino = saldos.get(destino.id) ?? 0;
    const origemEhCaixa = isCashAccountType(origem.type);
    if (origemEhCaixa && saldoDaOrigem + 0.0001 < valor) {
      recusar(`O caixa físico ${nomeDaConta(origem)} tem ${reais(saldoDaOrigem)}, menos que ${reais(valor)}. Caixa físico não fica negativo.`);
    }
    const dependencias = await getBankAccountDependencies(empresaDe(ctx), origem.id, ctx.tenantIds);
    if (dependencias.open_pdv_cash_sessions > 0) {
      recusar(`${nomeDaConta(origem)} tem um caixa do PDV aberto. Feche o caixa no PDV antes de transferir.`);
    }
    const avisos: string[] = [];
    if (!origemEhCaixa && saldoDaOrigem < valor) avisos.push(`${nomeDaConta(origem)} tem ${reais(saldoDaOrigem)}; depois da transferência fica negativa.`);
    if (dia > hojeEmBrasilia()) avisos.push(`A data (${dataBr(dia)}) está no futuro.`);

    return {
      previa: {
        o_que_vai_acontecer: "O valor sai de uma conta e entra na outra, com o motivo registrado nas duas.",
        de: `${nomeDaConta(origem)} (${nomeDoTipoDeConta(origem.type)})`,
        para: `${nomeDaConta(destino)} (${nomeDoTipoDeConta(destino.type)})`,
        valor: reais(valor),
        dia: dataBr(dia),
        motivo,
        saldo_da_origem: { antes: reais(saldoDaOrigem), depois: reais(saldoDaOrigem - valor) },
        saldo_do_destino: { antes: reais(saldoDoDestino), depois: reais(saldoDoDestino + valor) },
      },
      resumo: `Prévia de transferência de ${reais(valor)}: ${nomeDaConta(origem)} para ${nomeDaConta(destino)}`,
      avisos,
      carga: { origemId: origem.id, destinoId: destino.id, origem: nomeDaConta(origem) ?? "", destino: nomeDaConta(destino) ?? "", valor, dia, motivo },
    };
  },
  async gravar(ctx, carga) {
    if (!canManageFinancialCritical(ctx.empresa, ctx.user.id)) recusar("Só o dono ou um administrador da empresa pode transferir entre contas.");
    const resultado = await transferirEntreContas({
      supabase: ctx.supabase,
      companyId: empresaDe(ctx),
      tenantIds: ctx.tenantIds,
      userId: ctx.user.id,
      body: {
        source_bank_account_id: carga.origemId,
        destination_bank_account_id: carga.destinoId,
        amount: carga.valor,
        date: carga.dia,
        reason: carga.motivo,
      },
    });
    if (!resultado.ok) erroDaGravacao(resultado.corpo, "fazer a transferência", resultado.status);
    const transferencia = resultado.corpo.transfer as { source?: { after_balance?: number | null }; destination?: { after_balance?: number | null } } | undefined;
    return {
      dados: {
        transferido: true,
        valor: carga.valor,
        de: carga.origem,
        para: carga.destino,
        dia: carga.dia,
        saldo_da_origem_agora: transferencia?.source?.after_balance ?? null,
        saldo_do_destino_agora: transferencia?.destination?.after_balance ?? null,
      },
      resumo: `Transferiu ${reais(carga.valor)} de ${carga.origem} para ${carga.destino} (${carga.motivo})`,
    };
  },
};

/* ---------------------------------------------------------- link de pagamento */

type CargaDoLink = { body: Record<string, unknown>; titulo: string; valor: number };

export const criarLinkDePagamento: FerramentaDeEscrita<CargaDoLink> = {
  nome: "criar_link_de_pagamento",
  titulo: "Criar link de pagamento",
  descricao:
    "Cria um link de pagamento avulso para mandar ao cliente (ele paga por Pix ou cartão pelo link). Devolve o endereço pronto para enviar. Para cobrar um orçamento ou pedido, prefira o link do próprio orçamento.",
  campos: {
    titulo: { tipo: "texto", descricao: "O que está sendo cobrado. Aparece para o cliente.", obrigatorio: true, max: 120 },
    valor: { tipo: "numero", descricao: "Valor em reais.", obrigatorio: true, min: 1, max: 1_000_000 },
    descricao: { tipo: "texto", descricao: "Detalhes para o cliente. Opcional.", max: 1000 },
    aceitar_pix: { tipo: "booleano", descricao: "Aceitar Pix. O padrão é sim." },
    aceitar_cartao: { tipo: "booleano", descricao: "Aceitar cartão de crédito. O padrão é sim." },
    vale_ate: { tipo: "data", descricao: "Último dia em que o link aceita pagamento. Opcional." },
  },
  tipo: "escrita",
  /* Cobra o cliente: sempre com a aprovação da pessoa. */
  sensivel: true,
  permissoes: ["financial.links.write"],
  async preparar(ctx, args) {
    exigir(ctx, "financial.links.write", "criar links de pagamento");
    const titulo = String(args.titulo).trim();
    /* Título e descrição o cliente lê: nada de link, telefone ou Pix novo escrito pelo assistente. */
    const contatos = contatosNovos(null, [args.titulo, args.descricao].filter(Boolean).join(" "));
    if (contatos.length) recusar(recusaDeContatos(contatos, "em Financeiro › Links de pagamento"));
    const valor = dinheiro(args.valor);
    const pix = args.aceitar_pix !== false;
    const cartao = args.aceitar_cartao !== false;
    if (!pix && !cartao) recusar("O link precisa aceitar ao menos uma forma: Pix ou cartão.");
    const valeAte = args.vale_ate ? String(args.vale_ate) : null;
    if (valeAte && valeAte < hojeEmBrasilia()) recusar("A data de validade já passou.");
    const descricao = texto(args.descricao, 1000);
    return {
      previa: {
        o_que_vai_acontecer: "Um link de pagamento novo será criado, já ativo.",
        titulo,
        valor: reais(valor),
        aceita: [pix ? "Pix" : null, cartao ? "cartão de crédito" : null].filter(Boolean).join(" e "),
        vale_ate: valeAte ? dataBr(valeAte) : "sem prazo",
        descricao,
      },
      resumo: `Prévia de link de pagamento: ${titulo}, ${reais(valor)}`,
      avisos: [],
      carga: {
        body: {
          title: titulo,
          description: descricao ?? "",
          amount: valor,
          status: "active",
          type: "one_time",
          allow_pix: pix,
          allow_credit_card: cartao,
          allow_boleto: false,
          expires_at: valeAte ? `${valeAte}T23:59:59-03:00` : "",
        },
        titulo,
        valor,
      },
    };
  },
  async gravar(ctx, carga) {
    let link: Record<string, unknown>;
    let origem: string;
    try {
      const resolved = await contextoDosLinks(ctx);
      origem = resolved.origin;
      const criado = await createPaymentLink({ resolved, body: carga.body });
      link = criado.link as Record<string, unknown>;
    } catch (erro) {
      erroDoServicoDeLinks(erro, "criar o link de pagamento");
    }
    const codigo = String(link.code ?? "");
    return {
      dados: { criado: true, link_id: link.id, titulo: carga.titulo, valor: carga.valor, link: `${origem}/lp/${encodeURIComponent(codigo)}` },
      resumo: `Criou o link de pagamento "${carga.titulo}" de ${reais(carga.valor)}`,
    };
  },
};

export const FERRAMENTAS_DE_FINANCEIRO: Ferramenta[] = [
  ...LEITURAS_DE_FINANCEIRO,
  criarContaAPagarFerramenta,
  editarContaAPagar,
  darBaixaContaAPagar,
  desfazerPagamentoContaAPagar,
  cancelarContaAPagar,
  criarContaAReceberFerramenta,
  editarContaAReceber,
  receberContaAReceber,
  cancelarContaAReceber,
  registrarPagamentoDoPedidoFerramenta,
  transferirEntreContasFerramenta,
  criarLinkDePagamento,
] as Ferramenta[];
