///|
/// Negotiation failures which can be mapped to a safe HTTP error response.
pub(all) suberror HttpMetricsError {
  MalformedAccept(String)
  AcceptHeaderTooLarge
  NotAcceptable
} derive(Debug, Eq)

///|
let max_accept_header_bytes = 8192

///|
let max_accept_ranges = 32

///|
fn parse_quality(value : String) -> Int? {
  if value.is_empty() {
    return None
  }
  let mut seen_dot = false
  let mut whole = -1
  let mut fraction = 0
  let mut fraction_digits = 0
  for char in value {
    if char == '.' {
      if seen_dot {
        return None
      }
      seen_dot = true
    } else if char >= '0' && char <= '9' {
      let digit = match char {
        '0' => 0
        '1' => 1
        '2' => 2
        '3' => 3
        '4' => 4
        '5' => 5
        '6' => 6
        '7' => 7
        '8' => 8
        '9' => 9
        _ => abort("digit guard is exhaustive")
      }
      if !seen_dot {
        if whole != -1 {
          return None
        }
        whole = digit
      } else {
        if fraction_digits >= 3 {
          return None
        }
        fraction = fraction * 10 + digit
        fraction_digits += 1
      }
    } else {
      return None
    }
  }
  if whole < 0 || whole > 1 {
    return None
  }
  if whole == 1 && fraction != 0 {
    return None
  }
  let mut scaled_fraction = fraction
  for _ in fraction_digits..<3 {
    scaled_fraction *= 10
  }
  Some(whole * 1000 + scaled_fraction)
}

///|
fn parse_accept_range(
  value : String,
) -> (String, Int, String?) raise HttpMetricsError {
  let parts = [ for part in value.split(";") => part.trim().to_owned() ]
  if parts.is_empty() || parts[0].is_empty() || !parts[0].contains("/") {
    raise MalformedAccept("invalid media range")
  }
  let media_type = parts[0].to_lower()
  let mut quality = 1000
  let mut version : String? = None
  for index in 1..
        match parse_quality(pair[1]) {
          Some(parsed) => quality = parsed
          None => raise MalformedAccept("invalid q value")
        }
      "version" => version = Some(pair[1])
      _ => ()
    }
  }
  (media_type, quality, version)
}

///|
fn match_specificity(
  media_type : String,
  version : String?,
  format : @openmetrics.ExpositionFormat,
) -> Int? {
  match format {
    PrometheusText => {
      let version_matches = match version {
        Some(value) => value == "0.0.4"
        None => true
      }
      if !version_matches {
        None
      } else if media_type == "text/plain" {
        Some(2)
      } else if media_type == "text/*" {
        Some(1)
      } else if media_type == "*/*" {
        Some(0)
      } else {
        None
      }
    }
    OpenMetricsText => {
      let version_matches = match version {
        Some(value) => value == "1.0.0"
        None => true
      }
      if !version_matches {
        None
      } else if media_type == "application/openmetrics-text" {
        Some(2)
      } else if media_type == "application/*" {
        Some(1)
      } else if media_type == "*/*" {
        Some(0)
      } else {
        None
      }
    }
  }
}

///|
fn update_candidate(
  current_quality : Int,
  current_specificity : Int,
  quality : Int,
  specificity : Int,
) -> (Int, Int) {
  // RFC-style media-range specificity is resolved before quality. This keeps
  // `application/openmetrics-text;q=0, */*;q=1` from selecting OpenMetrics.
  if specificity > current_specificity ||
    (specificity == current_specificity && quality > current_quality) {
    (quality, specificity)
  } else {
    (current_quality, current_specificity)
  }
}

///|
/// Select a supported exposition format from an HTTP Accept header.
///
/// Missing or blank Accept defaults to Prometheus text 0.0.4 for legacy
/// scraper compatibility. For equal quality and equally specific explicit
/// formats, OpenMetrics wins; a wildcard-only tie remains Prometheus.
pub fn negotiate(
  accept : String?,
) -> @openmetrics.ExpositionFormat raise HttpMetricsError {
  match accept {
    None => PrometheusText
    Some(header) => {
      if header.length() > max_accept_header_bytes {
        raise AcceptHeaderTooLarge
      }
      if header.trim().to_owned().is_empty() {
        return PrometheusText
      }
      let mut prometheus_quality = -1
      let mut prometheus_specificity = -1
      let mut openmetrics_quality = -1
      let mut openmetrics_specificity = -1
      let mut ranges = 0
      for raw_range in header.split(",") {
        let range = raw_range.trim().to_owned()
        if range.is_empty() {
          continue
        }
        ranges += 1
        if ranges > max_accept_ranges {
          raise AcceptHeaderTooLarge
        }
        let (media_type, quality, version) = parse_accept_range(range)
        match match_specificity(media_type, version, PrometheusText) {
          Some(specificity) => {
            let (next_quality, next_specificity) = update_candidate(
              prometheus_quality, prometheus_specificity, quality, specificity,
            )
            prometheus_quality = next_quality
            prometheus_specificity = next_specificity
          }
          None => ()
        }
        match match_specificity(media_type, version, OpenMetricsText) {
          Some(specificity) => {
            let (next_quality, next_specificity) = update_candidate(
              openmetrics_quality, openmetrics_specificity, quality, specificity,
            )
            openmetrics_quality = next_quality
            openmetrics_specificity = next_specificity
          }
          None => ()
        }
      }
      if prometheus_quality <= 0 && openmetrics_quality <= 0 {
        raise NotAcceptable
      }
      if prometheus_quality > openmetrics_quality {
        PrometheusText
      } else if openmetrics_quality > prometheus_quality {
        OpenMetricsText
      } else if prometheus_specificity > openmetrics_specificity {
        PrometheusText
      } else if openmetrics_specificity > prometheus_specificity {
        OpenMetricsText
      } else if prometheus_specificity == 0 {
        PrometheusText
      } else {
        OpenMetricsText
      }
    }
  }
}