/*!
 * WASM Runtime Imports - External function imports for WASM modules
 *
 * Phase 8.2 PoC1: Implements env.print import for debugging
 * Future: Additional runtime functions (file I/O, network, etc.)
 */

use super::WasmError;

/// Runtime import definitions for WASM modules
pub struct RuntimeImports {
    /// Available imports
    imports: Vec<ImportFunction>,
}

/// External function import definition
#[derive(Debug, Clone)]
pub struct ImportFunction {
    pub module: String,
    pub name: String,
    pub params: Vec<String>,
    pub result: Option<String>,
}

impl RuntimeImports {
    pub fn new() -> Self {
        let mut runtime = Self {
            imports: Vec::new(),
        };

        // Add standard runtime imports
        runtime.add_standard_imports();
        runtime
    }

    /// Add standard runtime function imports
    fn add_standard_imports(&mut self) {
        // env.print for debugging output
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "print".to_string(),
            params: vec!["i32".to_string()],
            result: None,
        });

        // env.print_str for string debugging (ptr, len)
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "print_str".to_string(),
            params: vec!["i32".to_string(), "i32".to_string()],
            result: None,
        });

        // Phase 9.7: Box FFI/ABI imports per BID specifications

        // env.console_log for console.log(message) - (string_ptr, string_len)
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "console_log".to_string(),
            params: vec!["i32".to_string(), "i32".to_string()],
            result: None,
        });

        // env.canvas_fillRect for canvas.fillRect(canvas_id, x, y, w, h, color)
        // Parameters: (canvas_id_ptr, canvas_id_len, x, y, w, h, color_ptr, color_len)
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "canvas_fillRect".to_string(),
            params: vec![
                "i32".to_string(),
                "i32".to_string(), // canvas_id (ptr, len)
                "i32".to_string(),
                "i32".to_string(),
                "i32".to_string(),
                "i32".to_string(), // x, y, w, h
                "i32".to_string(),
                "i32".to_string(), // color (ptr, len)
            ],
            result: None,
        });

        // env.canvas_fillText for canvas.fillText(canvas_id, text, x, y, font, color)
        // Parameters: (canvas_id_ptr, canvas_id_len, text_ptr, text_len, x, y, font_ptr, font_len, color_ptr, color_len)
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "canvas_fillText".to_string(),
            params: vec![
                "i32".to_string(),
                "i32".to_string(), // canvas_id (ptr, len)
                "i32".to_string(),
                "i32".to_string(), // text (ptr, len)
                "i32".to_string(),
                "i32".to_string(), // x, y
                "i32".to_string(),
                "i32".to_string(), // font (ptr, len)
                "i32".to_string(),
                "i32".to_string(), // color (ptr, len)
            ],
            result: None,
        });

        // Phase 9.77: BoxCall runtime functions

        // box_to_string - Convert any Box to string representation
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "box_to_string".to_string(),
            params: vec!["i32".to_string()], // box_ptr
            result: Some("i32".to_string()), // string_box_ptr
        });

        // box_print - Print any Box to console
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "box_print".to_string(),
            params: vec!["i32".to_string()], // box_ptr
            result: None,
        });

        // box_equals - Compare two Boxes for equality
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "box_equals".to_string(),
            params: vec!["i32".to_string(), "i32".to_string()], // box1_ptr, box2_ptr
            result: Some("i32".to_string()),                    // bool result
        });

        // box_clone - Clone a Box
        self.imports.push(ImportFunction {
            module: "env".to_string(),
            name: "box_clone".to_string(),
            params: vec!["i32".to_string()], // box_ptr
            result: Some("i32".to_string()), // cloned_box_ptr
        });

        // Future: env.file_read, env.file_write for file I/O
        // Future: env.http_request for network access
    }

    /// Get all import declarations in WAT format
    pub fn get_imports(&self) -> Vec<String> {
        self.imports
            .iter()
            .map(|import| {
                let params = if import.params.is_empty() {
                    String::new()
                } else {
                    format!("(param {})", import.params.join(" "))
                };

                let result = if let Some(ref result_type) = import.result {
                    format!("(result {})", result_type)
                } else {
                    String::new()
                };

                format!(
                    "(import \"{}\" \"{}\" (func ${} {} {}))",
                    import.module, import.name, import.name, params, result
                )
            })
            .collect()
    }

    /// Add custom import function
    pub fn add_import(
        &mut self,
        module: String,
        name: String,
        params: Vec<String>,
        result: Option<String>,
    ) {
        self.imports.push(ImportFunction {
            module,
            name,
            params,
            result,
        });
    }

    /// Check if an import is available
    pub fn has_import(&self, name: &str) -> bool {
        self.imports.iter().any(|import| import.name == name)
    }

    /// Get import function by name
    pub fn get_import(&self, name: &str) -> Option<&ImportFunction> {
        self.imports.iter().find(|import| import.name == name)
    }

    /// Generate JavaScript import object for browser execution
    pub fn get_js_import_object(&self) -> String {
        let mut js = String::new();
        js.push_str("const importObject = {\n");

        // Group by module
        let mut modules: std::collections::HashMap<String, Vec<&ImportFunction>> =
            std::collections::HashMap::new();
        for import in &self.imports {
            modules
                .entry(import.module.clone())
                .or_default()
                .push(import);
        }

        for (module_name, functions) in modules {
            js.push_str(&format!("  {}: {{\n", module_name));

            for function in functions {
                match function.name.as_str() {
                    "print" => {
                        js.push_str("    print: (value) => console.log(value),\n");
                    }
                    "print_str" => {
                        js.push_str("    print_str: (ptr, len) => {\n");
                        js.push_str("      const memory = instance.exports.memory;\n");
                        js.push_str("      const str = new TextDecoder().decode(new Uint8Array(memory.buffer, ptr, len));\n");
                        js.push_str("      console.log(str);\n");
                        js.push_str("    },\n");
                    }
                    "console_log" => {
                        js.push_str("    console_log: (ptr, len) => {\n");
                        js.push_str("      const memory = instance.exports.memory;\n");
                        js.push_str("      const str = new TextDecoder().decode(new Uint8Array(memory.buffer, ptr, len));\n");
                        js.push_str("      console.log(str);\n");
                        js.push_str("    },\n");
                    }
                    "canvas_fillRect" => {
                        js.push_str("    canvas_fillRect: (canvasIdPtr, canvasIdLen, x, y, w, h, colorPtr, colorLen) => {\n");
                        js.push_str("      const memory = instance.exports.memory;\n");
                        js.push_str("      const canvasId = new TextDecoder().decode(new Uint8Array(memory.buffer, canvasIdPtr, canvasIdLen));\n");
                        js.push_str("      const color = new TextDecoder().decode(new Uint8Array(memory.buffer, colorPtr, colorLen));\n");
                        js.push_str("      const canvas = document.getElementById(canvasId);\n");
                        js.push_str("      if (canvas) {\n");
                        js.push_str("        const ctx = canvas.getContext('2d');\n");
                        js.push_str("        ctx.fillStyle = color;\n");
                        js.push_str("        ctx.fillRect(x, y, w, h);\n");
                        js.push_str("      }\n");
                        js.push_str("    },\n");
                    }
                    "canvas_fillText" => {
                        js.push_str("    canvas_fillText: (canvasIdPtr, canvasIdLen, textPtr, textLen, x, y, fontPtr, fontLen, colorPtr, colorLen) => {\n");
                        js.push_str("      const memory = instance.exports.memory;\n");
                        js.push_str("      const canvasId = new TextDecoder().decode(new Uint8Array(memory.buffer, canvasIdPtr, canvasIdLen));\n");
                        js.push_str("      const text = new TextDecoder().decode(new Uint8Array(memory.buffer, textPtr, textLen));\n");
                        js.push_str("      const font = new TextDecoder().decode(new Uint8Array(memory.buffer, fontPtr, fontLen));\n");
                        js.push_str("      const color = new TextDecoder().decode(new Uint8Array(memory.buffer, colorPtr, colorLen));\n");
                        js.push_str("      const canvas = document.getElementById(canvasId);\n");
                        js.push_str("      if (canvas) {\n");
                        js.push_str("        const ctx = canvas.getContext('2d');\n");
                        js.push_str("        ctx.font = font;\n");
                        js.push_str("        ctx.fillStyle = color;\n");
                        js.push_str("        ctx.fillText(text, x, y);\n");
                        js.push_str("      }\n");
                        js.push_str("    },\n");
                    }
                    _ => {
                        js.push_str(&format!(
                            "    {}: () => {{ throw new Error('Not implemented: {}'); }},\n",
                            function.name, function.name
                        ));
                    }
                }
            }

            js.push_str("  },\n");
        }

        js.push_str("};\n");
        js
    }

    /// Generate Rust wasmtime import bindings
    pub fn get_wasmtime_imports(&self) -> Result<String, WasmError> {
        let mut rust_code = String::new();
        rust_code.push_str("// Wasmtime import bindings\n");
        rust_code.push_str("let mut imports = Vec::new();\n\n");

        for import in &self.imports {
            match import.name.as_str() {
                "print" => {
                    rust_code.push_str(
                        r#"
let print_func = wasmtime::Func::wrap(&mut store, |value: i32| {
    println!("{}", value);
});
imports.push(print_func.into());
"#,
                    );
                }
                _ => {
                    rust_code.push_str(&format!(
                        r#"
// TODO: Implement {} import
let {}_func = wasmtime::Func::wrap(&mut store, || {{
    panic!("Not implemented: {}")
}});
imports.push({}_func.into());
"#,
                        import.name, import.name, import.name, import.name
                    ));
                }
            }
        }

        Ok(rust_code)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_runtime_imports_creation() {
        let runtime = RuntimeImports::new();
        assert!(!runtime.imports.is_empty());
        assert!(runtime.has_import("print"));
    }

    #[test]
    fn test_import_wat_generation() {
        let runtime = RuntimeImports::new();
        let imports = runtime.get_imports();

        assert!(!imports.is_empty());
        assert!(imports[0].contains("import"));
        assert!(imports[0].contains("env"));
        assert!(imports[0].contains("print"));
    }

    #[test]
    fn test_custom_import_addition() {
        let mut runtime = RuntimeImports::new();
        runtime.add_import(
            "custom".to_string(),
            "test_func".to_string(),
            vec!["i32".to_string(), "i32".to_string()],
            Some("i32".to_string()),
        );

        assert!(runtime.has_import("test_func"));
        let import = runtime.get_import("test_func").unwrap();
        assert_eq!(import.module, "custom");
        assert_eq!(import.params.len(), 2);
        assert!(import.result.is_some());
    }

    #[test]
    fn test_js_import_object_generation() {
        let runtime = RuntimeImports::new();
        let js = runtime.get_js_import_object();

        assert!(js.contains("importObject"));
        assert!(js.contains("env"));
        assert!(js.contains("print"));
        assert!(js.contains("console.log"));
    }

    #[test]
    fn test_wasmtime_imports_generation() {
        let runtime = RuntimeImports::new();
        let rust_code = runtime.get_wasmtime_imports().unwrap();

        assert!(rust_code.contains("wasmtime::Func::wrap"));
        assert!(rust_code.contains("print_func"));
        assert!(rust_code.contains("println!"));
    }
}
