///|
pub struct ReviewEvidence {
  id : String
  evidence : Evidence
} derive(Eq, @debug.Debug)

///|
pub fn ReviewEvidence::id(self : ReviewEvidence) -> String {
  self.id
}

///|
pub fn ReviewEvidence::evidence(self : ReviewEvidence) -> Evidence {
  self.evidence
}

///|
pub fn parse_review_evidence(
  source : String,
) -> Result[ReviewEvidence, Diagnostic] {
  if source.is_empty() || source.length() > 1048576 {
    return Err(
      Diagnostic::new(
        "evidence.input.limit",
        "evidence",
        "evidence is empty or exceeds the input limit",
        "1 through 1048576 characters",
        source.length().to_string(),
      ),
    )
  }
  let lines = split_owned_lines(source)
  if lines.is_empty() || lines[0] != "MOONCHANGE_EVIDENCE 1" {
    return Err(
      syntax_error(
        "evidence.header",
        0,
        "evidence header is missing or unsupported",
        "MOONCHANGE_EVIDENCE 1",
        if lines.is_empty() {
          "end of input"
        } else {
          lines[0]
        },
      ),
    )
  }
  let mut id : String? = None
  let mut actor : String? = None
  let approvals : Array[String] = []
  let checks : Array[CheckResult] = []
  let labels : Array[String] = []
  let mut release_note = false
  let mut saw_release_note = false
  for index = 1; index < lines.length(); index = index + 1 {
    let line = lines[index]
    if line.is_empty() || line.has_prefix("#") {
      continue
    }
    let fields = split_fields(line)
    match fields[0] {
      "ID" => {
        if fields.length() != 2 || id is Some(_) || !is_identifier(fields[1]) {
          return Err(
            syntax_error(
              "evidence.id.shape", index, "ID is duplicate, malformed, or invalid",
              "one simple ID value", line,
            ),
          )
        }
        id = Some(fields[1])
      }
      "ACTOR" => {
        if fields.length() != 2 || actor is Some(_) || !is_principal(fields[1]) {
          return Err(
            syntax_error(
              "evidence.actor.shape", index, "ACTOR is duplicate, malformed, or invalid",
              "one @principal", line,
            ),
          )
        }
        actor = Some(fields[1])
      }
      "APPROVAL" => {
        if fields.length() != 2 || !is_principal(fields[1]) {
          return Err(
            syntax_error(
              "evidence.approval.shape", index, "APPROVAL is malformed", "APPROVAL @principal",
              line,
            ),
          )
        }
        push_unique_string(approvals, fields[1])
      }
      "CHECK" => {
        if fields.length() != 3 || !is_simple_name(fields[1]) {
          return Err(
            syntax_error(
              "evidence.check.shape", index, "CHECK is malformed", "CHECK name passed|failed|pending",
              line,
            ),
          )
        }
        let state = match CheckState::parse(fields[2]) {
          Some(value) => value
          None =>
            return Err(
              syntax_error(
                "evidence.check.state",
                index,
                "check state is invalid",
                "passed, failed, or pending",
                fields[2],
              ),
            )
        }
        for existing in checks {
          if existing.name == fields[1] {
            return Err(
              syntax_error(
                "evidence.check.duplicate",
                index,
                "check appears more than once",
                "unique check name",
                fields[1],
              ),
            )
          }
        }
        checks.push(CheckResult::new(fields[1], state))
      }
      "LABEL" => {
        if fields.length() != 2 || !is_simple_name(fields[1]) {
          return Err(
            syntax_error(
              "evidence.label.shape", index, "LABEL is malformed", "LABEL name",
              line,
            ),
          )
        }
        push_unique_string(labels, fields[1])
      }
      "RELEASE_NOTE" => {
        if fields.length() != 2 || saw_release_note {
          return Err(
            syntax_error(
              "evidence.release_note.shape", index, "RELEASE_NOTE is duplicate or malformed",
              "one RELEASE_NOTE yes|no", line,
            ),
          )
        }
        saw_release_note = true
        release_note = match
          parse_yes_no(
            fields[1],
            "lines[" + index.to_string() + "].release_note",
          ) {
          Ok(value) => value
          Err(error) => return Err(error)
        }
      }
      command =>
        return Err(
          syntax_error(
            "evidence.command.unknown", index, "evidence command is unknown", "ID, ACTOR, APPROVAL, CHECK, LABEL, or RELEASE_NOTE",
            command,
          ),
        )
    }
  }
  if id is None {
    return Err(
      Diagnostic::new(
        "evidence.id.missing", "evidence", "evidence has no ID", "ID value", "missing",
      ),
    )
  }
  if actor is None {
    return Err(
      Diagnostic::new(
        "evidence.actor.missing", "evidence", "evidence has no actor", "ACTOR @principal",
        "missing",
      ),
    )
  }
  approvals.sort_by(fn(left, right) { left.lexical_compare(right) })
  labels.sort_by(fn(left, right) { left.lexical_compare(right) })
  Ok({
    id: id.unwrap(),
    evidence: Evidence::new(
      actor.unwrap(),
      approvals,
      checks,
      labels,
      release_note~,
    ),
  })
}

///|
pub fn ReviewEvidence::to_text(self : ReviewEvidence) -> String {
  let output = StringBuilder()
  let release_note = if self.evidence.release_note { "yes" } else { "no" }
  output.write_string(
    "MOONCHANGE_EVIDENCE 1\nID " + self.id + "\nACTOR " + self.evidence.actor,
  )
  for approval in self.evidence.approvals {
    output.write_string("\nAPPROVAL " + approval)
  }
  for check in self.evidence.checks {
    output.write_string("\nCHECK " + check.name + " " + check.state.to_text())
  }
  for label in self.evidence.labels {
    output.write_string("\nLABEL " + label)
  }
  output.write_string("\nRELEASE_NOTE " + release_note)
  output.to_string()
}