///|
/// Advisory finding; audit never changes parser validity.
pub(all) enum AuditFindingKind {
  TooManyRanges
  OverlappingRanges
  ManySmallRanges
  DescendingOrder
  DuplicateRanges
  ZeroLengthSuffix
  LargeNumericValue
  LargeHeader
  UnknownRangeUnit
  AdjacentRanges
  InefficientRangeSet
} derive(Eq, Debug)

///|
pub struct AuditFinding {
  kind : AuditFindingKind
  context : String
} derive(Eq, Debug)

///|
pub struct AuditReport {
  findings : Array[AuditFinding]
} derive(Eq, Debug)

///|
pub fn AuditFinding::kind(self : AuditFinding) -> AuditFindingKind {
  self.kind
}

///|
pub fn AuditFinding::context(self : AuditFinding) -> String {
  self.context
}

///|
pub fn AuditReport::findings(self : AuditReport) -> Array[AuditFinding] {
  self.findings.copy()
}

///|
pub fn AuditReport::is_clean(self : AuditReport) -> Bool {
  self.findings.is_empty()
}

///|
pub fn AuditFindingKind::name(self : AuditFindingKind) -> String {
  match self {
    TooManyRanges => "TooManyRanges"
    OverlappingRanges => "OverlappingRanges"
    ManySmallRanges => "ManySmallRanges"
    DescendingOrder => "DescendingOrder"
    DuplicateRanges => "DuplicateRanges"
    ZeroLengthSuffix => "ZeroLengthSuffix"
    LargeNumericValue => "LargeNumericValue"
    LargeHeader => "LargeHeader"
    UnknownRangeUnit => "UnknownRangeUnit"
    AdjacentRanges => "AdjacentRanges"
    InefficientRangeSet => "InefficientRangeSet"
  }
}

///|
/// Audit raw request shape and, when available, resolved byte ranges.
pub fn audit_range_request(
  request : RangeRequest,
  raw_input? : String,
  representation_length? : Int64,
) -> AuditReport {
  let findings : Array[AuditFinding] = []
  match raw_input {
    Some(raw) if utf8_length(raw) > 1_024 =>
      add_finding(findings, LargeHeader, "Range field exceeds 1024 UTF-8 bytes")
    _ => ()
  }
  if !request.unit().is_bytes() {
    add_finding(findings, UnknownRangeUnit, "range-unit semantics are unknown")
    return { findings, }
  }
  let specs = request.specs()
  if specs.length() > 16 {
    add_finding(findings, TooManyRanges, "request contains more than 16 ranges")
  }
  let mut small = 0
  for spec in specs {
    match spec {
      Suffix(0L) =>
        add_finding(findings, ZeroLengthSuffix, "suffix range has zero length")
      Closed(first, last) => {
        if first > 1_000_000_000_000L || last > 1_000_000_000_000L {
          add_finding(
            findings,
            LargeNumericValue,
            "range uses a very large byte position",
          )
        }
        if last - first + 1L <= 16L {
          small = small + 1
        }
      }
      OpenEnded(first) if first > 1_000_000_000_000L =>
        add_finding(
          findings,
          LargeNumericValue,
          "open range uses a very large byte position",
        )
      Suffix(length) if length > 1_000_000_000_000L =>
        add_finding(
          findings,
          LargeNumericValue,
          "suffix uses a very large length",
        )
      _ => ()
    }
  }
  if small >= 4 {
    add_finding(
      findings,
      ManySmallRanges,
      "request contains at least four ranges of 16 bytes or fewer",
    )
  }
  match representation_length {
    Some(length) =>
      match resolve_byte_ranges(request, length) {
        Ok(resolved) => audit_concrete_ranges(resolved.ranges(), findings)
        Err(_) => ()
      }
    None => ()
  }
  if findings.length() >= 3 {
    add_finding(
      findings,
      InefficientRangeSet,
      "multiple audit signals indicate an inefficient range set",
    )
  }
  { findings, }
}

///|
fn audit_concrete_ranges(
  ranges : Array[ConcreteRange],
  findings : Array[AuditFinding],
) -> Unit {
  for i = 1; i < ranges.length(); i = i + 1 {
    let previous = ranges[i - 1]
    let current = ranges[i]
    if current.first() < previous.first() {
      add_finding_once(
        findings,
        DescendingOrder,
        "resolved ranges are not ascending",
      )
    }
    if current == previous {
      add_finding_once(
        findings,
        DuplicateRanges,
        "duplicate resolved ranges are present",
      )
    }
    if current.first() <= previous.last() {
      add_finding_once(findings, OverlappingRanges, "resolved ranges overlap")
    }
    if previous.last() != @int64.MAX_VALUE &&
      current.first() == previous.last() + 1L {
      add_finding_once(
        findings,
        AdjacentRanges,
        "resolved ranges are adjacent and may be coalesced",
      )
    }
  }
}

///|
fn add_finding(
  findings : Array[AuditFinding],
  kind : AuditFindingKind,
  context : String,
) -> Unit {
  findings.push({ kind, context })
}

///|
fn add_finding_once(
  findings : Array[AuditFinding],
  kind : AuditFindingKind,
  context : String,
) -> Unit {
  for finding in findings {
    if finding.kind() == kind {
      return
    }
  }
  add_finding(findings, kind, context)
}