function toNumber(value, fallback = 0) {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value))) {
    return Number(value);
  }
  return fallback;
}
function round(value) {
  return Math.round((value + Number.EPSILON) * 1e4) / 1e4;
}
function extractAdjustmentTaxRate(adjustment) {
  const metadata = adjustment.metadata ?? {};
  const candidate = metadata.taxRate ?? metadata?.tax_rate ?? metadata?.taxRateValue ?? metadata?.tax_rate_value ?? null;
  const parsed = toNumber(candidate, NaN);
  return Number.isFinite(parsed) ? parsed : null;
}
function resolveAdjustmentAmounts(adjustments, baseNet, baseGross) {
  return adjustments.map((adj) => {
    const rate = toNumber(adj.rate, NaN);
    const taxRate = extractAdjustmentTaxRate(adj);
    const hasAmountNet = Number.isFinite(toNumber(adj.amountNet, NaN));
    const hasAmountGross = Number.isFinite(toNumber(adj.amountGross, NaN));
    const hasRate = Number.isFinite(rate) && !hasAmountNet && !hasAmountGross;
    const hasTaxRate = taxRate !== null;
    let amountNet = toNumber(adj.amountNet, NaN);
    let amountGross = toNumber(adj.amountGross, NaN);
    if (hasRate) {
      const multiplier = rate / 100;
      amountNet = round(Math.max(baseNet, 0) * multiplier);
      if (adj.kind === "tax") {
        amountGross = amountNet;
      } else if (hasTaxRate) {
        amountGross = round(amountNet * (1 + taxRate / 100));
      } else {
        amountGross = round(Math.max(baseGross, 0) * multiplier);
      }
    } else {
      if (!Number.isFinite(amountNet) && Number.isFinite(amountGross) && hasTaxRate) {
        amountNet = round(amountGross / (1 + taxRate / 100));
      }
      if (!Number.isFinite(amountGross) && Number.isFinite(amountNet) && hasTaxRate) {
        amountGross = round(amountNet * (1 + taxRate / 100));
      }
    }
    return {
      ...adj,
      amountNet: Number.isFinite(amountNet) ? amountNet : adj.amountNet,
      amountGross: Number.isFinite(amountGross) ? amountGross : adj.amountGross
    };
  });
}
function buildBaseLineResult(line) {
  const quantity = Math.max(toNumber(line.quantity, 0), 0);
  const taxRate = toNumber(line.taxRate, 0) / 100;
  const unitNet = line.unitPriceNet ?? (line.unitPriceGross !== null && line.unitPriceGross !== void 0 ? toNumber(line.unitPriceGross) / (1 + taxRate) : 0);
  const discountPerUnit = line.discountAmount ?? (line.discountPercent !== null && line.discountPercent !== void 0 ? toNumber(line.discountPercent, 0) / 100 * toNumber(unitNet, 0) : 0);
  const netSubtotalBeforeDiscount = toNumber(unitNet, 0) * quantity;
  const discountTotal = Math.min(Math.max(discountPerUnit * quantity, 0), netSubtotalBeforeDiscount);
  const netSubtotal = Math.max(netSubtotalBeforeDiscount - discountTotal, 0);
  const explicitTaxAmount = line.taxAmount !== null && line.taxAmount !== void 0;
  let taxAmount = explicitTaxAmount ? toNumber(line.taxAmount, 0) : round(netSubtotal * Math.max(taxRate, 0));
  const grossSubtotal = line.totalGrossAmount !== null && line.totalGrossAmount !== void 0 ? toNumber(line.totalGrossAmount, 0) : round(netSubtotal + taxAmount);
  if (!explicitTaxAmount && taxAmount <= 0) {
    const grossNetDelta = round(grossSubtotal - netSubtotal);
    if (grossNetDelta > 0) taxAmount = grossNetDelta;
  }
  return {
    line,
    netAmount: round(netSubtotal),
    grossAmount: round(grossSubtotal),
    taxAmount: round(taxAmount),
    discountAmount: round(discountTotal),
    adjustments: []
  };
}
function buildBaseDocumentResult(params) {
  const { documentKind, lines, adjustments, currencyCode } = params;
  const orderedAdjustments = [...adjustments ?? []].sort(
    (a, b) => (a.position ?? 0) - (b.position ?? 0)
  );
  let baseSubtotalNet = 0;
  let baseSubtotalGross = 0;
  let subtotalNet = 0;
  let subtotalGross = 0;
  let discountTotal = 0;
  let taxTotal = 0;
  let shippingNet = 0;
  let shippingGross = 0;
  let surchargeTotal = 0;
  for (const line of lines) {
    const net = toNumber(line.netAmount, 0);
    const gross = toNumber(line.grossAmount, 0);
    subtotalNet += net;
    subtotalGross += gross;
    baseSubtotalNet += net;
    baseSubtotalGross += gross;
    discountTotal += toNumber(line.discountAmount, 0);
    taxTotal += toNumber(line.taxAmount, 0);
  }
  const resolvedAdjustments = resolveAdjustmentAmounts(orderedAdjustments, baseSubtotalNet, baseSubtotalGross);
  const scopedAdjustments = resolvedAdjustments.filter(
    (adj) => !adj.scope || adj.scope === "order"
  );
  for (const adj of scopedAdjustments) {
    const rawNet = toNumber(adj.amountNet, toNumber(adj.amountGross));
    const rawGross = toNumber(adj.amountGross, rawNet);
    const isNonNegativeKind = adj.kind === "discount" || adj.kind === "surcharge" || adj.kind === "shipping" || adj.kind === "tax";
    const net = isNonNegativeKind ? Math.abs(rawNet) : rawNet;
    const gross = isNonNegativeKind ? Math.abs(rawGross) : rawGross;
    const taxRate = extractAdjustmentTaxRate(adj);
    const taxPortion = taxRate !== null ? round(gross - net) : 0;
    switch (adj.kind) {
      case "discount":
        discountTotal += net;
        subtotalNet = Math.max(subtotalNet - net, 0);
        subtotalGross = Math.max(subtotalGross - gross, 0);
        if (taxPortion) {
          taxTotal = round(taxTotal - taxPortion);
        }
        break;
      case "tax":
        taxTotal += gross || net;
        subtotalGross += gross || net;
        break;
      case "shipping":
        shippingNet += net;
        shippingGross += gross;
        subtotalNet += net;
        subtotalGross += gross;
        if (taxPortion) {
          taxTotal += taxPortion;
        }
        break;
      case "surcharge":
        surchargeTotal += net || gross;
        subtotalNet += net || gross;
        subtotalGross += gross || net;
        if (taxPortion) {
          taxTotal += taxPortion;
        }
        break;
      default:
        break;
    }
  }
  for (const adj of resolvedAdjustments) {
    if (adj.kind !== "return") continue;
    const net = toNumber(adj.amountNet, toNumber(adj.amountGross));
    const gross = toNumber(adj.amountGross, net);
    const netDelta = -Math.abs(net);
    const grossDelta = -Math.abs(gross);
    subtotalNet = Math.max(subtotalNet + netDelta, 0);
    subtotalGross = Math.max(subtotalGross + grossDelta, 0);
  }
  const grandTotalNet = round(subtotalNet);
  const grandTotalGross = round(subtotalGross);
  const paidTotalAmount = Math.max(toNumber(params.existingTotals?.paidTotalAmount, 0), 0);
  const refundedTotalAmount = Math.max(toNumber(params.existingTotals?.refundedTotalAmount, 0), 0);
  const outstandingAmount = Math.max(grandTotalGross - paidTotalAmount + refundedTotalAmount, 0);
  return {
    kind: documentKind,
    currencyCode,
    lines,
    adjustments: resolvedAdjustments,
    metadata: {},
    totals: {
      subtotalNetAmount: round(subtotalNet),
      subtotalGrossAmount: round(subtotalGross),
      discountTotalAmount: round(discountTotal),
      taxTotalAmount: round(taxTotal),
      shippingNetAmount: round(shippingNet),
      shippingGrossAmount: round(shippingGross),
      surchargeTotalAmount: round(surchargeTotal),
      grandTotalNetAmount: grandTotalNet,
      grandTotalGrossAmount: grandTotalGross,
      paidTotalAmount,
      refundedTotalAmount,
      outstandingAmount
    }
  };
}
class SalesCalculationRegistry {
  constructor() {
    this.lineCalculators = [];
    this.totalsCalculators = [];
  }
  registerLineCalculator(hook, opts) {
    if (opts?.prepend) this.lineCalculators.unshift(hook);
    else this.lineCalculators.push(hook);
    return () => {
      this.lineCalculators = this.lineCalculators.filter((item) => item !== hook);
    };
  }
  registerTotalsCalculator(hook, opts) {
    if (opts?.prepend) this.totalsCalculators.unshift(hook);
    else this.totalsCalculators.push(hook);
    return () => {
      this.totalsCalculators = this.totalsCalculators.filter((item) => item !== hook);
    };
  }
  async calculateLine(opts) {
    const { documentKind, line, context, eventBus } = opts;
    let current = buildBaseLineResult(line);
    if (eventBus) {
      await eventBus.emitEvent("sales.line.calculate.before", {
        documentKind,
        line,
        context,
        result: current,
        setResult(next) {
          current = next;
        }
      });
    }
    for (const hook of this.lineCalculators) {
      const next = await hook({ documentKind, line, context, current });
      if (next) current = next;
    }
    if (eventBus) {
      await eventBus.emitEvent("sales.line.calculate.after", {
        documentKind,
        line,
        context,
        result: current,
        setResult(next) {
          current = next;
        }
      });
    }
    return current;
  }
  async calculateDocument(opts) {
    const { documentKind, lines, adjustments = [], context, eventBus, existingTotals } = opts;
    const resolvedLines = [];
    for (const line of lines) {
      const result = await this.calculateLine({ documentKind, line, context, eventBus });
      resolvedLines.push(result);
    }
    let current = buildBaseDocumentResult({
      documentKind,
      lines: resolvedLines,
      adjustments,
      currencyCode: context.currencyCode,
      existingTotals
    });
    if (eventBus) {
      await eventBus.emitEvent("sales.document.calculate.before", {
        documentKind,
        lines: resolvedLines,
        context,
        adjustments,
        result: current,
        setResult(next) {
          current = next;
        }
      });
    }
    for (const hook of this.totalsCalculators) {
      const next = await hook({
        documentKind,
        lines: resolvedLines,
        existingAdjustments: adjustments,
        context,
        current,
        eventBus
      });
      if (next) current = next;
    }
    if (eventBus) {
      await eventBus.emitEvent("sales.document.calculate.after", {
        documentKind,
        lines: resolvedLines,
        context,
        adjustments,
        result: current,
        setResult(next) {
          current = next;
        }
      });
    }
    if (existingTotals) {
      const paidTotalAmount = Math.max(toNumber(existingTotals.paidTotalAmount, 0), 0);
      const refundedTotalAmount = Math.max(toNumber(existingTotals.refundedTotalAmount, 0), 0);
      current.totals = {
        ...current.totals,
        paidTotalAmount,
        refundedTotalAmount,
        outstandingAmount: round(
          Math.max(current.totals.grandTotalGrossAmount - paidTotalAmount + refundedTotalAmount, 0)
        )
      };
    }
    return current;
  }
}
function createSalesCalculationRegistry() {
  return new SalesCalculationRegistry();
}
const salesCalculations = createSalesCalculationRegistry();
async function calculateLine(opts) {
  return salesCalculations.calculateLine(opts);
}
async function calculateDocumentTotals(opts) {
  return salesCalculations.calculateDocument(opts);
}
function registerSalesLineCalculator(hook, opts) {
  return salesCalculations.registerLineCalculator(hook, opts);
}
function registerSalesTotalsCalculator(hook, opts) {
  return salesCalculations.registerTotalsCalculator(hook, opts);
}
function rebuildDocumentResult(params) {
  const result = buildBaseDocumentResult({
    documentKind: params.documentKind,
    lines: params.lines,
    adjustments: params.adjustments,
    currencyCode: params.currencyCode
  });
  result.metadata = params.metadata ?? {};
  return result;
}
export {
  calculateDocumentTotals,
  calculateLine,
  createSalesCalculationRegistry,
  rebuildDocumentResult,
  registerSalesLineCalculator,
  registerSalesTotalsCalculator,
  salesCalculations
};
//# sourceMappingURL=calculations.js.map
