import { isApiV1Error } from "@/lib/api-v1/errors";
import { avisosDoEstoqueV2, empresaNoEstoqueV2 } from "./estoque-do-canal";
import { createQuote } from "@/lib/api-v1/services/quotes-service";
import { camposParaLer } from "@/lib/campos-proprios";
import { lerDefinicoes } from "@/lib/campos-proprios-do-banco";
import { normalizarCondicoes, resumirCondicoes, type CondicoesComerciais } from "@/lib/condicoes-comerciais";
import { condicoesDoPadrao } from "@/lib/modelos-de-condicoes";
import { calculateEffectiveQuantity } from "@/lib/price-tiers";

import { contatosNovos, recusaDeContatos } from "../contatos";
import type { ContextoDoAssistente } from "../contexto";
import {
  AVISO_DE_DADOS,
  SITUACAO_DO_ORCAMENTO,
  SITUACAO_DO_PEDIDO,
  UNIDADE,
  baseDaLoja,
  contagem,
  dinheiro,
  exigir,
  falha,
  hojeEmBrasilia,
  inicioDoDia,
  limparBusca,
  nomeDoCliente,
  pode,
  reais,
  recusar,
  somarDias,
  texto,
  um,
} from "./comum";
import type { Campos } from "../esquema";
import { totaisDoOrcamento } from "./conta-do-orcamento";
import { textoDoMinimoAplicado } from "@/lib/adicionais";
import {
  CAMPOS_DAS_CONDICOES,
  CAMPOS_DO_ITEM,
  CAMPOS_DOS_CAMPOS_PROPRIOS,
  arteLigada,
  camposPropriosParaGravar,
  carregarProdutos,
  condicoesPedidas,
  conferirCondicoes,
  gravarArte,
  medidasDaLinha,
  modelosDaEmpresa,
  montarItem,
  opcoesParaLer,
  prepararArte,
  type ArtePedida,
  type ArtePronta,
  type ItemMontado,
} from "./orcamento-itens";
import type { Argumentos, FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

type ClienteEmbutido = { id?: string | null; name?: string | null; company_name?: string | null; whatsapp?: string | null; email?: string | null };

type OrcamentoDaLista = {
  id: string;
  number: number | null;
  status: string | null;
  title: string | null;
  total: number | null;
  created_at: string | null;
  expires_at: string | null;
  delivery_date: string | null;
  public_view_count: number | null;
  public_last_viewed_at: string | null;
  clients?: ClienteEmbutido | ClienteEmbutido[] | null;
};

const SITUACOES = ["rascunho", "enviado", "aprovado", "recusado", "todos"] as const;
const SITUACAO_NO_BANCO: Record<string, string> = { rascunho: "draft", enviado: "sent", aprovado: "approved", recusado: "rejected" };

function linhaDoOrcamento(row: OrcamentoDaLista) {
  const cliente = um(row.clients);
  return {
    id: row.id,
    numero: row.number,
    situacao: SITUACAO_DO_ORCAMENTO[row.status ?? ""] ?? row.status,
    titulo: row.title || null,
    cliente: nomeDoCliente(cliente),
    cliente_id: cliente?.id ?? null,
    total: dinheiro(row.total),
    criado_em: row.created_at,
    validade: row.expires_at,
    entrega: row.delivery_date,
    aberturas_do_link: Number(row.public_view_count ?? 0),
    ultima_abertura_do_link: row.public_last_viewed_at,
  };
}

export const listarOrcamentos: FerramentaDeLeitura = {
  nome: "listar_orcamentos",
  titulo: "Listar orçamentos",
  descricao:
    "Lista orçamentos, do mais novo para o mais antigo, com cliente, valor, situação e quantas vezes o cliente abriu o link. Filtra por situação, cliente, período ou busca (número, título ou nome do cliente). Para achar quem abriu o link e não respondeu, peça os enviados e olhe aberturas_do_link.",
  campos: {
    situacao: { tipo: "opcao", descricao: "Situação do orçamento. O padrão é todos.", opcoes: SITUACOES },
    busca: { tipo: "texto", descricao: "Número do orçamento, parte do título ou nome do cliente.", max: 80 },
    cliente_id: { tipo: "id", descricao: "Só os orçamentos deste cliente." },
    criado_de: { tipo: "data", descricao: "Criados a partir deste dia." },
    criado_ate: { tipo: "data", descricao: "Criados até este dia." },
    limite: { tipo: "inteiro", descricao: "Quantos devolver. O padrão é 20.", min: 1, max: 50 },
  },
  tipo: "leitura",
  permissoes: ["quotes.read"],
  async executar(ctx, args) {
    exigir(ctx, "quotes.read", "ver os orçamentos");
    const limite = (args.limite as number | undefined) ?? 20;
    const situacao = (args.situacao as string | undefined) ?? "todos";
    const busca = limparBusca(args.busca);

    let consulta = ctx.supabase
      .from("quotes")
      .select(
        "id,number,status,title,total,created_at,expires_at,delivery_date,public_view_count,public_last_viewed_at,clients(id,name,company_name)",
        { count: "exact" }
      )
      .in("user_id", ctx.tenantIds)
      .order("created_at", { ascending: false })
      .limit(limite);

    if (situacao !== "todos") consulta = consulta.eq("status", SITUACAO_NO_BANCO[situacao]);
    if (args.cliente_id) consulta = consulta.eq("client_id", String(args.cliente_id));
    if (args.criado_de) consulta = consulta.gte("created_at", inicioDoDia(String(args.criado_de)));
    if (args.criado_ate) consulta = consulta.lt("created_at", inicioDoDia(somarDias(String(args.criado_ate), 1)));

    if (busca) {
      const soNumero = busca.replace(/^#/, "");
      if (/^\d{1,9}$/.test(soNumero)) {
        consulta = consulta.eq("number", Number(soNumero));
      } else {
        /* O nome do cliente mora em outra tabela: acha os clientes primeiro e
           procura os orçamentos deles junto com os de título parecido. */
        const { data: clientes } = await ctx.supabase
          .from("clients")
          .select("id")
          .in("user_id", ctx.tenantIds)
          .or(`name.ilike.%${busca}%,company_name.ilike.%${busca}%`)
          .limit(40);
        const ids = ((clientes ?? []) as Array<{ id: string }>).map((cliente) => cliente.id);
        const filtros = [`title.ilike.%${busca}%`];
        if (ids.length) filtros.push(`client_id.in.(${ids.join(",")})`);
        consulta = consulta.or(filtros.join(","));
      }
    }

    const { data, error, count } = await consulta;
    if (error) falha("listar os orçamentos", error);
    const orcamentos = ((data ?? []) as unknown as OrcamentoDaLista[]).map(linhaDoOrcamento);

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        orcamentos,
        total_encontrado: count ?? orcamentos.length,
        soma_dos_listados: dinheiro(orcamentos.reduce((soma, orcamento) => soma + orcamento.total, 0)),
        ...(count && count > orcamentos.length ? { tem_mais: true } : {}),
      },
      resumo: `Listou orçamentos${situacao !== "todos" ? ` ${situacao}s` : ""} (${contagem(count ?? orcamentos.length, "encontrado", "encontrados")})`,
    };
  },
};

/* O link público de um orçamento, montado como a criação monta. */
export async function linkDoOrcamento(ctx: ContextoDoAssistente, token: string | null, ligado: boolean | null) {
  if (!token || ligado === false) return null;
  const perfilId = ctx.donoDosDados;
  const [{ data: perfil }, { data: dominio }] = await Promise.all([
    ctx.supabase.from("profiles").select("slug").eq("id", perfilId).maybeSingle(),
    ctx.supabase.from("profile_custom_domains").select("domain,verified").eq("profile_id", perfilId).maybeSingle(),
  ]);
  const dominioProprio = dominio?.verified && dominio.domain ? String(dominio.domain).trim().toLowerCase() : "";
  if (dominioProprio) return `https://${dominioProprio}/o/${token}`;
  const slug = (perfil as { slug?: string | null } | null)?.slug;
  return slug ? `${baseDaLoja(ctx)}/${slug}/o/${token}` : null;
}
type ItemDoBanco = {
  id: string;
  position: number | null;
  title: string | null;
  description: string | null;
  quantity: number | null;
  unit_price: number | null;
  unit_cost: number | null;
  line_total: number | null;
  unit_type: string | null;
  width_m: number | null;
  height_m: number | null;
  length_m: number | null;
  product_id: string | null;
  selected_options: Record<string, unknown> | null;
  valor_minimo_aplicado?: number | null;
  valor_sem_minimo?: number | null;
};

type ArteDoBanco = { quote_item_id: string | null; title: string | null; notes: string | null; image_urls: string[] | null; approval_status: string | null };

const SITUACAO_DA_ARTE: Record<string, string> = { pending: "aguardando o cliente", approved: "aprovada", rejected: "recusada", changes_requested: "cliente pediu ajuste" };

export const verOrcamento: FerramentaDeLeitura = {
  nome: "ver_orcamento",
  titulo: "Ver um orçamento",
  descricao:
    "Mostra um orçamento por inteiro, como a tela: cliente, itens (com item_id, posição, as opções escolhidas de cada atributo, medidas, preço, custo e arte), desconto, frete, condições comerciais, vendedores, campos próprios, observações, o link público, quantas vezes o cliente abriu e o pedido que nasceu dele. Informe o id ou o número. Use item_id ou posição dos itens em alterar_orcamento.",
  campos: {
    orcamento_id: { tipo: "id", descricao: "Id do orçamento, devolvido por listar_orcamentos." },
    numero: { tipo: "inteiro", descricao: "Número do orçamento, como aparece no painel. Ex.: 275.", min: 1 },
  },
  tipo: "leitura",
  permissoes: ["quotes.read"],
  async executar(ctx, args) {
    exigir(ctx, "quotes.read", "ver os orçamentos");
    if (!args.orcamento_id && !args.numero) recusar("Informe orcamento_id ou numero.");

    let consulta = ctx.supabase
      .from("quotes")
      .select(
        "id,number,status,title,subtotal,discount,shipping,total,created_at,updated_at,expires_at,delivery_date,notes,internal_notes,share_enabled,share_token,share_expires_at,public_view_count,public_first_viewed_at,public_last_viewed_at,list_price_mode,payment_terms,seller_mode,campos_proprios,clients(id,name,company_name,whatsapp,email)"
      )
      .in("user_id", ctx.tenantIds)
      .order("created_at", { ascending: false })
      .limit(1);
    consulta = args.orcamento_id ? consulta.eq("id", String(args.orcamento_id)) : consulta.eq("number", Number(args.numero));

    const { data, error } = await consulta;
    if (error) falha("ler o orçamento", error);
    const orcamento = ((data ?? []) as unknown as Array<Record<string, unknown>>)[0];
    if (!orcamento) recusar("Orçamento não encontrado. Use listar_orcamentos para achar o id ou o número certo.");

    const id = String(orcamento.id);
    const [itens, pedido, link, artes, vendedores, definicoes] = await Promise.all([
      ctx.supabase
        .from("quote_items")
        .select("id,position,title,description,quantity,unit_price,unit_cost,line_total,unit_type,width_m,height_m,length_m,product_id,selected_options,valor_minimo_aplicado,valor_sem_minimo")
        .eq("quote_id", id)
        .order("position"),
      ctx.supabase.from("orders").select("id,number,status").eq("quote_id", id).in("user_id", ctx.tenantIds).limit(1),
      linkDoOrcamento(ctx, orcamento.share_token as string | null, orcamento.share_enabled as boolean | null),
      ctx.supabase.from("quote_item_arts").select("quote_item_id,title,notes,image_urls,approval_status").eq("quote_id", id),
      ctx.supabase.from("sale_commission_allocations").select("seller_user_id,allocation_percent,is_primary").eq("quote_id", id),
      lerDefinicoes(ctx.supabase, "orcamento"),
    ]);
    if (itens.error) falha("ler os itens do orçamento", itens.error);

    const linhas = (itens.data ?? []) as ItemDoBanco[];
    const veCusto = pode(ctx, "pricing.read");
    const custo = linhas.reduce(
      (soma, item) => soma + calculateEffectiveQuantity({ quantity: item.quantity ?? 0, unit_type: item.unit_type || "unit", width_m: item.width_m, height_m: item.height_m, length_m: item.length_m }) * Number(item.unit_cost || 0),
      0
    );
    const cliente = um(orcamento.clients as ClienteEmbutido | ClienteEmbutido[] | null);
    const pedidoGerado = ((pedido.data ?? []) as Array<{ id: string; number: number | null; status: string | null }>)[0];
    const subtotal = dinheiro(orcamento.subtotal);
    const total = dinheiro(orcamento.total);
    const listaDeArtes = ((artes.error ? [] : artes.data) as ArteDoBanco[] | null) ?? [];
    const artesPorItem = new Map(listaDeArtes.map((arte) => [arte.quote_item_id, arte]));
    const condicoes = normalizarCondicoes(orcamento.payment_terms);
    const campos = definicoes.erro ? [] : camposParaLer(definicoes.definicoes, orcamento.campos_proprios);
    const rateio = ((vendedores.error ? [] : vendedores.data) as Array<{ seller_user_id: string; allocation_percent: number; is_primary: boolean }> | null) ?? [];

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        orcamento: {
          id,
          numero: orcamento.number,
          situacao: SITUACAO_DO_ORCAMENTO[String(orcamento.status ?? "")] ?? orcamento.status,
          titulo: orcamento.title || null,
          cliente: cliente
            ? { id: cliente.id, nome: nomeDoCliente(cliente), whatsapp: cliente.whatsapp || null, email: cliente.email || null }
            : null,
          itens: linhas.map((item, indice) => {
            const arte = artesPorItem.get(item.id);
            return {
              item_id: item.id,
              posicao: indice + 1,
              titulo: item.title ?? "",
              descricao: texto(item.description, 400),
              quantidade: Number(item.quantity ?? 0),
              vendido_por: UNIDADE[item.unit_type || "unit"] ?? "unidade",
              medidas: medidas(item),
              opcoes: opcoesParaLer(item.selected_options),
              preco_unitario: dinheiro(item.unit_price),
              total: dinheiro(item.line_total),
              ...(Number(item.valor_minimo_aplicado) > 0
                ? { valor_minimo: textoDoMinimoAplicado(item.valor_minimo_aplicado, item.valor_sem_minimo ?? null) }
                : {}),
              ...(veCusto ? { custo_unitario: dinheiro(item.unit_cost) } : {}),
              produto_id: item.product_id,
              ...(arte
                ? {
                    arte: {
                      titulo: texto(arte.title, 160),
                      observacao: texto(arte.notes, 600),
                      imagens: (arte.image_urls ?? []).length,
                      situacao: SITUACAO_DA_ARTE[arte.approval_status ?? ""] ?? arte.approval_status ?? null,
                    },
                  }
                : {}),
            };
          }),
          subtotal,
          desconto: dinheiro(orcamento.discount),
          frete: dinheiro(orcamento.shipping),
          total,
          ...(veCusto && custo > 0
            ? { custo_estimado: dinheiro(custo), lucro_estimado: dinheiro(subtotal - dinheiro(orcamento.discount) - custo) }
            : {}),
          lista_de_precos: Boolean(orcamento.list_price_mode),
          condicoes_comerciais: condicoes ? resumirCondicoes(condicoes, total) : null,
          vendedores: rateio.map((linha) => ({ vendedor_id: linha.seller_user_id, percentual: Number(linha.allocation_percent), principal: linha.is_primary })),
          campos_proprios: campos.length ? Object.fromEntries(campos.map((campo) => [campo.campo, campo.texto])) : null,
          criado_em: orcamento.created_at,
          validade: orcamento.expires_at,
          entrega: orcamento.delivery_date,
          observacoes: texto(orcamento.notes, 1200),
          observacoes_internas: texto(orcamento.internal_notes, 600),
          link_publico: link,
          link_vence_em: orcamento.share_expires_at ?? null,
          aberturas_do_link: Number(orcamento.public_view_count ?? 0),
          primeira_abertura: orcamento.public_first_viewed_at ?? null,
          ultima_abertura: orcamento.public_last_viewed_at ?? null,
          pedido: pedidoGerado
            ? { id: pedidoGerado.id, numero: pedidoGerado.number, situacao: SITUACAO_DO_PEDIDO[pedidoGerado.status ?? ""] ?? pedidoGerado.status }
            : null,
        },
      },
      resumo: `Consultou o orçamento nº ${orcamento.number}`,
    };
  },
};

function medidas(item: { unit_type: string | null; width_m: number | null; height_m: number | null; length_m: number | null }) {
  return medidasDaLinha({ unit_type: item.unit_type || "unit", width_m: item.width_m, height_m: item.height_m, length_m: item.length_m });
}

/* --------------------------------------------------------- criar orçamento */

type CargaDoOrcamento = {
  corpo: Record<string, unknown>;
  cliente: string;
  total: number;
  camposProprios: Record<string, unknown> | null;
  artes: Array<{ itemId: string; arte: ArtePronta }>;
};

/* Os vendedores do rateio, como a aba Financeiro da tela. */
export const CAMPOS_DO_RATEIO: Campos = {
  vendedor_id: { tipo: "id", descricao: "Id da pessoa da equipe (de ver_opcoes_do_orcamento).", obrigatorio: true },
  percentual: { tipo: "numero", descricao: "Parte da venda deste vendedor, em %. O rateio soma 100.", obrigatorio: true, min: 0.01, max: 100 },
};

const SITUACOES_NA_CRIACAO = ["rascunho", "enviado"] as const;

/** O texto que o assistente escreveu e o cliente lê: não pode trazer contato novo. */
export function conferirContatos(partes: Array<unknown>, onde: string, antes: string | null = null) {
  const escrito = partes.filter((parte): parte is string => typeof parte === "string" && Boolean(parte)).join("\n");
  const contatos = contatosNovos(antes, escrito);
  if (contatos.length) recusar(recusaDeContatos(contatos, onde));
}

/** O vendedor e o rateio, no formato que o serviço de orçamentos e a tela gravam. */
function vendedoresPedidos(ctx: ContextoDoAssistente, args: Argumentos) {
  const rateio = (args.rateio as Array<{ vendedor_id: string; percentual: number }> | undefined) ?? [];
  if (rateio.length) {
    return {
      seller_mode: "split",
      primary_seller_user_id: rateio[0].vendedor_id,
      allocations: rateio.map((linha, indice) => ({ seller_user_id: linha.vendedor_id, allocation_percent: linha.percentual, is_primary: indice === 0 })),
    };
  }
  /* Sem escolha, quem vende é a pessoa da chave: é em nome dela que o assistente age. */
  const vendedor = (args.vendedor_id as string | undefined) ?? ctx.user.id;
  return { seller_mode: "single", primary_seller_user_id: vendedor, allocations: [{ seller_user_id: vendedor, allocation_percent: 100, is_primary: true }] };
}

async function montarOrcamento(ctx: ContextoDoAssistente, args: Argumentos) {
  exigir(ctx, "quotes.write", "criar orçamentos");

  const clienteId = String(args.cliente_id);
  const { data: cliente, error: erroDoCliente } = await ctx.supabase
    .from("clients")
    .select("id,name,company_name,is_deleted")
    .eq("id", clienteId)
    .in("user_id", ctx.tenantIds)
    .maybeSingle();
  if (erroDoCliente) falha("conferir o cliente", erroDoCliente);
  if (!cliente || (cliente as { is_deleted?: boolean | null }).is_deleted) {
    recusar("Cliente não encontrado. Use buscar_clientes para achar o id, ou criar_cliente para cadastrar.");
  }

  const pedidos = (args.itens as Argumentos[]) ?? [];
  const produtos = await carregarProdutos(ctx, pedidos.map((item) => item.produto_id as string));

  const avisos: string[] = [];
  const estoqueV2 = await empresaNoEstoqueV2(ctx);
  const montados: ItemMontado[] = pedidos.map((pedido, indice) =>
    montarItem({ ctx, posicao: indice + 1, pedido, produtos, modo: "criacao", estoqueV2 })
  );
  for (const montado of montados) avisos.push(...montado.avisos);
  const itens = montados.map((montado) => montado.linha);
  /* Estoque v2: o que falta, pela mesma conta da loja e do PDV (só aviso). */
  if (estoqueV2) avisos.push(...(await avisosDoEstoqueV2(ctx, itens)));

  if (args.desconto !== undefined && args.desconto_percentual !== undefined) recusar("Mande o desconto em reais (desconto) ou em percentual (desconto_percentual), não os dois.");
  const totais = totaisDoOrcamento(itens, {
    desconto: dinheiro(args.desconto),
    descontoPercentual: (args.desconto_percentual as number | undefined) ?? null,
    frete: dinheiro(args.frete),
  });
  /* Cada campo tem o seu teto, mas quantidade, medidas e preço se multiplicam.
     Um total absurdo é erro de digitação (ou de quem monta o pedido), e
     recusar aqui é melhor que deixar o banco estourar na hora de gravar. */
  if (!Number.isFinite(totais.subtotal) || totais.subtotal > TOTAL_MAXIMO) {
    recusar("O total do orçamento passou de R$ 100 milhões. Confira as quantidades, as medidas e os preços dos itens.");
  }
  if (totais.desconto > totais.subtotal + totais.frete) recusar("O desconto é maior que o valor do orçamento.");

  const hoje = hojeEmBrasilia();
  if (args.validade && String(args.validade) < hoje) recusar("A validade já passou. Escolha uma data de hoje em diante.");
  if (args.entrega && String(args.entrega) < hoje) recusar("A data de entrega já passou. Escolha uma data de hoje em diante.");

  /* Condições comerciais: a tela começa no modelo padrão da empresa (se houver)
     e a pessoa liga, troca ou desliga. */
  const modelos = await modelosDaEmpresa(ctx);
  let condicoes: CondicoesComerciais | null;
  if (args.sem_condicoes === true) condicoes = null;
  else if (args.condicoes) condicoes = condicoesPedidas(args.condicoes as Argumentos, modelos, null);
  else condicoes = condicoesDoPadrao(modelos);
  const condicoesEscritas = conferirCondicoes(condicoes, totais.total);

  /* O que o cliente lê e foi o assistente quem escreveu. */
  conferirContatos(
    [
      args.titulo,
      args.observacoes,
      (args.condicoes as Argumentos | undefined)?.observacoes,
      ...montados.flatMap((montado) => [montado.doCadastro ? null : montado.linha.title, montado.linha.description, montado.arte?.titulo, montado.arte?.observacao]),
    ],
    "na tela do orçamento, depois de criado"
  );

  const campos = await camposPropriosParaGravar(ctx, "orcamento", args.campos as Array<{ campo: string; valor?: string }> | undefined, null);

  const comArte = montados.filter((montado) => montado.arte);
  if (comArte.length && !(await arteLigada(ctx))) {
    recusar("O cadastro de arte no orçamento está desligado nesta empresa (Orçamentos, Configurações). Ligue lá ou mande os itens sem arte.");
  }
  const artes: CargaDoOrcamento["artes"] = [];
  for (const montado of comArte) artes.push({ itemId: montado.linha.id, arte: await prepararArte(ctx, montado.arte as ArtePedida) });

  const situacao = (args.situacao as string | undefined) ?? "rascunho";
  const nome = nomeDoCliente(cliente as ClienteEmbutido) ?? "cliente";
  const total = dinheiro(totais.total);
  const carga: CargaDoOrcamento = {
    cliente: nome,
    total,
    camposProprios: campos && Object.keys(campos.valores).length ? campos.valores : null,
    artes,
    corpo: {
      client_id: clienteId,
      status: SITUACAO_NO_BANCO[situacao] ?? "draft",
      title: (args.titulo as string | undefined) ?? "",
      items: itens,
      discount: totais.desconto,
      shipping: totais.frete,
      notes: (args.observacoes as string | undefined) ?? "",
      internal_notes: (args.observacoes_internas as string | undefined) ?? "",
      expires_at: (args.validade as string | undefined) ?? null,
      delivery_date: (args.entrega as string | undefined) ?? null,
      list_price_mode: args.lista_de_precos === true,
      payment_terms: condicoes,
      ...vendedoresPedidos(ctx, args),
    },
  };

  return {
    previa: {
      o_que_vai_acontecer: `Um orçamento novo será criado como ${situacao}, com link público para enviar ao cliente.`,
      cliente: nome,
      titulo: (args.titulo as string | undefined) ?? null,
      itens: montados.map((montado) => montado.previa),
      subtotal: dinheiro(totais.subtotal),
      desconto: dinheiro(totais.desconto),
      frete: dinheiro(totais.frete),
      total,
      condicoes_comerciais: condicoesEscritas,
      ...(args.lista_de_precos === true ? { lista_de_precos: "o cliente vê os preços unitários, sem o total" } : {}),
      validade: (args.validade as string | undefined) ?? null,
      entrega: (args.entrega as string | undefined) ?? null,
      observacoes: (args.observacoes as string | undefined) ?? null,
      ...(args.observacoes_internas ? { observacoes_internas: args.observacoes_internas } : {}),
      ...(campos && Object.keys(campos.mudou).length ? { campos_proprios: campos.mudou } : {}),
      ...(artes.length ? { artes: artes.map((a) => ({ item_id: a.itemId, imagens: a.arte.origens })) } : {}),
    },
    resumo: `Prévia de orçamento para ${nome} (${reais(total)})`,
    avisos,
    carga,
  };
}

const TOTAL_MAXIMO = 100_000_000;

/* As recusas que o serviço de orçamentos escreveu para uma pessoa ler. Só
   elas voltam ao assistente. As demais carregam a mensagem crua do banco (nome
   de tabela, de coluna, de regra), que fica no log do servidor. */
const RECUSAS_QUE_VOLTAM = new Set([
  "forbidden",
  "missing_client_id",
  "missing_items",
  "invalid_payment_terms",
  "client_not_found",
  "invalid_client",
  "missing_primary_seller",
  "invalid_allocations",
  "duplicate_seller",
  "missing_sellers",
  "invalid_seller",
  "invalid_product",
  "invalid_quantity",
]);

const CAMPOS_DO_ORCAMENTO_NOVO: Campos = {
  cliente_id: { tipo: "id", descricao: "Id do cliente (de buscar_clientes ou criar_cliente).", obrigatorio: true },
  titulo: { tipo: "texto", descricao: "Título do orçamento. Ex.: Canecas para o evento de dezembro.", max: 160 },
  itens: { tipo: "lista", descricao: "Os itens do orçamento, na ordem em que aparecem.", obrigatorio: true, itens: CAMPOS_DO_ITEM, minItens: 1, maxItens: 50 },
  desconto: { tipo: "numero", descricao: "Desconto em reais sobre o total.", min: 0, max: 10_000_000 },
  desconto_percentual: { tipo: "numero", descricao: "Ou o desconto em % do subtotal (a tela tem as duas formas).", min: 0, max: 100 },
  frete: { tipo: "numero", descricao: "Frete em reais.", min: 0, max: 1_000_000 },
  observacoes: { tipo: "texto", descricao: "Observações que o cliente lê no orçamento.", max: 2000 },
  observacoes_internas: { tipo: "texto", descricao: "Observações internas: só a equipe vê.", max: 2000 },
  validade: { tipo: "data", descricao: "Até quando o orçamento vale." },
  entrega: { tipo: "data", descricao: "Data de entrega combinada." },
  situacao: { tipo: "opcao", descricao: "Como nasce: rascunho (padrão) ou enviado.", opcoes: SITUACOES_NA_CRIACAO },
  lista_de_precos: { tipo: "booleano", descricao: "Modo lista de preços: o cliente vê os preços unitários, sem somar o total." },
  condicoes: {
    tipo: "objeto",
    descricao: "Condições comerciais (forma e prazo de pagamento). Sem isto, vale o modelo padrão da empresa, como na tela.",
    campos: CAMPOS_DAS_CONDICOES,
  },
  sem_condicoes: { tipo: "booleano", descricao: "Tira as condições comerciais da proposta (a chave desligada na tela)." },
  vendedor_id: { tipo: "id", descricao: "Quem vende (de ver_opcoes_do_orcamento). O padrão é a pessoa conectada." },
  rateio: { tipo: "lista", descricao: "Ou a venda dividida entre vendedores (soma 100%). O primeiro é o principal.", itens: CAMPOS_DO_RATEIO, minItens: 2, maxItens: 10 },
  campos: { tipo: "lista", descricao: "Campos próprios do orçamento (os obrigatórios precisam vir, como na tela).", itens: CAMPOS_DOS_CAMPOS_PROPRIOS, maxItens: 40 },
};

export const criarOrcamento: FerramentaDeEscrita<CargaDoOrcamento> = {
  nome: "criar_orcamento",
  titulo: "Criar um orçamento",
  descricao:
    "Cria um orçamento como a tela Novo orçamento: itens do cadastro (produto_id + as opções de cada atributo + quantidade + medidas; o preço é calculado igual ao painel: faixa, opções, m²) ou avulsos (titulo e preco_unitario), arte do item, desconto (em reais ou %), frete, validade, entrega, condições comerciais, vendedor ou rateio, observações (do cliente e internas), modo lista de preços e campos próprios. Antes, veja as opções do produto em ver_opcoes_do_produto e PERGUNTE à pessoa a opção de cada atributo — muitos produtos têm preço zero e o valor vem das opções. Para conferir o valor antes, use calcular_preco. Nasce como rascunho, com o link público para enviar ao cliente.",
  campos: CAMPOS_DO_ORCAMENTO_NOVO,
  tipo: "escrita",
  permissoes: ["quotes.write"],
  preparar: montarOrcamento,
  async gravar(ctx, carga) {
    let criado: Awaited<ReturnType<typeof createQuote>>;
    try {
      criado = await createQuote({
        supabase: ctx.supabase,
        user: ctx.user,
        /* O link público é montado a partir do endereço do painel, e este
           pedido chegou por outro endereço. */
        request: new Request(`${ctx.origemDoAdmin}/api/mcp`),
        body: carga.corpo,
        entregaDoLink: { admin: ctx.admin },
      });
    } catch (erro) {
      if (isApiV1Error(erro) && RECUSAS_QUE_VOLTAM.has(erro.code)) recusar(erro.message);
      falha("criar o orçamento", erro as { message?: string });
    }
    /* Como a tela: o orçamento nasce pela API, e campos próprios e artes vão logo em seguida. */
    const pendencias: string[] = [];
    if (carga.camposProprios) {
      const { error } = await ctx.supabase.from("quotes").update({ campos_proprios: carga.camposProprios }).eq("id", criado.quoteId).in("user_id", ctx.tenantIds);
      if (error) pendencias.push("O orçamento foi criado, mas os campos próprios não foram salvos. Mande de novo com alterar_orcamento.");
    }
    if (carga.artes.length) {
      const { data: linha } = await ctx.supabase.from("quotes").select("user_id").eq("id", criado.quoteId).maybeSingle();
      const dono = (linha as { user_id?: string } | null)?.user_id ?? ctx.donoDosDados;
      for (const { itemId, arte } of carga.artes) {
        try {
          await gravarArte(ctx, { orcamentoId: criado.quoteId, donoDoOrcamento: dono, itemId, arte });
        } catch {
          pendencias.push("O orçamento foi criado, mas uma arte não foi salva. Mande de novo com alterar_orcamento.");
        }
      }
    }
    return {
      dados: {
        criado: true,
        orcamento_id: criado.quoteId,
        numero: criado.quoteNumber,
        cliente: criado.client.name,
        total: dinheiro(criado.total),
        situacao: SITUACAO_DO_ORCAMENTO[String(carga.corpo.status)] ?? "rascunho",
        link_publico: criado.shareUrl || null,
        ...(pendencias.length ? { pendencias } : {}),
        proximo_passo: "Envie o link ao cliente. Ele aprova por lá, e o orçamento vira pedido. Para mudar alguma coisa, use alterar_orcamento.",
      },
      resumo: `Criou o orçamento nº ${criado.quoteNumber} para ${criado.client.name} (${reais(criado.total)})`,
    };
  },
};
