/*
  Conferir o carrinho inteiro no banco — estoque_conferir_itens — com a
  mesma resposta por outro caminho quando ela falha.

  Por que o outro caminho: a função da etapa 1 limpa a tabela temporária com
  um DELETE sem WHERE, e o Supabase recusa isso vindo da API ("DELETE
  requires a WHERE clause"). A correção está na migration 212 (ainda não
  aplicada). Até lá, no estoque v2, a conta é refeita aqui com
  estoque_disponivel item a item (a MESMA função do banco, que diz de qual
  célula sai cada item e quanto precisa), somando por célula: dois produtos
  que usam a mesma camiseta lisa disputam o mesmo saldo.

  Fora do v2 não há outro caminho: devolve null ("não deu para conferir"),
  que é exatamente o que acontecia até hoje — quem decide é a trava da
  criação do pedido, como sempre.

  Cópia igual em apps/admin/src/lib/estoque-da-venda.ts (conferirItensNoBanco).
*/

type Rpc = {
  rpc: (fn: string, params: Record<string, unknown>) => PromiseLike<{ data: unknown; error: { message?: string } | null }>;
};

export type ItemDaConferencia = {
  product_id: string;
  quantity: number;
  selected_options?: unknown;
  width_m?: number | null;
  height_m?: number | null;
  title?: string | null;
};

export type ProblemaDaConferencia = {
  indice: number;
  produto_id: string;
  titulo: string;
  celula_id: string | null;
  disponivel: number;
  mensagem: string;
};

type ItemDoDisponivel = {
  item_id?: string | null;
  celula_id?: string | null;
  nome?: string | null;
  rotulo?: string | null;
  saldo?: number | null;
  precisa?: number | null;
  permite_negativo?: boolean | null;
  sem_celula?: boolean | null;
  falta_escolher?: string | null;
};

function numero(valor: number): string {
  const arredondado = Number(valor.toFixed(4));
  return new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 4 }).format(arredondado);
}

/** A conta da função do banco, refeita com as respostas de estoque_disponivel (só v2). */
export function somarPorCelula(
  itens: ItemDaConferencia[],
  respostas: Array<{ v2?: boolean; itens?: ItemDoDisponivel[] | null } | null>
): { ok: boolean; problemas: ProblemaDaConferencia[] } | null {
  if (respostas.some((r) => !r || r.v2 !== true)) return null;
  const porCelula = new Map<string, number>();
  respostas.forEach((r) => {
    for (const linha of r!.itens || []) {
      if (!linha.celula_id) continue;
      porCelula.set(linha.celula_id, (porCelula.get(linha.celula_id) || 0) + Number(linha.precisa || 0));
    }
  });
  const problemas: ProblemaDaConferencia[] = [];
  respostas.forEach((r, indice) => {
    const titulo = String(itens[indice]?.title || "produto");
    const vistos = new Set<string>();
    for (const linha of r!.itens || []) {
      const chave = String(linha.celula_id || `sem:${linha.item_id || ""}`);
      if (vistos.has(chave)) continue;
      if (!linha.celula_id) {
        vistos.add(chave);
        problemas.push({
          indice,
          produto_id: itens[indice].product_id,
          titulo,
          celula_id: null,
          disponivel: 0,
          mensagem: linha.item_id
            ? `Escolha ${linha.falta_escolher || "a variação"} de "${titulo}".`
            : `"${titulo}" sai como pronto, mas não tem item de estoque ligado.`,
        });
        continue;
      }
      if (linha.permite_negativo !== false) continue;
      const saldo = Number(linha.saldo || 0);
      const precisa = porCelula.get(linha.celula_id) || 0;
      if (saldo + 0.0000001 >= precisa) continue;
      vistos.add(chave);
      problemas.push({
        indice,
        produto_id: itens[indice].product_id,
        titulo,
        celula_id: linha.celula_id,
        disponivel: Math.max(saldo, 0),
        mensagem: `Acabou o estoque de "${linha.rotulo || linha.nome || "item"}" para "${titulo}": tem ${numero(Math.max(saldo, 0))}, o pedido precisa de ${numero(precisa)}.`,
      });
    }
  });
  return { ok: problemas.length === 0, problemas };
}

/**
 * `disponivel` do banco é o saldo da CÉLULA, na unidade do item (peças, m²).
 * Para o cliente a frase é em unidades do produto: o kit com 2 peças e saldo
 * 5 dá 2 kits (não "só 5"); o banner de 2 m² com 2 m² de lona dá 1 banner.
 * `precisa` = quanto a linha inteira tira da célula (de estoque_disponivel).
 */
export function disponivelEmUnidadesDoProduto(saldoDaCelula: number, precisaDaLinha: number, quantidadeDaLinha: number): number {
  const saldo = Math.max(0, Number(saldoDaCelula) || 0);
  const precisa = Number(precisaDaLinha) || 0;
  const quantidade = Number(quantidadeDaLinha) || 0;
  if (precisa <= 0 || quantidade <= 0) return Math.floor(saldo);
  const porUnidade = precisa / quantidade;
  return Math.max(0, Math.floor(saldo / porUnidade + 0.0000001));
}

async function emUnidadesDoProduto(
  cliente: Rpc,
  itens: ItemDaConferencia[],
  resposta: { ok: boolean; problemas: ProblemaDaConferencia[] }
): Promise<{ ok: boolean; problemas: ProblemaDaConferencia[] }> {
  const problemas = Array.isArray(resposta.problemas) ? resposta.problemas : [];
  if (!problemas.some((p) => p?.celula_id && Number(p.disponivel) > 0)) return resposta;
  const convertidos = await Promise.all(
    problemas.map(async (problema) => {
      const item = itens[Number(problema?.indice)];
      if (!problema?.celula_id || !(Number(problema.disponivel) > 0) || !item) return problema;
      try {
        const { data, error } = await cliente.rpc("estoque_disponivel", {
          p_produto: item.product_id,
          p_selected_options: item.selected_options && typeof item.selected_options === "object" ? item.selected_options : null,
          p_quantidade: Number(item.quantity) || 0,
          p_largura: Number(item.width_m) > 0 ? Number(item.width_m) : null,
          p_altura: Number(item.height_m) > 0 ? Number(item.height_m) : null,
          p_maximo: false,
        });
        const linha = error ? null : ((data as { itens?: ItemDoDisponivel[] } | null)?.itens || []).find((i) => i.celula_id === problema.celula_id);
        if (!linha) return problema;
        return { ...problema, disponivel: disponivelEmUnidadesDoProduto(problema.disponivel, Number(linha.precisa || 0), Number(item.quantity) || 0) };
      } catch {
        return problema;
      }
    })
  );
  return { ...resposta, problemas: convertidos };
}

/** O carrinho no banco. null = não deu para conferir (a trava do pedido decide). */
export async function conferirItensNoBanco(
  cliente: Rpc,
  dono: string,
  itens: ItemDaConferencia[]
): Promise<{ ok: boolean; problemas: ProblemaDaConferencia[] } | null> {
  if (!dono || !itens.length) return { ok: true, problemas: [] };
  const direta = await cliente.rpc("estoque_conferir_itens", { p_dono: dono, p_itens: itens });
  if (!direta.error && direta.data && typeof direta.data === "object") {
    return emUnidadesDoProduto(cliente, itens, direta.data as { ok: boolean; problemas: ProblemaDaConferencia[] });
  }
  const perguntar = async (item: ItemDaConferencia) => {
    const { data, error } = await cliente.rpc("estoque_disponivel", {
      p_produto: item.product_id,
      p_selected_options: item.selected_options && typeof item.selected_options === "object" ? item.selected_options : null,
      p_quantidade: Number(item.quantity) || 0,
      p_largura: Number(item.width_m) > 0 ? Number(item.width_m) : null,
      p_altura: Number(item.height_m) > 0 ? Number(item.height_m) : null,
      p_maximo: false,
    });
    return error ? null : ((data ?? null) as { v2?: boolean; itens?: ItemDoDisponivel[] } | null);
  };
  /* O primeiro diz se a empresa está no v2; fora dele não há outro caminho
     e não vale gastar uma pergunta por item. */
  const primeira = await perguntar(itens[0]);
  if (!primeira || primeira.v2 !== true) return null;
  const resto = await Promise.all(itens.slice(1).map(perguntar));
  const somado = somarPorCelula(itens, [primeira, ...resto]);
  return somado ? emUnidadesDoProduto(cliente, itens, somado) : null;
}
