//! `random::` — CSPRNG primitives (`rand::rngs::OsRng`).
//!
//! Only the OS RNG is exposed. No "fast non-crypto" variant — scripts
//! should not pick between secure and insecure entropy. Output sizes
//! are capped to keep a single script call from blowing host memory.

use rand::distributions::{Alphanumeric, DistString};
use rand::{rngs::OsRng, Rng, RngCore};
use rhai::{Blob, Engine as RhaiEngine, EvalAltResult, Module};
use uuid::Uuid;

const MAX_BYTES: i64 = 65_536;
const MAX_STRING: i64 = 4_096;

pub fn register(engine: &mut RhaiEngine) {
    let mut module = Module::new();
    register_int(&mut module);
    register_float(&mut module);
    register_bytes(&mut module);
    register_string(&mut module);
    register_uuid(&mut module);
    engine.register_static_module("random", module.into());
}

fn register_int(module: &mut Module) {
    module.set_native_fn(
        "int",
        |min: i64, max: i64| -> Result<i64, Box<EvalAltResult>> {
            if min > max {
                return Err(format!("random::int: min ({min}) > max ({max})").into());
            }
            Ok(OsRng.gen_range(min..=max))
        },
    );
}

fn register_float(module: &mut Module) {
    module.set_native_fn("float", || -> Result<f64, Box<EvalAltResult>> {
        Ok(OsRng.gen::<f64>())
    });
}

fn register_bytes(module: &mut Module) {
    module.set_native_fn("bytes", |n: i64| -> Result<Blob, Box<EvalAltResult>> {
        if !(0..=MAX_BYTES).contains(&n) {
            return Err(format!("random::bytes: n must be in 0..={MAX_BYTES}, got {n}").into());
        }
        // Safe: n is non-negative and bounded by MAX_BYTES, which fits in usize.
        let len = usize::try_from(n).expect("n bounded above by MAX_BYTES");
        let mut buf = vec![0u8; len];
        OsRng.fill_bytes(&mut buf);
        Ok(buf)
    });
}

fn register_string(module: &mut Module) {
    module.set_native_fn("string", |n: i64| -> Result<String, Box<EvalAltResult>> {
        if !(0..=MAX_STRING).contains(&n) {
            return Err(format!("random::string: n must be in 0..={MAX_STRING}, got {n}").into());
        }
        let len = usize::try_from(n).expect("n bounded above by MAX_STRING");
        Ok(Alphanumeric.sample_string(&mut OsRng, len))
    });
}

fn register_uuid(module: &mut Module) {
    module.set_native_fn("uuid", || -> Result<String, Box<EvalAltResult>> {
        Ok(Uuid::new_v4().to_string())
    });
}
