use dpm_model::{Applicability, DependencyKind, DependencyPolicy, Endpoint, Release, WorkStatus};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum Transition {
#[default]
Claim,
Start,
Submit,
Verify,
}
impl Transition {
pub const ALL: [Self; 4] = [Self::Claim, Self::Start, Self::Submit, Self::Verify];
#[must_use]
pub fn lifecycle(self) -> WorkStatus {
match self {
Self::Claim => WorkStatus::Planned,
Self::Start => WorkStatus::Claimed,
Self::Submit => WorkStatus::InProgress,
Self::Verify => WorkStatus::Submitted,
}
}
#[must_use]
pub fn governs(self, kind: DependencyKind) -> bool {
match self {
Self::Claim | Self::Start => kind.successor_endpoint() == Endpoint::Start,
Self::Submit => kind.successor_endpoint() == Endpoint::Finish,
Self::Verify => true,
}
}
#[must_use]
pub fn starts(self) -> bool {
matches!(self, Self::Claim | Self::Start)
}
#[must_use]
pub fn checks_basis(self) -> bool {
matches!(self, Self::Submit | Self::Verify)
}
#[must_use]
pub fn checks_decisions(self) -> bool {
!matches!(self, Self::Submit)
}
}
impl fmt::Display for Transition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Claim => "claim",
Self::Start => "start",
Self::Submit => "submit",
Self::Verify => "verify",
})
}
}
pub(super) fn describe_release(
key: &str,
requires: Endpoint,
release: Release,
edge: &str,
policy: DependencyPolicy,
) -> String {
let event = match requires {
Endpoint::Start => "start",
Endpoint::Finish => "verified finish",
};
match release {
Release::AwaitingEvent => format!("awaits the {event} of {key} ({edge})"),
Release::Elapsing { event_at, opens_at } => {
format!("{key} {event} recorded at {event_at}; lag elapses at {opens_at} ({edge})")
}
Release::UnrecordedEventTime => {
let remedy = match policy {
DependencyPolicy::Soft => "a human or service may waive this Soft edge",
DependencyPolicy::Hard => {
"a Hard edge cannot be waived; a reviewed plan change may alter its lag only while neither the successor nor any work depending on it has been claimed, and otherwise the gate stays closed"
}
};
format!(
"the {event} time of {key} was not recorded, so its positive lag cannot be shown to have elapsed ({edge}); {remedy}"
)
}
Release::LagOutOfRange { event_at } => format!(
"{key} {event} recorded at {event_at}, but the lag exceeds the supported time range ({edge})"
),
Release::Released { .. } => format!("{key} {event} released ({edge})"),
Release::SkippedBranch { .. } => {
format!(
"{key} was not selected; this active-branch join treats it as an absent branch ({edge})"
)
}
Release::NotSelected => format!(
"{key} was not selected, and an ordinary dependency on skipped work never releases ({edge}); declare an active-branch join or change the plan"
),
}
}
#[must_use]
pub fn describe_applicability(applicability: &Applicability) -> String {
match applicability {
Applicability::Applicable => "work is applicable".into(),
Applicability::Undecided { decision, option } => {
format!("work applies only if {decision} selects {option}, and it is undecided")
}
Applicability::NotSelected {
decision,
option,
selected,
} => format!(
"work applies only if {decision} selects {option}, but it selected {selected}; it cannot be claimed or completed"
),
Applicability::AwaitingChoice { predecessor } => format!(
"prerequisite {predecessor} depends on an undecided choice, so this work is not yet committed"
),
Applicability::Stranded { predecessor, .. } => format!(
"prerequisite {predecessor} was not selected or can never proceed, and an ordinary dependency never releases from it; only a reviewed plan change that rewires the dependency, or a waiver of a Soft dependency, lets this work proceed"
),
Applicability::EmptyJoin => "every branch into this join was not selected, and the join does not permit an empty result".into(),
Applicability::AllChildrenExcluded { decisions } => format!(
"every child of this work package was excluded by {}; the package is excluded with them",
join_keys(decisions)
),
Applicability::ChildrenStranded { child } => format!(
"no child of this work package is applicable and {child} can never proceed, so the package cannot complete without a reviewed plan change"
),
}
}
fn join_keys(keys: &[dpm_model::Key]) -> String {
keys.iter()
.map(|k| k.0.as_str())
.collect::<Vec<_>>()
.join(", ")
}