///|
/// Compact metrics for a SARIF log.
pub struct SarifSummary {
  run_count : Int
  result_count : Int
  error_count : Int
  warning_count : Int
  note_count : Int
  none_count : Int
  unknown_level_count : Int
  unique_rule_count : Int
} derive(Debug, Eq, ToJson)

///|
/// Build an aggregate summary across every run.
pub fn summarize(log : SarifLog) -> SarifSummary {
  let mut result_count = 0
  let mut error_count = 0
  let mut warning_count = 0
  let mut note_count = 0
  let mut none_count = 0
  let mut unknown_level_count = 0
  let rules : Map[String, Unit] = Map([])
  for run in log.runs {
    match run.results {
      Some(results) =>
        for result in results {
          result_count += 1
          match result.level {
            Some("error") => error_count += 1
            Some("warning") => warning_count += 1
            Some("note") => note_count += 1
            Some("none") | None => none_count += 1
            Some(_) => unknown_level_count += 1
          }
          match result.ruleId {
            Some(rule_id) => rules[rule_id] = ()
            None => ()
          }
        }
      None => ()
    }
  }
  {
    run_count: log.runs.length(),
    result_count,
    error_count,
    warning_count,
    note_count,
    none_count,
    unknown_level_count,
    unique_rule_count: rules.length(),
  }
}

///|
fn result_has_path(result : SarifResult, needle : StringView) -> Bool {
  match result.locations {
    Some(locations) =>
      locations.any(fn(location) {
        match location.physicalLocation {
          Some(physical) =>
            match physical.artifactLocation {
              Some(artifact) =>
                match artifact.uri {
                  Some(uri) => normalize_path(uri).contains(needle)
                  None => false
                }
              None => false
            }
          None => false
        }
      })
    None => false
  }
}

///|
/// Filter results while preserving run and tool metadata.
pub fn filter_results(
  log : SarifLog,
  level? : String,
  rule_id? : String,
  path_contains? : String,
) -> SarifLog {
  let runs = log.runs.map(fn(run) {
    let results = match run.results {
      Some(items) =>
        Some(
          items.filter(fn(result) {
            let level_matches = match level {
              Some(expected) => result.level == Some(expected)
              None => true
            }
            let rule_matches = match rule_id {
              Some(expected) => result.ruleId == Some(expected)
              None => true
            }
            let path_matches = match path_contains {
              Some(expected) =>
                result_has_path(result, normalize_path(expected))
              None => true
            }
            level_matches && rule_matches && path_matches
          }),
        )
      None => None
    }
    { tool: run.tool, results, automationDetails: run.automationDetails }
  })
  { version: log.version, schema: log.schema, runs }
}

///|
/// Merge logs by concatenating runs. All inputs must use the same version.
pub fn merge(logs : ArrayView[SarifLog]) -> SarifLog? {
  guard logs is [first, .. rest] else { return None }
  let runs = first.runs.copy()
  for log in rest {
    if log.version != first.version {
      return None
    }
    for run in log.runs {
      runs.push(run)
    }
  }
  Some({ version: first.version, schema: first.schema, runs })
}