///|
let _headers_next_owner_id : Ref[Int] = { val: 0 }

///|
const HEADERS_SHARED_OWNER_ID : Int = -1

///|
/// A mutable HTTP header collection with special handling for `Set-Cookie`.
priv struct HeaderStore {
  mut owner_id : Int
  headers : @http.Headers
  mut set_cookie : Array[StringView]
}

///|
fn HeaderStore::copy(self : HeaderStore, owner_id? : Int) -> HeaderStore {
  {
    owner_id: owner_id.unwrap_or(self.owner_id),
    headers: self.headers.copy(),
    set_cookie: self.set_cookie.copy(),
  }
}

///|
fn HeaderStore::iter(self : HeaderStore) -> Iter[(String, String)] {
  Iter::concat(
    self.headers
    .iter()
    .map(pair => {
      let (name, value) = pair
      (name.0.to_lower(), value)
    }),
    self.set_cookie.iter().map(cookie => ("set-cookie", cookie.to_owned())),
  )
}

///|
fn HeaderStore::get(self : HeaderStore, name : String) -> StringView? {
  let name = name.to_lower()
  if name == "set-cookie" {
    return match self.set_cookie {
      [] => None
      [cookie] => Some(cookie)
      cookies => Some(cookies.join(", "))
    }
  }
  match self.headers.get(name.to_lower()) {
    None => None
    Some(value) => Some(value)
  }
}

///|
fn HeaderStore::get_all(
  self : HeaderStore,
  name : String,
) -> ReadOnlyArray[StringView] {
  let name = name.to_lower()
  if name == "set-cookie" {
    return ReadOnlyArray::from_array(self.set_cookie)
  }
  match self.headers.get(name) {
    None => []
    Some(value) => ReadOnlyArray::from_iter(value.split(",").map(s => s.trim()))
  }
}

///|
fn HeaderStore::set(self : HeaderStore, name : String, value : String) -> Unit {
  let name = name.to_lower()
  if name == "set-cookie" {
    self.set_cookie = [value]
    return
  }
  self.headers[name.to_lower()] = value
}

///|
fn HeaderStore::add(self : HeaderStore, name : String, value : String) -> Unit {
  let name = name.to_lower()
  if name == "set-cookie" {
    self.set_cookie.push(value)
    return
  }
  match self.headers.get(name) {
    None => self.headers[name] = value
    Some(old) => self.headers[name] = old + ", " + value
  }
}

///|
fn HeaderStore::add_set_cookie(
  self : HeaderStore,
  cookie : @http.Cookie,
) -> Unit {
  self.set_cookie.push(cookie_to_header_value(cookie))
}

///|
fn HeaderStore::remove(self : HeaderStore, name : String) -> Unit {
  let name = name.to_lower()
  if name == "set-cookie" {
    self.set_cookie = []
    return
  }
  self.headers.remove(name)
}

///|
#valtype
struct Headers {
  store : HeaderStore
}

///|
let default_headers : Headers = {
  store: { owner_id: HEADERS_SHARED_OWNER_ID, headers: {}, set_cookie: [] },
}

///|
pub fn Headers::Headers() -> Headers {
  default_headers
}

///|
pub impl Default for Headers with fn default() -> Headers {
  default_headers
}

///|
pub extend Headers with Default::{default}

///|
/// Converts any `ToHeaders` value into `Headers`.
pub fn Headers::from(from : &ToHeaders) -> Headers {
  from.to_headers()
}

///|
fn Headers::from_request(headers : @http.Headers) -> Headers {
  let store = { owner_id: HEADERS_SHARED_OWNER_ID, headers, set_cookie: [] }
  { store, }
}

///|
pub fn Headers::to_mut(self : Headers) -> MutHeaders {
  let owner_id = _headers_next_owner_id.val
  _headers_next_owner_id.val += 1
  { owner_id, store: self.store }
}

///|
/// Returns a header value by name.
/// Multiple values are joined with `, `.
pub fn Headers::get(self : Headers, name : String) -> StringView? {
  self.store.get(name)
}

///|
/// Returns all values for a header name.
/// For regular headers, comma-separated values are split into a list.
pub fn Headers::get_all(
  self : Headers,
  name : String,
) -> ReadOnlyArray[StringView] {
  self.store.get_all(name)
}

///|
/// Returns all values for a header name.
/// For regular headers, comma-separated values are split into a list.
pub fn Headers::get_list(
  self : Headers,
  name : String,
) -> ReadOnlyArray[StringView] {
  self.store.get_all(name)
}

///|
/// Iterates over all headers as lowercase name-value pairs.
pub fn Headers::iter(self : Headers) -> Iter[(String, String)] {
  self.store.iter()
}

///|
pub trait ToHeaders {
  fn to_headers(Self) -> Headers
}

///|
struct MutHeaders {
  owner_id : Int
  mut store : HeaderStore
}

///|
pub fn MutHeaders::MutHeaders() -> MutHeaders {
  let owner_id = _headers_next_owner_id.val
  _headers_next_owner_id.val += 1
  { owner_id, store: { owner_id, headers: {}, set_cookie: [] } }
}

///|
pub fn MutHeaders::from(headers : &ToHeaders) -> MutHeaders {
  headers.to_headers().to_mut()
}

///|
fn MutHeaders::ensure_owned(self : MutHeaders) -> Unit {
  if self.owner_id != self.store.owner_id {
    self.store = self.store.copy(owner_id=self.owner_id)
  }
}

///|
/// Returns a header value by name.
/// Multiple values are joined with `, `.
pub fn MutHeaders::get(self : MutHeaders, name : String) -> StringView? {
  self.store.get(name)
}

///|
/// Returns all values for a header name.
/// For regular headers, comma-separated values are split into a list.
pub fn MutHeaders::get_all(
  self : MutHeaders,
  name : String,
) -> ReadOnlyArray[StringView] {
  self.store.get_all(name)
}

///|
/// Iterates over all headers as lowercase name-value pairs.
pub fn MutHeaders::iter(self : MutHeaders) -> Iter[(String, String)] {
  self.store.iter()
}

///|
/// Sets a header value, replacing any existing values.
pub fn MutHeaders::set(
  self : MutHeaders,
  name : String,
  value : String,
) -> Unit {
  self.ensure_owned()
  self.store.set(name, value)
}

///|
/// Adds a header value to the existing values for `name`.
pub fn MutHeaders::add(
  self : MutHeaders,
  name : String,
  value : String,
) -> Unit {
  self.ensure_owned()
  self.store.add(name, value)
}

///|
/// Adds a `Set-Cookie` header value.
pub fn MutHeaders::add_set_cookie(
  self : MutHeaders,
  cookie : @http.Cookie,
) -> Unit {
  self.ensure_owned()
  self.store.add_set_cookie(cookie)
}

///|
/// Removes all values for the given header name.
pub fn MutHeaders::remove(self : MutHeaders, name : String) -> Unit {
  self.ensure_owned()
  self.store.remove(name)
}

///|
pub fn MutHeaders::to_immut(self : MutHeaders) -> Headers {
  self.store.owner_id = HEADERS_SHARED_OWNER_ID
  { store: self.store }
}

///|
pub impl ToHeaders for MutHeaders with fn to_headers(self) -> Headers {
  self.to_immut()
}

///|
pub extend MutHeaders with ToHeaders::{to_headers}

///|
pub impl ToHeaders for Headers with fn to_headers(self) -> Headers {
  self
}

///|
pub extend Headers with ToHeaders::{to_headers}

///|
pub impl ToHeaders for Map[String, String] with fn to_headers(self) -> Headers {
  let headers : @http.Headers = Map([])
  let set_cookie = []
  for k, v in self {
    let k = k.to_lower()
    if k == "set-cookie" {
      set_cookie.push(v[:])
      continue
    }
    headers[k] = v
  }
  let store = { owner_id: HEADERS_SHARED_OWNER_ID, headers, set_cookie }
  { store, }
}

///|
pub impl ToHeaders for Array[(String, String)] with fn to_headers(self) -> Headers {
  let headers : @http.Headers = Map([])
  let set_cookie = []
  for pair in self {
    let (k, v) = pair
    let k = k.to_lower()
    if k == "set-cookie" {
      set_cookie.push(v[:])
      continue
    }
    match headers.get(k) {
      None => headers[k] = v
      Some(old) => headers[k] = old + ", " + v
    }
  }
  let store = { owner_id: HEADERS_SHARED_OWNER_ID, headers, set_cookie }
  { store, }
}