// packages/commerce-cart-react/src/revive.ts
import type { Cart, CartLine, PriceSnapshot } from '@platform-modules/commerce-cart'
import { CartWireError } from './errors.js'

function asObject(v: unknown, ctx: string): Record<string, unknown> {
  if (typeof v !== 'object' || v === null) {
    throw new CartWireError(`${ctx}: expected an object, got ${v === null ? 'null' : typeof v}`)
  }
  return v as Record<string, unknown>
}

// Copied from commerce-catalog-react/src/revive.ts (one revival convention across siblings).
function reviveAmount(v: unknown, ctx: string): bigint {
  if (typeof v === 'bigint') return v
  if (typeof v === 'number') {
    if (!Number.isSafeInteger(v)) throw new CartWireError(`${ctx}: amount must be a safe-integer minor-unit value (values ≥ 2^53 must be string-encoded), got ${v}`)
    return BigInt(v)
  }
  if (typeof v === 'string') {
    if (v.length > 22) {
      throw new CartWireError(`${ctx}: amount string exceeds 21 digits (length ${v.length})`)
    }
    if (!/^-?\d{1,21}$/.test(v)) throw new CartWireError(`${ctx}: amount is not a valid integer minor-unit string (≤21 digits): ${JSON.stringify(v)}`)
    return BigInt(v)
  }
  throw new CartWireError(`${ctx}: amount missing or wrong type (${typeof v})`)
}

function reviveQty(v: unknown, ctx: string): number {
  // qty is a JSON-faithful POSITIVE safe integer (stricter than a page count — a cart line
  // never holds qty<=0; the core itself rejects qty<=0). Reject 0/negative/fractional/≥2^53.
  if (typeof v !== 'number' || !Number.isSafeInteger(v) || v <= 0) {
    throw new CartWireError(`${ctx}: qty must be a positive safe integer, got ${String(v)}`)
  }
  return v
}

function revivePrice(v: unknown, ctx: string): PriceSnapshot {
  const p = asObject(v, ctx)
  if (typeof p.currency !== 'string') throw new CartWireError(`${ctx}: currency must be a string`)
  if (p.priceMode !== 'inclusive' && p.priceMode !== 'exclusive') {
    throw new CartWireError(`${ctx}: priceMode must be 'inclusive' | 'exclusive', got ${String(p.priceMode)}`)
  }
  return { amount: reviveAmount(p.amount, ctx), currency: p.currency, priceMode: p.priceMode }
}

function reviveLine(v: unknown, i: number): CartLine {
  const r = asObject(v, `reviveCart: lines[${i}]`)
  if (typeof r.lineId !== 'string') throw new CartWireError(`reviveCart: lines[${i}].lineId must be a string`)
  if (typeof r.variantId !== 'string') throw new CartWireError(`reviveCart: lines[${i}].variantId must be a string`)
  if (r.vendorId !== null && typeof r.vendorId !== 'string') throw new CartWireError(`reviveCart: lines[${i}].vendorId must be string|null`)
  return {
    lineId: r.lineId,
    variantId: r.variantId,
    qty: reviveQty(r.qty, `reviveCart: lines[${i}].qty`),
    price: revivePrice(r.price, `reviveCart: lines[${i}].price`),
    vendorId: r.vendorId,
  }
}

/** Wire JSON → typed Cart. Reconstructs every bigint field (subtotal + lines[].price.amount). NO date leg. §5 */
export function reviveCart(raw: unknown): Cart {
  const r = asObject(raw, 'reviveCart')
  if (typeof r.id !== 'string') throw new CartWireError('reviveCart: id must be a string')
  if (typeof r.currency !== 'string') throw new CartWireError('reviveCart: currency must be a string')
  if (!Array.isArray(r.lines)) throw new CartWireError('reviveCart: lines must be an array')
  return {
    id: r.id,
    currency: r.currency,
    lines: r.lines.map((l, i) => reviveLine(l, i)),
    subtotal: reviveAmount(r.subtotal, 'reviveCart: subtotal'),
  }
}