// URL 前沿队列(frontier):待抓取集合、已见去重与按 host 的礼貌间隔。
//
// 设计取舍:队列本身只做「去重入队 + FIFO 出队 + host 间隔推算」,
// 延迟等待由引擎执行(便于注入测试时钟/假 fetcher)。规范化是调用方
// 的前置义务——同一条 URL 的等价书写形式必须先折叠再入队。

///|
pub struct Frontier {
  priv queue : Array[String]
  priv mut head : Int32 // 消费游标,避免大数组 shift
  priv seen : Bloom
  priv hosts : Map[String, Int32] // host -> 最近一次开始抓取的时刻(ms)
  priv min_host_delay_ms : Int32
}

///|
/// `min_host_delay_ms`:同一 host 两次请求的最小间隔(礼貌默认 1000)。
pub fn Frontier::new(min_host_delay_ms : Int32) -> Frontier {
  {
    queue: [],
    head: 0,
    seen: Bloom::new(4096),
    hosts: Map([], capacity=64),
    min_host_delay_ms,
  }
}

///|
fn host_of(url : String) -> String {
  match parse_url(url) {
    Some(u) => u.host
    None => ""
  }
}

///|
/// 入队。已见过的 URL(含此前抓取过的)返回 false 且不入队。
pub fn Frontier::push(self : Frontier, url : String) -> Bool {
  if self.seen.maybe_contains(url) {
    return false
  }
  self.seen.add(url)
  let _ = self.queue.push(url)
  true
}

///|
/// 直接管账式登记「已见过」(用于种子的去重基线),不入队。
pub fn Frontier::mark_seen(self : Frontier, url : String) -> Unit {
  self.seen.add(url)
}

///|
/// FIFO 出队。空返回 None。
pub fn Frontier::pop(self : Frontier) -> String? {
  if self.head >= self.queue.length() {
    return None
  }
  let url = self.queue[self.head]
  self.head = self.head + 1
  Some(url)
}

///|
/// 批量出队,**每个 host 最多一个**。
///
/// 供并发调度使用:同 host 的请求必须串行(礼貌限速按站记账),
/// 不同 host 可以并行。本轮未取到的 URL 会被移到队尾,留待下一轮。
/// 扫描上界取调用时的队列长度快照,因此不会因为回填而无限循环。
pub fn Frontier::pop_batch(self : Frontier, limit : Int32) -> Array[String] {
  let out : Array[String] = []
  if limit <= 0 {
    return out
  }
  let picked : Map[String, Bool] = Map([], capacity=16)
  let stop : Int32 = self.queue.length()
  while self.head < stop && out.length() < limit {
    let url = self.queue[self.head]
    self.head = self.head + 1
    let host = host_of(url)
    match picked.get(host) {
      Some(_) => self.queue.push(url) // 同 host 本轮不取,排到队尾
      None => {
        let _ = picked.set(host, true)
        out.push(url)
      }
    }
  }
  out
}

///|
/// 待抓取数量。
pub fn Frontier::length(self : Frontier) -> Int32 {
  self.queue.length() - self.head
}

///|
/// 队列是否为空。
pub fn Frontier::is_empty(self : Frontier) -> Bool {
  self.length() == 0
}

///|
/// 抓取开始时登记:host 最近访问时刻 = `at_ms`。
pub fn Frontier::record_visit(
  self : Frontier,
  url : String,
  at_ms : Int32,
) -> Unit {
  let _ = self.hosts.set(host_of(url), at_ms)
}

///|
/// 该 URL 最早允许开始抓取的时刻(考虑 host 间隔与当前时刻)。
pub fn Frontier::next_allowed_ms(
  self : Frontier,
  url : String,
  now_ms : Int32,
) -> Int32 {
  let earliest = match self.hosts.get(host_of(url)) {
    Some(last) => last + self.min_host_delay_ms
    None => 0
  }
  if now_ms > earliest {
    now_ms
  } else {
    earliest
  }
}