///|
/// A structured command-line validation finding suitable for diagnostics.
/// Hosts can display these findings before execution and attach the argument
/// index to a UI or CI annotation without parsing human-readable text.
/// This keeps process launch policy separate from command rendering and makes
/// the same validation available to native and browser-hosted plans.
pub(all) struct CommandIssue {
  argument_index : Int
  code : String
  message : String
} derive(Debug, Eq)

///|
pub fn CommandIssue::to_text(self : CommandIssue) -> String {
  "argument[" +
  self.argument_index.to_string() +
  "] " +
  self.code +
  ": " +
  self.message
}

///|
fn command_has_whitespace(text : String) -> Bool {
  for ch in text {
    if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
      return true
    }
  }
  false
}

///|
/// Validate command structure before a host invokes a process.
pub fn CommandLine::validate(self : CommandLine) -> Array[CommandIssue] {
  let issues : Array[CommandIssue] = []
  if self.executable == "" {
    issues.push({
      argument_index: 0,
      code: "empty-executable",
      message: "command has no executable",
    })
  } else if command_has_whitespace(self.executable) {
    issues.push({
      argument_index: 0,
      code: "split-executable",
      message: "executable must be one argument; quote it before parsing",
    })
  }
  for index, argument in self.arguments {
    if argument == "" {
      issues.push({
        argument_index: index + 1,
        code: "empty-argument",
        message: "empty arguments must be intentional and quoted",
      })
    }
    if argument.contains("\n") || argument.contains("\r") {
      issues.push({
        argument_index: index + 1,
        code: "newline-argument",
        message: "newline in a process argument is not portable",
      })
    }
  }
  issues
}

///|
pub fn CommandLine::is_valid(self : CommandLine) -> Bool {
  self.validate().is_empty()
}