import type { PostgresTransaction } from '@platform-modules/db'
import { appendEntry } from './append.js'
import type { Schema } from '@platform-modules/db'
import type { LedgerSchema } from './schema.js'
import type {
  DebitWithReadFn,
  DebitWithReadInput,
  DebitWithReadResult,
} from './types.js'

/**
 * Pessimistic debit: `SELECT … FOR UPDATE` the target row(s), let the caller
 * `fn` decide sufficiency/split from the locked rows, then idempotent ledger
 * insert FIRST and apply the caller's mutation. On a duplicate key (retry) the
 * mutation is skipped; a caller `fn` that throws (insufficient) leaves no
 * partial state.
 *
 * MUST run inside a transaction (`db.transaction((tx) => debitWithRead(tx, …))`).
 * The `FOR UPDATE` lock and the insert-first/rollback atomicity only hold inside
 * a transaction — a non-transactional `Querier` neither holds the lock nor rolls
 * back the inserted ledger row if `fn` or `apply` throws.
 */
export async function debitWithRead<
  S extends Schema & LedgerSchema,
  TRow extends Record<string, unknown>,
>(
  tx: PostgresTransaction<S>,
  input: DebitWithReadInput,
  fn: DebitWithReadFn<S, TRow>,
): Promise<DebitWithReadResult> {
  const locked = await tx
    .select()
    .from(input.lock.table)
    .where(input.lock.where)
    .for('update')

  const plan = await fn(locked as TRow[])

  const { inserted, id } = await appendEntry(tx, {
    key: input.key,
    delta: plan.delta,
    reason: input.reason,
    ref: input.ref,
  })

  if (!inserted) {
    return { inserted: false, id: null }
  }

  await plan.apply(tx)
  return { inserted: true, id }
}
