///|
struct Querystring {
  entries : Map[StringView, StringView]
}

///|
pub fn Querystring::Querystring(str : StringView) -> Querystring {
  // To avoid borrowing issues, we create a new owned string and then split it.
  let raw = str.to_owned()
  let entries : Map[StringView, StringView] = {}
  for pair in raw.split("&") {
    match pair.split_once("=") {
      None => {
        let key = pair.trim()
        if !key.is_empty() {
          entries[key] = ""
        }
      }
      Some((key, value)) => {
        let key = key.trim()
        if !key.is_empty() {
          entries[key] = value.trim()
        }
      }
    }
  }
  Querystring::{ entries, }
}

///|
pub fn Querystring::string(
  self : Querystring,
  key : StringView,
) -> String raise {
  match self.entries.get(key) {
    None => fail("Key not found in query string")
    Some(value) => value.to_owned()
  }
}

///|
pub fn Querystring::string_opt(self : Querystring, key : StringView) -> String? {
  match self.entries.get(key) {
    None => None
    Some(value) => Some(value.to_owned())
  }
}

///|
pub fn Querystring::stringview(
  self : Querystring,
  key : StringView,
) -> StringView raise {
  match self.entries.get(key) {
    None => fail("Key not found in query string")
    Some(value) => value
  }
}

///|
pub fn Querystring::stringview_opt(
  self : Querystring,
  key : StringView,
) -> StringView? {
  self.entries.get(key)
}

///|
pub let default_truthy_values : @immut/sorted_set.SortedSet[StringView] = @immut/sorted_set.SortedSet::from_array([
    "1", "true", "yes", "on",
  ],
)

///|
pub fn Querystring::flag(
  self : Querystring,
  key : StringView,
  truthy_values? : @immut/sorted_set.SortedSet[StringView] = default_truthy_values,
) -> Bool {
  match self.entries.get(key) {
    None => false
    Some(value) => truthy_values.contains(value)
  }
}

///|
pub fn Querystring::int(self : Querystring, key : StringView) -> Int raise {
  match self.entries.get(key) {
    None => fail("Key not found in query string")
    Some(value) => @string.parse_int(value)
  }
}

///|
pub fn Querystring::int_opt(self : Querystring, key : StringView) -> Int? raise {
  match self.entries.get(key) {
    None => None
    Some(value) => Some(@string.parse_int(value))
  }
}