export interface AvgValuation {
  /** Current stock quantity. Must be a non-negative integer. */
  readonly quantity: number
  /** Weighted-average unit cost for current stock. */
  readonly averageCost: number
}

export interface Receipt {
  /** Quantity arriving in the receipt. Must be a non-negative integer. */
  readonly quantity: number
  /** Unit cost for this receipt. Must be a finite, non-negative number. */
  readonly unitCost: number
}

export interface ConsumeResult extends AvgValuation {
  /** Number of units consumed from the valuation layer. */
  readonly consumedQuantity: number
  /** Total cost of the consumed units at the current average. */
  readonly consumedValue: number
}

function assertNonNegativeInteger(value: number, field: string): void {
  if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
    throw new RangeError(`${field} must be a non-negative integer`)
  }
}

function assertFiniteNonNegativeNumber(value: number, field: string): void {
  if (!Number.isFinite(value) || value < 0) {
    throw new RangeError(`${field} must be a finite non-negative number`)
  }
}

function assertState(state: AvgValuation): void {
  assertNonNegativeInteger(state.quantity, 'quantity')
  assertFiniteNonNegativeNumber(state.averageCost, 'averageCost')
}

function assertReceipt(receipt: Receipt): void {
  assertNonNegativeInteger(receipt.quantity, 'receipt.quantity')
  assertFiniteNonNegativeNumber(receipt.unitCost, 'receipt.unitCost')
}

export function blendReceipt(
  state: AvgValuation,
  receipt: Receipt,
): AvgValuation {
  assertState(state)
  assertReceipt(receipt)

  const nextQuantity = state.quantity + receipt.quantity
  if (nextQuantity === 0) {
    return { quantity: 0, averageCost: 0 }
  }

  const currentValue = state.quantity * state.averageCost
  const incomingValue = receipt.quantity * receipt.unitCost
  const nextAverageCost = (currentValue + incomingValue) / nextQuantity

  return {
    quantity: nextQuantity,
    averageCost: nextAverageCost,
  }
}

export function consumeAvg(
  state: AvgValuation,
  consumeQuantity: number,
): ConsumeResult {
  assertState(state)
  assertNonNegativeInteger(consumeQuantity, 'consumeQuantity')
  if (consumeQuantity === 0) {
    return {
      ...state,
      consumedQuantity: 0,
      consumedValue: 0,
    }
  }
  if (consumeQuantity > state.quantity) {
    throw new RangeError('consumeQuantity cannot exceed available quantity')
  }

  const remainingQuantity = state.quantity - consumeQuantity
  const consumedValue = state.averageCost * consumeQuantity

  return {
    quantity: remainingQuantity,
    averageCost: remainingQuantity === 0 ? 0 : state.averageCost,
    consumedQuantity: consumeQuantity,
    consumedValue,
  }
}
