//! Process-wide memoization of results.

use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};

use crate::options::Resolved;

/// A cache key built from the raw inputs and resolved options.
///
/// The key concatenates the raw `min` and `max` with the four option values, so
/// `"001"` and `"1"` produce distinct keys and each option combination is its
/// own entry.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Key(String);

impl Key {
    /// Build a key from raw inputs and resolved options.
    pub(crate) fn new(min: &str, max: &str, opts: &Resolved) -> Key {
        Key(format!(
            "{min}:{max}={}{}{}{}",
            opts.relax_zeros, opts.shorthand, opts.capture, opts.wrap_false,
        ))
    }
}

fn store() -> &'static Mutex<HashMap<Key, String>> {
    static STORE: OnceLock<Mutex<HashMap<Key, String>>> = OnceLock::new();
    STORE.get_or_init(|| Mutex::new(HashMap::new()))
}

/// Look up a cached result.
pub(crate) fn get(key: &Key) -> Option<String> {
    store().lock().unwrap().get(key).cloned()
}

/// Store a result.
pub(crate) fn put(key: Key, result: &str) {
    store().lock().unwrap().insert(key, result.to_string());
}

/// Empty the cache.
///
/// The cache is a speed optimization. Clearing it does not change any returned
/// string. Call it to reclaim memory or to measure cold compile time.
pub fn clear_cache() {
    store().lock().unwrap().clear();
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::options::Options;
    use std::sync::Mutex;

    // Serialize the size-sensitive tests so they cannot interleave on the
    // shared store.
    static GUARD: Mutex<()> = Mutex::new(());

    fn opts() -> Resolved {
        Options::default().resolve()
    }

    #[test]
    fn put_then_get_round_trips() {
        let _g = GUARD.lock().unwrap();
        clear_cache();
        let key = Key::new("12", "34", &opts());
        assert_eq!(get(&key), None);
        put(key.clone(), "body");
        assert_eq!(get(&key).as_deref(), Some("body"));
    }

    #[test]
    fn put_adds_exactly_one_entry_per_fresh_key() {
        let _g = GUARD.lock().unwrap();
        clear_cache();
        let o = opts();
        put(Key::new("1", "9", &o), "a");
        assert_eq!(store().lock().unwrap().len(), 1);
        // A second put on the same key overwrites rather than growing.
        put(Key::new("1", "9", &o), "a");
        assert_eq!(store().lock().unwrap().len(), 1);
        // A distinct key grows the map.
        put(Key::new("1", "99", &o), "b");
        assert_eq!(store().lock().unwrap().len(), 2);
    }
}
