///|
fn profile_path(path : String) -> String {
  if path.has_prefix("/") {
    path
  } else {
    "\/{path}"
  }
}

///|
fn profile_type_matches(value : Json, expected : String) -> Bool {
  match expected {
    "object" => value is Object(_)
    "array" => value is Array(_)
    "string" => value is String(_)
    "number" => value is Number(_, ..)
    "boolean" =>
      match value {
        True | False => true
        _ => false
      }
    "null" => value is Null
    _ => false
  }
}

///|
fn profile_rule_issues(
  issues : Array[Issue],
  value : Json,
  rule : ProfileRule,
) -> Unit {
  match rule {
    Required(path) =>
      if query_first(value, path) is None {
        issues.push(
          issue(
            profile_path(path),
            "required",
            "profile field \{path} is required",
          ),
        )
      }
    Type(path, expected) =>
      match query_first(value, path) {
        None => ()
        Some(actual) =>
          if !profile_type_matches(actual, expected) {
            issues.push(
              issue(
                profile_path(path),
                "type",
                "profile field \{path} must be a \{expected}",
              ),
            )
          }
      }
    Allowed(path, allowed) =>
      match query_first(value, path) {
        None => ()
        Some(actual) =>
          match as_scalar_text(actual) {
            None =>
              issues.push(
                issue(
                  profile_path(path),
                  "type",
                  "profile allowed values require a scalar field",
                ),
              )
            Some(text) =>
              if !allowed.contains(text) {
                issues.push(
                  issue(
                    profile_path(path),
                    "value",
                    "profile field \{path} is outside the allowed values",
                  ),
                )
              }
          }
      }
    MinItems(path, minimum) => {
      let count = query_values(value, path).length()
      if count < minimum {
        issues.push(
          issue(
            profile_path(path),
            "required",
            "profile field \{path} must contain at least \{minimum} item(s)",
          ),
        )
      }
    }
  }
}

///|
pub fn validate_with_profile(
  value : Json,
  profile : Profile,
  version? : FhirVersion = R4,
) -> ValidationReport {
  let report = validate_resource(value, version~)
  for rule in profile.rules {
    profile_rule_issues(report.issues, value, rule)
  }
  report
}

///|
pub fn validate_profile_only(
  value : Json,
  profile : Profile,
) -> ValidationReport {
  let report = {
    resource_type: resource_type(value).unwrap_or(""),
    version: R4,
    issues: [],
  }
  for rule in profile.rules {
    profile_rule_issues(report.issues, value, rule)
  }
  report
}

///|
pub fn profile_is_valid(value : Json, profile : Profile) -> Bool {
  let report = validate_with_profile(value, profile)
  !report.issues.any(fn(item) { item.severity == "error" })
}

///|
pub fn profile_issue_count(value : Json, profile : Profile) -> Int {
  validate_with_profile(value, profile).issues.length()
}