import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { defineModule as defineItemManagementModule } from "../../item-management";
import { PAYMENT_TERM_SNAPSHOT_FIELDS } from "../db/purchaseOrder";
import type { Transaction } from "../generated/kysely-tailordb";
import { deriveReceiptStatus, deriveBillingStatus, type LineProgress } from "../lib/domain";
import {
  PaymentTermInactiveError,
  PaymentTermNotFoundError,
  EmptyAmendmentChangesError,
  PoNotFoundError,
  PoNotOrderedError,
  LineNotFoundError,
  ModifyQuantityBelowReceivedError,
  ModifyQuantityBelowBilledError,
  RemoveLineHasReceiptsError,
  RemoveLineHasBillsError,
  RemoveLineHasDownstreamReferencesError,
  AddInvalidQuantityError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  ProtectedHeaderFieldError,
  ProtectedLineFieldError,
  ReceivingSiteNotFoundError,
  ReceivingSiteRequiredError,
} from "../lib/errors.generated";
import type { InventoryCommands, InventoryQueries } from "../module";
type ItemManagementModule = ReturnType<typeof defineItemManagementModule>;
type ItemManagementQueries = ItemManagementModule["queries"];

type AmendModifyLineChange = {
  action: "MODIFY";
  lineId: string;
  quantity?: string;
  unitPrice?: string;
  receivingSiteId?: string | null;
  requiresPhysicalReceipt?: boolean;
};

type AmendRemoveLineChange = {
  action: "REMOVE";
  lineId: string;
};

type AmendAddLineChange = {
  action: "ADD";
  itemId: string;
  quantity: string;
  unitPrice: string;
  unitId: string;
  receivingSiteId?: string;
  requiresPhysicalReceipt?: boolean;
};

export type AmendOrderedPurchaseOrderChange<LCF extends Record<string, unknown> = {}> =
  | (AmendModifyLineChange & Partial<LCF>)
  | AmendRemoveLineChange
  | (AmendAddLineChange & LCF);

export type AmendStandardHeaderChanges = {
  paymentTermId?: string;
  receivingSiteId?: string | null;
  orderDate?: Date;
  externalSupplierOrderReference?: string | null;
};

export interface AmendOrderedPurchaseOrderInput {
  id: string;
  reason?: string;
  lineChanges?: AmendOrderedPurchaseOrderChange[];
  headerChanges?: AmendStandardHeaderChanges;
}

const PROTECTED_HEADER_FIELDS = new Set([
  "id",
  "companyId",
  "supplierId",
  "orderStatus",
  "receiptStatus",
  "billingStatus",
  "paymentTermSnapshotLines",
  "supplierSnapshotName",
  "rejectionReason",
  "closeReason",
  "createdAt",
  "updatedAt",
]);

const PROTECTED_LINE_FIELDS = new Set([
  "id",
  "purchaseOrderId",
  "itemId",
  "itemSnapshotName",
  "itemSnapshotSku",
  "unitId",
  "createdAt",
  "updatedAt",
]);

const STANDARD_MODIFY_KEYS = new Set([
  "action",
  "lineId",
  "quantity",
  "unitPrice",
  "receivingSiteId",
  "requiresPhysicalReceipt",
]);

const STANDARD_ADD_KEYS = new Set([
  "action",
  "itemId",
  "unitId",
  "quantity",
  "unitPrice",
  "receivingSiteId",
  "requiresPhysicalReceipt",
]);

const LINE_AUDIT_EXCLUDED_FIELDS = new Set([
  "id",
  "purchaseOrderId",
  "itemSnapshotName",
  "itemSnapshotSku",
  "createdAt",
  "updatedAt",
]);

function serializeValue(value: unknown): string | null {
  if (value === null || value === undefined) return null;
  if (value instanceof Date) return value.toISOString();
  if (typeof value === "string") return value;
  if (typeof value === "boolean") return value ? "true" : "false";
  if (typeof value === "number" || typeof value === "bigint") return String(value);
  return JSON.stringify(value) ?? null;
}

export async function run<
  CF extends Record<string, unknown> = Record<string, unknown>,
  LCF extends Record<string, unknown> = Record<string, unknown>,
>(
  db: Transaction,
  input: Omit<AmendOrderedPurchaseOrderInput, "headerChanges" | "lineChanges"> & {
    headerChanges?: AmendStandardHeaderChanges & Partial<CF>;
    lineChanges?: AmendOrderedPurchaseOrderChange<LCF>[];
  },
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
  inventoryCommands: Pick<
    InventoryCommands,
    "createInventorySupplyPlan" | "updateInventorySupplyPlan" | "closeInventorySupplyPlan"
  >,
  inventoryQueries: Pick<
    InventoryQueries,
    "getInventorySupplyPlan" | "listPurchaseReceiptEvidenceForMatching"
  >,
) {
  // 1. Fetch and validate PurchaseOrder
  const purchaseOrder = await db
    .selectFrom("PurchaseOrder")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!purchaseOrder) {
    return err(new PoNotFoundError(input.id));
  }
  if (purchaseOrder.orderStatus !== "ORDERED") {
    return err(new PoNotOrderedError(input.id));
  }

  const lineChanges = input.lineChanges ?? [];
  const headerChangeMap = new Map<string, unknown>();
  for (const [fieldName, value] of Object.entries(input.headerChanges ?? {})) {
    if (value === undefined) continue; // omitted keys are not changes
    headerChangeMap.set(fieldName, value);
  }
  if (lineChanges.length === 0 && headerChangeMap.size === 0) {
    return err(new EmptyAmendmentChangesError(input.id));
  }

  // 2. Validate header changes against the denylist (defense in depth — type already restricts standard keys)
  for (const fieldName of headerChangeMap.keys()) {
    if (PROTECTED_HEADER_FIELDS.has(fieldName)) {
      return err(new ProtectedHeaderFieldError(fieldName));
    }
  }
  const headerChanges = [...headerChangeMap]
    .map(([fieldName, value]) => ({ fieldName, value }))
    .filter((hc) => {
      const oldValue = (purchaseOrder as unknown as Record<string, unknown>)[hc.fieldName];
      return serializeValue(oldValue) !== serializeValue(hc.value);
    });
  if (lineChanges.length === 0 && headerChanges.length === 0) {
    return err(new EmptyAmendmentChangesError(input.id));
  }
  const actualHeaderChangeFields = new Set(headerChanges.map((hc) => hc.fieldName));
  const effectiveReceivingSiteId = actualHeaderChangeFields.has("receivingSiteId")
    ? (input.headerChanges?.receivingSiteId ?? null)
    : purchaseOrder.receivingSiteId;

  let paymentTermSnapshotLines: unknown;
  if (actualHeaderChangeFields.has("paymentTermId")) {
    const paymentTermId = input.headerChanges?.paymentTermId;
    if (paymentTermId === undefined) {
      return err(new PaymentTermNotFoundError("paymentTermId"));
    }
    const paymentTerm = await db
      .selectFrom("PurchasePaymentTerm")
      .selectAll()
      .where("id", "=", paymentTermId)
      .executeTakeFirst();
    if (!paymentTerm) {
      return err(new PaymentTermNotFoundError(paymentTermId));
    }
    if (paymentTerm.status !== "ACTIVE") {
      return err(new PaymentTermInactiveError(paymentTermId));
    }
    paymentTermSnapshotLines = await db
      .selectFrom("PurchasePaymentTermLine")
      .select([...PAYMENT_TERM_SNAPSHOT_FIELDS])
      .where("purchasePaymentTermId", "=", paymentTermId)
      .execute();
  }

  // 3. Fetch current PO lines
  const currentLines = await db
    .selectFrom("PurchaseOrderLine")
    .selectAll()
    .where("purchaseOrderId", "=", input.id)
    .execute();
  const lineMap = new Map(currentLines.map((l) => [l.id, l]));

  // 4. Collect all line IDs referenced by MODIFY/REMOVE to compute received/billed
  const referencedLineIds = lineChanges
    .filter(
      (c): c is Extract<AmendOrderedPurchaseOrderChange, { action: "MODIFY" | "REMOVE" }> =>
        c.action === "MODIFY" || c.action === "REMOVE",
    )
    .map((c) => c.lineId);

  // Fetch received quantities via inventory receipt evidence (POSTED + DRAFT)
  const receivedByLineId = new Map<string, Decimal>();
  if (referencedLineIds.length > 0) {
    const receiptEvidenceResult = await inventoryQueries.listPurchaseReceiptEvidenceForMatching(
      db,
      {
        purchaseOrderId: input.id,
        purchaseOrderLineIds: referencedLineIds,
        statuses: ["DRAFT", "POSTED"],
      },
      ctx,
    );
    for (const evidence of receiptEvidenceResult.value.receiptEvidence) {
      receivedByLineId.set(
        evidence.purchaseOrderLineId,
        (receivedByLineId.get(evidence.purchaseOrderLineId) ?? new Decimal(0)).plus(
          evidence.quantity,
        ),
      );
    }
  }

  // Fetch billed quantities from non-cancelled PurchaseBillLines (MATCHED + DRAFT + BLOCKED)
  const billedByLineId = new Map<string, Decimal>();
  if (referencedLineIds.length > 0) {
    const billLines = await db
      .selectFrom("PurchaseBillLine")
      .innerJoin("PurchaseBill", "PurchaseBill.id", "PurchaseBillLine.purchaseBillId")
      .select(["PurchaseBillLine.purchaseOrderLineId", "PurchaseBillLine.quantity"])
      .where("PurchaseBillLine.purchaseOrderLineId", "in", referencedLineIds)
      .where("PurchaseBill.status", "!=", "CANCELLED")
      .execute();
    for (const bl of billLines) {
      if (bl.purchaseOrderLineId) {
        billedByLineId.set(
          bl.purchaseOrderLineId,
          (billedByLineId.get(bl.purchaseOrderLineId) ?? new Decimal(0)).plus(bl.quantity),
        );
      }
    }
  }

  const now = new Date();

  // 5. Validate all line changes and collect pending operations
  type Line = (typeof currentLines)[number];
  const modifyOps: {
    line: Line;
    quantity: string;
    unitPrice: string;
    receivingSiteId: string | null;
    requiresPhysicalReceipt: boolean;
    customFields: Record<string, unknown>;
  }[] = [];
  const removeLines: Line[] = [];
  const addOps: {
    change: AmendAddLineChange;
    customFields: Record<string, unknown>;
    item: { name: string | null; sku: string | null };
  }[] = [];
  const siteIdsToValidate = new Set<string>();

  for (const change of lineChanges) {
    if (change.action === "MODIFY") {
      const line = lineMap.get(change.lineId);
      if (!line) return err(new LineNotFoundError(change.lineId));

      const newQuantity = change.quantity ?? line.quantity;
      const newQuantityDecimal = new Decimal(newQuantity);
      const newUnitPrice = change.unitPrice ?? line.unitPrice;

      if (change.quantity !== undefined) {
        if (newQuantityDecimal.lte(0)) return err(new InvalidQuantityError(change.lineId));
        const received = receivedByLineId.get(change.lineId) ?? new Decimal(0);
        const billed = billedByLineId.get(change.lineId) ?? new Decimal(0);
        if (newQuantityDecimal.lt(received))
          return err(new ModifyQuantityBelowReceivedError(change.lineId));
        if (newQuantityDecimal.lt(billed))
          return err(new ModifyQuantityBelowBilledError(change.lineId));
      }

      if (change.unitPrice !== undefined && new Decimal(change.unitPrice).lt(0)) {
        return err(new InvalidUnitPriceError(change.lineId));
      }

      const newReceivingSiteId =
        change.receivingSiteId !== undefined ? change.receivingSiteId : line.receivingSiteId;
      const newRequiresPhysicalReceipt =
        change.requiresPhysicalReceipt ?? line.requiresPhysicalReceipt;
      if (newRequiresPhysicalReceipt && !newReceivingSiteId && !effectiveReceivingSiteId) {
        return err(new ReceivingSiteRequiredError(change.lineId));
      }
      if (change.receivingSiteId) siteIdsToValidate.add(change.receivingSiteId);

      const customFields: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(change)) {
        if (STANDARD_MODIFY_KEYS.has(key)) continue;
        if (value === undefined) continue;
        if (PROTECTED_LINE_FIELDS.has(key)) {
          return err(new ProtectedLineFieldError(key));
        }
        customFields[key] = value;
      }
      const customFieldChanges: { fieldName: string; oldValue: unknown; newValue: unknown }[] = [];
      for (const [key, value] of Object.entries(customFields)) {
        const oldRaw = (line as unknown as Record<string, unknown>)[key];
        if (serializeValue(oldRaw) === serializeValue(value)) continue;
        customFieldChanges.push({ fieldName: key, oldValue: oldRaw, newValue: value });
      }

      if (
        newQuantity === line.quantity &&
        newUnitPrice === line.unitPrice &&
        newReceivingSiteId === line.receivingSiteId &&
        newRequiresPhysicalReceipt === line.requiresPhysicalReceipt &&
        customFieldChanges.length === 0
      ) {
        continue;
      }

      const effectiveCustomFields: Record<string, unknown> = {};
      for (const c of customFieldChanges) {
        effectiveCustomFields[c.fieldName] = c.newValue;
      }
      modifyOps.push({
        line,
        quantity: newQuantity,
        unitPrice: newUnitPrice,
        receivingSiteId: newReceivingSiteId,
        requiresPhysicalReceipt: newRequiresPhysicalReceipt,
        customFields: effectiveCustomFields,
      });

      // Update lineMap so subsequent changes for the same line see the amended state
      lineMap.set(line.id, {
        ...line,
        ...effectiveCustomFields,
        quantity: newQuantity,
        unitPrice: newUnitPrice,
        receivingSiteId: newReceivingSiteId,
        requiresPhysicalReceipt: newRequiresPhysicalReceipt,
      });
    } else if (change.action === "REMOVE") {
      const line = lineMap.get(change.lineId);
      if (!line) return err(new LineNotFoundError(change.lineId));

      const received = receivedByLineId.get(change.lineId) ?? new Decimal(0);
      const billed = billedByLineId.get(change.lineId) ?? new Decimal(0);
      if (received.gt(0)) return err(new RemoveLineHasReceiptsError(change.lineId));
      if (billed.gt(0)) return err(new RemoveLineHasBillsError(change.lineId));

      removeLines.push(line);
      lineMap.delete(line.id);
    } else if (change.action === "ADD") {
      const newQuantity = new Decimal(change.quantity);
      if (newQuantity.lte(0)) return err(new AddInvalidQuantityError(input.id));
      if (new Decimal(change.unitPrice).lt(0)) return err(new InvalidUnitPriceError(input.id));

      const receivingSiteId = change.receivingSiteId ?? null;
      const requiresPhysicalReceipt = change.requiresPhysicalReceipt ?? true;
      if (requiresPhysicalReceipt && !receivingSiteId && !effectiveReceivingSiteId) {
        return err(new ReceivingSiteRequiredError(input.id));
      }
      if (change.receivingSiteId) siteIdsToValidate.add(change.receivingSiteId);

      const customFields: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(change)) {
        if (STANDARD_ADD_KEYS.has(key)) continue;
        if (value === undefined) continue;
        if (PROTECTED_LINE_FIELDS.has(key)) {
          return err(new ProtectedLineFieldError(key));
        }
        customFields[key] = value;
      }

      const { item } = (await itemManagementQueries.getItem(db, { id: change.itemId }, ctx)).value;
      if (!item) return err(new ItemNotFoundError(change.itemId));

      addOps.push({ change, customFields, item });
    }
  }

  if (
    headerChanges.length === 0 &&
    modifyOps.length === 0 &&
    removeLines.length === 0 &&
    addOps.length === 0
  ) {
    return err(new EmptyAmendmentChangesError(input.id));
  }
  if (actualHeaderChangeFields.has("receivingSiteId") && !effectiveReceivingSiteId) {
    const lineWithoutReceivingSite = [...lineMap.values()].find(
      (line) => line.requiresPhysicalReceipt && !line.receivingSiteId,
    );
    if (lineWithoutReceivingSite) {
      return err(new ReceivingSiteRequiredError(lineWithoutReceivingSite.id));
    }
  }

  // Batch validate receiving sites
  if (actualHeaderChangeFields.has("receivingSiteId") && effectiveReceivingSiteId) {
    siteIdsToValidate.add(effectiveReceivingSiteId);
  }
  if (siteIdsToValidate.size > 0) {
    const siteIds = [...siteIdsToValidate];
    const sites = await db.selectFrom("Site").select("id").where("id", "in", siteIds).execute();
    const foundSiteIds = new Set(sites.map((s) => s.id));
    for (const siteId of siteIds) {
      if (!foundSiteIds.has(siteId)) return err(new ReceivingSiteNotFoundError(siteId));
    }
  }

  // Batch validate REMOVE downstream references
  if (removeLines.length > 0) {
    const removeLineIds = removeLines.map((l) => l.id);

    const draftReceiptResult = await inventoryQueries.listPurchaseReceiptEvidenceForMatching(
      db,
      { purchaseOrderId: input.id, purchaseOrderLineIds: removeLineIds, statuses: ["DRAFT"] },
      ctx,
    );
    const draftReceiptRef = draftReceiptResult.value.receiptEvidence[0];
    if (draftReceiptRef) {
      return err(
        new RemoveLineHasDownstreamReferencesError(String(draftReceiptRef.purchaseOrderLineId)),
      );
    }

    const draftOrBlockedBillRef = await db
      .selectFrom("PurchaseBillLine")
      .innerJoin("PurchaseBill", "PurchaseBill.id", "PurchaseBillLine.purchaseBillId")
      .select(["PurchaseBillLine.purchaseOrderLineId"])
      .where("PurchaseBillLine.purchaseOrderLineId", "in", removeLineIds)
      .where("PurchaseBill.status", "in", ["DRAFT", "BLOCKED"])
      .executeTakeFirst();
    if (draftOrBlockedBillRef) {
      return err(
        new RemoveLineHasDownstreamReferencesError(
          String(draftOrBlockedBillRef.purchaseOrderLineId),
        ),
      );
    }
  }

  // 6. Execute mutations

  // MODIFY: update each line (different values per row)
  for (const op of modifyOps) {
    await db
      .updateTable("PurchaseOrderLine")
      .set({
        ...(op.customFields as Record<string, unknown>),
        quantity: op.quantity,
        unitPrice: op.unitPrice,
        receivingSiteId: op.receivingSiteId,
        requiresPhysicalReceipt: op.requiresPhysicalReceipt,
      })
      .where("id", "=", op.line.id)
      .execute();
  }

  // REMOVE: batch delete
  if (removeLines.length > 0) {
    const removeLineIds = removeLines.map((l) => l.id);
    await db.deleteFrom("PurchaseOrderLine").where("id", "in", removeLineIds).execute();
  }

  // ADD: batch insert
  let addedLines: Line[] = [];
  if (addOps.length > 0) {
    addedLines = await db
      .insertInto("PurchaseOrderLine")
      .values(
        addOps.map((op) => ({
          ...(op.customFields as Record<string, unknown>),
          purchaseOrderId: input.id,
          itemId: op.change.itemId,
          itemSnapshotName: op.item.name ?? null,
          itemSnapshotSku: op.item.sku ?? null,
          quantity: op.change.quantity,
          unitPrice: op.change.unitPrice,
          unitId: op.change.unitId,
          receivingSiteId: op.change.receivingSiteId ?? null,
          requiresPhysicalReceipt: op.change.requiresPhysicalReceipt ?? true,
        })),
      )
      .returningAll()
      .execute();
    for (const line of addedLines) lineMap.set(line.id, line);
  }

  // 8. Recalculate receiptStatus and billingStatus from remaining lines
  const remainingLineIds = [...lineMap.keys()];

  const allReceivedByLine = new Map<string, Decimal>();
  const allBilledByLine = new Map<string, Decimal>();

  if (remainingLineIds.length > 0) {
    const postedReceiptResult = await inventoryQueries.listPurchaseReceiptEvidenceForMatching(
      db,
      { purchaseOrderId: input.id, purchaseOrderLineIds: remainingLineIds },
      ctx,
    );
    for (const evidence of postedReceiptResult.value.receiptEvidence) {
      allReceivedByLine.set(
        evidence.purchaseOrderLineId,
        (allReceivedByLine.get(evidence.purchaseOrderLineId) ?? new Decimal(0)).plus(
          evidence.quantity,
        ),
      );
    }

    const matchedBillLinesAll = await db
      .selectFrom("PurchaseBillLine")
      .innerJoin("PurchaseBill", "PurchaseBill.id", "PurchaseBillLine.purchaseBillId")
      .select(["PurchaseBillLine.purchaseOrderLineId", "PurchaseBillLine.quantity"])
      .where("PurchaseBillLine.purchaseOrderLineId", "in", remainingLineIds)
      .where("PurchaseBill.status", "=", "MATCHED")
      .execute();
    for (const bl of matchedBillLinesAll) {
      if (bl.purchaseOrderLineId) {
        allBilledByLine.set(
          bl.purchaseOrderLineId,
          (allBilledByLine.get(bl.purchaseOrderLineId) ?? new Decimal(0)).plus(bl.quantity),
        );
      }
    }
  }

  const lineProgress: LineProgress[] = [...lineMap.values()].map((ol) => ({
    quantity: ol.quantity,
    receivedQuantity: (allReceivedByLine.get(ol.id) ?? new Decimal(0)).toString(),
    billedQuantity: (allBilledByLine.get(ol.id) ?? new Decimal(0)).toString(),
  }));

  // 8. Update PurchaseOrder header (apply header field changes + statuses)
  const headerFieldSet: Record<string, unknown> = {};
  for (const hc of headerChanges) {
    headerFieldSet[hc.fieldName] =
      hc.fieldName === "receivingSiteId" ? effectiveReceivingSiteId : hc.value;
  }
  if (paymentTermSnapshotLines !== undefined) {
    headerFieldSet.paymentTermSnapshotLines = paymentTermSnapshotLines;
  }
  const updated = await db
    .updateTable("PurchaseOrder")
    .set({
      ...(headerFieldSet as Record<string, unknown>),
      receiptStatus: deriveReceiptStatus(lineProgress),
      billingStatus: deriveBillingStatus(lineProgress),
      updatedAt: now,
    })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  // 9. Insert PurchaseOrderRevision envelope (revisionNumber = MAX(existing) + 1)
  const lastRevision = await db
    .selectFrom("PurchaseOrderRevision")
    .select("revisionNumber")
    .where("purchaseOrderId", "=", input.id)
    .orderBy("revisionNumber", "desc")
    .limit(1)
    .executeTakeFirst();
  const newRevisionNumber = (lastRevision?.revisionNumber ?? 0) + 1;
  const envelope = await db
    .insertInto("PurchaseOrderRevision")
    .values({
      purchaseOrderId: input.id,
      revisionNumber: newRevisionNumber,
      reason: input.reason ?? null,
      amendedByUserId: ctx.actorId,
      createdAt: now,
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  // 12. Build and insert PurchaseOrderFieldChange rows
  type FieldChangeRow = {
    revisionId: string;
    recordType: "HEADER" | "LINE";
    recordId: string;
    fieldName: string;
    changeKind: "ADDED" | "MODIFIED" | "REMOVED";
    oldValue: string | null;
    newValue: string | null;
    createdAt: Date;
    updatedAt: null;
  };
  const fieldChanges: FieldChangeRow[] = [];

  // Header MODIFIED rows
  for (const hc of headerChanges) {
    const oldRaw = (purchaseOrder as unknown as Record<string, unknown>)[hc.fieldName];
    fieldChanges.push({
      revisionId: envelope.id,
      recordType: "HEADER",
      recordId: input.id,
      fieldName: hc.fieldName,
      changeKind: "MODIFIED",
      oldValue: serializeValue(oldRaw),
      newValue: serializeValue(hc.value),
      createdAt: now,
      updatedAt: null,
    });
  }

  // Line MODIFIED rows — only fields that actually changed (standard + extension fields)
  for (const op of modifyOps) {
    const before = op.line;
    const candidates: { field: string; oldVal: unknown; newVal: unknown }[] = [
      { field: "quantity", oldVal: before.quantity, newVal: op.quantity },
      { field: "unitPrice", oldVal: before.unitPrice, newVal: op.unitPrice },
      { field: "receivingSiteId", oldVal: before.receivingSiteId, newVal: op.receivingSiteId },
      {
        field: "requiresPhysicalReceipt",
        oldVal: before.requiresPhysicalReceipt,
        newVal: op.requiresPhysicalReceipt,
      },
    ];
    for (const [field, newVal] of Object.entries(op.customFields)) {
      candidates.push({
        field,
        oldVal: (before as unknown as Record<string, unknown>)[field],
        newVal,
      });
    }
    for (const c of candidates) {
      const oldStr = serializeValue(c.oldVal);
      const newStr = serializeValue(c.newVal);
      if (oldStr === newStr) continue;
      fieldChanges.push({
        revisionId: envelope.id,
        recordType: "LINE",
        recordId: op.line.id,
        fieldName: c.field,
        changeKind: "MODIFIED",
        oldValue: oldStr,
        newValue: newStr,
        createdAt: now,
        updatedAt: null,
      });
    }
  }

  // Line REMOVED rows — one per non-null user-meaningful field
  for (const line of removeLines) {
    for (const [field, oldRaw] of Object.entries(line as unknown as Record<string, unknown>)) {
      if (LINE_AUDIT_EXCLUDED_FIELDS.has(field)) continue;
      const oldValue = serializeValue(oldRaw);
      if (oldValue === null) continue;
      fieldChanges.push({
        revisionId: envelope.id,
        recordType: "LINE",
        recordId: line.id,
        fieldName: field,
        changeKind: "REMOVED",
        oldValue,
        newValue: null,
        createdAt: now,
        updatedAt: null,
      });
    }
  }

  // Line ADDED rows — one per non-null user-meaningful field
  for (const line of addedLines) {
    for (const [field, newRaw] of Object.entries(line as unknown as Record<string, unknown>)) {
      if (LINE_AUDIT_EXCLUDED_FIELDS.has(field)) continue;
      const newValue = serializeValue(newRaw);
      if (newValue === null) continue;
      fieldChanges.push({
        revisionId: envelope.id,
        recordType: "LINE",
        recordId: line.id,
        fieldName: field,
        changeKind: "ADDED",
        oldValue: null,
        newValue,
        createdAt: now,
        updatedAt: null,
      });
    }
  }

  if (fieldChanges.length > 0) {
    await db.insertInto("PurchaseOrderFieldChange").values(fieldChanges).execute();
  }

  // 13. Reconcile inventory supply plans after amendment persistence
  if (removeLines.length > 0) {
    const closeResult = await inventoryCommands.closeInventorySupplyPlan(
      db,
      {
        target: "SOURCE_LINES",
        sourceType: "PURCHASE_ORDER",
        sourceLineIds: removeLines.map((line) => line.id),
      },
      ctx,
    );
    if (!closeResult.ok) return closeResult;
  }

  const supplyLinesToReconcile = new Map<string, Line>();
  for (const op of modifyOps) {
    const line = lineMap.get(op.line.id);
    if (line) supplyLinesToReconcile.set(line.id, line);
  }
  for (const line of addedLines) {
    supplyLinesToReconcile.set(line.id, line);
  }
  if (
    actualHeaderChangeFields.has("receivingSiteId") ||
    actualHeaderChangeFields.has("orderDate")
  ) {
    for (const line of lineMap.values()) {
      supplyLinesToReconcile.set(line.id, line);
    }
  }

  const effectiveSupplyOrder = {
    id: purchaseOrder.id,
    receivingSiteId: effectiveReceivingSiteId,
    orderDate: actualHeaderChangeFields.has("orderDate")
      ? (input.headerChanges?.orderDate ?? purchaseOrder.orderDate)
      : purchaseOrder.orderDate,
  };
  const supplyPlansToCreate: Array<{
    sourceLineId: string;
    itemId: string;
    siteId: string;
    expectedQuantity: string;
    unitId: string;
    expectedDate: Date;
  }> = [];
  for (const line of supplyLinesToReconcile.values()) {
    const supplyPlanResult = await inventoryQueries.getInventorySupplyPlan(
      db,
      { sourceType: "PURCHASE_ORDER", sourceLineId: line.id },
      ctx,
    );

    const existingSupplyPlan = supplyPlanResult.value.supplyPlan;
    const siteId = line.receivingSiteId ?? effectiveSupplyOrder.receivingSiteId;
    if (!line.requiresPhysicalReceipt || !siteId) {
      if (existingSupplyPlan) {
        const updateResult = await inventoryCommands.updateInventorySupplyPlan(
          db,
          {
            id: existingSupplyPlan.id,
            expectedQuantity: "0",
          },
          ctx,
        );
        if (!updateResult.ok) return updateResult;
      }
      continue;
    }

    if (existingSupplyPlan) {
      const updateResult = await inventoryCommands.updateInventorySupplyPlan(
        db,
        {
          id: existingSupplyPlan.id,
          sourceType: "PURCHASE_ORDER",
          sourceId: effectiveSupplyOrder.id,
          sourceLineId: line.id,
          itemId: line.itemId,
          siteId,
          expectedQuantity: line.quantity,
          unitId: line.unitId,
          expectedDate: effectiveSupplyOrder.orderDate,
        },
        ctx,
      );
      if (!updateResult.ok) return updateResult;
      continue;
    }

    supplyPlansToCreate.push({
      sourceLineId: line.id,
      itemId: line.itemId,
      siteId,
      expectedQuantity: line.quantity,
      unitId: line.unitId,
      expectedDate: effectiveSupplyOrder.orderDate,
    });
  }

  if (supplyPlansToCreate.length > 0) {
    const createResult = await inventoryCommands.createInventorySupplyPlan(
      db,
      {
        sourceType: "PURCHASE_ORDER",
        sourceId: effectiveSupplyOrder.id,
        supplyPlans: supplyPlansToCreate,
      },
      ctx,
    );
    if (!createResult.ok) return createResult;
  }

  return ok({ purchaseOrder: updated });
}
