use crate::{NodeZoomLayerMap, TilePoint, Zoom};
use mlua::{Lua, Result as LuaResult, UserData};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

pub fn load_lua_script(lua: &Lua, lua_code: &str) -> LuaResult<mlua::Function> {
    // Execute the Lua script to define the functions
    lua.load(lua_code).exec()?;

    // Get the `node_function` from the Lua globals
    let globals = lua.globals();
    let node_function: mlua::Function = globals.get("node_function")?;
    Ok(node_function)
}

pub struct LuaState {
    pub lua: Lua,
    pub node_function: mlua::Function,
}

pub enum LuaProcess {
    None,
    Some(LuaState),
}

#[derive(Debug, Clone)]
pub struct OsmNode {
    id: i64,
    zoom: Zoom,
    tile_point: TilePoint,
    tags: std::collections::HashMap<String, String>,
}

impl OsmNode {
    pub fn new(id: i64, zoom: Zoom, tile_point: TilePoint, tags: HashMap<String, String>) -> Self {
        Self {
            id,
            zoom,
            tile_point,
            tags,
        }
    }
}

// Implement `UserData` for `OsmNode` to expose it to Lua
impl UserData for OsmNode {
    fn add_methods<'lua, M: mlua::UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("find", |_, node, key: String| {
            // Return the value for the given key, or nil if the key doesn't exist
            Ok(node.tags.get(&key).cloned())
        });
        methods.add_method("get_id", |_, node, ()| Ok(node.id));
        methods.add_method("get_lat", |_, node, ()| Ok(node.tile_point.x));
        methods.add_method("get_lon", |_, node, ()| Ok(node.tile_point.y));
        methods.add_method("find", |_, node, key: String| {
            Ok(node.tags.get(&key).cloned())
        });
    }
}

pub fn call_node_function(
    lua: &Lua,
    node_function: &mlua::Function,
    node: OsmNode,
    layered_nodes: Rc<RefCell<NodeZoomLayerMap>>,
) -> LuaResult<()> {
    // Create a Lua table for the output function
    let output = lua.create_function(|_, (node,): (mlua::Value,)| {
        println!("Output node: {:?}", node);
        Ok(())
    })?;

    let find_function = create_find_function(lua, node.clone())?;
    lua.globals().set("Find", find_function)?;

    let layer_function = create_layer_function(lua, layered_nodes.clone(), node.clone())?;
    lua.globals().set("Layer", layer_function)?;

    // Call the Lua function with the node and output
    node_function.call((node, output))
}

fn create_find_function(lua: &Lua, node: OsmNode) -> LuaResult<mlua::Function> {
    lua.create_function(move |_, key: String| Ok(node.tags.get(&key).cloned()))
}

fn create_layer_function(
    lua: &Lua,
    layered_nodes: Rc<RefCell<NodeZoomLayerMap>>,
    node: OsmNode,
) -> LuaResult<mlua::Function> {
    lua.create_function(move |_, layer_name: String| {
        Ok(layered_nodes.clone().borrow_mut().insert(
            node.zoom.clone(),
            layer_name,
            &node.tile_point,
        ))
    })
}
