use std::sync::Arc;

use etffi::{borrow_string, cstring::CStringSafe};

use crate::{js::{JSModuleMethod, SmartJSValue, create_callback, get_js_state, pxs_into_js, quickjs}, pxs_debug, shared::module::pxs_Module};

/// Module definition function
unsafe extern "C" fn init_module_function(ctx: *mut quickjs::JSContext, m: *mut quickjs::JSModuleDef) -> i32 {
    // Get module name.
    let module_name = unsafe {
        // Name ATOM
        let mna = quickjs::JS_GetModuleName(ctx, m);
        // Name ptr
        let mnp = quickjs::JS_AtomToCStringLen(ctx, std::ptr::null_mut(), mna);

        borrow_string!(mnp).to_string()
    };

    // Get state
    let state = get_js_state();
    
    unsafe {
        // Set methods
        if let Some(exports) = (*state).module_exports.get(&module_name) {
            // set
            let mut cstrsafe = CStringSafe::new();
            for export in exports {
                quickjs::JS_SetModuleExport(ctx, m, cstrsafe.new_string(&export.name), export.value.value);
            }
        }
    }

    0
}

/// Add a module to JS!
pub(super) fn add_module(context: *mut quickjs::JSContext, module: &Arc<pxs_Module>) {
    let mut cstrsafe = CStringSafe::new();

    // Set it up my man
    let js_mod = unsafe {
        quickjs::JS_NewCModule(context, cstrsafe.new_string(&module.name), Some(init_module_function))
    };

    let mut exports = vec![];
    // Create trampolines
    for method in module.callbacks.iter() {
        // Create method
        let mut cbk = create_callback(context, method.idx);
        cbk.owned = false;
        exports.push(JSModuleMethod{
            name: method.name.clone(),
            value: cbk
        });
        // Add export
        unsafe {
            quickjs::JS_AddModuleExport(context, js_mod, cstrsafe.new_string(&method.name));
        }
    }

    // Uh Uh Uh set variables
    for module_var in module.variables.iter() {
        // Create variables
        let var = pxs_into_js(context, &module_var.var);
        if let Err(err) = var {
            let err_msg = err.to_string();
            let mut exception = SmartJSValue::new_exception(context, err_msg, "PXSJSConversionError".to_string());
            exception.owned = false;
            exports.push(JSModuleMethod{
                name: module_var.name.clone(),
                value: exception
            });
        } else {
            let mut res = var.unwrap();
            res.owned = false;
            exports.push(JSModuleMethod { 
                name: module_var.name.clone(), 
                value: res, 
            });
        }

        // Add export
        unsafe {
            quickjs::JS_AddModuleExport(context, js_mod, cstrsafe.new_string(&module_var.name));
        }
    }

    // Save in state
    let state = get_js_state();
    unsafe {
        (*state).module_exports.insert(module.name.clone(), exports);

        // Save module
        (*state).modules.insert(module.name.clone(), js_mod);
    }

    // Add child modules
    for child in module.modules.iter() {
        add_module(context, child);
    }
}

/// Add a local module to JS engine.
pub(super) fn add_local_module(context: *mut quickjs::JSContext, code: &str, name: &str) -> *mut quickjs::JSModuleDef {
    let mut cstrsafe = CStringSafe::new();

    // Compile module
    let smart_module = SmartJSValue::new_owned(unsafe {
        quickjs::JS_Eval(context, cstrsafe.new_string(code), code.len(), cstrsafe.new_string(name), (quickjs::JS_EVAL_TYPE_MODULE | quickjs::JS_EVAL_FLAG_COMPILE_ONLY) as i32)
    }, context);

    // Check exception
    if smart_module.is_exception() || smart_module.is_error() {
        pxs_debug!("Error compiling module");
        return std::ptr::null_mut();
    }

    smart_module.get_module_ptr()
}