// Checked-out client lease lifecycle helpers.

///|
/// Start one client operation or reject work after release begins.
fn ClientLeaseState::begin_operation(
  self : ClientLeaseState,
) -> @client.Client raise {
  if self.releasing.val || self.released.val {
    raise PoolError::LeaseReleased
  }
  if self.scope_active.val {
    raise PoolError::OperationInProgress
  }
  match self.connection.client.val {
    Some(client) => {
      self.active_ops.val += 1
      client
    }
    None => raise PoolError::LeaseReleased
  }
}

///|
/// Finish one client operation and finalize delayed release if needed.
fn ClientLeaseState::finish_operation(self : ClientLeaseState) -> Unit {
  if self.active_ops.val > 0 {
    self.active_ops.val -= 1
  }
  if self.releasing.val && !self.released.val && self.active_ops.val == 0 {
    self.finalize_release()
  }
}

///|
/// Start one exclusive cancellable scope on this lease.
fn ClientLeaseState::begin_cancellable_scope(
  self : ClientLeaseState,
) -> ConnectionState raise {
  if self.releasing.val || self.released.val {
    raise PoolError::LeaseReleased
  }
  if self.scope_active.val || self.active_ops.val > 0 {
    raise PoolError::OperationInProgress
  }
  match self.connection.client.val {
    Some(_) => {
      self.scope_active.val = true
      self.active_ops.val += 1
      self.connection
    }
    None => raise PoolError::LeaseReleased
  }
}

///|
/// Finish one exclusive cancellable scope on this lease.
fn ClientLeaseState::finish_cancellable_scope(self : ClientLeaseState) -> Unit {
  self.scope_active.val = false
  self.finish_operation()
}

///|
/// Start one exclusive transaction scope on this lease.
fn ClientLeaseState::begin_transaction_scope(
  self : ClientLeaseState,
) -> (@client.Client, ConnectionState) raise {
  if self.releasing.val || self.released.val {
    raise PoolError::LeaseReleased
  }
  if self.scope_active.val || self.active_ops.val > 0 {
    raise PoolError::OperationInProgress
  }
  match self.connection.client.val {
    Some(client) => {
      self.scope_active.val = true
      self.active_ops.val += 1
      (client, self.connection)
    }
    None => raise PoolError::LeaseReleased
  }
}

///|
/// Finish one exclusive transaction scope on this lease.
fn ClientLeaseState::finish_transaction_scope(self : ClientLeaseState) -> Unit {
  self.scope_active.val = false
  self.finish_operation()
}

///|
/// Return the client to the pool once no active operations remain.
fn ClientLeaseState::finalize_release(self : ClientLeaseState) -> Unit {
  if self.released.val {
    return
  }
  self.released.val = true
  return_client(self.pool, self.connection)
}

///|
/// Run one async operation against the checked-out client.
async fn[T] Client::run_operation(
  self : Client,
  op : async (@client.Client) -> T,
) -> T {
  let client = self.state.begin_operation()
  defer self.state.finish_operation()
  @async.protect_from_cancel(() => op(client))
}

///|
/// Mark this pooled client lease for release.
///
/// Side effects: new scoped operations start failing with `PoolError::LeaseReleased`.
/// Existing in-flight operations are allowed to finish first, and the physical
/// connection is returned to the pool only after the active-operation count
/// reaches zero. Repeated calls are idempotent.
pub fn Client::release(self : Client) -> Unit {
  if self.state.releasing.val || self.state.released.val {
    return
  }
  self.state.releasing.val = true
  if self.state.active_ops.val == 0 {
    self.state.finalize_release()
  }
}

///|
/// Permanently detach the raw client from pool management.
///
/// Preconditions: the lease must still be active, and no operation,
/// transaction-building scope, or cancellable scope may be in progress.
/// Side effects: the pool forgets this physical connection, frees one capacity
/// slot, and stops recycling or closing the detached client for you. The caller
/// becomes responsible for closing the returned raw client. The returned
/// `@client.Client` keeps using the existing background connection driver, so it
/// can continue ordinary client operations immediately after detaching.
pub fn Client::detach_raw(self : Client) -> @client.Client raise {
  if self.state.releasing.val || self.state.released.val {
    raise PoolError::LeaseReleased
  }
  if self.state.scope_active.val || self.state.active_ops.val > 0 {
    raise PoolError::OperationInProgress
  }
  let client = match self.state.connection.client.val {
    Some(client) => client
    None => raise PoolError::LeaseReleased
  }
  self.state.releasing.val = true
  self.state.released.val = true
  retire_connection(self.state.pool, self.state.connection, close_client=false)
  restore_available_slot(self.state.pool)
  client
}