/**
 * Transaction System Errors
 *
 * Provides detailed error information for transaction failures.
 */

/**
 * Base class for all transaction errors
 */
export class TransactionError extends Error {
  constructor(
    message: string,
    public readonly context?: Record<string, any>
  ) {
    super(message)
    this.name = 'TransactionError'
    Error.captureStackTrace(this, this.constructor)
  }
}

/**
 * Error during transaction execution
 */
export class TransactionExecutionError extends TransactionError {
  constructor(
    message: string,
    public readonly operationIndex: number,
    public readonly operationName: string | undefined,
    public override readonly cause: Error
  ) {
    super(message, {
      operationIndex,
      operationName,
      cause: cause.message
    })
    this.name = 'TransactionExecutionError'
  }
}

/**
 * Error during transaction rollback
 */
export class TransactionRollbackError extends TransactionError {
  constructor(
    message: string,
    public readonly originalError: Error,
    public readonly rollbackErrors: Error[]
  ) {
    super(message, {
      originalError: originalError.message,
      rollbackErrorCount: rollbackErrors.length,
      rollbackErrors: rollbackErrors.map(e => e.message)
    })
    this.name = 'TransactionRollbackError'
  }
}

/**
 * Error for invalid transaction state
 */
export class InvalidTransactionStateError extends TransactionError {
  constructor(
    currentState: string,
    attemptedAction: string
  ) {
    super(
      `Cannot ${attemptedAction}: transaction is in state '${currentState}'`,
      { currentState, attemptedAction }
    )
    this.name = 'InvalidTransactionStateError'
  }
}

/**
 * Error for transaction timeout
 *
 * Machine-readable no-hot-retry contract: {@link retryable} and
 * {@link hotRetryUnsafe} are both always `true` on this class — they exist
 * so a caller can branch on the *shape* of the error instead of parsing
 * message text. Read them together: the operation may eventually succeed,
 * but never by looping on it immediately.
 *
 * `context` (inherited from {@link TransactionError}) carries the caller's
 * backoff inputs — see the field docs below.
 */
export class TransactionTimeoutError extends TransactionError {
  /**
   * The failed operation MAY succeed on a later attempt — once the
   * underlying slowness resolves (e.g. a cold page cache warms up) or the
   * budget is deliberately raised (`transactionBudgetFloorMs`, or a larger
   * `timeoutMs` override on the batch). This is a statement about eventual
   * retryability, not a license to retry now — see {@link hotRetryUnsafe}.
   */
  public readonly retryable = true

  /**
   * An immediate, identical retry re-pays the FULL cost of the work that
   * just timed out — it does not resume partway. Looping on this error
   * (hot-retrying) repeats that full cost every attempt and can cascade
   * into a CPU/resource storm on the caller's side. Callers MUST latch: on
   * this error, record `{ at: Date.now(), error }`, surface one loud
   * failure to their own caller, and hold a cooldown window before any
   * re-attempt (clearing the latch only on success). Never retry this error
   * in a tight loop.
   */
  public readonly hotRetryUnsafe = true

  constructor(
    timeoutMs: number,
    operationIndex: number,
    telemetry?: {
      /** Milliseconds elapsed in the transaction when the budget tripped. */
      elapsedMs?: number
      /** Total number of operations in the batch that timed out. */
      totalOperations?: number
      /** Name of the operation the batch was about to start when it tripped, if named. */
      operationName?: string
    }
  ) {
    const progress =
      telemetry?.totalOperations !== undefined
        ? `${operationIndex}/${telemetry.totalOperations}`
        : String(operationIndex)
    const name = telemetry?.operationName ? ` ('${telemetry.operationName}')` : ''
    const elapsed =
      telemetry?.elapsedMs !== undefined ? `${telemetry.elapsedMs}ms elapsed, ` : ''
    super(
      `Transaction timed out at operation ${progress}${name} — ${elapsed}budget ${timeoutMs}ms. ` +
        `The batch rolled back atomically; retryable after the underlying slowness resolves or ` +
        `the budget is raised, but hot-retry-unsafe — latch and back off, never loop.`,
      {
        // Caller backoff inputs — all present on every instance:
        /** Configured budget (ms) that was exceeded. */
        timeoutMs,
        /** Index of the operation the batch was about to start when it tripped. */
        operationIndex,
        ...telemetry
      }
    )
    this.name = 'TransactionTimeoutError'
  }
}
