use std::{
    collections::HashMap,
    rc::Rc,
    sync::{LazyLock, Mutex},
};

use super::{Environment, Node, NodeEnum, Precedence, node_ref::NodeRef};
use crate::environment::EnvironmentInternalSymbolKey;

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Symbol {
    value: EnvironmentInternalSymbolKey,
}

impl Node for Symbol {
    fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, String> {
        if let Some(value) = env.get(&self.value) {
            // TODO: Detect cyclic references and make sure they get
            // deleted at some point
            Ok(value.clone())
        } else {
            Ok(Rc::new(self.clone().into()))
        }
    }

    fn as_string(&self, env: Option<&Environment>) -> String {
        if let Some(env) = env {
            if let Some(value) = env.get(&self.value) {
                value.as_string(Some(env))
            } else {
                Symbol::ID_TO_STR_MAP
                    .lock()
                    .unwrap()
                    .get(&self.value)
                    .unwrap_or(&format!("{{#SYMBOL {}}}", self.value))
                    .clone()
            }
        } else {
            Symbol::ID_TO_STR_MAP
                .lock()
                .unwrap()
                .get(&self.value)
                .unwrap_or(&format!("{{#SYMBOL {}}}", self.value))
                .clone()
        }
    }

    fn precedence(&self) -> Precedence {
        Precedence::Primary
    }
}

impl Symbol {
    const STR_TO_ID_MAP: LazyLock<Mutex<HashMap<String, EnvironmentInternalSymbolKey>>> =
        LazyLock::new(|| HashMap::new().into());
    const ID_TO_STR_MAP: LazyLock<Mutex<HashMap<EnvironmentInternalSymbolKey, String>>> =
        LazyLock::new(|| HashMap::new().into());

    pub fn new(value: EnvironmentInternalSymbolKey) -> Self {
        Self { value }
    }

    pub fn new_from_str(str: impl Into<String>) -> Self {
        let str = str.into();
        if let Some(value) = Symbol::STR_TO_ID_MAP.lock().unwrap().get(str.as_str()) {
            Self::new(value.clone())
        } else {
            let id = Symbol::STR_TO_ID_MAP
                .lock()
                .unwrap()
                .len()
                .try_into()
                .unwrap();
            Symbol::STR_TO_ID_MAP
                .lock()
                .unwrap()
                .insert(str.clone(), id);
            Symbol::ID_TO_STR_MAP.lock().unwrap().insert(id, str);

            Self::new(id)
        }
    }

    pub fn get_value(&self) -> EnvironmentInternalSymbolKey {
        self.value
    }
}
