#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterfaceMode {
    Multilevel,
    Simple,
    Fgsea,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionPlan {
    Cpu(InterfaceMode),
    Gpu {
        mode: InterfaceMode,
        n_perm: usize,
        allow_multilevel: bool,
    },
}

pub fn parse_interface_mode(mode: &str) -> Result<InterfaceMode, String> {
    match mode.to_lowercase().as_str() {
        "multilevel" => Ok(InterfaceMode::Multilevel),
        "simple" => Ok(InterfaceMode::Simple),
        "fgsea" => Ok(InterfaceMode::Fgsea),
        other => Err(format!(
            "Invalid mode '{}'. Expected one of: fgsea, multilevel, simple.",
            other
        )),
    }
}

pub fn resolve_execution_plan(
    mode: InterfaceMode,
    gpu: bool,
    nperm: Option<usize>,
    n_perm_simple: usize,
) -> Result<ExecutionPlan, String> {
    if !gpu {
        return Ok(ExecutionPlan::Cpu(mode));
    }

    if mode == InterfaceMode::Multilevel && nperm.is_some() {
        return Err("nperm is only valid with mode='fgsea' or mode='simple'.".to_string());
    }

    let (n_perm, allow_multilevel) = match mode {
        InterfaceMode::Multilevel => (n_perm_simple, true),
        InterfaceMode::Simple => (nperm.unwrap_or(n_perm_simple), false),
        InterfaceMode::Fgsea => (nperm.unwrap_or(n_perm_simple), nperm.is_none()),
    };

    Ok(ExecutionPlan::Gpu {
        mode,
        n_perm,
        allow_multilevel,
    })
}

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

    #[test]
    fn parse_interface_mode_accepts_supported_values() {
        assert_eq!(parse_interface_mode("fgsea").unwrap(), InterfaceMode::Fgsea);
        assert_eq!(
            parse_interface_mode("multilevel").unwrap(),
            InterfaceMode::Multilevel
        );
        assert_eq!(
            parse_interface_mode("simple").unwrap(),
            InterfaceMode::Simple
        );
    }

    #[test]
    fn resolve_execution_plan_for_cpu_preserves_requested_mode() {
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Fgsea, false, None, 1000).unwrap(),
            ExecutionPlan::Cpu(InterfaceMode::Fgsea)
        );
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Multilevel, false, None, 1000).unwrap(),
            ExecutionPlan::Cpu(InterfaceMode::Multilevel)
        );
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Simple, false, Some(250), 1000).unwrap(),
            ExecutionPlan::Cpu(InterfaceMode::Simple)
        );
    }

    #[test]
    fn resolve_execution_plan_for_gpu_exposes_wrapper_and_explicit_modes() {
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Fgsea, true, None, 1000).unwrap(),
            ExecutionPlan::Gpu {
                mode: InterfaceMode::Fgsea,
                n_perm: 1000,
                allow_multilevel: true,
            }
        );
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Fgsea, true, Some(250), 1000).unwrap(),
            ExecutionPlan::Gpu {
                mode: InterfaceMode::Fgsea,
                n_perm: 250,
                allow_multilevel: false,
            }
        );
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Simple, true, Some(250), 1000).unwrap(),
            ExecutionPlan::Gpu {
                mode: InterfaceMode::Simple,
                n_perm: 250,
                allow_multilevel: false,
            }
        );
        assert_eq!(
            resolve_execution_plan(InterfaceMode::Multilevel, true, None, 1000).unwrap(),
            ExecutionPlan::Gpu {
                mode: InterfaceMode::Multilevel,
                n_perm: 1000,
                allow_multilevel: true,
            }
        );
    }

    #[test]
    fn resolve_execution_plan_rejects_nperm_for_multilevel_gpu() {
        let err =
            resolve_execution_plan(InterfaceMode::Multilevel, true, Some(250), 1000).unwrap_err();
        assert_eq!(
            err,
            "nperm is only valid with mode='fgsea' or mode='simple'."
        );
    }
}
