use rustc_hash::FxHashMap as HashMap;

use crate::escape_reserved;

#[derive(Clone, Debug)]
pub(crate) struct InlineExpr(String);

impl InlineExpr {
    pub(crate) fn new(text: impl Into<String>) -> Self {
        Self(text.into())
    }

    pub(crate) fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Clone, Debug)]
pub(crate) enum BindingValue {
    GoName(String),
    InlineExpr(InlineExpr),
}

impl BindingValue {
    pub(crate) fn as_go_name(&self) -> Option<&str> {
        match self {
            BindingValue::GoName(name) => Some(name.as_str()),
            BindingValue::InlineExpr(_) => None,
        }
    }
}

#[derive(Default)]
pub(crate) struct Bindings {
    map: HashMap<String, BindingValue>,
    stack: Vec<HashMap<String, BindingValue>>,
}

impl Bindings {
    pub(crate) fn new() -> Self {
        Self::default()
    }

    pub(crate) fn reset(&mut self) {
        self.map.clear();
        self.stack.clear();
    }

    pub(crate) fn bind_go_name(
        &mut self,
        key: impl Into<String>,
        value: impl Into<String>,
    ) -> String {
        let go_value = escape_reserved(&value.into()).into_owned();
        self.map
            .insert(key.into(), BindingValue::GoName(go_value.clone()));
        go_value
    }

    pub(crate) fn bind_inline_expr(&mut self, key: impl Into<String>, expression_text: InlineExpr) {
        self.map
            .insert(key.into(), BindingValue::InlineExpr(expression_text));
    }

    pub(crate) fn get(&self, name: &str) -> Option<&BindingValue> {
        self.map.get(name)
    }

    pub(crate) fn get_go_name(&self, name: &str) -> Option<&str> {
        self.map.get(name).and_then(BindingValue::as_go_name)
    }

    pub(crate) fn has_go_name(&self, go_name: &str) -> bool {
        self.map
            .values()
            .filter_map(BindingValue::as_go_name)
            .any(|v| v == go_name)
    }

    pub(crate) fn save(&mut self) {
        self.stack.push(self.map.clone());
    }

    pub(crate) fn restore(&mut self) {
        if let Some(saved) = self.stack.pop() {
            self.map = saved;
        }
    }

    pub(crate) fn snapshot(&self) -> HashMap<String, BindingValue> {
        self.map.clone()
    }

    pub(crate) fn restore_snapshot(&mut self, snapshot: HashMap<String, BindingValue>) {
        self.map = snapshot;
    }

    pub(crate) fn remove(&mut self, key: &str) {
        self.map.remove(key);
    }
}
