use std::{cell::UnsafeCell, collections::HashMap, path::PathBuf};

use rar_common::{
    Cred,
    database::score::{CmdMin, Score},
};
use serde_json::Value;

use crate::{Cli, error::SrResult};

use super::{
    BestExecSettings,
    de::{DConfigFinder, DLinkedRole, DLinkedTask},
    options::BorrowedOptStack,
};

#[cfg(feature = "hashchecker")]
mod hashchecker;
#[cfg(feature = "hierarchy")]
mod hierarchy;
#[cfg(feature = "landlock")]
pub mod landlock;
#[cfg(feature = "ssd")]
mod ssd;

thread_local! {
    static API: UnsafeCell<Api> = UnsafeCell::new(Api::new());
}

pub type EventCallbackFunction = dyn Fn(&mut ApiEvent) -> SrResult<()>;

pub struct Api {
    callbacks: HashMap<EventKey, Vec<Box<EventCallbackFunction>>>,
}

#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
pub enum EventKey {
    BestGlobalSettings,
    BestRoleSettings,
    BestTaskSettings,
    NewComplexCommand,
    ActorMatching,
    PreExec,
}

#[allow(dead_code)]
pub enum ApiEvent<'a, 't, 'c, 'f, 'g, 'h, 'i, 'j, 'k> {
    BestGlobalSettingsFound(
        &'f Cli,
        &'g DConfigFinder<'a>,
        &'j mut BorrowedOptStack<'a>,
        &'h mut BestExecSettings,
        &'i mut bool,
    ),
    BestRoleSettingsFound(
        &'f Cli,
        &'k Cred,
        &'g DLinkedRole<'c, 'a>,
        &'h mut BorrowedOptStack<'a>,
        &'k &'k [&'k str],
        &'i mut BestExecSettings,
        &'j mut bool,
    ),
    BestTaskSettingsFound(
        &'f Cli,
        &'g DLinkedTask<'t, 'c, 'a>,
        &'j mut BorrowedOptStack<'a>,
        &'h mut BestExecSettings,
        &'i mut Score,
    ),
    // NewComplexCommand (Value, env_path, cmd_path, cmd_args, cmd_min, final_path),
    ProcessComplexCommand(
        &'f Value,
        &'g [&'g str],
        &'h PathBuf,
        &'i [String],
        &'j mut CmdMin,
        &'k mut Option<PathBuf>,
    ),
    ActorMatching(
        &'f DLinkedRole<'c, 'a>,
        &'g mut BestExecSettings,
        &'h mut bool,
    ),
    PreExec(&'f Cli, &'h BestExecSettings),
}

impl ApiEvent<'_, '_, '_, '_, '_, '_, '_, '_, '_> {
    const fn get_key(&self) -> EventKey {
        match self {
            ApiEvent::BestGlobalSettingsFound(..) => EventKey::BestGlobalSettings,
            ApiEvent::BestRoleSettingsFound(..) => EventKey::BestRoleSettings,
            ApiEvent::BestTaskSettingsFound(..) => EventKey::BestTaskSettings,
            ApiEvent::ProcessComplexCommand(..) => EventKey::NewComplexCommand,
            ApiEvent::ActorMatching(..) => EventKey::ActorMatching,
            ApiEvent::PreExec(..) => EventKey::PreExec,
        }
    }
}

impl Api {
    fn new() -> Self {
        Self {
            callbacks: HashMap::new(),
        }
    }
    #[cfg(feature = "plugins")]
    pub fn notify(mut event: ApiEvent) -> SrResult<()> {
        let key = event.get_key();
        API.with(|api| -> SrResult<()> {
            let api = unsafe { &mut *api.get() };
            if let Some(callbacks) = api.callbacks.get(&key) {
                for callback in callbacks {
                    callback(&mut event)?;
                }
            }
            Ok(())
        })?;
        Ok(())
    }
    #[cfg(feature = "plugins")]
    pub fn register<F>(event: EventKey, function: F)
    where
        F: Fn(&mut ApiEvent) -> SrResult<()> + Send + 'static,
    {
        API.with(|api| unsafe {
            let api = &mut *api.get();
            let callbacks = api.callbacks.entry(event).or_default();
            callbacks.push(Box::new(function));
        });
    }
}

pub fn register_plugins() {
    #[cfg(feature = "ssd")]
    ssd::register();
    #[cfg(feature = "hashchecker")]
    hashchecker::register();
    #[cfg(feature = "hierarchy")]
    hierarchy::register();
    #[cfg(feature = "landlock")]
    landlock::register();
}
