use data::tables::{self, ProcedureConfigID};

use super::{Procedure, ProcedureAction, ProcedureError};

pub struct ProcedureManager {
    cur_procedure_id: Option<ProcedureConfigID>,
    procedures: Vec<Procedure>,
}

impl ProcedureManager {
    pub fn new(start_procedure_id: ProcedureConfigID) -> Self {
        Self {
            cur_procedure_id: Some(start_procedure_id),
            procedures: tables::procedure_config_template_tb::iter()
                .filter(|tmpl| tmpl.procedure_id.value() >= start_procedure_id.value())
                .map(Procedure::new)
                .collect(),
        }
    }

    pub fn try_complete_procedure(
        &mut self,
        procedure_id: ProcedureConfigID,
    ) -> Result<(), ProcedureError> {
        let Some(cur_procedure_id) = self.cur_procedure_id else {
            return Err(ProcedureError::ProcedureIsNull);
        };

        (cur_procedure_id == procedure_id).then_some(()).ok_or(
            ProcedureError::InvalidProcedureId(procedure_id, cur_procedure_id),
        )?;

        let procedure = self
            .procedures
            .iter()
            .find(|proc| proc.base().id() == procedure_id)
            .ok_or(ProcedureError::ProcedureIsNull)?;

        if procedure.base().is_finished() {
            self.cur_procedure_id = procedure.base().get_next_id();
            Ok(())
        } else {
            Err(ProcedureError::NotFinished)
        }
    }

    pub fn on_action(&mut self, action: ProcedureAction) -> Result<(), ProcedureError> {
        let Some(cur_procedure_id) = self.cur_procedure_id else {
            return Err(ProcedureError::ProcedureIsNull);
        };

        let procedure = self
            .procedures
            .iter_mut()
            .find(|proc| proc.base().id() == cur_procedure_id)
            .ok_or(ProcedureError::ProcedureIsNull)?;

        let state = procedure.base_mut().on_action(action)?;
        tracing::info!(
            "procedure action {action:?} performed, state: {state:?}, procedure id: {}",
            cur_procedure_id
        );

        Ok(())
    }

    pub fn procedure_id(&self) -> Option<ProcedureConfigID> {
        self.cur_procedure_id
    }

    pub fn is_end(&self) -> bool {
        self.cur_procedure_id.is_none()
    }
}
