///|
/// A grouped release task derived from one or more diagnostics.
pub(all) struct RemediationTask {
  priority : Int
  category : RuleCategory
  title : String
  actions : Array[String]
  rule_ids : Array[String]
  blocking : Bool
} derive(Debug, Eq)

///|
pub fn RemediationTask::new(
  priority : Int,
  category : RuleCategory,
  title : String,
  actions : Array[String],
  rule_ids : Array[String],
  blocking : Bool,
) -> RemediationTask {
  { priority, category, title, actions, rule_ids, blocking }
}

///|
/// Ordered plan that turns diagnostics into release work.
pub(all) struct RemediationPlan {
  project : String
  profile : String
  tasks : Array[RemediationTask]
  blocking_tasks : Int
  advisory_tasks : Int
} derive(Debug, Eq)

///|
pub fn RemediationPlan::new(
  project : String,
  profile : String,
  tasks : Array[RemediationTask],
) -> RemediationPlan {
  let mut blocking_tasks = 0
  let mut advisory_tasks = 0
  for task in tasks {
    if task.blocking {
      blocking_tasks += 1
    } else {
      advisory_tasks += 1
    }
  }
  { project, profile, tasks, blocking_tasks, advisory_tasks }
}

///|
pub fn RemediationPlan::is_release_ready(self : RemediationPlan) -> Bool {
  self.blocking_tasks == 0
}

///|
fn category_for_rule(rule_id : String) -> RuleCategory {
  match find_rule_definition(rule_id) {
    Some(rule) => rule.category
    None => Release
  }
}

///|
fn priority_for_severity(severity : Severity) -> Int {
  match severity {
    Error => 10
    Warn => 20
    Info => 30
  }
}

///|
fn task_title(category : RuleCategory, severity : Severity) -> String {
  let prefix = match severity {
    Error => "Blocker"
    Warn => "Release warning"
    Info => "Quality improvement"
  }
  prefix + ": " + category.label()
}

///|
fn add_unique(values : Array[String], value : String) -> Unit {
  if !values.contains(value) {
    values.push(value)
  }
}

///|
fn task_key(category : RuleCategory, severity : Severity) -> String {
  category.label() + ":" + severity.label()
}

///|
fn task_index(keys : Array[String], key : String) -> Int? {
  let mut index = 0
  for item in keys {
    if item == key {
      return Some(index)
    }
    index += 1
  }
  None
}

///|
fn add_diagnostic_to_tasks(
  tasks : Array[RemediationTask],
  keys : Array[String],
  diagnostic : Diagnostic,
) -> Unit {
  let category = category_for_rule(diagnostic.code)
  let key = task_key(category, diagnostic.severity)
  let actions = if diagnostic.remediation.length() == 0 {
    [diagnostic.detail]
  } else {
    diagnostic.remediation
  }
  match task_index(keys, key) {
    Some(index) => {
      let current = tasks[index]
      let updated_actions = current.actions
      let updated_rules = current.rule_ids
      for action in actions {
        add_unique(updated_actions, action)
      }
      add_unique(updated_rules, diagnostic.code)
      tasks[index] = {
        priority: current.priority,
        category: current.category,
        title: current.title,
        actions: updated_actions,
        rule_ids: updated_rules,
        blocking: current.blocking,
      }
    }
    None => {
      keys.push(key)
      tasks.push(
        RemediationTask::new(
          priority_for_severity(diagnostic.severity),
          category,
          task_title(category, diagnostic.severity),
          actions,
          [diagnostic.code],
          diagnostic.severity == Error || diagnostic.severity == Warn,
        ),
      )
    }
  }
}

///|
fn sort_tasks(tasks : Array[RemediationTask]) -> Array[RemediationTask] {
  let sorted = tasks.copy()
  for i in 0.. RemediationPlan {
  let tasks : Array[RemediationTask] = []
  let keys : Array[String] = []
  for diagnostic in report.diagnostics {
    add_diagnostic_to_tasks(tasks, keys, diagnostic)
  }
  RemediationPlan::new(report.root, report.profile, sort_tasks(tasks))
}

///|
pub fn render_remediation_markdown(plan : RemediationPlan) -> String {
  let builder = StringBuilder()
  builder.write_string("# Moon Doctor remediation plan\n\n")
  builder.write_string("Project: " + plan.project + "\n")
  builder.write_string("Profile: " + plan.profile + "\n\n")
  if plan.tasks.length() == 0 {
    builder.write_string(
      "No remediation tasks. The project is ready for the selected profile.\n",
    )
  } else {
    let mut number = 1
    for task in plan.tasks {
      builder.write_string("## " + "\{number}" + ". " + task.title + "\n\n")
      builder.write_string("Rules: " + task.rule_ids.join(", ") + "\n\n")
      for action in task.actions {
        builder.write_string("- " + action + "\n")
      }
      builder.write_string("\n")
      number += 1
    }
  }
  builder.to_string()
}

///|
pub fn remediation_plan_to_json(plan : RemediationPlan) -> Json {
  let tasks = plan.tasks.map(task => {
    Json::object({
      "priority": Json::number(task.priority.to_double()),
      "category": Json::string(task.category.label()),
      "title": Json::string(task.title),
      "actions": Json::array(task.actions.map(Json::string)),
      "rule_ids": Json::array(task.rule_ids.map(Json::string)),
      "blocking": Json::boolean(task.blocking),
    })
  })
  Json::object({
    "project": Json::string(plan.project),
    "profile": Json::string(plan.profile),
    "blocking_tasks": Json::number(plan.blocking_tasks.to_double()),
    "advisory_tasks": Json::number(plan.advisory_tasks.to_double()),
    "tasks": Json::array(tasks),
  })
}