/**
 * Module wallet read helpers — vesting projection via @platform-modules/ledger/vesting.
 */

import { getVesting, type VestingSchema } from '@platform-modules/ledger/vesting';
import type { Querier } from '@platform-modules/db';
import type { DrizzleClient } from '../db/client.js';

/** Guarded API boundary — agorot domain fits safe integer; refuse silent precision loss. */
export function minorForApi(v: bigint): number {
  if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
    throw new Error('MINOR_UNSAFE_FOR_API_NUMBER');
  }
  return Number(v);
}

function vestingQuerier(db: DrizzleClient): Querier<VestingSchema> {
  return db as unknown as Querier<VestingSchema>;
}

/**
 * Read wallet vesting projection for a user (module wallet_vesting).
 *
 * When opts.withLifetime is true, returns available credit (netMinor) and
 * lifetime-earned totals. Otherwise returns the whole-wallet sum
 * (pending + net) for admin/reconciliation echoes. netMinor is the spendable
 * store credit (matured, net of carried debt); withdrawableMinor is its
 * payout-gated subset for the affiliate withdrawal path and does NOT cap this
 * balance — a regular user's wallet is spendable store credit, not withdrawal-
 * gated.
 *
 * Falls back to 0 / { balanceAgorot: 0, lifetimeEarnedAgorot: 0 }
 * when the user has no wallet row yet.
 */
export async function getBalance(
  db: DrizzleClient,
  userId: string,
  opts?: { withLifetime?: boolean },
): Promise<number | { balanceAgorot: number; lifetimeEarnedAgorot: number }> {
  const v = await getVesting(vestingQuerier(db), userId);
  const totalMinor = v.pendingMinor + v.netMinor;

  return opts?.withLifetime
    ? {
        balanceAgorot: minorForApi(v.netMinor),
        lifetimeEarnedAgorot: minorForApi(v.lifetimeEarnedMinor),
      }
    : minorForApi(totalMinor);
}
