/// Priority class for worker scheduling.
///
/// Nodes with higher service class are served first when the scheduler
/// selects which node to process next.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub enum ServiceClass {
    /// Not playing, not needed soon. Lowest priority.
    #[default]
    Idle,
    /// Preloading or about to play. Medium priority.
    Warm,
    /// Currently audible. Highest priority.
    Audible,
}

/// Result of a single node tick.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TickResult {
    /// Node made progress (produced or consumed data, applied internal state change).
    Progress,
    /// Node is alive but waiting on an upstream source (`step_track()`
    /// returned `Blocked`). The scheduler interprets this as
    /// "progress is *expected* but not happening" — the hang watchdog
    /// ticks here so a forever-blocked source surfaces as a panic
    /// instead of an indefinite park.
    Waiting,
    /// Node is alive but its downstream consumer is not pulling
    /// (PCM ring full / outlet overflow). The scheduler treats this
    /// as a paused/idle player — progress is *not expected* until the
    /// consumer drains the ring, so the hang watchdog must NOT tick.
    /// Distinguishing this from `Waiting` is what keeps an idle
    /// `Audio` handle from panicking after the watchdog budget
    /// expires (the symptom that prompted bug #1).
    Backpressured,
    /// Node has finished its work (EOF, failed, terminal).
    Done,
}

/// A component that can be executed by the scheduler.
pub(crate) trait Node: Send + 'static {
    /// Called when the scheduler is cancelled or the node is unregistered.
    fn on_cancel(&mut self) {}

    /// Return the current service class (priority) of this node.
    fn service_class(&self) -> ServiceClass {
        ServiceClass::Audible
    }

    /// Perform one quantum of work.
    fn tick(&mut self) -> TickResult;
}

impl Node for Box<dyn Node> {
    fn on_cancel(&mut self) {
        (**self).on_cancel();
    }

    fn service_class(&self) -> ServiceClass {
        (**self).service_class()
    }

    fn tick(&mut self) -> TickResult {
        (**self).tick()
    }
}

#[cfg(test)]
mod tests {
    use kithara_test_utils::kithara;

    use super::*;

    #[kithara::test]
    fn service_class_ordering() {
        assert!(ServiceClass::Idle < ServiceClass::Warm);
        assert!(ServiceClass::Warm < ServiceClass::Audible);
    }

    #[kithara::test]
    fn service_class_default_is_idle() {
        assert_eq!(ServiceClass::default(), ServiceClass::Idle);
    }
}
