/**
 * Identifier-resolution pipeline for the Create PO page.
 *
 * Takes a list of `RawIdentifierRow` (from paste/upload parsing) and looks
 * them up in ONE backend call via `POST /api/products/resolve-identifiers`.
 * The endpoint does the SQL work: a single query with indexed equality
 * matches across sku, barcode, vendor_reference, notions_reference, and pid,
 * returning candidates grouped per input identifier in input order.
 *
 * Each row resolves to one of:
 *   - matched   → exactly one candidate, ready to add
 *   - ambiguous → multiple candidates; user must pick one in ReviewMatchesDialog
 *   - unmatched → zero candidates; surfaced so the user knows it was dropped
 *
 * Also exports `fetchBatchProducts`, which hydrates a list of pids into
 * full PoLineItem rows via `GET /api/products/batch`.
 */

import { apiClient } from "@/utils/apiClient";
import type {
  RawIdentifierRow,
  ResolveResult,
  SearchCandidate,
  PoLineItem,
} from "./types";
import { BATCH_LOOKUP_MAX_PIDS } from "./constants";

interface ResolveApiCandidate {
  pid: number | string;
  title: string;
  sku: string | null;
  barcode: string | null;
  vendor_reference: string | null;
  notions_reference: string | null;
  brand: string | null;
}

interface ResolveApiResponse {
  results: Array<{
    identifier: string;
    candidates: ResolveApiCandidate[];
  }>;
}

/**
 * Coerces a candidate from the API into the frontend's SearchCandidate
 * shape. pid may come back as a string if the backend ever regresses to
 * un-normalized BIGINT — Number() is defensive.
 */
function toSearchCandidate(c: ResolveApiCandidate): SearchCandidate {
  return {
    pid: Number(c.pid),
    title: c.title,
    sku: c.sku,
    barcode: c.barcode,
    brand: c.brand,
  };
}

export async function resolveIdentifiers(
  rows: RawIdentifierRow[]
): Promise<ResolveResult> {
  if (rows.length === 0) {
    return { matched: [], ambiguous: [], unmatched: [] };
  }

  const identifiers = rows.map((r) => r.identifier);

  const res = await apiClient.post<ResolveApiResponse>(
    "/api/products/resolve-identifiers",
    { identifiers }
  );

  const apiResults = res.data?.results ?? [];
  const result: ResolveResult = { matched: [], ambiguous: [], unmatched: [] };

  // The backend preserves input order and length, so we can zip by index.
  // If the response is shorter than the input (backend truncation or error),
  // missing rows are treated as unmatched so the user still sees them.
  rows.forEach((row, i) => {
    const apiRow = apiResults[i];
    const candidates = apiRow?.candidates ?? [];

    if (candidates.length === 0) {
      result.unmatched.push({ identifier: row.identifier, qty: row.qty });
    } else if (candidates.length === 1) {
      result.matched.push({
        pid: Number(candidates[0].pid),
        qty: row.qty,
        identifier: row.identifier,
      });
    } else {
      result.ambiguous.push({
        identifier: row.identifier,
        qty: row.qty,
        candidates: candidates.map(toSearchCandidate),
      });
    }
  });

  return result;
}

/**
 * Fetch full product display data for a list of pids. Chunks the request
 * to BATCH_LOOKUP_MAX_PIDS to stay under URL length limits even if the
 * caller passes hundreds of pids.
 *
 * When `supplierId` is provided, the backend computes `current_cost_price`
 * using the same fallback chain the legacy PHP `_product_add` writes onto
 * the PO line item (notions_cost_each / supplier_cost_each / receivings).
 * The three raw cost fields (`notions_cost_each`, `supplier_cost_each`,
 * `last_received_cost`) are always returned so the UI can re-derive cost
 * for a different supplier without a round-trip.
 *
 * Returns a flat array of PoLineItem with `qty` set to the value passed in
 * the `qtyByPid` map (default 1).
 */
export async function fetchBatchProducts(
  pids: number[],
  qtyByPid: Map<number, number> = new Map(),
  supplierId?: number
): Promise<PoLineItem[]> {
  if (pids.length === 0) return [];

  const uniqPids = Array.from(new Set(pids));
  const out: PoLineItem[] = [];

  for (let i = 0; i < uniqPids.length; i += BATCH_LOOKUP_MAX_PIDS) {
    const chunk = uniqPids.slice(i, i + BATCH_LOOKUP_MAX_PIDS);
    const params: Record<string, string> = { pids: chunk.join(",") };
    if (supplierId != null && Number.isFinite(supplierId)) {
      params.supplierId = String(supplierId);
    }
    const res = await apiClient.get<Omit<PoLineItem, "qty">[]>(
      "/api/products/batch",
      { params }
    );
    for (const row of res.data ?? []) {
      // Defensive Number() coercion: the backend already returns pid as a
      // Number, but if it ever regresses to a BIGINT string the numeric
      // Set/Map lookups downstream would silently fail instead of crashing.
      const pid = Number(row.pid);
      out.push({ ...row, pid, qty: qtyByPid.get(pid) ?? 1 });
    }
  }

  return out;
}
