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

///|
pub struct Diagnostic {
  severity_value : DiagnosticSeverity
  code_value : String
  message_value : String
  line_value : Int
} derive(Eq, Debug)

///|
pub fn Diagnostic::severity(self : Diagnostic) -> DiagnosticSeverity {
  self.severity_value
}

///|
pub fn Diagnostic::code(self : Diagnostic) -> String {
  self.code_value
}

///|
pub fn Diagnostic::message(self : Diagnostic) -> String {
  self.message_value
}

///|
pub fn Diagnostic::line(self : Diagnostic) -> Int {
  self.line_value
}

///|
pub struct SafetyPolicy {
  max_x_value : Double
  max_y_value : Double
  max_z_value : Double
  reject_negative_feed : Bool
} derive(Eq, Debug)

///|
pub fn SafetyPolicy::standard() -> SafetyPolicy {
  {
    max_x_value: 1000.0,
    max_y_value: 1000.0,
    max_z_value: 500.0,
    reject_negative_feed: true,
  }
}

///|
pub fn SafetyPolicy::with_workspace(
  self : SafetyPolicy,
  x : Double,
  y : Double,
  z : Double,
) -> SafetyPolicy {
  { ..self, max_x_value: x, max_y_value: y, max_z_value: z }
}

///|
/// Check workspace bounds and unsafe feed values without connecting to a machine.
pub fn diagnose(
  program : GCodeProgram,
  policy? : SafetyPolicy = SafetyPolicy::standard(),
) -> Array[Diagnostic] {
  let issues : Array[Diagnostic] = []
  for motion in program.motions() {
    let end = motion.end()
    if end.x() < 0.0 || end.x() > policy.max_x_value {
      issues.push({
        severity_value: Error,
        code_value: "axis.x_out_of_bounds",
        message_value: "X position exceeds workspace",
        line_value: motion.source_line(),
      })
    }
    if end.y() < 0.0 || end.y() > policy.max_y_value {
      issues.push({
        severity_value: Error,
        code_value: "axis.y_out_of_bounds",
        message_value: "Y position exceeds workspace",
        line_value: motion.source_line(),
      })
    }
    if end.z() < 0.0 || end.z() > policy.max_z_value {
      issues.push({
        severity_value: Error,
        code_value: "axis.z_out_of_bounds",
        message_value: "Z position exceeds workspace",
        line_value: motion.source_line(),
      })
    }
    match motion.feed() {
      Some(feed) =>
        if policy.reject_negative_feed && feed < 0.0 {
          issues.push({
            severity_value: Error,
            code_value: "feed.negative",
            message_value: "feed rate cannot be negative",
            line_value: motion.source_line(),
          })
        }
      None => ()
    }
  }
  issues
}

///|
pub fn render_diagnostics(diagnostics : Array[Diagnostic]) -> String {
  let mut output = "diagnostics=" + diagnostics.length().to_string() + "\n"
  for issue in diagnostics {
    let severity = match issue.severity() {
      Error => "error"
      Warning => "warning"
    }
    output = output +
      severity +
      " " +
      issue.code() +
      " line=" +
      issue.line().to_string() +
      ": " +
      issue.message() +
      "\n"
  }
  output
}