//! PM-43 wizard step model and publish phase.
//!
//! The durable composition lives in [`DraftThingInput`](super::draft); these
//! types are the *ephemeral* wizard UI state — which step is active and where
//! the publish action is in its lifecycle.

/// The five wizard steps, in order.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum WizardStep {
    Basics,
    Files,
    Images,
    Details,
    Review,
}

impl WizardStep {
    /// All steps in display order.
    pub const ALL: [WizardStep; 5] = [
        WizardStep::Basics,
        WizardStep::Files,
        WizardStep::Images,
        WizardStep::Details,
        WizardStep::Review,
    ];

    /// Zero-based position in the flow.
    pub fn index(self) -> usize {
        Self::ALL.iter().position(|s| *s == self).unwrap_or(0)
    }

    /// One-based step number for the stepper label.
    pub fn number(self) -> usize {
        self.index() + 1
    }

    /// Full label for the stepper and headings.
    pub fn label(self) -> &'static str {
        match self {
            WizardStep::Basics => "Basics",
            WizardStep::Files => "Files & parts",
            WizardStep::Images => "Images",
            WizardStep::Details => "Instructions & license",
            WizardStep::Review => "Review & publish",
        }
    }

    /// The next step, if any.
    pub fn next(self) -> Option<WizardStep> {
        Self::ALL.get(self.index() + 1).copied()
    }

    /// The previous step, if any.
    pub fn prev(self) -> Option<WizardStep> {
        if self.index() == 0 {
            None
        } else {
            Self::ALL.get(self.index() - 1).copied()
        }
    }
}

/// Lifecycle of the publish action, surfaced as the persistent status indicator.
#[derive(Clone, PartialEq, Debug)]
pub enum PublishPhase {
    /// Drafting; nothing is public.
    Editing,
    /// A publish request is in flight.
    Publishing,
    /// Published; the thing is reachable at this repo/rkey.
    Published { repo: String, rkey: String },
    /// The publish attempt failed; the draft is preserved.
    Failed(String),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn steps_are_ordered_and_navigable() {
        assert_eq!(WizardStep::Basics.number(), 1);
        assert_eq!(WizardStep::Review.number(), 5);
        assert_eq!(WizardStep::Basics.prev(), None);
        assert_eq!(WizardStep::Review.next(), None);
        assert_eq!(WizardStep::Basics.next(), Some(WizardStep::Files));
        assert_eq!(WizardStep::Files.prev(), Some(WizardStep::Basics));
    }

    #[test]
    fn all_steps_have_distinct_indices() {
        let mut seen = Vec::new();
        for step in WizardStep::ALL {
            let idx = step.index();
            assert!(!seen.contains(&idx), "duplicate index {idx}");
            seen.push(idx);
        }
        assert_eq!(seen, vec![0, 1, 2, 3, 4]);
    }
}
