// 连接池:以「响应体消费完才回池、取消 = 毒化」为前提的空闲连接缓存
// (docs/17 口径 4、分期表第 2 期;设计与契约映射见 docs/21)。
//
// 运行时模型决定了池不需要锁:moonbitlang/async 是单线程协作式调度,
// acquire / release 全程没有 await 点,协程不会在池操作半途被打断,
// 普通可变结构就是安全的。本期「每 host 上限」只约束**空闲**数量
// (用户拍板):桶满了直接关,不排队、不限并发;「并发上限 + 等待队列」
// 是后续增强(需要 Semaphore)。

///|
/// 一条空闲连接:连接本体 + 入池时刻(惰性过期的判据)。
priv struct PooledConn {
  conn : Connection
  idle_since : Int64
}

///|
/// 连接池本体。`buckets` 的键是「一条连接到底服务谁」的完整坐标
/// (scheme / tls / host / port / proxy,见 `pool_key`),值是 LIFO 栈——
/// 刚还回去的连接最可能还热着(TLS 会话、TCP 拥塞窗口都是热的)。
priv struct ConnectionPool {
  buckets : Map[String, Array[PooledConn]]
  /// 每 host 的空闲连接上限;0 = 禁池(release 一律关闭,行为退回 phase 1)
  max_idle_per_host : Int32
  /// 空闲连接的保活时长(毫秒);<= 0 = 不过期,对端关闭由取用失败兜底
  idle_timeout_ms : Int32
}

///|
/// 池的缺省参数:每 host 最多 4 条空闲连接(urllib3 的 pool_maxsize 同款),
/// 空闲保活 30 秒(Go 的 IdleConnTimeout 是 90s,取短一点对服务端友好,
/// 也少踩「服务端早就关了」的重试路径)。
const DEFAULT_MAX_IDLE_PER_HOST : Int32 = 4

///|
const DEFAULT_IDLE_TIMEOUT_MS : Int32 = 30_000

///|
fn ConnectionPool::new(
  max_idle_per_host : Int32,
  idle_timeout_ms : Int32,
) -> ConnectionPool {
  { buckets: {}, max_idle_per_host, idle_timeout_ms, }
}

///|
/// 池是否被禁用(max_idle_per_host = 0):release 一律关闭连接,
/// 请求头也回到 phase 1 的 `Connection: close` 口径。
fn ConnectionPool::disabled(self : ConnectionPool) -> Bool {
  self.max_idle_per_host <= 0
}

///|
/// 取一条空闲连接:LIFO 弹出,路上顺手清掉过期的。
/// 返回 `None` 表示池里没有可用的(该拨号了)。
fn ConnectionPool::acquire(self : ConnectionPool, key : String) -> Connection? {
  let bucket = match self.buckets.get(key) {
    Some(bucket) => bucket
    None => return None
  }
  while bucket.length() > 0 {
    let pooled = bucket.pop().unwrap()
    if self.expired(pooled.idle_since) {
      // 惰性过期:不在后台起定时器,取用时才发现、顺手清掉
      pooled.conn.close()
    } else {
      return Some(pooled.conn)
    }
  }
  // 空桶删掉,键空间(host 数量)才不会只增不减
  self.buckets.remove(key)
  None
}

///|
/// 还连接入池。池禁用 / 桶满时直接关——上限只约束空闲数量,
/// 不排队;多出来的连接当场回收。
fn ConnectionPool::release(
  self : ConnectionPool,
  key : String,
  conn : Connection,
) -> Unit {
  if self.disabled() {
    conn.close()
    return
  }
  let bucket = match self.buckets.get(key) {
    Some(bucket) => bucket
    None => {
      let fresh : Array[PooledConn] = []
      self.buckets.set(key, fresh)
      fresh
    }
  }
  if bucket.length() >= self.max_idle_per_host {
    conn.close()
    return
  }
  bucket.push({ conn, idle_since: @async.now(), })
}

///|
fn ConnectionPool::expired(self : ConnectionPool, idle_since : Int64) -> Bool {
  if self.idle_timeout_ms <= 0 {
    return false
  }
  @async.now() - idle_since > self.idle_timeout_ms.to_int64()
}

///|
/// 池的桶键:「一条连接到底服务谁」的完整坐标。同 host 不同端口的连接
/// 没有互换性;CONNECT 隧道按「目标 + 代理 + 凭据」建立,代理匿名与否、
/// 凭据换了一组,隧道都不能混用。tls 位显式入键(scheme 与它本应一一对应,
/// 双保险)。
///
/// **键里含代理凭据:只许当 Map 键用,绝不能打印或进日志。**
fn pool_key(target : Target, proxy : @transport.ProxyEndpoint?) -> String {
  let base = "\{target.scheme}|tls=\{target.use_tls}|\{target.host}:\{target.port}"
  match proxy {
    None => "\{base}|direct"
    Some(endpoint) => {
      let auth = match endpoint.authorization {
        Some(value) => value
        None => ""
      }
      "\{base}|proxy=\{endpoint.url}|\{auth}"
    }
  }
}