///|
pub(all) struct VmPoolConfig {
  size : Int
  max_uses_per_vm : Int
  max_leases_per_tenant : Int
  reset_on_release : Bool
}

///|
pub fn VmPoolConfig::isolated(
  size : Int,
  max_leases_per_tenant : Int,
) -> VmPoolConfig {
  {
    size: if size > 0 {
      size
    } else {
      1
    },
    max_uses_per_vm: 1,
    max_leases_per_tenant: if max_leases_per_tenant > 0 {
      max_leases_per_tenant
    } else {
      1
    },
    reset_on_release: true,
  }
}

///|
pub fn VmPoolConfig::new(
  size : Int,
  max_uses_per_vm : Int,
  max_leases_per_tenant : Int,
  reset_on_release : Bool,
) -> VmPoolConfig {
  {
    size: if size > 0 {
      size
    } else {
      1
    },
    max_uses_per_vm: if max_uses_per_vm > 0 {
      max_uses_per_vm
    } else {
      1
    },
    max_leases_per_tenant: if max_leases_per_tenant > 0 {
      max_leases_per_tenant
    } else {
      1
    },
    reset_on_release,
  }
}

///|
pub struct VmPoolLease {
  pool_identity : Int
  entry_index : Int
  token : Int
  tenant : String
}

///|
pub(all) enum VmPoolAcquireResult {
  VmPoolAcquired(VmPoolLease)
  VmPoolExhausted(String)
}

///|
pub(all) enum VmPoolUseResult[T] {
  VmPoolUsed(T)
  VmPoolLeaseRejected(String)
}

///|
pub(all) struct VmPoolStats {
  capacity : Int
  active : Int
  created : Int
  recycled : Int
  acquisitions : Int
  rejections : Int
}

///|
pub struct VmPoolEntry {
  mut vm : Vm
  mut busy : Bool
  mut token : Int
  mut uses : Int
  mut tenant : String?
}

///|
let vm_pool_identities : Array[Unit] = []

///|
/// Bounded VM pool with per-tenant concurrency controls and optional reset on
/// every release. The isolated configuration is the safe default for mutually
/// untrusted tenants.
pub struct VmPool {
  identity : Int
  config : VmPoolConfig
  factory : () -> Vm
  entries : Array[VmPoolEntry]
  mut closed : Bool
  mut next_token : Int
  mut created : Int
  mut recycled : Int
  mut acquisitions : Int
  mut rejections : Int
}

///|
pub fn VmPool::new(config : VmPoolConfig, factory : () -> Vm) -> VmPool {
  let identity = vm_pool_identities.length()
  vm_pool_identities.push(())
  let entries : Array[VmPoolEntry] = []
  for _index = 0; _index < config.size; _index = _index + 1 {
    entries.push({
      vm: factory(),
      busy: false,
      token: 0,
      uses: 0,
      tenant: None,
    })
  }
  {
    identity,
    config,
    factory,
    entries,
    closed: false,
    next_token: 1,
    created: entries.length(),
    recycled: 0,
    acquisitions: 0,
    rejections: 0,
  }
}

///|
fn VmPool::active_for_tenant(self : VmPool, tenant : String) -> Int {
  let mut active = 0
  for entry in self.entries {
    if entry.busy && entry.tenant == Some(tenant) {
      active = active + 1
    }
  }
  active
}

///|
fn VmPool::replace_entry(self : VmPool, entry : VmPoolEntry) -> Unit {
  entry.vm.close()
  entry.vm = (self.factory)()
  entry.uses = 0
  entry.tenant = None
  self.created = self.created + 1
  self.recycled = self.recycled + 1
}

///|
pub fn VmPool::acquire(self : VmPool, tenant : String) -> VmPoolAcquireResult {
  if self.closed {
    self.rejections = self.rejections + 1
    return VmPoolExhausted("VM pool is closed")
  }
  if tenant.length() == 0 {
    self.rejections = self.rejections + 1
    return VmPoolExhausted("tenant id is required")
  }
  if self.active_for_tenant(tenant) >= self.config.max_leases_per_tenant {
    self.rejections = self.rejections + 1
    return VmPoolExhausted("tenant lease limit reached")
  }
  for index = 0; index < self.entries.length(); index = index + 1 {
    let entry = self.entries[index]
    if !entry.busy {
      if !self.config.reset_on_release &&
        entry.tenant is Some(previous) &&
        previous != tenant {
        self.replace_entry(entry)
      }
      let token = self.next_token
      self.next_token = self.next_token + 1
      entry.busy = true
      entry.token = token
      entry.tenant = Some(tenant)
      self.acquisitions = self.acquisitions + 1
      return VmPoolAcquired({
        pool_identity: self.identity,
        entry_index: index,
        token,
        tenant,
      })
    }
  }
  self.rejections = self.rejections + 1
  VmPoolExhausted("VM pool has no free lease")
}

///|
fn VmPool::valid_entry(self : VmPool, lease : VmPoolLease) -> VmPoolEntry? {
  if self.closed ||
    lease.pool_identity != self.identity ||
    lease.entry_index < 0 ||
    lease.entry_index >= self.entries.length() {
    return None
  }
  let entry = self.entries[lease.entry_index]
  if !entry.busy ||
    entry.token != lease.token ||
    entry.tenant != Some(lease.tenant) {
    None
  } else {
    Some(entry)
  }
}

///|
/// Execute one host operation against a validated lease. The callback should
/// not retain the VM after it returns.
pub fn[T] VmPool::with_vm(
  self : VmPool,
  lease : VmPoolLease,
  operation : (Vm) -> T,
) -> VmPoolUseResult[T] {
  match self.valid_entry(lease) {
    Some(entry) => VmPoolUsed(operation(entry.vm))
    None => VmPoolLeaseRejected("stale or foreign VM pool lease")
  }
}

///|
/// Release a healthy lease. Isolated pools immediately replace its VM; reusable
/// pools replace it at the configured use limit.
pub fn VmPool::release(self : VmPool, lease : VmPoolLease) -> Bool {
  let entry = match self.valid_entry(lease) {
    Some(value) => value
    None => return false
  }
  entry.busy = false
  entry.uses = entry.uses + 1
  if self.config.reset_on_release || entry.uses >= self.config.max_uses_per_vm {
    self.replace_entry(entry)
  }
  true
}

///|
/// Release and unconditionally destroy the leased VM after a runtime failure or
/// suspected tenant contamination.
pub fn VmPool::invalidate(self : VmPool, lease : VmPoolLease) -> Bool {
  let entry = match self.valid_entry(lease) {
    Some(value) => value
    None => return false
  }
  entry.busy = false
  self.replace_entry(entry)
  true
}

///|
pub fn VmPool::stats(self : VmPool) -> VmPoolStats {
  let mut active = 0
  for entry in self.entries {
    if entry.busy {
      active = active + 1
    }
  }
  {
    capacity: self.entries.length(),
    active,
    created: self.created,
    recycled: self.recycled,
    acquisitions: self.acquisitions,
    rejections: self.rejections,
  }
}

///|
pub fn VmPool::close(self : VmPool) -> Unit {
  if self.closed {
    return
  }
  for entry in self.entries {
    entry.vm.close()
    entry.busy = false
  }
  self.closed = true
}