//! `regex::` — non-backtracking regular expressions (Rust `regex` crate).
//!
//! F-P-014: compiled patterns are cached in a thread-local LRU so a
//! tight script loop like `regex::is_match("\\d+", x)` doesn't recompile
//! every iteration. Compile dominates `is_match` on short strings;
//! caching shrinks per-call cost to a HashMap probe + `Arc::clone`.

use std::cell::RefCell;
use std::num::NonZeroUsize;
use std::sync::Arc;

use lru::LruCache;
use regex::Regex;
use rhai::{Array, Dynamic, Engine as RhaiEngine, EvalAltResult, Module};

/// Per-thread cap on compiled regex patterns. 128 is well above what
/// any sensible script uses but bounded enough to keep memory in
/// check on a fleet of long-running threads.
const REGEX_CACHE_SIZE: usize = 128;

thread_local! {
    static REGEX_CACHE: RefCell<LruCache<String, Arc<Regex>>> = RefCell::new(
        LruCache::new(NonZeroUsize::new(REGEX_CACHE_SIZE).expect("non-zero"))
    );
}

pub fn register(engine: &mut RhaiEngine) {
    let mut module = Module::new();
    register_is_match(&mut module);
    register_find(&mut module);
    register_find_all(&mut module);
    register_replace(&mut module);
    register_replace_all(&mut module);
    register_split(&mut module);
    register_captures(&mut module);
    engine.register_static_module("regex", module.into());
}

fn compile(pattern: &str) -> Result<Arc<Regex>, Box<EvalAltResult>> {
    REGEX_CACHE.with(|cell| {
        if let Some(cached) = cell.borrow_mut().get(pattern) {
            return Ok(cached.clone());
        }
        let re = Arc::new(
            Regex::new(pattern)
                .map_err(|e| -> Box<EvalAltResult> { format!("invalid regex: {e}").into() })?,
        );
        cell.borrow_mut().put(pattern.to_string(), re.clone());
        Ok(re)
    })
}

fn register_is_match(module: &mut Module) {
    module.set_native_fn(
        "is_match",
        |pattern: &str, text: &str| -> Result<bool, Box<EvalAltResult>> {
            Ok(compile(pattern)?.is_match(text))
        },
    );
}

fn register_find(module: &mut Module) {
    module.set_native_fn(
        "find",
        |pattern: &str, text: &str| -> Result<Dynamic, Box<EvalAltResult>> {
            Ok(compile(pattern)?
                .find(text)
                .map_or(Dynamic::UNIT, |m| Dynamic::from(m.as_str().to_string())))
        },
    );
}

fn register_find_all(module: &mut Module) {
    module.set_native_fn(
        "find_all",
        |pattern: &str, text: &str| -> Result<Array, Box<EvalAltResult>> {
            Ok(compile(pattern)?
                .find_iter(text)
                .map(|m| Dynamic::from(m.as_str().to_string()))
                .collect())
        },
    );
}

fn register_replace(module: &mut Module) {
    module.set_native_fn(
        "replace",
        |pattern: &str, text: &str, replacement: &str| -> Result<String, Box<EvalAltResult>> {
            Ok(compile(pattern)?.replace(text, replacement).into_owned())
        },
    );
}

fn register_replace_all(module: &mut Module) {
    module.set_native_fn(
        "replace_all",
        |pattern: &str, text: &str, replacement: &str| -> Result<String, Box<EvalAltResult>> {
            Ok(compile(pattern)?
                .replace_all(text, replacement)
                .into_owned())
        },
    );
}

fn register_split(module: &mut Module) {
    module.set_native_fn(
        "split",
        |pattern: &str, text: &str| -> Result<Array, Box<EvalAltResult>> {
            Ok(compile(pattern)?
                .split(text)
                .map(|s| Dynamic::from(s.to_string()))
                .collect())
        },
    );
}

fn register_captures(module: &mut Module) {
    module.set_native_fn(
        "captures",
        |pattern: &str, text: &str| -> Result<Dynamic, Box<EvalAltResult>> {
            let re = compile(pattern)?;
            Ok(re.captures(text).map_or(Dynamic::UNIT, |caps| {
                let arr: Array = caps
                    .iter()
                    .map(|m| m.map_or(Dynamic::UNIT, |m| Dynamic::from(m.as_str().to_string())))
                    .collect();
                Dynamic::from(arr)
            }))
        },
    );
}
