use super::*;

impl Interpreter {
    pub(super) fn builtin_namespace(name: &str) -> Option<&str> {
        name.split_once('.').map(|(namespace, _)| namespace)
    }

    pub(super) fn builtin_effects(name: &str) -> &'static [&'static str] {
        match Self::builtin_namespace(name) {
            Some("Args") => args::effects(name),
            Some("Console") => console::effects(name),
            Some("Http") => http::effects(name),
            Some("HttpServer") => http_server::effects(name),
            Some("Disk") => disk::effects(name),
            Some("Env") => env::effects(name),
            Some("Random") => random::effects(name),
            Some("Tcp") => tcp::effects(name),
            #[cfg(feature = "terminal")]
            Some("Terminal") => terminal::effects(name),
            Some("Time") => time::effects(name),
            Some("Bool") => bool::effects(name),
            Some("Int") => int::effects(name),
            Some("Float") => float::effects(name),
            Some("String") => string::effects(name),
            Some("List") => list::effects(name),
            Some("Map") => map::effects(name),
            Some("Vector") => vector::effects(name),
            Some("Char") => char::effects(name),
            Some("Byte") => byte::effects(name),
            Some("Result") => result::effects(name),
            Some("Option") => option::effects(name),
            _ => &[],
        }
    }

    pub(super) fn current_allowed_effects(&self) -> &[String] {
        self.call_stack
            .last()
            .map(|frame| frame.effects.as_ref().as_slice())
            .unwrap_or(&[])
    }

    pub(super) fn runtime_chain_with(&self, callee_name: &str) -> String {
        let mut chain = if self.call_stack.is_empty() {
            vec!["<top-level>".to_string()]
        } else {
            self.call_stack
                .iter()
                .map(|frame| frame.name.as_ref().clone())
                .collect()
        };
        chain.push(callee_name.to_string());
        chain.join(" -> ")
    }

    pub(super) fn ensure_effects_allowed<'a, I>(
        &self,
        callee_name: &str,
        required_effects: I,
    ) -> Result<(), RuntimeError>
    where
        I: IntoIterator<Item = &'a str>,
    {
        let mut missing = Vec::new();
        for effect in required_effects {
            if !self
                .current_allowed_effects()
                .iter()
                .any(|a| crate::effects::effect_satisfies(a, effect))
            {
                missing.push(effect.to_string());
            }
        }

        if missing.is_empty() {
            return Ok(());
        }

        let caller = self
            .call_stack
            .last()
            .map(|frame| frame.name.as_str())
            .unwrap_or("<top-level>");
        let chain = self.runtime_chain_with(callee_name);
        Err(RuntimeError::Error(format!(
            "Runtime effect violation: '{}' cannot call '{}' (missing effect(s): {}) [chain: {}]",
            caller,
            callee_name,
            missing.join(", "),
            chain
        )))
    }
}
