//! Faithful port of
//! `std::optional<std::string> TypeFunctionRuntime::registerFunction_DEPRECATED(AstStatTypeFunction* function)`
//! (Analysis/src/TypeFunctionRuntime.cpp:58-142).
use crate::functions::check_result_for_error_deprecated::check_result_for_error_deprecated;
use crate::records::type_function_runtime::TypeFunctionRuntime;
use crate::type_aliases::lua_state::lua_State;
use alloc::string::String;
use core::ffi::c_void;
use luaur_ast::records::allocator::Allocator;
use luaur_ast::records::ast_array::AstArray;
use luaur_ast::records::ast_expr::AstExpr;
use luaur_ast::records::ast_name_table::AstNameTable;
use luaur_ast::records::ast_stat::AstStat;
use luaur_ast::records::ast_stat_block::AstStatBlock;
use luaur_ast::records::ast_stat_return::AstStatReturn;
use luaur_ast::records::ast_stat_type_function::AstStatTypeFunction;
use luaur_ast::records::location::Location;
use luaur_ast::records::parse_result::ParseResult;
use luaur_ast::type_aliases::cst_node_map::CstNodeMap;
use luaur_bytecode::records::bytecode_builder::BytecodeBuilder;
use luaur_common::functions::format::format;
use luaur_compiler::records::compile_error::CompileError;
use luaur_compiler::records::compile_options::CompileOptions;
use luaur_vm::functions::lua_resume::lua_resume;
use luaur_vm::functions::lua_setreadonly::lua_setreadonly;
use luaur_vm::functions::lua_settable::lua_settable;
use luaur_vm::functions::lua_xmove::lua_xmove;
use luaur_vm::functions::luau_load::luau_load;

impl TypeFunctionRuntime {
    pub unsafe fn register_function_deprecated(
        &mut self,
        function: *mut AstStatTypeFunction,
    ) -> Option<String> {
        // If evaluation is disabled, we do not generate additional error messages
        if !self.allow_evaluation {
            return None;
        }

        // Do not evaluate type functions with parse errors inside
        if (*function).has_errors {
            return None;
        }

        self.prepare_state();

        // lua_State* global = state.get();
        let global = self.state.0;
        let global_vm = global as *mut luaur_vm::records::lua_state::lua_State;

        // Fetch to check if function is already registered
        // lua_pushlightuserdata(global, function); lua_gettable(global, LUA_REGISTRYINDEX);
        luaur_vm::macros::lua_pushlightuserdata::lua_pushlightuserdata(
            global as *mut c_void,
            function as *mut c_void,
        );
        luaur_vm::functions::lua_gettable::lua_gettable(
            global_vm,
            luaur_vm::macros::lua_registryindex::LUA_REGISTRYINDEX,
        );

        // if (!lua_isnil(global, -1)) { lua_pop(global, 1); return std::nullopt; }
        if !luaur_vm::lua_isnil!(global_vm, -1) {
            luaur_vm::macros::lua_pop::lua_pop(global_vm, 1);
            return None;
        }

        luaur_vm::macros::lua_pop::lua_pop(global_vm, 1);

        // AstName name = function->name;
        let name = (*function).name;
        let name_str = ast_name_to_string(name.value);

        // Construct ParseResult containing the type function
        // Allocator allocator; AstNameTable names(allocator);
        let mut allocator = Allocator::allocator();
        let mut names = AstNameTable::new(&mut allocator);

        // AstExpr* exprFunction = function->body;
        let mut expr_function: *mut AstExpr = (*function).body as *mut AstExpr;
        // AstArray<AstExpr*> exprReturns{&exprFunction, 1};
        let expr_returns: AstArray<*mut AstExpr> = AstArray {
            data: &mut expr_function as *mut *mut AstExpr,
            size: 1,
        };
        // AstStatReturn stmtReturn{Location{}, exprReturns};
        let mut stmt_return = AstStatReturn::new(Location::default(), expr_returns);
        // AstStat* stmtArray[] = {&stmtReturn};
        let mut stmt_array: [*mut AstStat; 1] =
            [&mut stmt_return as *mut AstStatReturn as *mut AstStat];
        // AstArray<AstStat*> stmts{stmtArray, 1};
        let stmts: AstArray<*mut AstStat> = AstArray {
            data: stmt_array.as_mut_ptr(),
            size: 1,
        };
        // AstStatBlock exec{Location{}, stmts};
        let mut exec = AstStatBlock::new(Location::default(), stmts, true);
        // ParseResult parseResult{&exec, 1, {}, {}, {}, CstNodeMap{nullptr}};
        let parse_result = ParseResult {
            root: &mut exec as *mut AstStatBlock,
            lines: 1,
            hotcomments: alloc::vec::Vec::new(),
            errors: alloc::vec::Vec::new(),
            comment_locations: alloc::vec::Vec::new(),
            cst_node_map: CstNodeMap::new(core::ptr::null_mut()),
        };

        // BytecodeBuilder builder;
        let mut builder = BytecodeBuilder::default();
        let options = CompileOptions::default();

        // try { compileOrThrow(builder, parseResult, names); }
        // catch (CompileError& e) {
        //     return format("'%s' type function failed to compile with error message: %s", name.value, e.what());
        // }
        let compile_outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            luaur_compiler::functions::compile_or_throw_compiler::compile_or_throw_bytecode_builder_parse_result_ast_name_table_compile_options(
                &mut builder,
                &parse_result,
                &mut names,
                &options,
            );
        }));

        if let Err(payload) = compile_outcome {
            if let Some(e) = payload.downcast_ref::<CompileError>() {
                let what = alloc::string::ToString::to_string(e);
                return Some(format(format_args!(
                    "'{}' type function failed to compile with error message: {}",
                    name_str, what
                )));
            }
            // Non-CompileError panic: re-raise to preserve faithful unwinding.
            std::panic::resume_unwind(payload);
        }

        // std::string bytecode = builder.getBytecode();
        let bytecode = builder.get_bytecode().clone();

        // Separate sandboxed thread for individual execution and private globals
        // lua_State* L = lua_newthread(global);
        let l_vm = luaur_vm::functions::lua_newthread::lua_newthread(global_vm);
        let l = l_vm as *mut lua_State;
        // LuauTempThreadPopper popper(global);
        let mut popper = crate::records::luau_temp_thread_popper::LuauTempThreadPopper::new(global);

        // luaL_sandboxthread(L);
        luaur_vm::functions::lua_l_sandboxthread::lua_l_sandboxthread(l_vm);

        // lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setreadonly(L, -1, true); lua_pop(L, 1);
        luaur_vm::functions::lua_pushvalue::lua_pushvalue(
            l_vm,
            luaur_vm::macros::lua_globalsindex::LUA_GLOBALSINDEX,
        );
        lua_setreadonly(l_vm, -1, 1);
        luaur_vm::macros::lua_pop::lua_pop(l_vm, 1);

        // Load bytecode into Luau state
        // if (auto error = checkResultForError_DEPRECATED(L, name.value, luau_load(...))) return error;
        let name_c = alloc::ffi::CString::new(name_str.as_bytes()).unwrap_or_default();
        let load_result = luau_load(
            l_vm,
            name_c.as_ptr(),
            bytecode.as_ptr() as *const core::ffi::c_char,
            bytecode.len(),
            0,
        );
        if let Some(error) = check_result_for_error_deprecated(l, &name_str, load_result) {
            popper.LuauTempThreadPopper();
            return Some(error);
        }

        // Execute the global function which should return our user-defined type function
        // if (auto error = checkResultForError_DEPRECATED(L, name.value, lua_resume(L, nullptr, 0))) return error;
        let resume_result = lua_resume(l_vm, core::ptr::null_mut(), 0);
        if let Some(error) = check_result_for_error_deprecated(l, &name_str, resume_result) {
            popper.LuauTempThreadPopper();
            return Some(error);
        }

        // if (!lua_isfunction(L, -1)) {
        //     lua_pop(L, 1);
        //     return format("Could not find '%s' type function in the global scope", name.value);
        // }
        if !luaur_vm::lua_isfunction!(l_vm, -1) {
            luaur_vm::macros::lua_pop::lua_pop(l_vm, 1);
            popper.LuauTempThreadPopper();
            return Some(format(format_args!(
                "Could not find '{}' type function in the global scope",
                name_str
            )));
        }

        // Store resulting function in the registry
        // lua_pushlightuserdata(global, function); lua_xmove(L, global, 1); lua_settable(global, LUA_REGISTRYINDEX);
        luaur_vm::macros::lua_pushlightuserdata::lua_pushlightuserdata(
            global as *mut c_void,
            function as *mut c_void,
        );
        lua_xmove(l_vm, global_vm, 1);
        lua_settable(
            global_vm,
            luaur_vm::macros::lua_registryindex::LUA_REGISTRYINDEX,
        );

        popper.LuauTempThreadPopper();
        None
    }
}

/// Helper: read an `AstName.value` (`*const c_char`) into an owned `String`.
unsafe fn ast_name_to_string(value: *const core::ffi::c_char) -> String {
    if value.is_null() {
        String::new()
    } else {
        core::ffi::CStr::from_ptr(value)
            .to_string_lossy()
            .into_owned()
    }
}
