// jar 的两个动作:喂 Set-Cookie(`store`)、按请求 URL 取 `Cookie` 头值
// (`cookie_header`),以及两者共用的小规则(域匹配、路径匹配、默认路径)。
// 解析在 parse.mbt,数据形态在 types.mbt。

///|
/// 把一条 Set-Cookie 头喂进 jar:解析 → 按请求 URL 算归属 → 存库或删除。
///
/// 解析失败、URL 连 host 都没有、`Domain` 属性不是请求 host 的后缀,
/// 这几种情况**静默忽略**(RFC 6265 §5.3 的口径:坏 cookie 不报错,直接扔)。
/// `Max-Age <= 0` 或 `Expires` 已过时刻的同名 cookie 会被移除——这是
/// 服务端「让我删 cookie」的正规通道;没有同名旧 cookie 时同样无事发生。
pub fn CookieJar::store(
  self : CookieJar,
  url : String,
  set_cookie : String,
) -> Unit {
  let parsed = match parse_set_cookie(set_cookie) {
    Some(parsed) => parsed
    None => return
  }
  let host = match @url.url_host(url) {
    Some(host) => host
    None => return
  }
  // 归属域:带 Domain 属性时它必须是请求 host 或其后缀,否则整条忽略
  // (RFC 6265 §5.3 step 6);不带就是 host-only,只有原主机收得到。
  let (domain, host_only) = match parsed.domain {
    Some(attr) =>
      if attr == host || host.has_suffix("." + attr) {
        (attr, false)
      } else {
        return
      }
    None => (host, true)
  }
  let request_path = @url.url_path(url).unwrap_or("/")
  let path = match parsed.path {
    Some(path) => path
    None => default_path(request_path)
  }
  // 过期时刻:Max-Age 优先于 Expires(RFC 6265 §5.3 step 3 的顺序)
  let now = now_ms()
  let expired : Bool = match parsed.max_age {
    Some(max_age) => max_age <= 0L
    None =>
      match parsed.expires_at {
        Some(at) => at <= now
        None => false
      }
  }
  let key : CookieKey = { domain, path, name: parsed.name, }
  if expired {
    // 「已过期」的写入按删除处理(RFC 6265 §4.1.1:过期时刻在过去的
    // Set-Cookie 就是被要求删掉旧 cookie)
    self.cookies.remove(key)
    return
  }
  let expires_at : UInt64? = match parsed.max_age {
    Some(max_age) => Some(add_max_age(now, max_age))
    None => parsed.expires_at
  }
  self.serial = self.serial + 1
  // 同名覆盖:值与属性换新,序号沿用旧的(RFC 把它当创建时间,
  // 覆盖不算重建,发送顺序不应因此变化)
  let serial = match self.cookies.get(key) {
    Some(old) => old.serial
    None => self.serial
  }
  self.cookies[key] = {
    name: parsed.name,
    value: parsed.value,
    domain,
    path,
    host_only,
    secure: parsed.secure,
    expires_at,
    serial,
  }
}

///|
/// 按请求 URL 算出该带的 `Cookie` 头值:过期清理 → 域 / 路径 / 安全匹配 →
/// 排序(归属路径长的在前,同长度按入库先后,RFC 6265 §5.4)→
/// 拼成 `n1=v1; n2=v2`。
///
/// 一个都不匹配时返回 `None`——调用方据此跳过注入,不会往请求上挂空的
/// `Cookie` 头。用户在请求里显式设置的 `Cookie` 头永远优先(注入走
/// `set_if_absent`,见 `Client::send_following_redirects`)。
pub fn CookieJar::cookie_header(self : CookieJar, url : String) -> String? {
  let host = match @url.url_host(url) {
    Some(host) => host
    None => return None
  }
  let path = @url.url_path(url).unwrap_or("/")
  let is_https = @url.url_scheme(url) == Some("https")
  let now = now_ms()
  let matched : Array[Cookie] = []
  // 过期不能边遍历边删(迭代中改 Map 的行为不可靠),先记下来最后一起删
  let expired_keys : Array[CookieKey] = []
  for key, cookie in self.cookies {
    match cookie.expires_at {
      Some(at) if at <= now => {
        expired_keys.push(key)
        continue
      }
      _ => ()
    }
    if domain_match(cookie, host) &&
      path_match(cookie.path, path) &&
      (!cookie.secure || is_https) {
      matched.push(cookie)
    }
  }
  for key in expired_keys {
    self.cookies.remove(key)
  }
  if matched.is_empty() {
    return None
  }
  sort_for_send(matched)
  let buf = StringBuilder()
  for index, cookie in matched {
    if index > 0 {
      buf.write_string("; ")
    }
    buf.write_string(cookie.name)
    buf.write_string("=")
    buf.write_string(cookie.value)
  }
  Some(buf.to_string())
}

///|
/// 当前时刻(Unix 毫秒)。过期判断的唯一时钟。
fn now_ms() -> UInt64 {
  @env.now()
}

///|
/// 域匹配(RFC 6265 §5.1.3 的精简版):host-only 要求精确相等;
/// 带 Domain 属性的,请求 host 等于它或以 `.它` 结尾即匹配。
fn domain_match(cookie : Cookie, host : String) -> Bool {
  if cookie.host_only {
    return cookie.domain == host
  }
  cookie.domain == host || host.has_suffix("." + cookie.domain)
}

///|
/// 路径匹配(RFC 6265 §5.1.4):相等,或 cookie-path 是请求路径的前缀
/// 且边界正好落在段上(请求路径以它续接 `/`,或 cookie-path 本身以 `/` 收尾)。
fn path_match(cookie_path : String, request_path : String) -> Bool {
  if request_path == cookie_path {
    return true
  }
  if request_path.has_prefix(cookie_path) {
    if cookie_path.has_suffix("/") {
      return true
    }
    if request_path[cookie_path.length():].has_prefix("/") {
      return true
    }
  }
  false
}

///|
/// 没带 Path 属性时的默认归属路径(RFC 6265 §5.1.4 的 default-path):
/// 请求路径去掉最后一个 `/` 及其之后的部分;只有开头的斜杠时是 `/`。
fn default_path(request_path : String) -> String {
  if !request_path.has_prefix("/") {
    return "/"
  }
  match request_path.rev_find("/") {
    // 只有开头的那个斜杠(如 `/account`):归属就是根路径
    Some(0) => "/"
    Some(index) => request_path[:index].to_owned()
    None => "/"
  }
}

///|
/// 发送顺序(RFC 6265 §5.4):归属路径长的在前;同长度按入库先后。
/// 匹配集通常只有几枚,插入排序足够,也免得引入比较器 API 的负担。
fn sort_for_send(cookies : Array[Cookie]) -> Unit {
  for index in 1.. 0 && after_in_send(cookies[slot - 1], cookie) {
      cookies[slot] = cookies[slot - 1]
      slot = slot - 1
    }
    cookies[slot] = cookie
  }
}

///|
/// `a` 是否该排在 `b` 后面(路径更短,或同路径但入库更晚)。
fn after_in_send(a : Cookie, b : Cookie) -> Bool {
  a.path.length() < b.path.length() ||
  (a.path.length() == b.path.length() && a.serial > b.serial)
}