import { AVISO_DE_DADOS, contagem, exigir, falha, limparBusca, pode, recusar } from "./comum";
import { disponibilidadeNoBanco, empresaNoEstoqueV2, resumoDoEstoque } from "./estoque-do-canal";
import {
  acharCelula,
  escolhaDoProduto,
  estaBaixo,
  gradeValida,
  lerCombinacao,
  normalizar,
  rotuloDaCelula,
  textoDaGrade,
  type AtributoSimples,
  type CelulaSimples,
} from "./estoque-v2-regras";
import type { Argumentos, Ferramenta, FerramentaDeEscrita, FerramentaDeLeitura, Saida } from "./tipos";
import type { ContextoDoAssistente } from "../contexto";

/*
  Estoque v2 pelo assistente: o mesmo motor das telas (MOTOR.md), sempre
  pelas funções do banco e pela sessão da pessoa (as funções conferem a
  permissão e a empresa de novo). Nada aqui escreve saldo direto.

  - Leitura: consultar_estoque (com grade, quando a empresa está no v2),
    ver_estoque_do_produto, conferir_estoque, historico_do_estoque,
    listar_compras_a_receber.
  - Escrita comum (grava direto): lancar_entrada_no_estoque,
    lancar_perda_no_estoque, contar_estoque, registrar_producao,
    configurar_estoque_do_produto, alterar_quando_sai_da_composicao.
  - Sensível (sempre com prévia e código): desfazer_producao, receber_compra
    (mexe na conta a pagar) e desfazer_estoque_novo (volta a empresa ao
    modelo antigo).
*/

type ErroDoBanco = { message?: string; hint?: string; code?: string } | null | undefined;

/** Erro do motor do estoque: a mensagem já é para a pessoa ("Estoque insuficiente de …"). */
function recusarDoBanco(oQue: string, erro: ErroDoBanco): never {
  const hint = String(erro?.hint || "");
  if (hint.startsWith("ESTOQUE_") || erro?.code === "42501") recusar(String(erro?.message || `Não deu para ${oQue}.`));
  falha(oQue, erro);
}

async function exigirV2(ctx: ContextoDoAssistente) {
  if (!(await empresaNoEstoqueV2(ctx))) {
    recusar("O estoque novo (com grade, composição e produção) ainda não está ligado nesta empresa. Use ajustar_estoque_do_produto e consultar_estoque.");
  }
}

const QUANTO = (n: unknown) => new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 3 }).format(Number(n) || 0);

type ItemDoEstoque = {
  id: string;
  name: string | null;
  unit: string | null;
  papel: string | null;
  grade: unknown;
  min_stock_alert: number | null;
  permitir_negativo: boolean | null;
  track_stock: boolean | null;
  produto_id: string | null;
  is_active?: boolean | null;
};
type CelulaDoBanco = CelulaSimples & { saldo: number | null; minimo: number | null; item_id?: string };

const COLUNAS_DO_ITEM = "id,name,unit,papel,grade,min_stock_alert,permitir_negativo,track_stock,produto_id,is_active";

async function carregarItem(ctx: ContextoDoAssistente, itemId: string) {
  const { data: item, error } = await ctx.supabase.from("materials").select(COLUNAS_DO_ITEM).eq("id", itemId).in("user_id", ctx.tenantIds).maybeSingle();
  if (error) falha("ler o item do estoque", error);
  if (!item) recusar("Item de estoque não encontrado nesta empresa. Use consultar_estoque para achar o id.");
  const { data: celulas, error: erroDasCelulas } = await ctx.supabase
    .from("estoque_celulas")
    .select("id,item_id,valor1,valor2,saldo,minimo,ativo")
    .eq("item_id", itemId);
  if (erroDasCelulas) falha("ler as células do item", erroDasCelulas);
  return { item: item as unknown as ItemDoEstoque, celulas: (celulas ?? []) as CelulaDoBanco[] };
}

function celulaDoTexto(item: ItemDoEstoque, celulas: CelulaDoBanco[], combinacao: unknown) {
  const achada = acharCelula(lerCombinacao(String(combinacao ?? "")), celulas, item.grade, item.name || "item");
  if (!achada.ok) recusar(achada.erro);
  return achada.celula as CelulaDoBanco;
}

async function carregarProduto(ctx: ContextoDoAssistente, produtoId: string) {
  const { data, error } = await ctx.supabase
    .from("products")
    .select("id,name,unit_type,estoque_modo,estoque_item_id,estoque_mapa,track_inventory,allow_negative_stock,product_attributes(name,options,position)")
    .eq("id", produtoId)
    .in("user_id", ctx.tenantIds)
    .maybeSingle();
  if (error) falha("ler o produto", error);
  if (!data) recusar("Produto não encontrado nesta empresa. Use buscar_produtos para achar o id.");
  return data as unknown as {
    id: string;
    name: string | null;
    unit_type: string | null;
    estoque_modo: string | null;
    estoque_item_id: string | null;
    estoque_mapa: unknown;
    product_attributes: AtributoSimples[] | null;
  };
}

function escolhaDoTexto(atributos: AtributoSimples[] | null, combinacao: unknown) {
  const montada = escolhaDoProduto(lerCombinacao(String(combinacao ?? "")), atributos || []);
  if (!montada.ok) recusar(montada.erro);
  return montada;
}

/* ================================================================ leitura */

/** consultar_estoque no v2: os itens (insumos e prontos) com a grade, saldo por célula. */
export async function consultarEstoqueV2(ctx: ContextoDoAssistente, args: Argumentos): Promise<Saida> {
  const veInsumos = pode(ctx, "materials.read");
  const veProntos = pode(ctx, "products.stock.read");
  const busca = limparBusca(args.busca);
  const apenasBaixo = args.apenas_baixo === true;

  let consulta = ctx.supabase
    .from("materials")
    .select(COLUNAS_DO_ITEM)
    .in("user_id", ctx.tenantIds)
    .eq("track_stock", true)
    .not("is_active", "is", false)
    .order("name")
    .limit(500);
  if (busca) consulta = consulta.ilike("name", `%${busca}%`);
  const { data, error } = await consulta;
  if (error) falha("ler o estoque", error);
  const itens = ((data ?? []) as unknown as ItemDoEstoque[]).filter((item) =>
    item.papel === "pronto" ? veProntos : veInsumos
  );

  const celulasPorItem = new Map<string, CelulaDoBanco[]>();
  for (let i = 0; i < itens.length; i += 100) {
    const ids = itens.slice(i, i + 100).map((item) => item.id);
    const { data: celulas, error: erroDasCelulas } = await ctx.supabase
      .from("estoque_celulas")
      .select("id,item_id,valor1,valor2,saldo,minimo,ativo")
      .in("item_id", ids);
    if (erroDasCelulas) falha("ler as células do estoque", erroDasCelulas);
    for (const celula of (celulas ?? []) as CelulaDoBanco[]) {
      const lista = celulasPorItem.get(String(celula.item_id)) || [];
      lista.push(celula);
      celulasPorItem.set(String(celula.item_id), lista);
    }
  }

  const montar = (item: ItemDoEstoque) => {
    const celulas = (celulasPorItem.get(item.id) || []).filter((c) => c.ativo !== false);
    const comGrade = gradeValida(item.grade).length > 0;
    const linhas = celulas.map((c) => {
      const saldo = Number(c.saldo ?? 0);
      return {
        celula_id: c.id,
        combinacao: [c.valor1, c.valor2].filter(Boolean).join(" / ") || null,
        saldo,
        minimo: c.minimo ?? item.min_stock_alert ?? null,
        baixo: estaBaixo(saldo, c.minimo, item.min_stock_alert),
      };
    });
    const total = linhas.reduce((soma, l) => soma + l.saldo, 0);
    const baixos = linhas.filter((l) => l.baixo);
    return {
      id: item.id,
      nome: item.name ?? "",
      unidade: item.unit || null,
      ...(item.produto_id ? { produto_id: item.produto_id } : {}),
      grade: textoDaGrade(item.grade),
      total,
      vende_sem_saldo: item.permitir_negativo !== false,
      baixo: baixos.length > 0,
      ...(comGrade ? { celulas: apenasBaixo ? baixos : linhas } : { celula_id: linhas[0]?.celula_id ?? null, minimo: linhas[0]?.minimo ?? null }),
    };
  };

  const montados = itens.map(montar).filter((item) => !apenasBaixo || item.baixo);
  const papelDe = new Map(itens.map((item) => [item.id, item.papel]));
  const insumos = montados.filter((m) => papelDe.get(m.id) !== "pronto");
  const prontos = montados.filter((m) => papelDe.get(m.id) === "pronto");
  const dados: Record<string, unknown> = {
    _observacao: AVISO_DE_DADOS,
    estoque_novo: true,
    como_ler:
      "Saldo por célula da grade (ex.: Camiseta lisa G / Preta). Itens 'pronto' são o produto já feito; 'insumos' são o que a produção consome. Para mexer: lancar_entrada_no_estoque, lancar_perda_no_estoque, contar_estoque, registrar_producao.",
  };
  if (veInsumos) {
    dados.insumos = insumos.slice(0, 100);
    if (insumos.length > 100) dados.insumos_cortados_em = 100;
  }
  if (veProntos) {
    dados.produtos_prontos = prontos.slice(0, 100);
    if (prontos.length > 100) dados.produtos_cortados_em = 100;
  }
  return {
    dados,
    resumo: `Consultou o estoque${apenasBaixo ? " baixo" : ""} (${contagem(montados.length, "item", "itens")})`,
  };
}

const verEstoqueDoProduto: FerramentaDeLeitura = {
  nome: "ver_estoque_do_produto",
  titulo: "Ver o estoque de um produto",
  descricao:
    "Diz de onde o produto sai quando vende (pronto, encomenda pela composição, ou nada), o que sai do estoque ao vender a quantidade pedida na combinação escolhida (ex.: Camiseta lisa G / Preta + 0,08 m² de DTF) e quanto dá para vender agora. É a mesma conta da loja, do PDV e do orçamento.",
  campos: {
    produto_id: { tipo: "id", descricao: "O produto (de buscar_produtos).", obrigatorio: true },
    combinacao: { tipo: "texto", descricao: 'As escolhas, como "Tamanho: G, Cor: Preta" (ou só "G / Preta").', max: 300 },
    quantidade: { tipo: "numero", descricao: "Quantas unidades (padrão 1).", min: 0.001, max: 10_000_000 },
    largura_m: { tipo: "numero", descricao: "Largura em metros (produto por m²).", min: 0, max: 1000 },
    altura_m: { tipo: "numero", descricao: "Altura em metros (produto por m²).", min: 0, max: 1000 },
  },
  tipo: "leitura",
  permissoes: ["products.catalog.read", "products.stock.read"],
  async executar(ctx, args) {
    if (!pode(ctx, "products.catalog.read") && !pode(ctx, "products.stock.read")) exigir(ctx, "products.stock.read", "ver o estoque");
    const produto = await carregarProduto(ctx, String(args.produto_id));
    const escolha = escolhaDoTexto(produto.product_attributes, args.combinacao);
    const quantidade = Number(args.quantidade ?? 1) || 1;
    const d = await disponibilidadeNoBanco(ctx, {
      produtoId: produto.id,
      selected: escolha.ok ? escolha.selected : null,
      quantidade,
      largura: (args.largura_m as number | undefined) ?? null,
      altura: (args.altura_m as number | undefined) ?? null,
    });
    if (!d) falha("ler o estoque do produto", null);
    return {
      dados: {
        produto: { id: produto.id, nome: produto.name },
        escolha: escolha.ok ? escolha.texto || null : null,
        quantidade,
        estoque_novo: d?.v2 === true,
        ...resumoDoEstoque(d),
      },
      resumo: `Viu o estoque de "${produto.name ?? "produto"}"`,
    };
  },
};

const conferirEstoque: FerramentaDeLeitura = {
  nome: "conferir_estoque",
  titulo: "Conferir o estoque",
  descricao:
    "A conferência do estoque novo: divergências (saldo diferente da linha do tempo), produtos sem célula para alguma combinação, saldos negativos, itens no mínimo, peças duplicadas (com sugestão de juntar) e itens fora de qualquer composição.",
  campos: {},
  tipo: "leitura",
  permissoes: ["products.stock.read"],
  async executar(ctx) {
    exigir(ctx, "products.stock.read", "conferir o estoque");
    await exigirV2(ctx);
    const { data, error } = await ctx.supabase.rpc("estoque_conferir", { p_empresa: ctx.donoDosDados });
    if (error) recusarDoBanco("conferir o estoque", error);
    const r = (data ?? {}) as Record<string, unknown[]>;
    const corte = (lista: unknown) => (Array.isArray(lista) ? lista.slice(0, 20) : []);
    const conta = (lista: unknown) => (Array.isArray(lista) ? lista.length : 0);
    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        quantos: {
          divergencias: conta(r.divergencias),
          sem_celula: conta(r.sem_celula),
          negativos: conta(r.negativos),
          no_minimo: conta(r.no_minimo),
          duplicados: conta(r.duplicados),
          fora_de_composicao: conta(r.fora_de_composicao),
        },
        divergencias: corte(r.divergencias),
        sem_celula: corte(r.sem_celula),
        negativos: corte(r.negativos),
        no_minimo: corte(r.no_minimo),
        duplicados: corte(r.duplicados),
        fora_de_composicao: corte(r.fora_de_composicao),
        listas_cortadas_em: 20,
      },
      resumo: "Conferiu o estoque",
    };
  },
};

const historicoDoEstoque: FerramentaDeLeitura = {
  nome: "historico_do_estoque",
  titulo: "Ver a linha do tempo do estoque",
  descricao:
    "Os movimentos do estoque novo, do mais novo para o mais antigo: venda, devolução, compra, entrada, produção, contagem, perda, com a origem (pedido, compra, produção). Sem item, traz a empresa inteira.",
  campos: {
    item_id: { tipo: "id", descricao: "Só deste item (de consultar_estoque)." },
    combinacao: { tipo: "texto", descricao: 'Só desta célula do item, como "G / Preta".', max: 200 },
    limite: { tipo: "inteiro", descricao: "Quantos movimentos (padrão 30, máximo 100).", min: 1, max: 100 },
  },
  tipo: "leitura",
  permissoes: ["products.stock.read"],
  async executar(ctx, args) {
    exigir(ctx, "products.stock.read", "ver o histórico do estoque");
    await exigirV2(ctx);
    let celulaId: string | null = null;
    if (args.item_id && args.combinacao) {
      const { item, celulas } = await carregarItem(ctx, String(args.item_id));
      celulaId = celulaDoTexto(item, celulas, args.combinacao).id;
    }
    const { data, error } = await ctx.supabase.rpc("estoque_historico", {
      p_empresa: ctx.donoDosDados,
      p_item: (args.item_id as string | undefined) ?? null,
      p_celula: celulaId,
      p_limite: Number(args.limite ?? 30),
      p_antes: null,
    });
    if (error) recusarDoBanco("ler o histórico do estoque", error);
    const movimentos = (Array.isArray(data) ? data : []) as Array<Record<string, unknown>>;
    return {
      dados: {
        _observacao: AVISO_DE_DADOS,
        movimentos: movimentos.map((m) => ({
          em: m.em,
          tipo: m.tipo,
          item: m.rotulo,
          quantidade: m.quantidade,
          saldo_depois: m.saldo_depois,
          observacao: m.observacao ?? null,
          origem: m.origem ?? null,
        })),
      },
      resumo: `Viu o histórico do estoque (${contagem(movimentos.length, "movimento", "movimentos")})`,
    };
  },
};

const listarComprasAReceber: FerramentaDeLeitura = {
  nome: "listar_compras_a_receber",
  titulo: "Listar compras a receber",
  descricao:
    "As compras de fornecedor que ainda não chegaram inteiras, com cada linha (item, quanto pediu, quanto já chegou e a célula da grade quando o item tem grade). Para dar entrada, use receber_compra.",
  campos: {},
  tipo: "leitura",
  permissoes: ["suppliers.list.read", "suppliers.list.write"],
  async executar(ctx) {
    if (!pode(ctx, "suppliers.list.read") && !pode(ctx, "suppliers.list.write")) exigir(ctx, "suppliers.list.read", "ver as compras");
    const { data: compras, error } = await ctx.admin
      .from("supplier_purchase_orders")
      .select("id,order_number,status,ordered_at,expected_at,total_amount,supplier_id")
      .eq("company_id", ctx.empresa.companyId)
      .not("status", "in", "(received,canceled,cancelled)")
      .order("ordered_at", { ascending: false })
      .limit(30);
    if (error) falha("ler as compras", error);
    const ids = ((compras ?? []) as Array<{ id: string }>).map((c) => c.id);
    const { data: linhas, error: erroDasLinhas } = ids.length
      ? await ctx.admin
          .from("supplier_purchase_order_items")
          .select("id,purchase_order_id,material_id,material_name,unit,quantity,received_quantity,unit_price,estoque_celula_id")
          .eq("company_id", ctx.empresa.companyId)
          .in("purchase_order_id", ids)
      : { data: [], error: null };
    if (erroDasLinhas) falha("ler as linhas das compras", erroDasLinhas);
    const porCompra = new Map<string, Array<Record<string, unknown>>>();
    for (const linha of (linhas ?? []) as Array<Record<string, unknown>>) {
      const lista = porCompra.get(String(linha.purchase_order_id)) || [];
      lista.push({
        linha_id: linha.id,
        item_id: linha.material_id,
        item: linha.material_name,
        unidade: linha.unit,
        pedido: linha.quantity,
        ja_chegou: linha.received_quantity ?? 0,
        preco_unitario: linha.unit_price,
        celula_id: linha.estoque_celula_id ?? null,
      });
      porCompra.set(String(linha.purchase_order_id), lista);
    }
    const lista = ((compras ?? []) as Array<Record<string, unknown>>).map((c) => ({
      compra_id: c.id,
      numero: c.order_number,
      situacao: c.status,
      pedida_em: c.ordered_at,
      previsao: c.expected_at,
      total: c.total_amount,
      linhas: porCompra.get(String(c.id)) || [],
    }));
    return { dados: { _observacao: AVISO_DE_DADOS, compras: lista }, resumo: `Listou as compras a receber (${contagem(lista.length, "compra", "compras")})` };
  },
};

/* ================================================================ escrita */

type CargaDoLancamento = { celula_id: string; tipo: "entrada" | "perda"; quantidade: number; custo: number | null; unidade: string | null; observacao: string | null; rotulo: string };

function lancamento(tipo: "entrada" | "perda"): FerramentaDeEscrita<CargaDoLancamento> {
  const entrada = tipo === "entrada";
  return {
    nome: entrada ? "lancar_entrada_no_estoque" : "lancar_perda_no_estoque",
    titulo: entrada ? "Lançar entrada no estoque" : "Lançar perda no estoque",
    descricao: entrada
      ? "Dá entrada num item do estoque novo (insumo ou produto pronto), na célula da grade quando o item tem grade. Com custo, o custo médio do item é refeito. A unidade pode ser outra que a do item (rolo, caixa, pacote, folha) quando a conversão está cadastrada."
      : "Tira do estoque o que se perdeu (quebra, erro de impressão, vencido), na célula da grade quando o item tem grade. Pode deixar o saldo negativo.",
    campos: {
      item_id: { tipo: "id", descricao: "O item do estoque (de consultar_estoque).", obrigatorio: true },
      combinacao: { tipo: "texto", descricao: 'A célula, quando o item tem grade: "Tamanho: G, Cor: Preta" ou "G / Preta".', max: 200 },
      quantidade: { tipo: "numero", descricao: "Quanto, na unidade informada (ou na do item).", obrigatorio: true, min: 0.0001, max: 100_000_000 },
      unidade: { tipo: "texto", descricao: "Outra unidade (rolo, caixa, pacote, folha). Sem isto, a do item.", max: 30 },
      ...(entrada ? { custo_unitario: { tipo: "numero" as const, descricao: "Custo de cada unidade informada, em reais (refaz o custo médio).", min: 0, max: 10_000_000 } } : {}),
      observacao: { tipo: "texto", descricao: "Observação que fica na linha do tempo.", max: 300 },
    },
    tipo: "escrita",
    permissoes: ["products.stock.write"],
    async preparar(ctx, args) {
      exigir(ctx, "products.stock.write", entrada ? "dar entrada no estoque" : "lançar perda no estoque");
      await exigirV2(ctx);
      const { item, celulas } = await carregarItem(ctx, String(args.item_id));
      const celula = celulaDoTexto(item, celulas, args.combinacao);
      const rotulo = rotuloDaCelula(item.name || "item", celula.valor1, celula.valor2);
      const quantidade = Number(args.quantidade);
      const unidade = args.unidade ? String(args.unidade).trim() : null;
      const saldo = Number(celula.saldo ?? 0);
      const avisos: string[] = [];
      if (!entrada && !unidade && saldo - quantidade < 0) avisos.push(`O saldo de ${rotulo} fica negativo (${QUANTO(saldo - quantidade)}).`);
      return {
        previa: {
          o_que_vai_acontecer: `${entrada ? "Entram" : "Saem (perda)"} ${QUANTO(quantidade)} ${unidade || item.unit || ""} de ${rotulo}.`.replace(/\s+/g, " "),
          saldo_agora: saldo,
          ...(unidade ? {} : { saldo_depois: entrada ? saldo + quantidade : saldo - quantidade }),
          ...(entrada && args.custo_unitario !== undefined ? { custo_unitario: Number(args.custo_unitario) } : {}),
        },
        resumo: `${entrada ? "Entrada" : "Perda"} de ${QUANTO(quantidade)} em ${rotulo}`,
        avisos,
        carga: {
          celula_id: celula.id,
          tipo,
          quantidade,
          custo: entrada && args.custo_unitario !== undefined ? Number(args.custo_unitario) : null,
          unidade,
          observacao: args.observacao ? String(args.observacao) : null,
          rotulo,
        },
      };
    },
    async gravar(ctx, carga) {
      const { data, error } = await ctx.supabase.rpc("estoque_lancar", {
        p_celula: carga.celula_id,
        p_tipo: carga.tipo,
        p_quantidade: carga.quantidade,
        p_custo_unitario: carga.custo,
        p_observacao: carga.observacao,
        p_unidade: carga.unidade,
      });
      if (error) recusarDoBanco(entrada ? "dar entrada no estoque" : "lançar a perda", error);
      const r = (data ?? {}) as { quantidade?: number; celula?: { saldo?: number }; saldo_do_item?: number; custo_medio?: number };
      return {
        dados: {
          feito: true,
          item: carga.rotulo,
          quantidade_na_unidade_do_item: r.quantidade ?? null,
          saldo_da_celula: r.celula?.saldo ?? null,
          saldo_do_item: r.saldo_do_item ?? null,
          ...(r.custo_medio !== undefined ? { custo_medio: r.custo_medio } : {}),
        },
        resumo: `${entrada ? "Entrada" : "Perda"} em ${carga.rotulo}: ${QUANTO(r.quantidade ?? carga.quantidade)}`,
      };
    },
  };
}

type CargaDaContagem = { linhas: Array<{ celula_id: string; saldo: number }>; observacao: string | null; rotulos: string[] };

const contarEstoque: FerramentaDeEscrita<CargaDaContagem> = {
  nome: "contar_estoque",
  titulo: "Lançar contagem do estoque",
  descricao:
    "Grava o que foi contado na prateleira: o saldo de cada item (ou de cada célula da grade) passa a ser o contado, e a diferença fica na linha do tempo como contagem. Vários de uma vez.",
  campos: {
    linhas: {
      tipo: "lista",
      descricao: "O que foi contado.",
      obrigatorio: true,
      minItens: 1,
      maxItens: 200,
      itens: {
        item_id: { tipo: "id", descricao: "O item (de consultar_estoque).", obrigatorio: true },
        combinacao: { tipo: "texto", descricao: 'A célula, quando o item tem grade: "G / Preta".', max: 200 },
        saldo: { tipo: "numero", descricao: "Quanto tem, contado, na unidade do item.", obrigatorio: true, min: -100_000_000, max: 100_000_000 },
      },
    },
    observacao: { tipo: "texto", descricao: "Observação da contagem.", max: 300 },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["products.stock.write"],
  async preparar(ctx, args) {
    exigir(ctx, "products.stock.write", "lançar contagem do estoque");
    await exigirV2(ctx);
    const pedidas = (args.linhas as Argumentos[]) ?? [];
    const cache = new Map<string, Awaited<ReturnType<typeof carregarItem>>>();
    const linhas: CargaDaContagem["linhas"] = [];
    const rotulos: string[] = [];
    const previa: Array<Record<string, unknown>> = [];
    for (const pedida of pedidas) {
      const itemId = String(pedida.item_id);
      if (!cache.has(itemId)) cache.set(itemId, await carregarItem(ctx, itemId));
      const { item, celulas } = cache.get(itemId)!;
      const celula = celulaDoTexto(item, celulas, pedida.combinacao);
      if (linhas.some((l) => l.celula_id === celula.id)) recusar(`${rotuloDaCelula(item.name || "item", celula.valor1, celula.valor2)} veio duas vezes.`);
      const rotulo = rotuloDaCelula(item.name || "item", celula.valor1, celula.valor2);
      linhas.push({ celula_id: celula.id, saldo: Number(pedida.saldo) });
      rotulos.push(rotulo);
      previa.push({ item: rotulo, antes: Number(celula.saldo ?? 0), contado: Number(pedida.saldo), diferenca: Number(pedida.saldo) - Number(celula.saldo ?? 0) });
    }
    return {
      previa: { contagem: previa },
      resumo: `Contagem de ${contagem(linhas.length, "item", "itens")}`,
      avisos: [],
      carga: { linhas, observacao: args.observacao ? String(args.observacao) : null, rotulos },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.rpc("estoque_contar", { p_linhas: carga.linhas, p_observacao: carga.observacao });
    if (error) recusarDoBanco("lançar a contagem", error);
    const r = (data ?? {}) as { contagem_id?: string; mudou?: number; linhas?: unknown[] };
    return {
      dados: { feito: true, contagem_id: r.contagem_id ?? null, mudaram: r.mudou ?? null, linhas: r.linhas ?? [] },
      resumo: `Contagem lançada (${contagem(carga.linhas.length, "item", "itens")})`,
    };
  },
};

type CargaDaProducao = { produto_id: string; nome: string; selected: unknown; texto: string; quantidade: number; largura: number | null; altura: number | null; observacao: string | null };

const registrarProducao: FerramentaDeEscrita<CargaDaProducao> = {
  nome: "registrar_producao",
  titulo: "Registrar produção",
  descricao:
    "Registra que a produção fez N peças de um produto (na combinação escolhida): sai do estoque a composição do produto × N (insumos) e entram N no produto pronto daquela combinação, com o custo dos insumos.",
  campos: {
    produto_id: { tipo: "id", descricao: "O produto (de buscar_produtos).", obrigatorio: true },
    combinacao: { tipo: "texto", descricao: 'As escolhas, como "Tamanho: G, Cor: Preta".', max: 300 },
    quantidade: { tipo: "numero", descricao: "Quantas peças ficaram prontas.", obrigatorio: true, min: 0.001, max: 10_000_000 },
    largura_m: { tipo: "numero", descricao: "Largura em metros (produto por m²).", min: 0, max: 1000 },
    altura_m: { tipo: "numero", descricao: "Altura em metros (produto por m²).", min: 0, max: 1000 },
    observacao: { tipo: "texto", descricao: "Observação da produção.", max: 300 },
  },
  tipo: "escrita",
  permissoes: ["products.stock.write"],
  async preparar(ctx, args) {
    exigir(ctx, "products.stock.write", "registrar produção");
    await exigirV2(ctx);
    const produto = await carregarProduto(ctx, String(args.produto_id));
    const escolha = escolhaDoTexto(produto.product_attributes, args.combinacao);
    const quantidade = Number(args.quantidade);
    const largura = (args.largura_m as number | undefined) ?? null;
    const altura = (args.altura_m as number | undefined) ?? null;
    const selected = escolha.ok ? escolha.selected : null;
    return {
      previa: {
        o_que_vai_acontecer: `Entram ${QUANTO(quantidade)} de "${produto.name}"${escolha.ok && escolha.texto ? ` (${escolha.texto})` : ""} no produto pronto, e sai a composição × ${QUANTO(quantidade)}.`,
      },
      resumo: `Produção de ${QUANTO(quantidade)} "${produto.name}"`,
      avisos: [],
      carga: {
        produto_id: produto.id,
        nome: produto.name || "produto",
        selected,
        texto: escolha.ok ? escolha.texto : "",
        quantidade,
        largura,
        altura,
        observacao: args.observacao ? String(args.observacao) : null,
      },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.rpc("estoque_registrar_producao", {
      p_produto: carga.produto_id,
      p_selected_options: carga.selected,
      p_quantidade: carga.quantidade,
      p_observacao: carga.observacao,
      p_largura: carga.largura,
      p_altura: carga.altura,
    });
    if (error) recusarDoBanco("registrar a produção", error);
    const r = (data ?? {}) as {
      producao_id?: string;
      entrou?: { celula?: { rotulo?: string; saldo?: number }; quantidade?: number };
      saiu?: Array<{ celula?: { rotulo?: string; saldo?: number }; quantidade?: number }>;
      custo_unitario?: number;
    };
    return {
      dados: {
        feito: true,
        producao_id: r.producao_id ?? null,
        entrou: r.entrou ? { item: r.entrou.celula?.rotulo, quantidade: r.entrou.quantidade, saldo_agora: r.entrou.celula?.saldo } : null,
        saiu: (r.saiu || []).map((s) => ({ item: s.celula?.rotulo, quantidade: s.quantidade, saldo_agora: s.celula?.saldo })),
        custo_unitario: r.custo_unitario ?? null,
        para_desfazer: "desfazer_producao com o producao_id",
      },
      resumo: `Produção registrada: ${QUANTO(carga.quantidade)} "${carga.nome}"${carga.texto ? ` (${carga.texto})` : ""}`,
    };
  },
};

const desfazerProducao: FerramentaDeEscrita<{ producao_id: string }> = {
  nome: "desfazer_producao",
  titulo: "Desfazer uma produção",
  descricao: "Desfaz uma produção registrada: os insumos voltam e as peças prontas saem. Se as peças já foram vendidas, o estoque recusa.",
  campos: { producao_id: { tipo: "id", descricao: "A produção (de registrar_producao ou historico_do_estoque).", obrigatorio: true } },
  tipo: "escrita",
  sensivel: true,
  sobrescreve: true,
  permissoes: ["products.stock.write"],
  async preparar(ctx, args) {
    exigir(ctx, "products.stock.write", "desfazer produção");
    await exigirV2(ctx);
    const { data, error } = await ctx.supabase
      .from("estoque_producoes")
      .select("id,produto_id,quantidade,desfeita_em,criado_em")
      .eq("id", String(args.producao_id))
      .maybeSingle();
    if (error) falha("ler a produção", error);
    if (!data) recusar("Produção não encontrada nesta empresa.");
    const producao = data as { id: string; quantidade: number; desfeita_em: string | null; criado_em: string };
    if (producao.desfeita_em) recusar("Esta produção já foi desfeita.");
    return {
      previa: { o_que_vai_acontecer: `Desfazer a produção de ${QUANTO(producao.quantidade)} peças (de ${String(producao.criado_em).slice(0, 10)}): os insumos voltam e as peças prontas saem.` },
      resumo: `Desfazer produção de ${QUANTO(producao.quantidade)}`,
      avisos: [],
      carga: { producao_id: producao.id },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.rpc("estoque_desfazer_producao", { p_producao: carga.producao_id });
    if (error) recusarDoBanco("desfazer a produção", error);
    return { dados: { feito: true, ...((data ?? {}) as Record<string, unknown>) }, resumo: "Produção desfeita" };
  },
};

type CargaDaConfiguracao = {
  produto_id: string;
  nome: string;
  modo: "pronto" | "encomenda" | "nenhum";
  item_id: string | null;
  criar_item: boolean;
  eixos: string[] | null;
  mapa: Array<{ combinacao: Record<string, string>; celula_id: string; quantidade?: number }> | null;
};

const configurarEstoqueDoProduto: FerramentaDeEscrita<CargaDaConfiguracao> = {
  nome: "configurar_estoque_do_produto",
  titulo: "Configurar de onde o produto sai",
  descricao:
    "Diz de onde o produto sai do estoque quando vende: 'pronto' (de um item pronto, uma célula por combinação — liga pelo nome da variação; trocas manuais em trocas), 'encomenda' (sai a composição, pelas linhas de alterar_quando_sai_da_composicao) ou 'nenhum'. Com criar_item_pronto, cria o item pronto com a grade pelos atributos do produto.",
  campos: {
    produto_id: { tipo: "id", descricao: "O produto.", obrigatorio: true },
    modo: { tipo: "opcao", descricao: "De onde sai.", obrigatorio: true, opcoes: ["pronto", "encomenda", "nenhum"] },
    item_id: { tipo: "id", descricao: "No modo pronto: o item pronto que já existe (de consultar_estoque)." },
    criar_item_pronto: { tipo: "booleano", descricao: "No modo pronto: cria o item pronto com o nome do produto." },
    eixos: { tipo: "textos", descricao: "Com criar_item_pronto: os atributos que viram a grade (até 2), ex.: [\"Tamanho\", \"Cor\"]. Sem isto, os do estoque do produto.", maxItens: 2, max: 60 },
    trocas: {
      tipo: "lista",
      descricao: "Trocas manuais: a combinação do produto que sai de outra célula (ou mais de uma peça por venda, num kit).",
      maxItens: 100,
      itens: {
        combinacao: { tipo: "texto", descricao: 'A escolha do produto, ex.: "Cor: Azul".', obrigatorio: true, max: 200 },
        celula: { tipo: "texto", descricao: 'A célula do item pronto, ex.: "Azul royal" ou "G / Azul".', obrigatorio: true, max: 200 },
        quantidade: { tipo: "numero", descricao: "Quantas peças da célula por unidade vendida (padrão 1).", min: 0.001, max: 1000 },
      },
    },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["products.catalog.write", "products.stock.write"],
  async preparar(ctx, args) {
    exigir(ctx, "products.catalog.write", "mudar de onde o produto sai do estoque");
    await exigirV2(ctx);
    const produto = await carregarProduto(ctx, String(args.produto_id));
    const modo = String(args.modo) as CargaDaConfiguracao["modo"];
    const criar = args.criar_item_pronto === true;
    let itemId = (args.item_id as string | undefined) ?? null;
    if (modo !== "pronto" && (itemId || criar)) recusar("item_id e criar_item_pronto só valem no modo pronto.");
    if (modo === "pronto" && !itemId && !criar) itemId = produto.estoque_item_id;
    if (modo === "pronto" && !itemId && !criar) recusar('No modo pronto diga o item pronto (item_id) ou peça criar_item_pronto: true.');
    const trocas = (args.trocas as Argumentos[] | undefined) ?? [];
    if (trocas.length && criar) recusar("Crie o item pronto primeiro (sem trocas) e mande as trocas depois, com o item_id.");
    let mapa: CargaDaConfiguracao["mapa"] = null;
    let nomeDoItem: string | null = null;
    if (itemId) {
      const { item, celulas } = await carregarItem(ctx, itemId);
      if (item.papel !== "pronto") recusar(`"${item.name}" é um insumo; o produto pronto sai de um item 'pronto'.`);
      nomeDoItem = item.name;
      if (trocas.length) {
        mapa = trocas.map((troca) => {
          const escolha = escolhaDoTexto(produto.product_attributes, troca.combinacao);
          if (!escolha.ok || !escolha.selected) recusar(`Troca sem combinação válida: "${troca.combinacao}".`);
          const combinacao = Object.fromEntries(escolha.selected!.options.map((o) => [o.attribute_name, o.label]));
          const celula = celulaDoTexto(item, celulas, troca.celula);
          return { combinacao, celula_id: celula.id, ...(troca.quantidade ? { quantidade: Number(troca.quantidade) } : {}) };
        });
      }
    }
    const avisos: string[] = [];
    if (produto.estoque_modo && produto.estoque_modo !== modo) avisos.push(`O produto saía como "${produto.estoque_modo}"; pedidos já feitos não mudam.`);
    return {
      previa: {
        produto: produto.name,
        de_onde_sai: modo,
        ...(modo === "pronto" ? { item_pronto: criar ? `novo: "${produto.name}"` : nomeDoItem } : {}),
        ...(mapa ? { trocas: mapa.length } : {}),
      },
      resumo: `"${produto.name}" passa a sair como ${modo}`,
      avisos,
      carga: {
        produto_id: produto.id,
        nome: produto.name || "produto",
        modo,
        item_id: itemId,
        criar_item: criar,
        eixos: Array.isArray(args.eixos) ? (args.eixos as string[]) : null,
        mapa,
      },
    };
  },
  async gravar(ctx, carga) {
    let itemId = carga.item_id;
    if (carga.criar_item) {
      const { data, error } = await ctx.supabase.rpc("estoque_criar_item_pronto", { p_produto: carga.produto_id, p_eixos: carga.eixos });
      if (error) recusarDoBanco("criar o item pronto", error);
      itemId = String(((data ?? {}) as { item?: { id?: string } }).item?.id || "") || null;
    }
    const { data, error } = await ctx.supabase.rpc("estoque_configurar_produto", {
      p_produto: carga.produto_id,
      p_modo: carga.modo,
      p_item: itemId,
      p_mapa: carga.mapa,
    });
    if (error) recusarDoBanco("configurar o estoque do produto", error);
    return {
      dados: { feito: true, ...((data ?? {}) as Record<string, unknown>) },
      resumo: `"${carga.nome}" sai como ${carga.modo}`,
    };
  },
};

type CargaDaComposicao = { produto_id: string; nome: string; linhas: Array<{ id: string; patch: Record<string, unknown>; texto: string }> };

const QUANDO_SAI = ["sempre", "pela_variacao", "se_opcao", "so_custo"] as const;

const alterarQuandoSaiDaComposicao: FerramentaDeEscrita<CargaDaComposicao> = {
  nome: "alterar_quando_sai_da_composicao",
  titulo: "Dizer quando cada insumo sai",
  descricao:
    "Na composição do produto (aba Composição), diz quando cada insumo sai do estoque na venda: 'sempre'; 'pela_variacao' (a célula da mesma cor/tamanho que o cliente escolheu); 'se_opcao' (só se a opção X = valor Y); 'so_custo' (entra no custo, nunca baixa). Também a quantidade por variação (ex.: DTF P 0,06 / GG 0,09). Pedidos já feitos não mudam.",
  campos: {
    produto_id: { tipo: "id", descricao: "O produto.", obrigatorio: true },
    linhas: {
      tipo: "lista",
      descricao: "As linhas da composição a mudar.",
      obrigatorio: true,
      minItens: 1,
      maxItens: 50,
      itens: {
        insumo: { tipo: "texto", descricao: "O insumo da linha: id do item ou parte do nome.", obrigatorio: true, max: 120 },
        quando_sai: { tipo: "opcao", descricao: "Quando sai.", obrigatorio: true, opcoes: QUANDO_SAI },
        se_atributo: { tipo: "texto", descricao: "Com se_opcao: o atributo, ex.: Entrega.", max: 80 },
        se_valor: { tipo: "texto", descricao: "Com se_opcao: o valor, ex.: Caixa.", max: 80 },
        quantidade_por_variacao: {
          tipo: "lista",
          descricao: "Quantidade por variação (sem linha, vale a quantidade da composição).",
          maxItens: 60,
          itens: {
            combinacao: { tipo: "texto", descricao: 'Ex.: "Tamanho: P".', obrigatorio: true, max: 200 },
            quantidade: { tipo: "numero", descricao: "Quanto sai por peça.", obrigatorio: true, min: 0, max: 1_000_000 },
          },
        },
      },
    },
  },
  tipo: "escrita",
  sobrescreve: true,
  permissoes: ["products.catalog.write"],
  async preparar(ctx, args) {
    exigir(ctx, "products.catalog.write", "mudar a composição do produto");
    await exigirV2(ctx);
    const produto = await carregarProduto(ctx, String(args.produto_id));
    const { data, error } = await ctx.supabase
      .from("product_materials")
      .select("id,material_id,quando_sai,materials(id,name,track_stock)")
      .eq("product_id", produto.id)
      .order("ordem", { nullsFirst: false })
      .order("id");
    if (error) falha("ler a composição", error);
    const linhasDoBanco = (data ?? []) as unknown as Array<{ id: string; material_id: string; materials: { id: string; name: string | null; track_stock: boolean | null } | null }>;
    const linhas: CargaDaComposicao["linhas"] = [];
    const avisos: string[] = [];
    for (const pedida of (args.linhas as Argumentos[]) ?? []) {
      const alvo = String(pedida.insumo).trim();
      const achadas = linhasDoBanco.filter(
        (l) => l.material_id === alvo || normalizar(l.materials?.name).includes(normalizar(alvo))
      );
      if (!achadas.length) recusar(`A composição de "${produto.name}" não tem o insumo "${alvo}". Linhas: ${linhasDoBanco.map((l) => l.materials?.name).filter(Boolean).join(", ") || "nenhuma"}.`);
      if (achadas.length > 1) recusar(`"${alvo}" casa com mais de uma linha (${achadas.map((l) => l.materials?.name).join(", ")}): use o id do insumo.`);
      const linha = achadas[0];
      const quando = String(pedida.quando_sai);
      const patch: Record<string, unknown> = { quando_sai: quando, condicao_opcao_id: null, condicao_atributo: null, condicao_valor: null };
      if (quando === "se_opcao") {
        if (!pedida.se_valor) recusar(`"${linha.materials?.name}": com se_opcao diga se_valor (e se_atributo).`);
        const escolha = escolhaDoTexto(
          produto.product_attributes,
          pedida.se_atributo ? `${pedida.se_atributo}: ${pedida.se_valor}` : String(pedida.se_valor)
        );
        const opcao = escolha.ok ? escolha.selected?.options[0] : null;
        patch.condicao_opcao_id = opcao?.option_id ?? null;
        patch.condicao_atributo = opcao?.attribute_name ?? (pedida.se_atributo ? String(pedida.se_atributo) : null);
        patch.condicao_valor = opcao?.label ?? String(pedida.se_valor);
      }
      if (pedida.quantidade_por_variacao !== undefined) {
        patch.qtd_por_variacao = ((pedida.quantidade_por_variacao as Argumentos[]) ?? []).map((q) => {
          const escolha = escolhaDoTexto(produto.product_attributes, q.combinacao);
          if (!escolha.ok || !escolha.selected) recusar(`Combinação inválida em quantidade_por_variacao: "${q.combinacao}".`);
          return { combinacao: Object.fromEntries(escolha.selected!.options.map((o) => [o.attribute_name, o.label])), quantidade: Number(q.quantidade) };
        });
      }
      if (quando !== "so_custo" && linha.materials?.track_stock === false) {
        avisos.push(`"${linha.materials?.name}" não controla estoque: a linha fica só no custo até ligar o controle do insumo.`);
      }
      linhas.push({ id: linha.id, patch, texto: `${linha.materials?.name}: ${quando}${patch.condicao_valor ? ` (${patch.condicao_atributo ?? ""}${patch.condicao_atributo ? " = " : ""}${patch.condicao_valor})` : ""}` });
    }
    return {
      previa: { produto: produto.name, linhas: linhas.map((l) => l.texto) },
      resumo: `Composição de "${produto.name}": ${contagem(linhas.length, "linha", "linhas")}`,
      avisos,
      carga: { produto_id: produto.id, nome: produto.name || "produto", linhas },
    };
  },
  async gravar(ctx, carga) {
    for (const linha of carga.linhas) {
      const { error } = await ctx.supabase.from("product_materials").update(linha.patch).eq("id", linha.id).eq("product_id", carga.produto_id);
      if (error) falha("gravar a composição", error);
    }
    return { dados: { feito: true, linhas: carga.linhas.map((l) => l.texto) }, resumo: `Composição de "${carga.nome}" alterada` };
  },
};

type CargaDoRecebimento = { compra_id: string; numero: unknown; itens: Array<{ item_id: string; quantidade: number; preco_unitario: number | null; celula_id?: string }>; observacao: string | null };

const receberCompra: FerramentaDeEscrita<CargaDoRecebimento> = {
  nome: "receber_compra",
  titulo: "Receber uma compra do fornecedor",
  descricao:
    "Dá entrada no estoque do que chegou de uma compra (de listar_compras_a_receber), linha por linha, na célula da grade quando o item tem grade. Refaz o custo médio e acerta a conta a pagar da compra, como a tela de Compras.",
  campos: {
    compra_id: { tipo: "id", descricao: "A compra.", obrigatorio: true },
    linhas: {
      tipo: "lista",
      descricao: "O que chegou.",
      obrigatorio: true,
      minItens: 1,
      maxItens: 100,
      itens: {
        linha_id: { tipo: "id", descricao: "A linha da compra (de listar_compras_a_receber).", obrigatorio: true },
        quantidade: { tipo: "numero", descricao: "Quanto chegou.", obrigatorio: true, min: 0.0001, max: 100_000_000 },
        preco_unitario: { tipo: "numero", descricao: "Preço de cada unidade, se mudou.", min: 0, max: 10_000_000 },
        combinacao: { tipo: "texto", descricao: 'A célula, quando o item tem grade: "G / Preta".', max: 200 },
      },
    },
    observacao: { tipo: "texto", descricao: "Observação do recebimento.", max: 300 },
  },
  tipo: "escrita",
  sensivel: true,
  permissoes: ["suppliers.list.write"],
  async preparar(ctx, args) {
    exigir(ctx, "suppliers.list.write", "receber compras");
    const { data: compra, error } = await ctx.admin
      .from("supplier_purchase_orders")
      .select("id,order_number,status")
      .eq("company_id", ctx.empresa.companyId)
      .eq("id", String(args.compra_id))
      .maybeSingle();
    if (error) falha("ler a compra", error);
    if (!compra) recusar("Compra não encontrada nesta empresa. Use listar_compras_a_receber.");
    const { data: linhas, error: erroDasLinhas } = await ctx.admin
      .from("supplier_purchase_order_items")
      .select("id,material_id,material_name,quantity,received_quantity,estoque_celula_id")
      .eq("company_id", ctx.empresa.companyId)
      .eq("purchase_order_id", (compra as { id: string }).id);
    if (erroDasLinhas) falha("ler as linhas da compra", erroDasLinhas);
    const doBanco = new Map(((linhas ?? []) as Array<Record<string, unknown>>).map((l) => [String(l.id), l]));
    const v2 = await empresaNoEstoqueV2(ctx);
    const itens: CargaDoRecebimento["itens"] = [];
    const previa: string[] = [];
    for (const pedida of (args.linhas as Argumentos[]) ?? []) {
      const linha = doBanco.get(String(pedida.linha_id));
      if (!linha) recusar(`A linha ${pedida.linha_id} não é desta compra.`);
      let celulaId: string | undefined;
      let rotulo = String(linha!.material_name || "item");
      if (v2 && linha!.material_id && (pedida.combinacao || !linha!.estoque_celula_id)) {
        const { item, celulas } = await carregarItem(ctx, String(linha!.material_id));
        if (gradeValida(item.grade).length || pedida.combinacao) {
          const celula = celulaDoTexto(item, celulas, pedida.combinacao);
          celulaId = celula.id;
          rotulo = rotuloDaCelula(item.name || rotulo, celula.valor1, celula.valor2);
        }
      }
      itens.push({
        item_id: String(linha!.id),
        quantidade: Number(pedida.quantidade),
        preco_unitario: pedida.preco_unitario !== undefined ? Number(pedida.preco_unitario) : null,
        ...(celulaId ? { celula_id: celulaId } : {}),
      });
      previa.push(`${rotulo}: chegam ${QUANTO(pedida.quantidade)} (pedido ${QUANTO(linha!.quantity)}, já tinha chegado ${QUANTO(linha!.received_quantity ?? 0)})`);
    }
    return {
      previa: { compra: (compra as { order_number?: unknown }).order_number, o_que_chega: previa },
      resumo: `Receber compra #${(compra as { order_number?: unknown }).order_number ?? ""}`,
      avisos: ["Refaz o custo médio dos itens e acerta o valor da conta a pagar da compra."],
      carga: { compra_id: (compra as { id: string }).id, numero: (compra as { order_number?: unknown }).order_number, itens, observacao: args.observacao ? String(args.observacao) : null },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.admin.rpc("receber_compra_do_fornecedor", {
      p_company: ctx.empresa.companyId,
      p_compra: carga.compra_id,
      p_itens: carga.itens,
      p_ator: ctx.user.id,
      p_observacao: carga.observacao,
    });
    if (error) recusar(error.message || "Não foi possível receber a compra.");
    return { dados: { feito: true, ...((data ?? {}) as Record<string, unknown>) }, resumo: `Compra #${carga.numero ?? ""} recebida` };
  },
};

const desfazerEstoqueNovo: FerramentaDeEscrita<{ empresa: string }> = {
  nome: "desfazer_estoque_novo",
  titulo: "Voltar ao estoque antigo",
  descricao:
    "Desliga o estoque novo desta empresa e volta ao modelo antigo com os saldos de agora (a linha do tempo fica arquivada). Só o dono. Use só quando a pessoa pedir com todas as letras.",
  campos: {},
  tipo: "escrita",
  sensivel: true,
  sobrescreve: true,
  permissoes: ["products.stock.write"],
  async preparar(ctx) {
    exigir(ctx, "products.stock.write", "desligar o estoque novo");
    if (ctx.empresa.ownerId && ctx.empresa.ownerId !== ctx.user.id) recusar("Só o dono da empresa pode voltar ao estoque antigo.");
    await exigirV2(ctx);
    return {
      previa: {
        o_que_vai_acontecer:
          "O estoque novo é desligado: produtos e insumos voltam ao modelo antigo com os saldos de hoje; grade, produção e linha do tempo ficam arquivadas. Pedidos não mudam.",
      },
      resumo: "Voltar ao estoque antigo",
      avisos: ["As telas novas de estoque somem para esta empresa até ligar de novo."],
      carga: { empresa: ctx.donoDosDados },
    };
  },
  async gravar(ctx, carga) {
    const { data, error } = await ctx.supabase.rpc("estoque_v2_desfazer", { p_empresa: carga.empresa });
    if (error) recusarDoBanco("voltar ao estoque antigo", error);
    return { dados: { feito: true, ...((data ?? {}) as Record<string, unknown>) }, resumo: "Estoque novo desligado (voltou ao antigo)" };
  },
};

export const FERRAMENTAS_DO_ESTOQUE_NOVO: Ferramenta[] = [
  verEstoqueDoProduto,
  conferirEstoque,
  historicoDoEstoque,
  listarComprasAReceber,
  lancamento("entrada") as Ferramenta,
  lancamento("perda") as Ferramenta,
  contarEstoque as Ferramenta,
  registrarProducao as Ferramenta,
  desfazerProducao as Ferramenta,
  configurarEstoqueDoProduto as Ferramenta,
  alterarQuandoSaiDaComposicao as Ferramenta,
  receberCompra as Ferramenta,
  desfazerEstoqueNovo as Ferramenta,
];
