// FILE: src/modules/extend.rs

use crate::parser::interpreter::eval::eval_expression;
use crate::parser::tokens::tokenize_pick_args;
use crate::parser::interpreter::Interpreter;
use crate::parser::types::Value;
use libloading::{Library, Symbol};
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
use std::ffi::c_void;
use std::path::Path;

pub fn handle_extend_call(interp: &mut Interpreter, expr: &str) -> Result<Value, String> {
    let inside = &expr["extend(".len()..expr.len() - 1].trim();
    let parts = tokenize_pick_args(inside, interp.is_verbose())?;

    if parts.is_empty() {
        return Err("extend(...) requires at least one argument (the library name/path)".to_string());
    }
    if parts.len() > 2 {
        return Err("extend(...) => at most 2 arguments: (libBaseName[, extraString])".to_string());
    }

    let so_path_val = eval_expression(interp, &parts[0])?;
    let so_path_raw = match so_path_val {
        Value::StrArray(ref ss) if ss.len() == 1 => ss[0].clone(),
        _ => {
            return Err(
                "extend(...) => first arg must be a single string for the plugin name/path".to_string()
            );
        }
    };

    let maybe_extra: Option<String> = if parts.len() == 2 {
        let val2 = eval_expression(interp, &parts[1])?;
        match val2 {
            Value::StrArray(ref ss) if ss.len() == 1 => Some(ss[0].clone()),
            Value::IntArray(ref ia) if ia.len() == 1 => Some(ia[0].to_string()),
            Value::Int(i) => Some(i.to_string()),
            _ => None,
        }
    } else {
        None
    };

    let user_path = Path::new(&so_path_raw);
    let filename = match user_path.file_name() {
        Some(osfn) => osfn.to_string_lossy().into_owned(),
        None => return Err(format!("extend => cannot extract filename from '{}'", so_path_raw)),
    };

    let _parent_dir = user_path.parent().unwrap_or_else(|| Path::new(""));

    let base_str = filename
        .trim_end_matches(".so")
        .trim_end_matches(".dll")
        .trim_end_matches(".dylib");
    let actual_filename = format!("{}mumu{}{}", DLL_PREFIX, base_str, DLL_SUFFIX);

    // 1. Try current directory (where binary is run)
    // 2. Try in plugin-named subfolder: e.g. "array/libmumuarray.so"
    // 3. Try /usr/local/lib
    let try_paths = vec![
        actual_filename.clone(),
        format!("{}/{}", base_str, actual_filename),
        format!("/usr/local/lib/{}", actual_filename),
    ];

    let old_names = interp
        .get_dynamic_functions_for_clone()
        .keys()
        .map(|k| k.clone())
        .collect::<Vec<_>>();

    let mut lib_opt: Option<Library> = None;
    let mut _last_err = None;

    for p in &try_paths {
        if interp.is_verbose() {
            eprintln!("[extend] Trying path: '{}'", p);
        }
        match unsafe { Library::new(p) } {
            Ok(l) => {
                lib_opt = Some(l);
                break;
            }
            Err(e) => {
                _last_err = Some(e.to_string());
                continue;
            }
        }
    }

    let lib = match lib_opt {
        Some(l) => l,
        None => {
            let msg = format!(
                "extend error: extend => could not load plugin. Tried:\n{}\n",
                try_paths
                    .iter()
                    .map(|l| format!("  - {}", l))
                    .collect::<Vec<_>>()
                    .join("\n")
            );
            return Err(msg);
        }
    };

    unsafe {
        let maybe_init: std::result::Result<
            Symbol<unsafe extern "C" fn(*mut c_void, *const c_void) -> i32>,
            libloading::Error,
        > = lib.get(b"Cargo_lock");

        if let Ok(init_fn) = maybe_init {
            let interp_ptr = interp as *mut Interpreter as *mut c_void;
            let extra_ptr: *const c_void = if let Some(ref extra_str) = maybe_extra {
                extra_str.as_ptr() as *const c_void
            } else {
                std::ptr::null()
            };

            if interp.is_verbose() {
                eprintln!("[extend] Found Cargo_lock => calling it now...");
            }

            let rc = init_fn(interp_ptr, extra_ptr);
            if rc != 0 {
                return Err(format!(
                    "extend => plugin returned error code {} from Cargo_lock()",
                    rc
                ));
            }

            if interp.is_verbose() {
                eprintln!(
                    "[extend] plugin => Cargo_lock() success. Extra arg = {:?}",
                    maybe_extra
                );
            }
        } else if interp.is_verbose() {
            eprintln!("[extend] plugin => no Cargo_lock symbol found. Kept loaded.");
        }
    }

    interp.add_library_handle(lib);

    let new_set = interp
        .get_dynamic_functions_for_clone()
        .keys()
        .map(|k| k.clone())
        .collect::<Vec<_>>();
    let mut newly_imported = Vec::new();
    for nm in new_set {
        if !old_names.contains(&nm) {
            newly_imported.push(nm);
        }
    }
    newly_imported.sort();

    Ok(Value::StrArray(newly_imported))
}
