///|
pub(all) struct Collection {
  id : String
  title : String?
  description : String
  license : String
} derive(Debug, Eq)

///|
pub(all) struct CollectionPage {
  collections : Array[Collection]
  next : String?
} derive(Debug, Eq)

///|
pub fn parse_collections(text : String) -> CollectionPage raise StacError {
  let v = @json.parse(text) catch { _ => raise Invalid("invalid JSON") }
  let values = match field(v, "collections") {
    Some(Array(xs)) => xs
    _ => raise Invalid("invalid collections")
  }
  let collections : Array[Collection] = []
  for value in values {
    if string_field(value, "type") != "Collection" {
      raise Invalid("expected Collection")
    }
    let version = string_field(value, "stac_version")
    if version != "1.0.0" && version != "1.1.0" {
      raise Invalid("unsupported stac_version")
    }
    let id = string_field(value, "id")
    let title = optional_string(value, "title")
    let description = string_field(value, "description")
    let license = string_field(value, "license")
    ignore(validate_links(value, "collection"))
    validate_extent(value)
    collections.push({ id, title, description, license })
  }
  ignore(validate_links(v, "collection page"))
  let links = match field(v, "links") {
    Some(Array(xs)) => xs
    _ => raise Invalid("invalid links")
  }
  let mut next : String? = None
  for link in links {
    if optional_string(link, "rel") == Some("next") {
      if next != None {
        raise Invalid("multiple next links")
      }
      next = Some(string_field(link, "href"))
    }
  }
  { collections, next }
}

///|
fn validate_extent(value : Json) -> Unit raise StacError {
  let extent = match field(value, "extent") {
    Some(Object(m)) => Json::object(m)
    _ => raise Invalid("invalid extent")
  }
  let spatial = match field(extent, "spatial") {
    Some(Object(m)) => Json::object(m)
    _ => raise Invalid("invalid spatial extent")
  }
  let temporal = match field(extent, "temporal") {
    Some(Object(m)) => Json::object(m)
    _ => raise Invalid("invalid temporal extent")
  }
  match field(spatial, "bbox") {
    Some(Array(xs)) if xs.length() > 0 => ()
    _ => raise Invalid("invalid spatial bbox")
  }
  match field(temporal, "interval") {
    Some(Array(xs)) if xs.length() > 0 => ()
    _ => raise Invalid("invalid temporal interval")
  }
}

///|
pub fn collections_summary(input : String) -> String {
  collections_summary_checked(input) catch {
    Invalid(message) => error_json(message)
  }
}

///|
fn collections_summary_checked(input : String) -> String raise StacError {
  let page = parse_collections(input)
  let collections : Array[Json] = []
  for collection in page.collections {
    let entry : Json = {
      "id": collection.id,
      "title": match collection.title {
        Some(s) => Json::string(s)
        None => Json::null()
      },
      "description": collection.description,
      "license": collection.license,
    }
    collections.push(entry)
  }
  let output : Json = {
    "status": "ok",
    "collections": Json::array(collections),
    "next": match page.next {
      Some(s) => Json::string(s)
      None => Json::null()
    },
  }
  output.stringify()
}