///|
pub(all) enum WorkStatus {
  Proposed
  Ready
  Active
  Waiting
  Blocked
  Review
  Accepted
  Failed
  Cancelled
  Superseded
} derive(Debug, Eq, Compare, Hash, ToJson)

///|
pub fn WorkStatus::id(self : WorkStatus) -> String {
  match self {
    Proposed => "proposed"
    Ready => "ready"
    Active => "active"
    Waiting => "waiting"
    Blocked => "blocked"
    Review => "review"
    Accepted => "accepted"
    Failed => "failed"
    Cancelled => "cancelled"
    Superseded => "superseded"
  }
}

///|
pub fn WorkStatus::is_terminal(self : WorkStatus) -> Bool {
  match self {
    Accepted | Failed | Cancelled | Superseded => true
    _ => false
  }
}

///|
pub(all) struct ArtifactRef {
  artifact_id : String
  producer : String
  digest : String
  uri : String
  authority : String
  recorded_at : String
} derive(Debug, Eq, ToJson)

///|
pub fn ArtifactRef::quality_issues(self : ArtifactRef) -> Array[String] {
  let issues : Array[String] = []
  if self.artifact_id.trim().is_empty() {
    issues.push("artifact_id is required")
  }
  if self.producer.trim().is_empty() {
    issues.push("artifact producer is required")
  }
  if self.digest.trim().is_empty() {
    issues.push("artifact digest is required")
  }
  if self.uri.trim().is_empty() {
    issues.push("artifact uri is required")
  }
  if self.authority.trim().is_empty() {
    issues.push("artifact authority is required")
  }
  issues
}

///|
pub(all) struct WorkItem {
  work_item_id : String
  declaration_id : String
  product_id : String
  status : WorkStatus
  depends_on : Array[String]
  input_artifacts : Array[ArtifactRef]
  output_artifacts : Array[ArtifactRef]
  requested_authority : AuthorityClass
  attempt_count : Int
  blocker : String
} derive(Debug, Eq, ToJson)

///|
pub(all) struct WorkGraph {
  graph_id : String
  book_id : String
  declaration_revision : String
  items : Array[WorkItem]
  recorded_at : String
} derive(Debug, Eq, ToJson)

///|
fn WorkGraph::item_ids(self : WorkGraph) -> Array[String] {
  self.items.map(item => item.work_item_id)
}

///|
fn WorkGraph::has_cycle(self : WorkGraph) -> Bool {
  let ids = self.item_ids()
  let processed : Array[String] = []
  for ;; {
    let before = processed.length()
    for item in self.items {
      if !processed.contains(item.work_item_id) {
        let mut ready = true
        for dependency in item.depends_on {
          if ids.contains(dependency) && !processed.contains(dependency) {
            ready = false
          }
        }
        if ready {
          processed.push(item.work_item_id)
        }
      }
    }
    if processed.length() == self.items.length() {
      break
    }
    if processed.length() == before {
      break
    }
  }
  processed.length() != self.items.length()
}

///|
pub fn WorkGraph::quality_issues(self : WorkGraph) -> Array[String] {
  let issues : Array[String] = []
  if self.graph_id.trim().is_empty() {
    issues.push("graph_id is required")
  }
  if self.book_id.trim().is_empty() {
    issues.push("book_id is required")
  }
  if self.declaration_revision.trim().is_empty() {
    issues.push("declaration_revision is required")
  }
  let ids : Array[String] = []
  for item in self.items {
    if item.work_item_id.trim().is_empty() {
      issues.push("work_item_id is required")
    } else if ids.contains(item.work_item_id) {
      issues.push("duplicate work_item_id: \{item.work_item_id}")
    } else {
      ids.push(item.work_item_id)
    }
    if item.declaration_id.trim().is_empty() {
      issues.push("declaration_id is required for \{item.work_item_id}")
    }
    if item.product_id.trim().is_empty() {
      issues.push("product_id is required for \{item.work_item_id}")
    }
    if item.attempt_count < 0 {
      issues.push("attempt_count cannot be negative for \{item.work_item_id}")
    }
  }
  for item in self.items {
    for dependency in item.depends_on {
      if dependency == item.work_item_id {
        issues.push("work item cannot depend on itself: \{item.work_item_id}")
      } else if !ids.contains(dependency) {
        issues.push("unknown dependency \{dependency} for \{item.work_item_id}")
      }
    }
  }
  if self.has_cycle() {
    issues.push("work graph contains a dependency cycle")
  }
  issues
}

///|
pub fn WorkGraph::runnable_item_ids(self : WorkGraph) -> Array[String] {
  let accepted : Array[String] = []
  for item in self.items {
    if item.status == Accepted {
      accepted.push(item.work_item_id)
    }
  }
  let runnable : Array[String] = []
  for item in self.items {
    if item.status == Proposed || item.status == Ready || item.status == Waiting {
      let mut dependencies_accepted = true
      for dependency in item.depends_on {
        if !accepted.contains(dependency) {
          dependencies_accepted = false
        }
      }
      if dependencies_accepted {
        runnable.push(item.work_item_id)
      }
    }
  }
  runnable
}