use mlua::prelude::*;
use std::path::{Path, PathBuf};

pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
    let t = lua.create_table()?;

    t.set(
        "join",
        lua.create_function(|_, parts: LuaMultiValue| {
            let mut path = PathBuf::new();
            for part in &parts {
                if let LuaValue::String(s) = part {
                    let segment: String = s.to_str()?.to_string();
                    path.push(&segment);
                }
            }
            Ok(path.to_string_lossy().to_string())
        })?,
    )?;

    t.set(
        "parent",
        lua.create_function(|_, p: String| {
            Ok(Path::new(&p)
                .parent()
                .map(|p| p.to_string_lossy().to_string()))
        })?,
    )?;

    t.set(
        "filename",
        lua.create_function(|_, p: String| {
            Ok(Path::new(&p)
                .file_name()
                .map(|f| f.to_string_lossy().to_string()))
        })?,
    )?;

    t.set(
        "stem",
        lua.create_function(|_, p: String| {
            Ok(Path::new(&p)
                .file_stem()
                .map(|f| f.to_string_lossy().to_string()))
        })?,
    )?;

    t.set(
        "ext",
        lua.create_function(|_, p: String| {
            Ok(Path::new(&p)
                .extension()
                .map(|e| e.to_string_lossy().to_string()))
        })?,
    )?;

    t.set(
        "absolute",
        lua.create_function(|_, p: String| {
            std::fs::canonicalize(&p)
                .map(|p| p.to_string_lossy().to_string())
                .map_err(LuaError::external)
        })?,
    )?;

    t.set(
        "is_absolute",
        lua.create_function(|_, p: String| Ok(Path::new(&p).is_absolute()))?,
    )?;

    Ok(t)
}

#[cfg(test)]
mod tests {
    use crate::runtime;

    fn eval<T: mlua::FromLua>(code: &str) -> T {
        let lua = runtime::create_lua(&[], &[]).unwrap();
        lua.load(code).eval().unwrap()
    }

    #[test]
    fn join_two_parts() {
        let s: String = eval(
            r#"
            local path = require("senl.path")
            return path.join("/usr", "local", "bin")
        "#,
        );
        assert_eq!(s, "/usr/local/bin");
    }

    #[test]
    fn parent_of_file() {
        let s: String = eval(
            r#"
            local path = require("senl.path")
            return path.parent("/usr/local/bin/foo")
        "#,
        );
        assert_eq!(s, "/usr/local/bin");
    }

    #[test]
    fn filename_extraction() {
        let s: String = eval(
            r#"
            local path = require("senl.path")
            return path.filename("/usr/local/config.toml")
        "#,
        );
        assert_eq!(s, "config.toml");
    }

    #[test]
    fn stem_without_extension() {
        let s: String = eval(
            r#"
            local path = require("senl.path")
            return path.stem("/usr/local/config.toml")
        "#,
        );
        assert_eq!(s, "config");
    }

    #[test]
    fn ext_extraction() {
        let s: String = eval(
            r#"
            local path = require("senl.path")
            return path.ext("/usr/local/config.toml")
        "#,
        );
        assert_eq!(s, "toml");
    }

    #[test]
    fn is_absolute_true() {
        let b: bool = eval(
            r#"
            local path = require("senl.path")
            return path.is_absolute("/usr/local")
        "#,
        );
        assert!(b);
    }

    #[test]
    fn is_absolute_false() {
        let b: bool = eval(
            r#"
            local path = require("senl.path")
            return path.is_absolute("relative/path")
        "#,
        );
        assert!(!b);
    }

    #[test]
    fn parent_of_root_is_nil() {
        let b: bool = eval(
            r#"
            local path = require("senl.path")
            return path.parent("/") == nil
        "#,
        );
        assert!(b);
    }
}
