// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Compiled-pattern caches for Rego builtins.
//!
//! When the `cache` feature is enabled, compiled [`regex::Regex`] and
//! [`globset::GlobMatcher`] objects are held in bounded LRU caches so that
//! repeated evaluations of the same pattern avoid recompilation.
//!
//! # Examples
//!
//! ```ignore
//! use regorus::cache;
//!
//! // Configure cache capacities (0 = disabled).
//! cache::configure(cache::Config {
//!     regex: 256,
//!     glob: 128,
//! });
//!
//! // Flush all cached patterns.
//! cache::clear();
//! ```

#[cfg(any(feature = "regex", feature = "glob"))]
use core::num::NonZeroUsize;
#[cfg(any(feature = "regex", feature = "glob"))]
use lazy_static::lazy_static;
#[cfg(all(feature = "std", any(feature = "regex", feature = "glob")))]
use parking_lot::Mutex;
#[cfg(all(not(feature = "std"), any(feature = "regex", feature = "glob")))]
use spin::Mutex;

#[cfg(any(feature = "regex", feature = "glob"))]
use alloc::string::String;

/// Configuration for builtin pattern caches.
///
/// Each field controls the maximum number of compiled patterns held in the
/// corresponding LRU cache.  A value of `0` disables that cache entirely
/// (every lookup recompiles).  Values exceeding [`Config::MAX_CAPACITY`] are
/// clamped silently.
#[derive(Debug, Clone, Copy)]
pub struct Config {
    /// Maximum compiled regex patterns (default 256).
    pub regex: usize,
    /// Maximum compiled glob matchers (default 128).
    pub glob: usize,
}

impl Config {
    /// Hard upper bound for any single cache capacity (2^16 = 65 536).
    pub const MAX_CAPACITY: usize = 1 << 16;
}

impl Default for Config {
    fn default() -> Self {
        Self {
            regex: 256,
            glob: 128,
        }
    }
}

// ---------------------------------------------------------------------------
// Internal generic LRU wrapper
// ---------------------------------------------------------------------------

#[cfg(any(feature = "regex", feature = "glob"))]
pub(crate) struct LruCache<V> {
    inner: Option<lru::LruCache<String, V>>,
}

#[cfg(any(feature = "regex", feature = "glob"))]
impl<V> LruCache<V> {
    pub(crate) fn new(capacity: usize) -> Self {
        Self {
            inner: NonZeroUsize::new(capacity).map(lru::LruCache::new),
        }
    }

    /// Look up a key, returning a reference if present. Promotes to most-recent.
    pub(crate) fn get(&mut self, key: &str) -> Option<&V> {
        self.inner.as_mut()?.get(key)
    }

    /// Insert a key-value pair. Evicts the least-recently-used entry if full.
    pub(crate) fn put(&mut self, key: String, value: V) {
        if let Some(cache) = self.inner.as_mut() {
            cache.put(key, value);
        }
    }

    /// Remove all entries.
    pub(crate) fn clear(&mut self) {
        if let Some(cache) = self.inner.as_mut() {
            cache.clear();
        }
    }

    /// Resize the cache. If new capacity is 0, disables the cache.
    pub(crate) fn resize(&mut self, capacity: usize) {
        match NonZeroUsize::new(capacity) {
            Some(cap) => match self.inner.as_mut() {
                Some(cache) => cache.resize(cap),
                None => self.inner = Some(lru::LruCache::new(cap)),
            },
            None => {
                self.inner = None;
            }
        }
    }

    /// Number of entries currently cached.
    #[allow(dead_code)]
    pub(crate) fn len(&self) -> usize {
        self.inner.as_ref().map_or(0, lru::LruCache::len)
    }
}

// ---------------------------------------------------------------------------
// Global regex cache
// ---------------------------------------------------------------------------

#[cfg(feature = "regex")]
lazy_static! {
    pub(crate) static ref REGEX_CACHE: Mutex<LruCache<regex::Regex>> =
        Mutex::new(LruCache::new(Config::default().regex));
}

// ---------------------------------------------------------------------------
// Global glob cache
// ---------------------------------------------------------------------------

#[cfg(feature = "glob")]
lazy_static! {
    pub(crate) static ref GLOB_CACHE: Mutex<LruCache<globset::GlobMatcher>> =
        Mutex::new(LruCache::new(Config::default().glob));
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/// Apply a new cache configuration.
///
/// Resizes each cache to the specified capacity.  Existing entries are
/// preserved (subject to LRU eviction if the new capacity is smaller).
/// Values exceeding [`Config::MAX_CAPACITY`] are clamped.
pub fn configure(config: Config) {
    let regex = config.regex.min(Config::MAX_CAPACITY);
    let glob = config.glob.min(Config::MAX_CAPACITY);

    #[cfg(feature = "regex")]
    REGEX_CACHE.lock().resize(regex);

    #[cfg(feature = "glob")]
    GLOB_CACHE.lock().resize(glob);

    // Suppress unused-variable warnings when neither regex nor glob is enabled.
    let _ = (regex, glob);
}

/// Remove all entries from every pattern cache.
pub fn clear() {
    #[cfg(feature = "regex")]
    REGEX_CACHE.lock().clear();

    #[cfg(feature = "glob")]
    GLOB_CACHE.lock().clear();
}
