///|
pub(all) struct Headers[T](T)

///|
pub(open) trait FromHeaders {
  /// Schema for the request header fields, not an OpenAPI Header Object.
  /// false means no header input is documented; the default {} is unconstrained.
  fn schema() -> @json.Json = _
  fn from_headers(@moonback.Headers) -> Self raise HandlerError
}

///|
impl FromHeaders with fn schema() {
  {}
}

///|
pub impl FromHeaders for Unit with fn from_headers(_) {
  ()
}

///|
pub impl FromHeaders for Unit with fn schema() {
  @json.Json::boolean(false)
}

///|
pub impl FromHeaders for @moonback.Headers with fn from_headers(headers) {
  headers
}

///|
pub impl FromHeaders for @moonback.Headers with fn schema() {
  { "type": "object", "additionalProperties": { "type": "string" } }
}

///|
/// Header names are lowercase; repeated entries keep the last value.
pub impl[T : FromString] FromHeaders for Map[String, T] with fn from_headers(
  headers,
) {
  let values : Map[String, T] = {}
  for (name, value) in headers.iter() {
    let parsed : T = FromString::from_string(value) catch {
      error =>
        raise HeaderMismatch("Invalid header '\{name}': \{(error : Error)}")
    }
    values[name] = parsed
  }
  values
}

///|
pub impl[T : FromString] FromHeaders for Map[String, T] with fn schema() {
  { "type": "object", "additionalProperties": T::schema() }
}

///|
/// Preserves the entries yielded by moonback without splitting header values.
pub impl[T : FromString] FromHeaders for Array[(String, T)] with fn from_headers(
  headers,
) {
  let values : Array[(String, T)] = []
  for (name, value) in headers.iter() {
    let parsed : T = FromString::from_string(value) catch {
      error =>
        raise HeaderMismatch("Invalid header '\{name}': \{(error : Error)}")
    }
    values.push((name, parsed))
  }
  values
}

///|
pub impl[T : FromString] FromHeaders for Array[(String, T)] with fn schema() {
  {
    "type": "object",
    "additionalProperties": {
      "anyOf": [T::schema(), { "type": "array", "items": T::schema() }],
    },
  }
}

///|
pub impl[T : FromJson] FromHeaders for ViaJson[T] with fn from_headers(headers) {
  let map = Map([])
  for (name, value) in headers.iter() {
    map[name] = @json.Json::string(value)
  }
  let value : T = @json.from_json(@json.Json::object(map)) catch {
    error => raise HeaderMismatch(error.to_string())
  }
  ViaJson(value)
}