///|
/// Validation rules to apply to a SARIF log.
pub enum ValidationProfile {
  Generic
  Github
  Strict
} derive(Debug, Eq, ToJson)

///|
/// Return the generic validation profile.
pub fn generic_validation_profile() -> ValidationProfile {
  Generic
}

///|
/// Return the GitHub validation profile.
pub fn github_validation_profile() -> ValidationProfile {
  Github
}

///|
/// Return the strict validation profile.
pub fn strict_validation_profile() -> ValidationProfile {
  Strict
}

///|
/// Parse the profile name used by the command-line interface.
pub fn parse_validation_profile(value : String) -> ValidationProfile? {
  match value {
    "generic" => Some(Generic)
    "github" => Some(Github)
    "strict" => Some(Strict)
    _ => None
  }
}

///|
/// Validate a SARIF log using a named engineering profile.
pub fn validate_with_profile(
  log : SarifLog,
  profile : ValidationProfile,
) -> ValidationReport {
  match profile {
    Generic => validate(log)
    Github => validate_github_compatibility(log)
    Strict => validate_strict(log)
  }
}

///|
fn validate_strict(log : SarifLog) -> ValidationReport {
  let base = validate_github_compatibility(log)
  let issues = base.issues.copy()
  let fingerprints : Map[String, Unit] = Map([])
  for run_index, run in log.runs {
    let run_path = "$.runs[\{run_index}]"
    let known_rules : Map[String, Unit] = Map([])
    match run.tool.driver.rules {
      Some(rules) =>
        for rule in rules {
          known_rules[rule.id] = ()
        }
      None => ()
    }
    match run.results {
      Some(results) =>
        for result_index, result in results {
          let result_path = run_path + ".results[\{result_index}]"
          match result.ruleId {
            Some(rule_id) if known_rules.contains(rule_id) => ()
            Some(rule_id) =>
              add_issue(
                issues,
                IssueError,
                "strict.result.undeclaredRule",
                result_path + ".ruleId",
                "ruleId '\{rule_id}' must be declared by the tool driver",
              )
            None =>
              add_issue(
                issues,
                IssueError,
                "strict.result.ruleId",
                result_path + ".ruleId",
                "strict profile requires result ruleId",
              )
          }
          match result.locations {
            Some(locations) if !locations.is_empty() =>
              for _, location in locations {
                match location.physicalLocation {
                  Some(physical) =>
                    match physical.artifactLocation {
                      Some(artifact) =>
                        match artifact.uri {
                          Some(uri) if is_relative_uri(uri) => ()
                          Some(_) | None =>
                            add_issue(
                              issues,
                              IssueError,
                              "strict.location.uri",
                              result_path + ".locations",
                              "strict profile requires a relative artifact URI",
                            )
                        }
                      None =>
                        add_issue(
                          issues,
                          IssueError,
                          "strict.location.uri",
                          result_path + ".locations",
                          "strict profile requires an artifact location",
                        )
                    }
                  None =>
                    add_issue(
                      issues,
                      IssueError,
                      "strict.location.physical",
                      result_path + ".locations",
                      "strict profile requires a physical location",
                    )
                }
              }
            Some(_) | None =>
              add_issue(
                issues,
                IssueError,
                "strict.result.location",
                result_path + ".locations",
                "strict profile requires at least one location",
              )
          }
          let key = fingerprint(result)
          if fingerprints.contains(key) {
            add_issue(
              issues,
              IssueError,
              "strict.result.duplicateFingerprint",
              result_path,
              "strict profile forbids duplicate result fingerprints",
            )
          } else {
            fingerprints[key] = ()
          }
        }
      None => ()
    }
  }
  { issues, }
}

///|
fn is_relative_uri(uri : String) -> Bool {
  !uri.trim().is_empty() &&
  !uri.has_prefix("/") &&
  !uri.has_prefix("\\") &&
  !uri.contains("://") &&
  !(uri.length() >= 2 && uri[1] == ':')
}