/*!
 * BoxIndex — minimal view over aliases and plugin box types
 *
 * Purpose: allow using/namespace resolver to make decisions that depend
 * on plugin-visible type names (e.g., enforcing strict prefix rules) and
 * to surface aliases defined in nyash.toml/env.
 */

use once_cell::sync::Lazy;
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
use std::sync::RwLock;
use std::time::SystemTime;

#[derive(Clone, Default)]
pub struct BoxIndex {
    #[allow(dead_code)]
    pub aliases: HashMap<String, String>,
    pub plugin_boxes: HashSet<String>,
    #[allow(dead_code)]
    pub plugin_meta: HashMap<String, PluginMeta>,
    pub plugin_meta_by_box: HashMap<String, PluginMeta>,
    pub plugins_require_prefix_global: bool,
}

impl BoxIndex {
    pub fn build_current() -> Self {
        // aliases from nyash.toml and env
        let mut aliases: HashMap<String, String> = HashMap::new();
        if let Ok(text) = std::fs::read_to_string("nyash.toml") {
            if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
                if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
                    for (k, v) in alias_tbl.iter() {
                        if let Some(target) = v.as_str() {
                            aliases.insert(k.to_string(), target.to_string());
                        }
                    }
                }
            }
        }
        if let Ok(raw) = std::env::var("NYASH_ALIASES") {
            for ent in raw.split(',') {
                if let Some((k, v)) = ent.split_once('=') {
                    let k = k.trim();
                    let v = v.trim();
                    if !k.is_empty() && !v.is_empty() {
                        aliases.insert(k.to_string(), v.to_string());
                    }
                }
            }
        }

        // plugin box types (best-effort; may be empty if host not initialized yet)
        let mut plugin_boxes: HashSet<String> = HashSet::new();
        let mut plugin_meta: HashMap<String, PluginMeta> = HashMap::new();
        let mut plugin_meta_by_box: HashMap<String, PluginMeta> = HashMap::new();
        let mut plugins_require_prefix_global = false;

        // Read per-plugin meta and global flags from nyash.toml when available
        if let Ok(text) = std::fs::read_to_string("nyash.toml") {
            if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
                if let Some(plugins_tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
                    // Global switch: [plugins].require_prefix = true
                    if let Some(v) = plugins_tbl.get("require_prefix").and_then(|v| v.as_bool()) {
                        plugins_require_prefix_global = v;
                    }
                    for (k, v) in plugins_tbl.iter() {
                        // Skip non-table entries (string entries are plugin roots)
                        if let Some(t) = v.as_table() {
                            let prefix = t
                                .get("prefix")
                                .and_then(|x| x.as_str())
                                .map(|s| s.to_string());
                            let require_prefix = t
                                .get("require_prefix")
                                .and_then(|x| x.as_bool())
                                .unwrap_or(false);
                            let expose_short_names = t
                                .get("expose_short_names")
                                .and_then(|x| x.as_bool())
                                .unwrap_or(true);
                            let meta = PluginMeta {
                                prefix,
                                require_prefix,
                                expose_short_names,
                            };
                            plugin_meta.insert(k.clone(), meta.clone());
                            if let Some(arr) = t.get("boxes").and_then(|x| x.as_array()) {
                                for b in arr {
                                    if let Some(name) = b.as_str() {
                                        plugin_meta_by_box.insert(name.to_string(), meta.clone());
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        let host = crate::runtime::get_global_plugin_host();
        if let Ok(h) = host.read() {
            if let Some(cfg) = h.config_ref() {
                for (lib, def) in &cfg.libraries {
                    for bt in &def.boxes {
                        plugin_boxes.insert(bt.clone());
                        if let Some(meta) = plugin_meta.get(lib) {
                            plugin_meta_by_box.insert(bt.clone(), meta.clone());
                        }
                    }
                }
            }
        }

        Self {
            aliases,
            plugin_boxes,
            plugin_meta,
            plugin_meta_by_box,
            plugins_require_prefix_global,
        }
    }

    pub fn is_known_plugin_short(name: &str) -> bool {
        // Prefer global index view
        if GLOBAL
            .read()
            .ok()
            .map(|g| g.plugin_boxes.contains(name))
            .unwrap_or(false)
        {
            return true;
        }
        // Env override list
        if let Ok(raw) = std::env::var("NYASH_KNOWN_PLUGIN_SHORTNAMES") {
            let set: HashSet<String> = raw.split(',').map(|s| s.trim().to_string()).collect();
            if set.contains(name) {
                return true;
            }
        }
        // Minimal fallback set
        const KNOWN: &[&str] = &[
            "ArrayBox",
            "MapBox",
            "StringBox",
            "ConsoleBox",
            "FileBox",
            "PathBox",
            "MathBox",
            "IntegerBox",
            "TOMLBox",
        ];
        KNOWN.iter().any(|k| *k == name)
    }
}

// Global BoxIndex view (rebuilt on-demand)
static GLOBAL: Lazy<RwLock<BoxIndex>> = Lazy::new(|| RwLock::new(BoxIndex::default()));

// Global resolve cache (keyed by tgt|base|strict|paths)
static RESOLVE_CACHE: Lazy<RwLock<HashMap<String, String>>> =
    Lazy::new(|| RwLock::new(HashMap::new()));

// Track env/file state to invalidate index and cache when changed
#[derive(Clone, Default)]
struct IndexState {
    aliases_env: Option<String>,
    toml_mtime: Option<SystemTime>,
    toml_size: Option<u64>,
}

static LAST_STATE: Lazy<Mutex<IndexState>> = Lazy::new(|| Mutex::new(IndexState::default()));

pub fn refresh_box_index() {
    let next = BoxIndex::build_current();
    if let Ok(mut w) = GLOBAL.write() {
        *w = next;
    }
}

pub fn get_box_index() -> BoxIndex {
    GLOBAL.read().ok().map(|g| g.clone()).unwrap_or_default()
}

pub fn cache_get(key: &str) -> Option<String> {
    RESOLVE_CACHE.read().ok().and_then(|m| m.get(key).cloned())
}

pub fn cache_put(key: &str, value: String) {
    if let Ok(mut m) = RESOLVE_CACHE.write() {
        m.insert(key.to_string(), value);
    }
}

pub fn cache_clear() {
    if let Ok(mut m) = RESOLVE_CACHE.write() {
        m.clear();
    }
}

/// Rebuild BoxIndex and clear resolve cache if env/toml changed
pub fn rebuild_if_env_changed() {
    let cur_env = std::env::var("NYASH_ALIASES").ok();
    let meta = std::fs::metadata("nyash.toml").ok();
    let (mtime, size) = if let Some(m) = meta {
        (m.modified().ok(), Some(m.len()))
    } else {
        (None, None)
    };
    let mut last = LAST_STATE.lock().expect("state");
    let changed = last.aliases_env != cur_env || last.toml_mtime != mtime || last.toml_size != size;
    if changed {
        last.aliases_env = cur_env;
        last.toml_mtime = mtime;
        last.toml_size = size;
        refresh_box_index();
        cache_clear();
    }
}

#[derive(Clone, Debug, Default)]
pub struct PluginMeta {
    pub prefix: Option<String>,
    pub require_prefix: bool,
    pub expose_short_names: bool,
}

#[allow(dead_code)]
pub fn get_plugin_meta(plugin: &str) -> Option<PluginMeta> {
    GLOBAL
        .read()
        .ok()
        .and_then(|g| g.plugin_meta.get(plugin).cloned())
}
