import { ROTULO_DO_TIPO } from "@/lib/campos-proprios";
import { avisosDoEstoqueV2, disponibilidadeNoBanco, empresaNoEstoqueV2, resumoDoEstoque } from "./estoque-do-canal";
import { lerDefinicoes } from "@/lib/campos-proprios-do-banco";
import { normalizarCondicoes, resumirCondicoes, type CondicoesComerciais } from "@/lib/condicoes-comerciais";
import { resolveSaleQuantityLimits, sortProductPriceTiers } from "@/lib/price-tiers";
import { COLUNAS_DO_VALOR_MINIMO, textoDoMinimoAplicado, valorMinimoDoItem } from "@/lib/adicionais";
import { lerGrupos, lerServicos, servicosDoProduto, vitrineDeServicos } from "@/lib/servicos-opcionais";

import type { Campos } from "../esquema";
import type { ContextoDoAssistente } from "../contexto";
import {
  AVISO_DE_DADOS,
  SITUACAO_DO_ORCAMENTO,
  UNIDADE,
  contagem,
  dinheiro,
  exigir,
  falha,
  hojeEmBrasilia,
  pode,
  reais,
  recusar,
  texto,
} from "./comum";
import {
  atributosOrdenados,
  casarEscolhas,
  centavosDoFormulario,
  comoOPrecoSeForma,
  detalheDaConta,
  efeitoNoPreco,
  listaDeEscolhas,
  obrigatoriosFaltando,
  opcoesDoAtributo,
  avisoDeEstoque,
  montarSelectedOptions,
  precoDoPainel,
  quantidadeEfetiva,
  regraDeQuantidade,
  resumoDasEscolhas,
  totaisDoOrcamento,
  totalDaLinhaEmCentavos,
  type EscolhaPedida,
} from "./conta-do-orcamento";
import {
  CAMPOS_DAS_CONDICOES,
  CAMPOS_DAS_OPCOES,
  CAMPOS_DO_ITEM,
  CAMPOS_DOS_CAMPOS_PROPRIOS,
  camposPropriosParaGravar,
  carregarProdutos,
  condicoesPedidas,
  conferirCondicoes,
  gravarArte,
  modelosDaEmpresa,
  montarItem,
  prepararArte,
  type ArtePedida,
  type ArtePronta,
  type LinhaDoOrcamento,
} from "./orcamento-itens";
import { conferirContatos } from "./orcamentos";
import type { Argumentos, FerramentaDeEscrita, FerramentaDeLeitura } from "./tipos";

/*
 * O que falta para o assistente fazer tudo o que as telas de orçamento fazem:
 * ver as opções de um produto (de onde vem o preço), calcular o preço igual ao
 * painel, ver as escolhas da empresa (vendedores, modelos de condições, campos
 * próprios) e alterar um orçamento já criado — como o botão Salvar da tela
 * "Editar orçamento" (app/dashboard/quotes/[id]).
 */

/* ------------------------------------------------- ver opções do produto */

type ProdutoParaVer = {
  id: string;
  name: string;
  description: string | null;
  price: number | null;
  unit_type: string | null;
  active: boolean | null;
  price_on_request: boolean | null;
  use_variation_price_only: boolean | null;
  min_sale_qty_enabled: boolean | null;
  min_sale_qty: number | null;
  max_sale_qty_enabled: boolean | null;
  max_sale_qty: number | null;
  allowed_sale_qty_enabled: boolean | null;
  allowed_sale_quantities: number[] | null;
  production_time_value: number | null;
  production_time_unit: string | null;
  track_inventory: boolean | null;
  inventory_scope: string | null;
  inventory_attribute_name: string | null;
  stock: number | null;
  product_attributes: Array<{ id?: string; name: string; allow_multiple: boolean | null; is_required: boolean | null; position: number | null; options: unknown[] | null }> | null;
  product_price_tiers: Array<{ min_qty: number; max_qty: number | null; unit_price: number; sort_order?: number | null; active?: boolean | null }> | null;
  valor_minimo_item?: number | null;
  valor_minimo_orcamentos?: boolean | null;
  valor_minimo_loja?: boolean | null;
  valor_minimo_pdv?: boolean | null;
};

const PRAZO: Record<string, [string, string]> = {
  hours: ["hora", "horas"],
  days: ["dia", "dias"],
  business_days: ["dia útil", "dias úteis"],
  weeks: ["semana", "semanas"],
};

const CAMPO_DO_PRODUTO: Campos = {
  produto_id: { tipo: "id", descricao: "Id do produto (de buscar_produtos).", obrigatorio: true },
};

export const verOpcoesDoProduto: FerramentaDeLeitura = {
  nome: "ver_opcoes_do_produto",
  titulo: "Ver as opções e o preço de um produto",
  descricao:
    "Mostra tudo o que é preciso para orçar um produto: como é vendido (unidade, m², metro), preço base, cada atributo (ex.: Papel, Cor) com as opções e quanto cada uma soma no preço (por m², por peça ou uma vez no pedido), quais são obrigatórios, faixas de preço por quantidade, quantidade mínima/máxima, estoque por opção e prazo de produção. Use ANTES de criar_orcamento: muitos produtos têm preço zero e o valor vem das opções — pergunte à pessoa qual opção de cada atributo.",
  campos: CAMPO_DO_PRODUTO,
  tipo: "leitura",
  permissoes: ["quotes.read", "products.catalog.read"],
  async executar(ctx, args) {
    if (!pode(ctx, "quotes.read") && !pode(ctx, "products.catalog.read")) exigir(ctx, "products.catalog.read", "ver os produtos");
    const { data, error } = await ctx.supabase
      .from("products")
      .select(
        "id,name,description,price,unit_type,active,price_on_request,use_variation_price_only,min_sale_qty_enabled,min_sale_qty,max_sale_qty_enabled,max_sale_qty,allowed_sale_qty_enabled,allowed_sale_quantities,production_time_value,production_time_unit,track_inventory,inventory_scope,inventory_attribute_name,stock,product_attributes(id,name,allow_multiple,is_required,position,options),product_price_tiers(min_qty,max_qty,unit_price,sort_order,active)," +
          COLUNAS_DO_VALOR_MINIMO
      )
      .eq("id", String(args.produto_id))
      .in("user_id", ctx.tenantIds)
      .maybeSingle();
    if (error) falha("ler o produto", error);
    if (!data) recusar("Produto não encontrado. Use buscar_produtos para achar o id certo.");
    const produto = data as unknown as ProdutoParaVer;
    const comoProduto = { ...produto, cost_total: null } as Parameters<typeof atributosOrdenados>[0] & Parameters<typeof comoOPrecoSeForma>[0];

    const escopo = produto.inventory_scope || "product";
    const atributoDoEstoque = (produto.inventory_attribute_name || "").trim().toLowerCase();
    const atributos = atributosOrdenados(comoProduto).map((atributo) => {
      const controlaEstoque = produto.track_inventory !== false && escopo === "option" && atributo.name.trim().toLowerCase() === atributoDoEstoque;
      return {
        atributo: atributo.name,
        obrigatorio: Boolean(atributo.is_required),
        escolha: atributo.allow_multiple ? "pode marcar várias" : "uma opção",
        opcoes: opcoesDoAtributo(atributo).map((opcao) => ({
          opcao: opcao.label,
          ...(opcao.id ? { opcao_id: opcao.id } : {}),
          preco: opcao.price,
          efeito_no_preco: efeitoNoPreco(opcao, produto.unit_type),
          ...(controlaEstoque && typeof opcao.stock === "number" ? { estoque: opcao.stock } : {}),
        })),
      };
    });

    const limites = resolveSaleQuantityLimits(produto);
    const faixas = sortProductPriceTiers((produto.product_price_tiers ?? []).filter((faixa) => faixa.active !== false)).map((faixa) => ({
      de: Number(faixa.min_qty),
      ate: faixa.max_qty === null ? null : Number(faixa.max_qty),
      preco: dinheiro(faixa.unit_price),
    }));
    const prazo = Number(produto.production_time_value ?? 0);
    const nomesDoPrazo = PRAZO[produto.production_time_unit ?? ""] ?? PRAZO.business_days;

    /* Serviços opcionais: a loja virtual oferece; a tela do orçamento não. */
    let servicos: Array<{ grupo: string; servico: string; preco: number; cobrado: string }> = [];
    try {
      const [grupos, biblioteca, vinculos] = await Promise.all([
        ctx.supabase.from("service_groups").select("id,name,selection_mode,required,position,active").in("user_id", ctx.tenantIds),
        ctx.supabase.from("optional_services").select("id,group_id,title,description,details,icon,price,charge_per_unit,apply_to_all,position,active").in("user_id", ctx.tenantIds),
        ctx.supabase.from("product_optional_services").select("service_id,enabled").eq("product_id", produto.id),
      ]);
      if (!grupos.error && !biblioteca.error) {
        const oferecidos = servicosDoProduto(lerServicos(biblioteca.data), vinculos.error ? [] : (vinculos.data as Array<{ service_id?: unknown; enabled?: unknown }>));
        servicos = vitrineDeServicos(lerGrupos(grupos.data), oferecidos).flatMap((caixa) =>
          caixa.servicos.map((servico) => ({ grupo: caixa.grupo.nome, servico: servico.titulo, preco: servico.preco, cobrado: servico.porPeca ? "por peça" : "uma vez no pedido" }))
        );
      }
    } catch {
      servicos = [];
    }

    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        produto: {
          id: produto.id,
          nome: produto.name,
          ativo: produto.active !== false,
          vendido_por: UNIDADE[produto.unit_type || "unit"] ?? "unidade",
          preco_base: produto.price_on_request ? null : dinheiro(produto.price),
          sob_consulta: Boolean(produto.price_on_request),
          preco_so_pelas_opcoes: Boolean(produto.use_variation_price_only),
          como_o_preco_se_forma: comoOPrecoSeForma(comoProduto),
          atributos,
          faixas_de_preco: faixas,
          ...(valorMinimoDoItem(produto, "orcamentos") !== null ||
          valorMinimoDoItem(produto, "loja") !== null ||
          valorMinimoDoItem(produto, "pdv") !== null
            ? {
                valor_minimo_do_item: {
                  valor: Number(produto.valor_minimo_item),
                  vale_nos_orcamentos: produto.valor_minimo_orcamentos !== false,
                  vale_na_loja: produto.valor_minimo_loja !== false,
                  vale_no_pdv: produto.valor_minimo_pdv !== false,
                  como_funciona:
                    "Cada linha (todas as peças daquela linha juntas) é comparada sozinha com o mínimo; se a conta der menos, a linha custa o mínimo. Itens diferentes não se somam para chegar nele.",
                },
              }
            : {}),
          quantidade_minima: limites.minQty,
          quantidade_maxima: limites.maxQty,
          quantidades_permitidas: limites.allowedQuantities.length ? limites.allowedQuantities : null,
          ...(produto.unit_type === "square_meter" ? { medidas: "Peça largura_m e altura_m de cada peça; a quantidade é o número de peças." } : {}),
          ...(produto.unit_type === "linear_meter" || produto.unit_type === "meter" ? { medidas: "Peça comprimento_m de cada peça; a quantidade é o número de peças." } : {}),
          estoque:
            produto.track_inventory === false
              ? "sem controle (sob demanda)"
              : escopo === "product"
                ? Number(produto.stock ?? 0)
                : escopo === "option"
                  ? `por opção de ${produto.inventory_attribute_name ?? "?"} (veja em atributos)`
                  : "por combinação de opções",
          prazo_de_producao: prazo > 0 ? `${prazo} ${prazo === 1 ? nomesDoPrazo[0] : nomesDoPrazo[1]}` : null,
          descricao: texto(produto.description, 800),
          ...(servicos.length
            ? {
                servicos_da_loja: servicos,
                sobre_os_servicos:
                  "Estes serviços aparecem na loja virtual. A tela do orçamento não os oferece como opção do item: para cobrar um deles no orçamento, inclua um item avulso com o nome e o preço do serviço.",
              }
            : {}),
        },
        proximo_passo: atributos.length
          ? `Pergunte à pessoa: ${listaDeEscolhas(comoProduto)}. Depois confira o valor com calcular_preco e monte com criar_orcamento (campo opcoes de cada item).`
          : "Este produto não tem opções: basta a quantidade (e as medidas, se for por metro).",
      },
      resumo: `Consultou as opções do produto ${produto.name}`,
    };
  },
};

/* ------------------------------------------------------- calcular preço */

export const calcularPreco: FerramentaDeLeitura = {
  nome: "calcular_preco",
  titulo: "Calcular o preço de um item",
  descricao:
    "Calcula o preço de um produto com as opções escolhidas, a quantidade e as medidas, com a mesma conta do painel (faixa pela quantidade total, opções por m², por peça ou por pedido, preço só pelas opções). Devolve o preço unitário que entra no orçamento, o total do item e o detalhe da conta. Se faltar a opção de um atributo obrigatório, diz quais são as escolhas: pergunte à pessoa. Não grava nada.",
  campos: {
    ...CAMPO_DO_PRODUTO,
    quantidade: { tipo: "numero", descricao: "Quantidade de peças.", obrigatorio: true, min: 1, max: 1_000_000 },
    opcoes: { tipo: "lista", descricao: "As opções escolhidas (atributo + opção, ou opcao_id).", itens: CAMPOS_DAS_OPCOES, maxItens: 60 },
    largura_m: CAMPOS_DO_ITEM.largura_m,
    altura_m: CAMPOS_DO_ITEM.altura_m,
    comprimento_m: CAMPOS_DO_ITEM.comprimento_m,
  },
  tipo: "leitura",
  permissoes: ["quotes.read", "quotes.write", "products.catalog.read"],
  async executar(ctx, args) {
    if (!pode(ctx, "quotes.read") && !pode(ctx, "quotes.write") && !pode(ctx, "products.catalog.read")) exigir(ctx, "quotes.read", "ver os orçamentos");
    const produtos = await carregarProdutos(ctx, [String(args.produto_id)]);
    const produto = produtos.get(String(args.produto_id));
    if (!produto) recusar("Produto não encontrado. Use buscar_produtos para achar o id certo.");

    const casado = casarEscolhas(produto, (args.opcoes as EscolhaPedida[] | undefined) ?? []);
    if (!casado.ok) recusar(casado.erro);
    const escolhas = casado.escolhas;
    const faltando = obrigatoriosFaltando(produto, escolhas);
    if (faltando.length) {
      recusar(
        `Falta escolher ${faltando.map((a) => a.name).join(", ")} em "${produto.name}" — é obrigatório e o preço depende disso. Pergunte à pessoa. Escolhas possíveis: ${listaDeEscolhas(produto, faltando)}.`
      );
    }

    const medido = produto.unit_type === "square_meter";
    const porMetro = produto.unit_type === "linear_meter" || produto.unit_type === "meter";
    const medidas = {
      quantidade: Number(args.quantidade),
      largura: medido ? ((args.largura_m as number | undefined) ?? null) : null,
      altura: medido ? ((args.altura_m as number | undefined) ?? null) : null,
      comprimento: porMetro ? ((args.comprimento_m as number | undefined) ?? null) : null,
    };
    const efetiva = quantidadeEfetiva(produto.unit_type, medidas);
    const avisos: string[] = [];
    const regra = regraDeQuantidade(produto, efetiva);
    if (regra) avisos.push(`A tela não salva com esta quantidade. ${regra}`);
    /* Estoque: no v2 a conta do banco (a mesma da loja e do PDV, pela
       combinação escolhida); fora dele o aviso de sempre. */
    let estoqueDaEscolha: ReturnType<typeof resumoDoEstoque> = null;
    if (await empresaNoEstoqueV2(ctx)) {
      estoqueDaEscolha = resumoDoEstoque(
        await disponibilidadeNoBanco(ctx, {
          produtoId: produto.id,
          selected: montarSelectedOptions(produto, escolhas),
          quantidade: medidas.quantidade,
          largura: medidas.largura,
          altura: medidas.altura,
        })
      );
      for (const falta of estoqueDaEscolha?.faltas ?? []) avisos.push(`${falta} Só aviso: o orçamento salva; o pedido não sai sem estoque.`);
    } else {
      const estoque = avisoDeEstoque(produto, escolhas, efetiva);
      if (estoque) avisos.push(estoque);
    }
    if (medido && !(medidas.largura && medidas.altura)) avisos.push("Sem largura e altura, a quantidade conta como metros quadrados.");
    if (porMetro && !medidas.comprimento) avisos.push("Sem comprimento, a quantidade conta como metros.");
    if (produto.active === false) avisos.push("Este produto está inativo: a tela do orçamento não o oferece.");

    const exato = precoDoPainel(produto, medidas, escolhas);
    const conta = detalheDaConta(produto, medidas, escolhas);
    const minimoAplicado = produto.price_on_request ? null : conta.valor_minimo_aplicado;
    /* Com o valor mínimo do item, o total é o mínimo e o preço por medida vai inteiro. */
    const noOrcamentoNovo = minimoAplicado !== null ? Math.round(exato * 1e6) / 1e6 : centavosDoFormulario(exato);
    const totalDoItem = minimoAplicado !== null ? minimoAplicado : dinheiro(efetiva * noOrcamentoNovo);
    if (produto.price_on_request) avisos.push("Produto sob consulta: o cadastro não tem preço. Combine o preço e mande em preco_unitario.");
    if (minimoAplicado !== null) avisos.push(textoDoMinimoAplicado(minimoAplicado, conta.total_sem_minimo) + ".");

    return {
      dados: {
        produto: { id: produto.id, nome: produto.name, vendido_por: UNIDADE[produto.unit_type || "unit"] ?? "unidade" },
        opcoes: resumoDasEscolhas(escolhas) || null,
        quantidade: medidas.quantidade,
        preco_unitario: minimoAplicado !== null ? dinheiro(noOrcamentoNovo) : noOrcamentoNovo,
        preco_unitario_e_por: produto.unit_type === "square_meter" ? "m²" : porMetro ? "metro" : "unidade",
        total_do_item: totalDoItem,
        ...(minimoAplicado !== null ? { valor_minimo_aplicado: minimoAplicado, total_sem_minimo: conta.total_sem_minimo } : {}),
        conta,
        ...(estoqueDaEscolha ? { estoque: estoqueDaEscolha } : {}),
        ...(avisos.length ? { avisos } : {}),
        como_usar: "Para orçar, mande em criar_orcamento o mesmo produto_id, quantidade, medidas e opcoes: o preço sai igual.",
      },
      resumo: `Calculou o preço de ${produto.name} (${reais(totalDoItem)})`,
    };
  },
};

/* ------------------------------------------------ ver opções do orçamento */

export const verOpcoesDoOrcamento: FerramentaDeLeitura = {
  nome: "ver_opcoes_do_orcamento",
  titulo: "Ver as escolhas da tela de orçamento",
  descricao:
    "Mostra o que a tela de orçamento oferece nesta empresa: os vendedores (para vendedor_id e rateio), os modelos de condições comerciais (forma e prazo de pagamento, e qual é o padrão), os campos próprios do orçamento (e quais são obrigatórios), se o cadastro de arte está ligado e as situações possíveis.",
  campos: {},
  tipo: "leitura",
  permissoes: ["quotes.read"],
  async executar(ctx) {
    exigir(ctx, "quotes.read", "ver os orçamentos");
    const [equipe, modelos, definicoes, perfil] = await Promise.all([
      ctx.admin.from("company_users").select("user_id,email,role,active").eq("company_id", ctx.empresa.companyId).eq("active", true).order("created_at", { ascending: true }),
      modelosDaEmpresa(ctx),
      lerDefinicoes(ctx.supabase, "orcamento"),
      ctx.supabase.from("profiles").select("quote_art_registration_enabled").eq("id", ctx.donoDosDados).maybeSingle(),
    ]);
    if (equipe.error) falha("listar os vendedores", equipe.error);
    const vendedores = ((equipe.data ?? []) as Array<{ user_id: string; email: string | null; role: string | null }>).map((linha) => ({
      vendedor_id: linha.user_id,
      email: linha.email,
      papel: linha.role,
      ...(linha.user_id === ctx.user.id ? { e_quem_esta_conectado: true } : {}),
    }));
    if (ctx.empresa.ownerId && !vendedores.some((v) => v.vendedor_id === ctx.empresa.ownerId)) {
      vendedores.unshift({ vendedor_id: ctx.empresa.ownerId, email: null, papel: "dono", ...(ctx.empresa.ownerId === ctx.user.id ? { e_quem_esta_conectado: true } : {}) });
    }
    return {
      dados: {
        vendedores,
        modelos_de_condicoes: modelos.map((modelo) => ({ nome: modelo.nome, padrao: modelo.padrao, condicoes: resumirCondicoes(modelo.condicoes, 0) })),
        campos_proprios: (definicoes.erro ? [] : definicoes.definicoes)
          .filter((def) => def.ativo)
          .map((def) => ({ chave: def.chave, nome: def.rotulo, tipo: ROTULO_DO_TIPO[def.tipo], obrigatorio: def.obrigatorio, ...(def.tipo === "opcoes" ? { opcoes: def.opcoes } : {}) })),
        arte_no_orcamento: Boolean((perfil.data as { quote_art_registration_enabled?: boolean | null } | null)?.quote_art_registration_enabled),
        situacoes: Object.values(SITUACAO_DO_ORCAMENTO),
      },
      resumo: "Consultou as escolhas da tela de orçamento",
    };
  },
};

/* ------------------------------------------------------ alterar orçamento */

const SITUACOES = ["rascunho", "enviado", "aprovado", "recusado", "em_producao", "entregue"] as const;
const SITUACAO_NO_BANCO: Record<string, string> = {
  rascunho: "draft",
  enviado: "sent",
  aprovado: "approved",
  recusado: "rejected",
  em_producao: "production",
  entregue: "delivered",
};
const LIMPAVEIS = ["titulo", "validade", "entrega", "observacoes", "observacoes_internas", "desconto", "frete"] as const;

const CAMPOS_DO_ITEM_ALTERADO: Campos = {
  item: { tipo: "texto", descricao: "Qual item: a posição (1, 2, 3...) ou o item_id, como em ver_orcamento.", obrigatorio: true, max: 40 },
  ...CAMPOS_DO_ITEM,
  quantidade: { tipo: "numero", descricao: "Nova quantidade de peças (no mínimo 1).", min: 0.001, max: 1_000_000 },
  opcoes: {
    tipo: "lista",
    descricao: "Opções a escolher. Cada atributo mencionado troca de opção; os outros ficam como estavam (use limpar_opcoes para começar do zero).",
    itens: CAMPOS_DAS_OPCOES,
    maxItens: 60,
  },
  limpar_opcoes: { tipo: "booleano", descricao: "Tira todas as opções escolhidas antes de aplicar as novas." },
  manter_preco: { tipo: "booleano", descricao: "Mantém o preço atual do item mesmo mudando quantidade, medida ou opção (o cadeado da tela)." },
  preco_automatico: { tipo: "booleano", descricao: "Volta o preço para o calculado pelo cadastro (abre o cadeado da tela)." },
  remover_arte: { tipo: "booleano", descricao: "Apaga a arte do item." },
};

type ArteGravada = { id: string; quote_item_id: string | null; title: string | null; notes: string | null; image_urls: string[] | null };

type CargaDaAlteracao = {
  orcamentoId: string;
  numero: number | null;
  donoDoOrcamento: string;
  orcamento: Record<string, unknown>;
  itens: Array<LinhaDoOrcamento & { position: number; line_total: number }>;
  idsQueFicam: string[];
  artesParaApagar: string[];
  artes: Array<{ itemId: string; arte: ArtePronta; existente: { id: string; image_urls: string[] } | null }>;
  totalAntes: number;
  totalDepois: number;
};

async function acharOrcamento(ctx: ContextoDoAssistente, args: Argumentos) {
  if (!args.orcamento_id && !args.numero) recusar("Informe orcamento_id ou numero.");
  let consulta = ctx.supabase
    .from("quotes")
    .select(
      "id,user_id,number,status,title,client_id,subtotal,discount,shipping,total,expires_at,delivery_date,notes,internal_notes,list_price_mode,payment_terms,campos_proprios,public_view_count,orders(id,number)"
    )
    .in("user_id", ctx.tenantIds)
    .limit(2);
  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 lista = (data ?? []) as unknown as Array<Record<string, unknown>>;
  if (!lista.length) recusar("Orçamento não encontrado. Use listar_orcamentos para achar o número ou o id certo.");
  if (lista.length > 1) recusar("Há mais de um orçamento com esse número. Use o orcamento_id (de listar_orcamentos).");
  return lista[0];
}

/** "2" ou um item_id -> o índice na lista atual. */
function indiceDoItem(referencia: string, itens: LinhaDoOrcamento[]) {
  const texto = String(referencia).trim();
  if (/^\d{1,3}$/.test(texto)) {
    const posicao = Number(texto);
    if (posicao < 1 || posicao > itens.length) recusar(`Não existe o item ${posicao}: o orçamento tem ${contagem(itens.length, "item", "itens")}.`);
    return posicao - 1;
  }
  const indice = itens.findIndex((item) => item.id === texto.toLowerCase());
  if (indice < 0) recusar(`Não existe item com o id ${texto} neste orçamento. Veja os itens em ver_orcamento.`);
  return indice;
}

async function prepararAlteracao(ctx: ContextoDoAssistente, args: Argumentos) {
  exigir(ctx, "quotes.write", "alterar orçamentos");
  const orcamento = await acharOrcamento(ctx, args);
  const id = String(orcamento.id);
  const status = String(orcamento.status ?? "draft");
  /* A tela deixa o orçamento recusado só para leitura. */
  if (status === "rejected") {
    recusar("Este orçamento está recusado, e a tela deixa orçamento recusado só para leitura. Para aproveitar, use duplicar_orcamento e altere a cópia.");
  }
  const pedidoGerado = (Array.isArray(orcamento.orders) ? orcamento.orders[0] : orcamento.orders) as { id: string; number: number | null } | null | undefined;

  const [{ data: linhasDoBanco, error: erroDosItens }, { data: artesDoBanco }] = await Promise.all([
    ctx.supabase
      .from("quote_items")
      .select("id,position,product_id,unit_type,width_m,height_m,length_m,title,description,quantity,unit_price,unit_cost,selected_options,valor_minimo_aplicado,valor_sem_minimo")
      .eq("quote_id", id)
      .order("position", { ascending: true }),
    ctx.supabase.from("quote_item_arts").select("id,quote_item_id,title,notes,image_urls").eq("quote_id", id),
  ]);
  if (erroDosItens) falha("ler os itens do orçamento", erroDosItens);
  const atuais: LinhaDoOrcamento[] = ((linhasDoBanco ?? []) as Array<Record<string, unknown>>).map((linha) => ({
    id: String(linha.id),
    product_id: (linha.product_id as string | null) || null,
    unit_type: (linha.unit_type as string | null) || "unit",
    width_m: Number(linha.width_m) > 0 ? Number(linha.width_m) : null,
    height_m: Number(linha.height_m) > 0 ? Number(linha.height_m) : null,
    length_m: Number(linha.length_m) > 0 ? Number(linha.length_m) : null,
    title: String(linha.title ?? ""),
    description: (linha.description as string | null) || null,
    quantity: Number(linha.quantity) || 0,
    unit_price: Number(linha.unit_price) || 0,
    unit_cost: Number(linha.unit_cost) || 0,
    selected_options: linha.selected_options && typeof linha.selected_options === "object" ? (linha.selected_options as Record<string, unknown>) : null,
    valor_minimo_aplicado: Number(linha.valor_minimo_aplicado) > 0 ? Number(linha.valor_minimo_aplicado) : null,
    valor_sem_minimo: Number(linha.valor_minimo_aplicado) > 0 && linha.valor_sem_minimo != null ? Number(linha.valor_sem_minimo) : null,
  }));
  const artes = ((artesDoBanco ?? []) as ArteGravada[]).filter((arte) => arte.quote_item_id);

  const alteracoes = (args.alterar_itens as Argumentos[] | undefined) ?? [];
  const novos = (args.adicionar_itens as Argumentos[] | undefined) ?? [];
  const removidos = (args.remover_itens as string[] | undefined) ?? [];
  const ordem = (args.nova_ordem as string[] | undefined) ?? [];
  const limpar = new Set((args.limpar as string[] | undefined) ?? []);
  for (const nome of limpar) {
    if (!(LIMPAVEIS as readonly string[]).includes(nome)) recusar(`"limpar" aceita: ${LIMPAVEIS.join(", ")}.`);
  }

  const produtos = await carregarProdutos(ctx, [
    ...atuais.map((item) => item.product_id as string),
    ...alteracoes.map((item) => item.produto_id as string),
    ...novos.map((item) => item.produto_id as string),
  ]);

  const avisos: string[] = [];
  const estoqueV2 = await empresaNoEstoqueV2(ctx);
  const mudancasDosItens: string[] = [];
  const itens: LinhaDoOrcamento[] = atuais.map((item) => ({ ...item }));
  const tocados = new Set<string>();
  const artesNovas: Array<{ itemId: string; arte: ArtePedida }> = [];
  const artesParaApagar = new Set<string>();

  /* 1. Alterar (pela posição de antes de qualquer mudança). */
  const alteradosAgora = new Set<number>();
  for (const alteracao of alteracoes) {
    const indice = indiceDoItem(String(alteracao.item), atuais);
    if (alteradosAgora.has(indice)) recusar(`O item ${indice + 1} veio duas vezes em alterar_itens. Junte as mudanças numa entrada só.`);
    alteradosAgora.add(indice);
    const pedido: Argumentos = { ...alteracao };
    delete pedido.item;
    const montado = montarItem({ ctx, posicao: indice + 1, pedido, produtos, modo: "edicao", atual: atuais[indice], estoqueV2 });
    itens[indice] = montado.linha;
    tocados.add(montado.linha.id);
    avisos.push(...montado.avisos);
    mudancasDosItens.push(`Item ${indice + 1} (${montado.linha.title}) alterado: ${resumoDoItem(montado.linha)}`);
    if (alteracao.remover_arte === true) {
      const existente = artes.find((arte) => arte.quote_item_id === montado.linha.id);
      if (existente) artesParaApagar.add(existente.id);
    }
    if (montado.arte) artesNovas.push({ itemId: montado.linha.id, arte: montado.arte });
  }

  /* 2. Remover. */
  const indicesRemovidos = new Set(removidos.map((referencia) => indiceDoItem(referencia, atuais)));
  for (const indice of indicesRemovidos) {
    if (alteradosAgora.has(indice)) recusar(`O item ${indice + 1} está em alterar_itens e em remover_itens ao mesmo tempo.`);
    mudancasDosItens.push(`Item ${indice + 1} (${atuais[indice].title}) removido.`);
    for (const arte of artes) if (arte.quote_item_id === atuais[indice].id) artesParaApagar.add(arte.id);
  }
  let lista = itens.filter((_, indice) => !indicesRemovidos.has(indice));

  /* 3. Reordenar (posições de antes). */
  if (ordem.length) {
    const indices = ordem.map((referencia) => indiceDoItem(referencia, atuais));
    const restantes = atuais.map((_, indice) => indice).filter((indice) => !indicesRemovidos.has(indice));
    if (new Set(indices).size !== indices.length || indices.length !== restantes.length || !restantes.every((indice) => indices.includes(indice))) {
      recusar(`nova_ordem precisa listar cada item que fica uma vez só (${restantes.map((i) => i + 1).join(", ")}), na ordem nova.`);
    }
    lista = indices.map((indice) => itens[indice]);
    mudancasDosItens.push(`Itens na ordem: ${indices.map((i) => i + 1).join(", ")}.`);
  }

  /* 4. Adicionar no fim. */
  novos.forEach((pedido) => {
    const posicao = lista.length + 1;
    const montado = montarItem({ ctx, posicao, pedido, produtos, modo: "edicao", estoqueV2 });
    lista.push(montado.linha);
    tocados.add(montado.linha.id);
    avisos.push(...montado.avisos);
    mudancasDosItens.push(`Item novo ${posicao} (${montado.linha.title}): ${resumoDoItem(montado.linha)}`);
    if (montado.arte) artesNovas.push({ itemId: montado.linha.id, arte: montado.arte });
  });

  if (!lista.length) recusar("O orçamento não pode ficar sem itens (a tela pede ao menos 1). Para desistir dele, mude a situação para recusado.");

  /* A tela confere TODOS os itens ao salvar: opção obrigatória e quantidade. */
  lista.forEach((linha, indice) => {
    if (tocados.has(linha.id)) return;
    montarItem({ ctx, posicao: indice + 1, pedido: {}, produtos, modo: "edicao", atual: linha, estoqueV2 });
  });
  /* Estoque v2: o que falta no orçamento inteiro (só aviso; salvar não trava). */
  if (estoqueV2) avisos.push(...(await avisosDoEstoqueV2(ctx, lista)));

  /* Desconto: a tela guarda em reais; o percentual vira reais na hora. */
  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 descontoAtual = limpar.has("desconto") ? 0 : Number(orcamento.discount) || 0;
  const freteAtual = limpar.has("frete") ? 0 : Number(orcamento.shipping) || 0;
  const totais = totaisDoOrcamento(lista, {
    desconto: args.desconto !== undefined ? dinheiro(args.desconto) : descontoAtual,
    descontoPercentual: (args.desconto_percentual as number | undefined) ?? null,
    frete: args.frete !== undefined ? dinheiro(args.frete) : freteAtual,
  });
  if (!Number.isFinite(totais.subtotal) || totais.subtotal > 100_000_000) 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.");

  /* Cliente. */
  let clienteNome: string | null = null;
  if (args.cliente_id && args.cliente_id !== orcamento.client_id) {
    const { data: cliente, error } = await ctx.supabase.from("clients").select("id,name,company_name,is_deleted").eq("id", String(args.cliente_id)).in("user_id", ctx.tenantIds).maybeSingle();
    if (error) falha("conferir o cliente", error);
    if (!cliente || (cliente as { is_deleted?: boolean | null }).is_deleted) recusar("Cliente não encontrado. Use buscar_clientes para achar o id.");
    clienteNome = (cliente as { company_name?: string | null; name?: string | null }).company_name || (cliente as { name?: string | null }).name || "cliente";
  }

  /* Condições comerciais. */
  const condicoesAtuais = normalizarCondicoes(orcamento.payment_terms);
  let condicoes: CondicoesComerciais | null = condicoesAtuais;
  if (args.sem_condicoes === true) condicoes = null;
  else if (args.condicoes) condicoes = condicoesPedidas(args.condicoes as Argumentos, await modelosDaEmpresa(ctx), condicoesAtuais);
  const condicoesEscritas = conferirCondicoes(condicoes, totais.total);

  /* Campos próprios (a tela exige os obrigatórios para salvar). */
  const campos = await camposPropriosParaGravar(ctx, "orcamento", args.campos as Array<{ campo: string; valor?: string }> | undefined, orcamento.campos_proprios);

  /* Texto que o cliente lê e o assistente escreveu: nada de contato novo. */
  const textosDeAntes = [orcamento.title, orcamento.notes, ...atuais.flatMap((item) => [item.title, item.description])]
    .filter((parte): parte is string => typeof parte === "string")
    .join("\n");
  conferirContatos(
    [
      args.titulo,
      args.observacoes,
      (args.condicoes as Argumentos | undefined)?.observacoes,
      ...lista.filter((linha) => tocados.has(linha.id)).flatMap((linha) => [linha.title, linha.description]),
      ...artesNovas.flatMap(({ arte }) => [arte.titulo, arte.observacao]),
    ],
    "na tela do orçamento",
    textosDeAntes
  );

  const artesProntas: CargaDaAlteracao["artes"] = [];
  for (const { itemId, arte } of artesNovas) {
    const existente = artes.find((gravada) => gravada.quote_item_id === itemId && !artesParaApagar.has(gravada.id));
    artesProntas.push({ itemId, arte: await prepararArte(ctx, arte), existente: existente ? { id: existente.id, image_urls: existente.image_urls ?? [] } : null });
  }

  const situacao = args.situacao ? SITUACAO_NO_BANCO[String(args.situacao)] : status;
  const valor = (campo: string, coluna: string) => (limpar.has(campo) ? null : args[campo] !== undefined ? String(args[campo]).trim() || null : (orcamento[coluna] as string | null));
  const novoOrcamento: Record<string, unknown> = {
    client_id: args.cliente_id ?? orcamento.client_id,
    status: situacao,
    title: valor("titulo", "title"),
    subtotal: totais.subtotal,
    discount: totais.desconto,
    shipping: totais.frete,
    list_price_mode: args.lista_de_precos !== undefined ? args.lista_de_precos === true : Boolean(orcamento.list_price_mode),
    payment_terms: condicoes,
    total: totais.total,
    expires_at: valor("validade", "expires_at"),
    delivery_date: valor("entrega", "delivery_date"),
    notes: valor("observacoes", "notes"),
    internal_notes: valor("observacoes_internas", "internal_notes"),
    ...(campos ? { campos_proprios: campos.valores } : {}),
  };

  /* O que muda, para a prévia. */
  const mudancas: Record<string, unknown> = {};
  const rotulo: Record<string, string> = {
    client_id: "cliente",
    status: "situação",
    title: "título",
    discount: "desconto",
    shipping: "frete",
    list_price_mode: "lista de preços",
    expires_at: "validade",
    delivery_date: "entrega",
    notes: "observações",
    internal_notes: "observações internas",
  };
  for (const [coluna, nome] of Object.entries(rotulo)) {
    const antes = orcamento[coluna] ?? null;
    const depois = novoOrcamento[coluna] ?? null;
    const iguais = typeof depois === "number" ? Math.abs(Number(antes ?? 0) - depois) < 0.005 : String(antes ?? "") === String(depois ?? "");
    if (iguais) continue;
    if (coluna === "client_id") mudancas[nome] = clienteNome;
    else if (coluna === "status") mudancas[nome] = SITUACAO_DO_ORCAMENTO[String(depois)] ?? depois;
    else if (coluna === "discount" || coluna === "shipping") mudancas[nome] = { de: dinheiro(antes), para: dinheiro(depois) };
    else mudancas[nome] = depois;
  }
  if (JSON.stringify(condicoesAtuais) !== JSON.stringify(condicoes)) mudancas["condições comerciais"] = condicoesEscritas ?? "sem condições";
  if (campos && Object.keys(campos.mudou).length) mudancas["campos próprios"] = campos.mudou;
  if (artesProntas.length) mudancas.artes = artesProntas.map((a) => ({ item_id: a.itemId, imagens: a.arte.origens }));
  if (artesParaApagar.size) mudancas["artes apagadas"] = artesParaApagar.size;
  if (mudancasDosItens.length) mudancas.itens = mudancasDosItens;

  const totalAntes = dinheiro(orcamento.total);
  const totalDepois = dinheiro(totais.total);
  if (!Object.keys(mudancas).length && Math.abs(totalAntes - totalDepois) < 0.005) recusar("Nada para mudar: o orçamento já está assim.");

  if (pedidoGerado) {
    avisos.push(
      `Este orçamento já virou o pedido nº ${pedidoGerado.number}. O que mudar aqui fica só no orçamento; para levar ao pedido, use alterar_pedido com atualizar_pelo_orcamento.`
    );
  }
  if (args.situacao === "aprovado" && !pedidoGerado) avisos.push("Marcar como aprovado não gera o pedido. Para gerar o pedido de produção, use aprovar_orcamento.");

  /* Total mudou num orçamento que o cliente já viu (ou já recebeu): passa pela prévia. */
  const clienteJaViu = status !== "draft" || Number(orcamento.public_view_count ?? 0) > 0 || Boolean(pedidoGerado);
  const sensivel = clienteJaViu && Math.abs(totalAntes - totalDepois) >= 0.005;

  const itensFinais = lista.map((linha, indice) => ({
    ...linha,
    title: linha.title || "Item",
    quantity: Math.max(1, linha.quantity || 1),
    unit_price: Math.max(0, linha.unit_price || 0),
    unit_cost: Math.max(0, linha.unit_cost || 0),
    position: indice + 1,
    line_total: totalDaLinhaEmCentavos(linha),
  }));

  return {
    previa: {
      o_que_vai_acontecer: `O orçamento nº ${orcamento.number} vai mudar.`,
      mudancas,
      itens: itensFinais.map((linha) => ({
        posicao: linha.position,
        titulo: linha.title,
        quantidade: linha.quantity,
        ...(resumoDasOpcoes(linha) ? { opcoes: resumoDasOpcoes(linha) } : {}),
        preco_unitario: dinheiro(linha.unit_price),
        total: linha.line_total,
        ...(linha.valor_minimo_aplicado != null
          ? { valor_minimo: textoDoMinimoAplicado(linha.valor_minimo_aplicado, linha.valor_sem_minimo ?? null) }
          : {}),
      })),
      total: { de: totalAntes, para: totalDepois },
    },
    resumo: `Prévia da alteração do orçamento nº ${orcamento.number} (${reais(totalDepois)})`,
    avisos,
    sensivel,
    carga: {
      orcamentoId: id,
      numero: orcamento.number as number | null,
      donoDoOrcamento: String(orcamento.user_id),
      orcamento: novoOrcamento,
      itens: itensFinais,
      idsQueFicam: itensFinais.map((linha) => linha.id),
      artesParaApagar: [...artesParaApagar],
      artes: artesProntas,
      totalAntes,
      totalDepois,
    } satisfies CargaDaAlteracao,
  };
}

function resumoDasOpcoes(linha: LinhaDoOrcamento) {
  const opcoes = (linha.selected_options as { options?: Array<{ attribute_name?: string; label?: string }> } | null)?.options ?? [];
  const mapa: Record<string, string[]> = {};
  for (const opcao of opcoes) {
    if (!opcao?.attribute_name || !opcao.label) continue;
    (mapa[opcao.attribute_name] ??= []).push(opcao.label);
  }
  return resumoDasEscolhas(mapa);
}

function resumoDoItem(linha: LinhaDoOrcamento) {
  const opcoes = resumoDasOpcoes(linha);
  if (linha.valor_minimo_aplicado != null) {
    return `${linha.quantity} × valor mínimo ${reais(linha.valor_minimo_aplicado)}${opcoes ? ` (${opcoes})` : ""}`;
  }
  return `${linha.quantity} × ${reais(linha.unit_price)}${opcoes ? ` (${opcoes})` : ""}`;
}

export const alterarOrcamento: FerramentaDeEscrita<CargaDaAlteracao> = {
  nome: "alterar_orcamento",
  titulo: "Alterar um orçamento",
  descricao:
    "Altera um orçamento como o Salvar da tela Editar orçamento: cliente, título, situação, validade, entrega, observações (do cliente e internas), desconto (reais ou %), frete, lista de preços, condições comerciais, campos próprios e os itens — adicionar_itens, alterar_itens (produto, opções de cada atributo, quantidade, medidas, preço manual ou automático, custo, descrição, arte), remover_itens e nova_ordem. O preço dos itens é recalculado como na tela (ao mudar quantidade, medida ou opção) e o total também. Mande só o que muda. Itens se referem pela posição ou pelo item_id de ver_orcamento. Remover item, ou mudar o total de um orçamento que o cliente já recebeu, passa pela prévia com código.",
  campos: {
    orcamento_id: { tipo: "id", descricao: "Id do orçamento (de listar_orcamentos)." },
    numero: { tipo: "inteiro", descricao: "Ou o número do orçamento, como aparece no painel.", min: 1 },
    cliente_id: { tipo: "id", descricao: "Trocar o cliente (de buscar_clientes)." },
    titulo: { tipo: "texto", descricao: "Título novo.", max: 160 },
    situacao: { tipo: "opcao", descricao: "A situação, como no seletor da tela. Para gerar o pedido, use aprovar_orcamento.", opcoes: SITUACOES },
    validade: { tipo: "data", descricao: "Nova validade." },
    entrega: { tipo: "data", descricao: "Nova data de entrega." },
    observacoes: { tipo: "texto", descricao: "Observações que o cliente lê (substitui as atuais).", max: 2000 },
    observacoes_internas: { tipo: "texto", descricao: "Observações internas (substitui as atuais).", max: 2000 },
    desconto: { tipo: "numero", descricao: "Desconto em reais.", min: 0, max: 10_000_000 },
    desconto_percentual: { tipo: "numero", descricao: "Ou o desconto em % do subtotal.", min: 0, max: 100 },
    frete: { tipo: "numero", descricao: "Frete em reais.", min: 0, max: 1_000_000 },
    lista_de_precos: { tipo: "booleano", descricao: "Liga ou desliga o modo lista de preços (o cliente vê os preços sem o total)." },
    condicoes: { tipo: "objeto", descricao: "Condições comerciais. Parte das atuais (ou de um modelo, com o campo modelo) e troca o que vier.", campos: CAMPOS_DAS_CONDICOES },
    sem_condicoes: { tipo: "booleano", descricao: "Tira as condições comerciais da proposta." },
    campos: { tipo: "lista", descricao: "Campos próprios a preencher (valor vazio apaga).", itens: CAMPOS_DOS_CAMPOS_PROPRIOS, maxItens: 40 },
    limpar: { tipo: "textos", descricao: `Campos a apagar: ${LIMPAVEIS.join(", ")}.`, maxItens: LIMPAVEIS.length, max: 40 },
    adicionar_itens: { tipo: "lista", descricao: "Itens novos, no fim da lista.", itens: CAMPOS_DO_ITEM, maxItens: 50 },
    alterar_itens: { tipo: "lista", descricao: "Mudanças em itens que já existem: só os campos que mudam.", itens: CAMPOS_DO_ITEM_ALTERADO, maxItens: 50 },
    remover_itens: { tipo: "textos", descricao: "Itens a remover: posições (\"2\") ou item_id.", maxItens: 50, max: 40 },
    nova_ordem: { tipo: "textos", descricao: "A ordem nova dos itens que ficam, pelas posições de agora (ex.: [\"3\",\"1\",\"2\"]) ou item_id.", maxItens: 60, max: 40 },
  },
  tipo: "escrita",
  sobrescreve: true,
  /* Tirar item é perder o que estava combinado: sempre com a prévia. O total
     mudando num orçamento que o cliente já recebeu vira prévia pelo resultado. */
  sensivel: (args) => Array.isArray(args.remover_itens) && (args.remover_itens as unknown[]).length > 0,
  permissoes: ["quotes.write"],
  preparar: prepararAlteracao,
  async gravar(ctx, carga) {
    const agora = new Date().toISOString();
    /* Na ordem do Salvar da tela: o orçamento, os itens que saem, os que ficam. */
    const { data: gravado, error: erroDoOrcamento } = await ctx.supabase
      .from("quotes")
      .update({ ...carga.orcamento, updated_at: agora })
      .eq("id", carga.orcamentoId)
      .in("user_id", ctx.tenantIds)
      .select("id");
    if (erroDoOrcamento) falha("alterar o orçamento", erroDoOrcamento);
    if (!gravado || !gravado.length) recusar("O orçamento não foi alterado: esta pessoa não tem permissão para mudar este orçamento.");

    const lista = carga.idsQueFicam.map((id) => `"${id}"`).join(",");
    const { error: erroAoTirar } = await ctx.supabase.from("quote_items").delete().eq("quote_id", carga.orcamentoId).not("id", "in", `(${lista})`);
    if (erroAoTirar) falha("tirar os itens do orçamento", erroAoTirar);

    const linhas = carga.itens.map((linha) => ({
      id: linha.id,
      quote_id: carga.orcamentoId,
      position: linha.position,
      product_id: linha.product_id,
      unit_type: linha.unit_type,
      width_m: linha.width_m,
      height_m: linha.height_m,
      length_m: linha.length_m,
      title: linha.title,
      description: linha.description,
      quantity: linha.quantity,
      unit_price: linha.unit_price,
      unit_cost: linha.unit_cost,
      selected_options: linha.selected_options || null,
      line_total: linha.line_total,
      valor_minimo_aplicado: linha.valor_minimo_aplicado ?? null,
      valor_sem_minimo: linha.valor_minimo_aplicado != null ? linha.valor_sem_minimo ?? null : null,
    }));
    const { error: erroDosItens } = await ctx.supabase.from("quote_items").upsert(linhas, { onConflict: "id" });
    if (erroDosItens) falha("gravar os itens do orçamento", erroDosItens);

    const pendencias: string[] = [];
    if (carga.artesParaApagar.length) {
      const { error } = await ctx.supabase.from("quote_item_arts").delete().in("id", carga.artesParaApagar).eq("quote_id", carga.orcamentoId);
      if (error) pendencias.push("Uma arte não foi apagada. Tente de novo pelo painel.");
    }
    for (const { itemId, arte, existente } of carga.artes) {
      try {
        await gravarArte(ctx, { orcamentoId: carga.orcamentoId, donoDoOrcamento: carga.donoDoOrcamento, itemId, arte, existente });
      } catch {
        pendencias.push("Uma arte não foi salva. Tente de novo.");
      }
    }

    return {
      dados: {
        alterado: true,
        orcamento_id: carga.orcamentoId,
        numero: carga.numero,
        itens: carga.itens.length,
        total: { de: carga.totalAntes, para: carga.totalDepois },
        ...(pendencias.length ? { pendencias } : {}),
        proximo_passo: "Confira com ver_orcamento. Se o cliente já tinha o link, ele vê a versão nova no mesmo link.",
      },
      resumo: `Alterou o orçamento nº ${carga.numero} (${reais(carga.totalDepois)})`,
    };
  },
};

export const FERRAMENTAS_DE_ORCAMENTO_COMPLETO = [verOpcoesDoProduto, calcularPreco, verOpcoesDoOrcamento, alterarOrcamento];
