///|
/// Static features extracted from one GitHub Actions workflow.
pub(all) struct WorkflowFacts {
  path : String
  name : String?
  triggers : Array[String]
  permissions : Array[String]
  commands : Array[String]
  actions : Array[String]
  jobs : Array[String]
  has_moon_setup : Bool
  has_moon_check : Bool
  has_moon_build : Bool
  has_moon_test : Bool
  has_moon_package : Bool
  has_artifact_upload : Bool
} derive(Debug, Eq)

///|
pub fn WorkflowFacts::new(path : String) -> WorkflowFacts {
  {
    path,
    name: None,
    triggers: [],
    permissions: [],
    commands: [],
    actions: [],
    jobs: [],
    has_moon_setup: false,
    has_moon_check: false,
    has_moon_build: false,
    has_moon_test: false,
    has_moon_package: false,
    has_artifact_upload: false,
  }
}

///|
pub fn WorkflowFacts::summary(self : WorkflowFacts) -> String {
  "\{self.jobs.length()} jobs, \{self.commands.length()} commands, moon check=\{self.has_moon_check}, moon test=\{self.has_moon_test}"
}

///|
fn yaml_value(line : String) -> String? {
  let parts = line.split(":").to_array()
  if parts.length() < 2 {
    None
  } else {
    let values : Array[StringView] = []
    for index in 1.. String? {
  let trimmed = line.trim().to_owned()
  let parts = trimmed.split(":").to_array()
  if parts.length() == 0 {
    None
  } else {
    Some(parts[0].trim().to_owned())
  }
}

///|
fn indentation(line : String) -> Int {
  let mut count = 0
  for character in line {
    if character == ' ' {
      count += 1
    } else {
      break
    }
  }
  count
}

///|
fn record_command(facts : WorkflowFacts, command : String) -> WorkflowFacts {
  let commands = facts.commands
  commands.push(command)
  {
    path: facts.path,
    name: facts.name,
    triggers: facts.triggers,
    permissions: facts.permissions,
    commands,
    actions: facts.actions,
    jobs: facts.jobs,
    has_moon_setup: facts.has_moon_setup ||
    command.contains("cli.moonbitlang.com"),
    has_moon_check: facts.has_moon_check || command.contains("moon check"),
    has_moon_build: facts.has_moon_build || command.contains("moon build"),
    has_moon_test: facts.has_moon_test || command.contains("moon test"),
    has_moon_package: facts.has_moon_package || command.contains("moon package"),
    has_artifact_upload: facts.has_artifact_upload ||
    command.contains("upload-artifact"),
  }
}

///|
fn record_action(facts : WorkflowFacts, action : String) -> WorkflowFacts {
  let actions = facts.actions
  actions.push(action)
  {
    path: facts.path,
    name: facts.name,
    triggers: facts.triggers,
    permissions: facts.permissions,
    commands: facts.commands,
    actions,
    jobs: facts.jobs,
    has_moon_setup: facts.has_moon_setup,
    has_moon_check: facts.has_moon_check,
    has_moon_build: facts.has_moon_build,
    has_moon_test: facts.has_moon_test,
    has_moon_package: facts.has_moon_package,
    has_artifact_upload: facts.has_artifact_upload ||
    action.contains("actions/upload-artifact"),
  }
}

///|
fn add_trigger(facts : WorkflowFacts, trigger : String) -> WorkflowFacts {
  let triggers = facts.triggers
  if !triggers.contains(trigger) {
    triggers.push(trigger)
  }
  {
    path: facts.path,
    name: facts.name,
    triggers,
    permissions: facts.permissions,
    commands: facts.commands,
    actions: facts.actions,
    jobs: facts.jobs,
    has_moon_setup: facts.has_moon_setup,
    has_moon_check: facts.has_moon_check,
    has_moon_build: facts.has_moon_build,
    has_moon_test: facts.has_moon_test,
    has_moon_package: facts.has_moon_package,
    has_artifact_upload: facts.has_artifact_upload,
  }
}

///|
/// Extract release-relevant YAML facts without implementing a general YAML parser.
pub fn analyze_workflow(file : ProjectFile) -> WorkflowFacts {
  let mut facts = WorkflowFacts::new(file.path)
  let mut in_run = false
  let mut run_indent = 0
  let mut in_jobs = false
  for raw in file.content.split("\n") {
    let line = raw.to_owned()
    let trimmed = line.trim().to_owned()
    if trimmed.length() == 0 || trimmed.has_prefix("#") {
      continue
    }
    let indent = indentation(line)
    if in_run && indent > run_indent {
      facts = record_command(facts, trimmed)
      continue
    }
    in_run = false
    if trimmed == "on:" {
      facts = add_trigger(facts, "configured")
    }
    if trimmed.has_prefix("- uses:") || trimmed.has_prefix("uses:") {
      match yaml_value(trimmed) {
        Some(action) => facts = record_action(facts, action)
        None => ()
      }
    }
    if trimmed.has_prefix("run:") {
      match yaml_value(trimmed) {
        Some(command) => {
          facts = record_command(facts, command)
          in_run = command == "|"
          run_indent = indent
        }
        None => ()
      }
    }
    if trimmed == "jobs:" {
      in_jobs = true
      continue
    }
    if in_jobs && indent == 2 && trimmed.has_suffix(":") {
      match yaml_key(trimmed) {
        Some(job) => facts.jobs.push(job)
        None => ()
      }
    }
    if trimmed.has_prefix("permissions:") {
      facts.permissions.push("configured")
    }
  }
  facts
}

///|
pub fn analyze_workflows(inventory : ProjectInventory) -> Array[WorkflowFacts] {
  let workflows : Array[WorkflowFacts] = []
  for file in inventory.files_under(".github/workflows") {
    if file.path.has_suffix(".yml") || file.path.has_suffix(".yaml") {
      workflows.push(analyze_workflow(file))
    }
  }
  workflows
}

///|
pub fn workflow_release_report(inventory : ProjectInventory) -> String {
  let builder = StringBuilder()
  for workflow in analyze_workflows(inventory) {
    builder.write_string(workflow.path + ": " + workflow.summary() + "\n")
  }
  builder.to_string()
}