use mlua::prelude::*;

/// Classification of a Lua table as either a contiguous integer-keyed
/// array or a general key-value map.
pub(super) enum TableKind {
    /// Contiguous integer keys `1..=len` with no extra keys.
    Array(usize),
    /// General key-value pairs (may include integer keys).
    Map(Vec<(LuaValue, LuaValue)>),
}

/// Inspect a Lua table and classify it as array or map.
///
/// A table is considered an array when:
/// - `raw_len() > 0`
/// - every key is an integer in `1..=raw_len()`
/// - the number of pairs equals `raw_len()`
pub(super) fn classify(table: &LuaTable) -> LuaResult<TableKind> {
    let len = table.raw_len();
    let pairs: Vec<(LuaValue, LuaValue)> = table
        .pairs::<LuaValue, LuaValue>()
        .collect::<LuaResult<Vec<_>>>()?;

    let len_i64 = i64::try_from(len).unwrap_or(i64::MAX);
    let is_array = len > 0
        && pairs.len() == len
        && pairs
            .iter()
            .all(|(k, _)| matches!(k, LuaValue::Integer(i) if *i >= 1 && *i <= len_i64));

    if is_array {
        Ok(TableKind::Array(len))
    } else {
        Ok(TableKind::Map(pairs))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn lua() -> Lua {
        Lua::new()
    }

    #[test]
    fn empty_table_is_map() {
        let l = lua();
        let t: LuaTable = l.load("return {}").eval().unwrap();
        assert!(matches!(classify(&t).unwrap(), TableKind::Map(pairs) if pairs.is_empty()));
    }

    #[test]
    fn contiguous_array() {
        let l = lua();
        let t: LuaTable = l.load("return {10, 20, 30}").eval().unwrap();
        assert!(matches!(classify(&t).unwrap(), TableKind::Array(3)));
    }

    #[test]
    fn string_keyed_is_map() {
        let l = lua();
        let t: LuaTable = l.load("return {a=1, b=2}").eval().unwrap();
        assert!(matches!(classify(&t).unwrap(), TableKind::Map(pairs) if pairs.len() == 2));
    }

    #[test]
    fn mixed_keys_is_map() {
        let l = lua();
        let t: LuaTable = l.load("return {10, 20, a='x'}").eval().unwrap();
        assert!(matches!(classify(&t).unwrap(), TableKind::Map(pairs) if pairs.len() == 3));
    }

    #[test]
    fn sparse_int_keys_is_map() {
        let l = lua();
        let t: LuaTable = l
            .load("local t = {}; t[1] = 'a'; t[3] = 'c'; return t")
            .eval()
            .unwrap();
        assert!(matches!(classify(&t).unwrap(), TableKind::Map(_)));
    }

    #[test]
    fn single_element_array() {
        let l = lua();
        let t: LuaTable = l.load("return {'only'}").eval().unwrap();
        assert!(matches!(classify(&t).unwrap(), TableKind::Array(1)));
    }
}
