///|
/// Set-Cookie header
pub(all) struct SetCookie {
  name : String
  value : String
  expires : HttpDate?
  max_age : Int?
  domain : String?
  path : String?
  secure : Bool
  http_only : Bool
  same_site : SameSite?
} derive(Eq)

///|
pub fn SetCookie::new(
  name : String,
  value : String,
) -> Result[SetCookie, CookieError] {
  if name.length() == 0 || !ck_is_valid_cookie_name(name) {
    return Err(InvalidName)
  }
  if !ck_is_valid_cookie_value(value) {
    return Err(InvalidValue)
  }
  Ok({
    name,
    value,
    expires: None,
    max_age: None,
    domain: None,
    path: None,
    secure: false,
    http_only: false,
    same_site: None,
  })
}

///|
pub fn SetCookie::parse(input : String) -> Result[SetCookie, CookieError] {
  let s = ck_trim_string(input)
  if s.length() == 0 {
    return Err(Empty)
  }
  let parts = ck_split_params(s)
  if parts.length() == 0 {
    return Err(InvalidFormat)
  }
  let first = ck_trim_string(parts[0])
  let parse_result = ck_parse_cookie_pair(first)
  match parse_result {
    Err(e) => return Err(e)
    Ok((name, value)) => {
      let mut set_cookie = {
        name,
        value,
        expires: None,
        max_age: None,
        domain: None,
        path: None,
        secure: false,
        http_only: false,
        same_site: None,
      }
      let mut part_idx = 1
      while part_idx < parts.length() {
        let part = ck_trim_string(parts[part_idx])
        if part.length() > 0 {
          let eq_pos = ck_find_char(part, 61) // =
          match eq_pos {
            Some(pos) => {
              let attr_name = ck_to_lower_case(
                ck_trim_string(ck_string_slice(part, 0, pos)),
              )
              let attr_value = ck_trim_string(
                ck_string_slice_from(part, pos + 1),
              )
              match attr_name {
                "expires" => {
                  let date_result = HttpDate::parse(attr_value)
                  match date_result {
                    Ok(d) =>
                      set_cookie = {
                        name: set_cookie.name,
                        value: set_cookie.value,
                        expires: Some(d),
                        max_age: set_cookie.max_age,
                        domain: set_cookie.domain,
                        path: set_cookie.path,
                        secure: set_cookie.secure,
                        http_only: set_cookie.http_only,
                        same_site: set_cookie.same_site,
                      }
                    Err(_) => return Err(InvalidExpires)
                  }
                }
                "max-age" => {
                  let age_result = ck_parse_i64(attr_value)
                  match age_result {
                    Some(a) =>
                      set_cookie = {
                        name: set_cookie.name,
                        value: set_cookie.value,
                        expires: set_cookie.expires,
                        max_age: Some(a),
                        domain: set_cookie.domain,
                        path: set_cookie.path,
                        secure: set_cookie.secure,
                        http_only: set_cookie.http_only,
                        same_site: set_cookie.same_site,
                      }
                    None => return Err(InvalidMaxAge)
                  }
                }
                "domain" =>
                  set_cookie = {
                    name: set_cookie.name,
                    value: set_cookie.value,
                    expires: set_cookie.expires,
                    max_age: set_cookie.max_age,
                    domain: Some(attr_value),
                    path: set_cookie.path,
                    secure: set_cookie.secure,
                    http_only: set_cookie.http_only,
                    same_site: set_cookie.same_site,
                  }
                "path" =>
                  set_cookie = {
                    name: set_cookie.name,
                    value: set_cookie.value,
                    expires: set_cookie.expires,
                    max_age: set_cookie.max_age,
                    domain: set_cookie.domain,
                    path: Some(attr_value),
                    secure: set_cookie.secure,
                    http_only: set_cookie.http_only,
                    same_site: set_cookie.same_site,
                  }
                "samesite" => {
                  let ss_result = ck_parse_same_site(attr_value)
                  match ss_result {
                    Ok(ss) =>
                      set_cookie = {
                        name: set_cookie.name,
                        value: set_cookie.value,
                        expires: set_cookie.expires,
                        max_age: set_cookie.max_age,
                        domain: set_cookie.domain,
                        path: set_cookie.path,
                        secure: set_cookie.secure,
                        http_only: set_cookie.http_only,
                        same_site: Some(ss),
                      }
                    Err(_) => return Err(InvalidSameSite)
                  }
                }
                _ => {
                  let _ = ()
                }
              }
            }
            None => {
              let part_lower = ck_to_lower_case(part)
              match part_lower {
                "secure" =>
                  set_cookie = {
                    name: set_cookie.name,
                    value: set_cookie.value,
                    expires: set_cookie.expires,
                    max_age: set_cookie.max_age,
                    domain: set_cookie.domain,
                    path: set_cookie.path,
                    secure: true,
                    http_only: set_cookie.http_only,
                    same_site: set_cookie.same_site,
                  }
                "httponly" =>
                  set_cookie = {
                    name: set_cookie.name,
                    value: set_cookie.value,
                    expires: set_cookie.expires,
                    max_age: set_cookie.max_age,
                    domain: set_cookie.domain,
                    path: set_cookie.path,
                    secure: set_cookie.secure,
                    http_only: true,
                    same_site: set_cookie.same_site,
                  }
                _ => {
                  let _ = ()
                }
              }
            }
          }
        }
        part_idx = part_idx + 1
      }
      Ok(set_cookie)
    }
  }
}

///|
pub fn SetCookie::name(self : SetCookie) -> String {
  self.name
}

///|
pub fn SetCookie::value(self : SetCookie) -> String {
  self.value
}

///|
pub fn SetCookie::expires(self : SetCookie) -> HttpDate? {
  self.expires
}

///|
pub fn SetCookie::max_age(self : SetCookie) -> Int? {
  self.max_age
}

///|
pub fn SetCookie::domain(self : SetCookie) -> String? {
  self.domain
}

///|
pub fn SetCookie::path(self : SetCookie) -> String? {
  self.path
}

///|
pub fn SetCookie::secure(self : SetCookie) -> Bool {
  self.secure
}

///|
pub fn SetCookie::http_only(self : SetCookie) -> Bool {
  self.http_only
}

///|
pub fn SetCookie::same_site(self : SetCookie) -> SameSite? {
  self.same_site
}

///|
pub fn SetCookie::with_expires(
  self : SetCookie,
  expires : HttpDate,
) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: Some(expires),
    max_age: self.max_age,
    domain: self.domain,
    path: self.path,
    secure: self.secure,
    http_only: self.http_only,
    same_site: self.same_site,
  }
}

///|
pub fn SetCookie::with_max_age(self : SetCookie, max_age : Int) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: self.expires,
    max_age: Some(max_age),
    domain: self.domain,
    path: self.path,
    secure: self.secure,
    http_only: self.http_only,
    same_site: self.same_site,
  }
}

///|
pub fn SetCookie::with_domain(self : SetCookie, domain : String) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: self.expires,
    max_age: self.max_age,
    domain: Some(domain),
    path: self.path,
    secure: self.secure,
    http_only: self.http_only,
    same_site: self.same_site,
  }
}

///|
pub fn SetCookie::with_path(self : SetCookie, path : String) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: self.expires,
    max_age: self.max_age,
    domain: self.domain,
    path: Some(path),
    secure: self.secure,
    http_only: self.http_only,
    same_site: self.same_site,
  }
}

///|
pub fn SetCookie::with_secure(self : SetCookie, secure : Bool) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: self.expires,
    max_age: self.max_age,
    domain: self.domain,
    path: self.path,
    secure,
    http_only: self.http_only,
    same_site: self.same_site,
  }
}

///|
pub fn SetCookie::with_http_only(
  self : SetCookie,
  http_only : Bool,
) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: self.expires,
    max_age: self.max_age,
    domain: self.domain,
    path: self.path,
    secure: self.secure,
    http_only,
    same_site: self.same_site,
  }
}

///|
pub fn SetCookie::with_same_site(
  self : SetCookie,
  same_site : SameSite,
) -> SetCookie {
  {
    name: self.name,
    value: self.value,
    expires: self.expires,
    max_age: self.max_age,
    domain: self.domain,
    path: self.path,
    secure: self.secure,
    http_only: self.http_only,
    same_site: Some(same_site),
  }
}

///|
pub fn SetCookie::to_string(self : SetCookie) -> String {
  let mut result = self.name + "=" + self.value
  match self.expires {
    Some(e) => result = result + "; Expires=" + e.to_string()
    None => {
      let _ = ()
    }
  }
  match self.max_age {
    Some(a) => result = result + "; Max-Age=" + a.to_string()
    None => {
      let _ = ()
    }
  }
  match self.domain {
    Some(d) => result = result + "; Domain=" + d
    None => {
      let _ = ()
    }
  }
  match self.path {
    Some(p) => result = result + "; Path=" + p
    None => {
      let _ = ()
    }
  }
  if self.secure {
    result = result + "; Secure"
  }
  if self.http_only {
    result = result + "; HttpOnly"
  }
  match self.same_site {
    Some(ss) => result = result + "; SameSite=" + ss.to_string()
    None => {
      let _ = ()
    }
  }
  result
}