use crate::explore::{SimulatedTransition, WorkflowModel};
use crate::workflows::simulate;
use crate::workflows::traveler::command::{TravelerCommand, TravelerEvent};
use crate::workflows::traveler::definition::TravelerWorkflow;
use crate::workflows::traveler::state::{
DeclineReason, FieldState, TravelerOutcome, TravelerState, TravelerStatus,
};
pub const SAMPLE_NAME: &str = "Marta Bianchi";
pub const SAMPLE_EMAIL: &str = "marta@aurora.example";
pub const OTHER_EMAIL: &str = "marta.bianchi@aurora.example";
pub const SAMPLE_LOYALTY_NUMBER: &str = "AZ1234567";
#[must_use]
pub fn incomplete_draft() -> TravelerState {
TravelerState {
full_name: Some(SAMPLE_NAME.to_owned()),
..TravelerState::default()
}
}
#[must_use]
pub fn awaiting_activation() -> TravelerState {
TravelerState {
full_name: Some(SAMPLE_NAME.to_owned()),
email: Some(SAMPLE_EMAIL.to_owned()),
loyalty_number: FieldState::answered(SAMPLE_LOYALTY_NUMBER),
status: TravelerStatus::Draft,
}
}
#[must_use]
pub fn declined_loyalty_number(reason: DeclineReason) -> TravelerState {
TravelerState {
loyalty_number: FieldState::declined(reason),
..awaiting_activation()
}
}
#[must_use]
pub fn active_traveler() -> TravelerState {
TravelerState {
status: TravelerStatus::Active,
..awaiting_activation()
}
}
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct TravelerModel {
pub workflow: TravelerWorkflow,
}
impl TravelerModel {
#[must_use]
pub const fn new() -> Self {
Self::of(TravelerWorkflow::new())
}
#[must_use]
pub const fn of(workflow: TravelerWorkflow) -> Self {
Self { workflow }
}
}
impl WorkflowModel<TravelerWorkflow> for TravelerModel {
fn initial_states(&self) -> Vec<Option<TravelerState>> {
vec![None]
}
fn candidate_commands(&self, state: Option<&TravelerState>) -> Vec<TravelerCommand> {
let Some(state) = state else {
return vec![TravelerCommand::CreateDraft];
};
let mut commands = Vec::new();
match state.status {
TravelerStatus::Draft => {
if state.full_name.is_none() {
commands.push(TravelerCommand::SetName {
value: SAMPLE_NAME.to_owned(),
});
}
if state.email.is_none() {
commands.push(TravelerCommand::ChangeEmail {
value: SAMPLE_EMAIL.to_owned(),
});
}
if !state.loyalty_number.is_answered() {
commands.push(TravelerCommand::SetLoyaltyNumber {
value: SAMPLE_LOYALTY_NUMBER.to_owned(),
});
}
match state.loyalty_number.decline_reason() {
None => {
commands.push(TravelerCommand::DeclineLoyaltyNumber {
reason: DeclineReason::NotApplicable,
});
commands.push(TravelerCommand::DeclineLoyaltyNumber {
reason: DeclineReason::Unknown,
});
}
Some(DeclineReason::Unknown) => {
commands.push(TravelerCommand::DeclineLoyaltyNumber {
reason: DeclineReason::Withheld,
});
}
Some(_) => {}
}
commands.push(TravelerCommand::Activate);
commands.push(TravelerCommand::Delete);
}
TravelerStatus::Active => {
if state.email.as_deref() == Some(SAMPLE_EMAIL) {
commands.push(TravelerCommand::ChangeEmail {
value: OTHER_EMAIL.to_owned(),
});
}
commands.push(TravelerCommand::Archive);
commands.push(TravelerCommand::Delete);
}
TravelerStatus::Archived | TravelerStatus::Deleted => {}
}
if state.status.is_editable() {
commands.push(TravelerCommand::ChangeEmail {
value: "not-an-address".to_owned(),
});
if state.loyalty_number.is_answered() {
commands.push(TravelerCommand::DeclineLoyaltyNumber {
reason: DeclineReason::Withheld,
});
}
}
commands
}
fn simulate(
&self,
state: Option<&TravelerState>,
command: &TravelerCommand,
) -> SimulatedTransition<TravelerState, TravelerEvent> {
simulate(&self.workflow, state, command)
}
fn declared_outcomes(&self) -> Vec<TravelerOutcome> {
vec![TravelerOutcome::Archived, TravelerOutcome::Deleted]
}
}