///|
pub(all) enum ValidationSeverity {
  Error
  Warning
} derive(Debug, Eq)

///|
pub(all) struct ValidationIssue {
  code : String
  message : String
  subject : String
  severity : ValidationSeverity
} derive(Debug, Eq)

///|
pub fn ValidationIssue::to_text(self : ValidationIssue) -> String {
  let level = match self.severity {
    Error => "error"
    Warning => "warning"
  }
  level + "[" + self.code + "] " + self.message + ": " + self.subject
}

///|
fn validation_issue(
  code : String,
  message : String,
  subject : String,
  severity : ValidationSeverity,
) -> ValidationIssue {
  { code, message, subject, severity }
}

///|
/// Check the supported Ninja subset before graph planning or execution.
///
/// Errors indicate an invalid build graph. Warnings describe valid but
/// unusual edges, such as a source-generating rule with no inputs.
pub fn Manifest::validation_issues(self : Manifest) -> Array[ValidationIssue] {
  let issues : Array[ValidationIssue] = []
  if self.rules.is_empty() && self.builds.is_empty() {
    issues.push(
      validation_issue(
        "empty-manifest",
        "manifest has no rules or build edges",
        "manifest",
        Error,
      ),
    )
    return issues
  }

  if self.rules.is_empty() {
    issues.push(
      validation_issue("empty-rules", "manifest has no rules", "rules", Error),
    )
  }
  if self.builds.is_empty() {
    issues.push(
      validation_issue(
        "empty-builds",
        "manifest has no build edges",
        "builds",
        Warning,
      ),
    )
  }

  let outputs : Map[String, Int] = Map([])
  for edge in self.builds {
    if edge.outputs.is_empty() {
      issues.push(
        validation_issue(
          "empty-outputs",
          "build edge must produce at least one output",
          edge.key(),
          Error,
        ),
      )
    }
    if !self.rules.contains(edge.rule) {
      issues.push(
        validation_issue(
          "unknown-rule",
          "build edge references a rule that is not declared",
          edge.rule,
          Error,
        ),
      )
    }
    if edge.inputs.is_empty() {
      issues.push(
        validation_issue(
          "source-generator",
          "build edge has no inputs and is treated as a source generator",
          edge.key(),
          Warning,
        ),
      )
    }
    for output in edge.outputs {
      let count = match outputs.get(output) {
        Some(value) => value + 1
        None => 1
      }
      outputs[output] = count
    }
  }

  for output, count in outputs {
    if count > 1 {
      issues.push(
        validation_issue(
          "duplicate-output",
          "multiple build edges produce the same output",
          output,
          Error,
        ),
      )
    }
  }
  issues
}

///|
pub fn Manifest::is_valid(self : Manifest) -> Bool {
  for issue in self.validation_issues() {
    if issue.severity == Error {
      return false
    }
  }
  true
}

///|
pub fn Manifest::validate(self : Manifest) -> Result[Unit, String] {
  let issues = self.validation_issues()
  let errors : Array[String] = []
  for issue in issues {
    if issue.severity == Error {
      errors.push(issue.to_text())
    }
  }
  if errors.is_empty() {
    Ok(())
  } else {
    Err(join_strings(errors, "\n"))
  }
}