/*!
 * WASM Target Generator - Generate WebAssembly import declarations
 */

use crate::bid::{BidDefinition, BidInterface, BidMethod, BidParameter, BidResult, BidError};
use crate::bid::codegen::{CodeGenOptions, GeneratedFile};
use std::path::PathBuf;

pub struct WasmGenerator;

impl WasmGenerator {
    /// Generate WASM import declarations and host implementation templates
    pub fn generate(bid: &BidDefinition, options: &CodeGenOptions) -> BidResult<Vec<GeneratedFile>> {
        let bid_name = bid.name();
        let mut files = Vec::new();
        
        // Generate WAT import declarations
        let wat_content = Self::generate_wat_imports(bid)?;
        let wat_path = options.output_dir.join(format!("{}_imports.wat", bid_name));
        files.push(GeneratedFile::new(wat_path, wat_content));
        
        // Generate JavaScript host implementation template
        let js_content = Self::generate_js_host_template(bid)?;
        let js_path = options.output_dir.join(format!("{}_host.js", bid_name));
        files.push(GeneratedFile::new(js_path, js_content));
        
        // Generate README with usage instructions
        let readme_content = Self::generate_readme(bid)?;
        let readme_path = options.output_dir.join("README.md");
        files.push(GeneratedFile::new(readme_path, readme_content));
        
        Ok(files)
    }
    
    /// Generate WAT import declarations
    fn generate_wat_imports(bid: &BidDefinition) -> BidResult<String> {
        let mut content = String::new();
        
        content.push_str(&format!(
            ";; WebAssembly import declarations for {}\n",
            bid.name()
        ));
        content.push_str(";; Generated by Nyash BID code generator\n\n");
        
        content.push_str("(module\n");
        
        for interface in &bid.interfaces {
            content.push_str(&format!("  ;; Interface: {}\n", interface.name));
            
            for method in &interface.methods {
                let wasm_import = Self::generate_wasm_import(interface, method)?;
                content.push_str(&format!("  {}\n", wasm_import));
            }
            
            content.push_str("\n");
        }
        
        content.push_str("  ;; Your WASM functions go here\n");
        content.push_str("  ;; Example:\n");
        content.push_str("  ;; (func $main (export \"main\")\n");
        content.push_str("  ;;   (call $env.console.log (i32.const 0) (i32.const 13)) ;; \"Hello, World!\"\n");
        content.push_str("  ;; )\n\n");
        
        content.push_str("  ;; Memory for string data\n");
        content.push_str("  (memory $memory 1)\n");
        content.push_str("  (export \"memory\" (memory $memory))\n");
        content.push_str(")\n");
        
        Ok(content)
    }
    
    /// Generate single WASM import declaration
    fn generate_wasm_import(interface: &BidInterface, method: &BidMethod) -> BidResult<String> {
        let import_name = format!("{}.{}", interface.name, method.name);
        let func_name = format!("${}", import_name);
        
        // Convert parameters to WASM types
        let mut param_types = Vec::new();
        for param in &method.params {
            let wasm_type = Self::bid_type_to_wasm_type(&param.get_type())?;
            param_types.push(wasm_type);
        }
        
        // Convert return type
        let return_type = if method.returns.is_void() {
            String::new()
        } else {
            format!(" (result {})", Self::bid_type_to_wasm_type(&method.returns.type_name())?)
        };
        
        // Build parameter list
        let params = if param_types.is_empty() {
            String::new()
        } else {
            format!(" (param {})", param_types.join(" "))
        };
        
        Ok(format!(
            "(import \"env\" \"{}\" (func {}{}{}))",
            import_name,
            func_name,
            params,
            return_type
        ))
    }
    
    /// Convert BID type to WASM type
    fn bid_type_to_wasm_type(bid_type: &str) -> BidResult<String> {
        match bid_type {
            "bool" => Ok("i32".to_string()),      // 0/1 in i32
            "i32" => Ok("i32".to_string()),
            "i64" => Ok("i64".to_string()),
            "f32" => Ok("f32".to_string()),
            "f64" => Ok("f64".to_string()),
            "string" => Ok("i32 i32".to_string()), // ptr + len
            "bytes" => Ok("i32 i32".to_string()),  // ptr + len
            "handle" => Ok("i64".to_string()),     // Handle as u64
            "void" => Ok("".to_string()),
            _ => Err(BidError::CodeGenError(format!("Unsupported type for WASM: {}", bid_type))),
        }
    }
    
    /// Generate JavaScript host implementation template
    fn generate_js_host_template(bid: &BidDefinition) -> BidResult<String> {
        let mut content = String::new();
        
        content.push_str(&format!(
            "// JavaScript host implementation for {}\n",
            bid.name()
        ));
        content.push_str("// Generated by Nyash BID code generator\n\n");
        
        content.push_str("class NyashWasmHost {\n");
        content.push_str("  constructor() {\n");
        content.push_str("    this.memory = null;\n");
        content.push_str("    this.textDecoder = new TextDecoder();\n");
        content.push_str("    this.textEncoder = new TextEncoder();\n");
        content.push_str("  }\n\n");
        
        content.push_str("  // Called when WASM instance is created\n");
        content.push_str("  setMemory(memory) {\n");
        content.push_str("    this.memory = memory;\n");
        content.push_str("  }\n\n");
        
        content.push_str("  // Helper: Read string from WASM memory\n");
        content.push_str("  readString(ptr, len) {\n");
        content.push_str("    const bytes = new Uint8Array(this.memory.buffer, ptr, len);\n");
        content.push_str("    return this.textDecoder.decode(bytes);\n");
        content.push_str("  }\n\n");
        
        content.push_str("  // Import object for WASM instantiation\n");
        content.push_str("  getImportObject() {\n");
        content.push_str("    return {\n");
        content.push_str("      env: {\n");
        
        for interface in &bid.interfaces {
            for method in &interface.methods {
                let js_method = Self::generate_js_method(interface, method)?;
                content.push_str(&format!("        {}\n", js_method));
            }
        }
        
        content.push_str("      }\n");
        content.push_str("    };\n");
        content.push_str("  }\n");
        content.push_str("}\n\n");
        
        content.push_str("// Usage example:\n");
        content.push_str("// const host = new NyashWasmHost();\n");
        content.push_str("// const importObject = host.getImportObject();\n");
        content.push_str("// const wasmInstance = await WebAssembly.instantiateStreaming(\n");
        content.push_str("//   fetch('your_module.wasm'),\n");
        content.push_str("//   importObject\n");
        content.push_str("// );\n");
        content.push_str("// host.setMemory(wasmInstance.instance.exports.memory);\n");
        content.push_str("// wasmInstance.instance.exports.main();\n\n");
        
        content.push_str("module.exports = { NyashWasmHost };\n");
        
        Ok(content)
    }
    
    /// Generate JavaScript method implementation
    fn generate_js_method(interface: &BidInterface, method: &BidMethod) -> BidResult<String> {
        let import_name = format!("{}.{}", interface.name, method.name);
        
        // Build parameter list with type comments
        let mut params = Vec::new();
        for param in &method.params {
            match param.get_type().as_str() {
                "string" => params.extend(vec!["ptr".to_string(), "len".to_string()]),
                "bytes" => params.extend(vec!["ptr".to_string(), "len".to_string()]),
                typ if typ.starts_with("i") || typ.starts_with("f") || typ == "bool" => {
                    params.push(param.get_name().unwrap_or_else(|| "value".to_string()));
                },
                "handle" => params.push("handle".to_string()),
                _ => params.push("param".to_string()),
            }
        }
        
        let param_list = params.join(", ");
        
        // Generate method body
        let mut body = String::new();
        
        // Handle string parameters
        let mut string_conversions = Vec::new();
        let mut converted_params = Vec::new();
        
        for param in &method.params {
            let param_name = param.get_name().unwrap_or_else(|| "value".to_string());
            match param.get_type().as_str() {
                "string" => {
                    string_conversions.push(format!(
                        "      const {} = this.readString(ptr, len);",
                        param_name
                    ));
                    converted_params.push(param_name);
                },
                _ => {
                    converted_params.push(param_name);
                }
            }
        }
        
        if !string_conversions.is_empty() {
            body.push_str(&string_conversions.join("\n"));
            body.push_str("\n");
        }
        
        // Generate method call
        body.push_str(&format!(
            "      // TODO: Implement {} method\n",
            method.name
        ));
        body.push_str(&format!(
            "      console.log('Called {}({})');",
            method.name,
            converted_params.join(", ")
        ));
        
        if !method.returns.is_void() {
            body.push_str("\n      // TODO: Return appropriate value");
            body.push_str(&format!(
                "\n      return 0; // Placeholder for {} return",
                method.returns.type_name()
            ));
        }
        
        Ok(format!(
            "\"{}\": ({}) => {{\n{}\n    }},",
            import_name,
            param_list,
            body
        ))
    }
    
    /// Generate README with usage instructions
    fn generate_readme(bid: &BidDefinition) -> BidResult<String> {
        let bid_name = bid.name();
        let mut content = String::new();
        
        content.push_str(&format!("# {} - WASM Integration\n\n", bid_name));
        content.push_str("Generated by Nyash BID code generator for WebAssembly target.\n\n");
        
        content.push_str("## Files\n\n");
        content.push_str(&format!("- `{}_imports.wat` - WebAssembly import declarations\n", bid_name));
        content.push_str(&format!("- `{}_host.js` - JavaScript host implementation template\n", bid_name));
        content.push_str("- `README.md` - This file\n\n");
        
        content.push_str("## Usage\n\n");
        content.push_str("1. Include the import declarations in your WASM module:\n");
        content.push_str("   ```wat\n");
        content.push_str(&format!("   ;; Copy imports from {}_imports.wat\n", bid_name));
        content.push_str("   ```\n\n");
        
        content.push_str("2. Implement the host functions in JavaScript:\n");
        content.push_str("   ```javascript\n");
        content.push_str(&format!("   const {{ NyashWasmHost }} = require('./{}_host.js');\n", bid_name));
        content.push_str("   const host = new NyashWasmHost();\n");
        content.push_str("   \n");
        content.push_str("   // Customize the host implementation\n");
        content.push_str("   const importObject = host.getImportObject();\n");
        content.push_str("   ```\n\n");
        
        content.push_str("3. Load and run your WASM module:\n");
        content.push_str("   ```javascript\n");
        content.push_str("   const wasmInstance = await WebAssembly.instantiateStreaming(\n");
        content.push_str("     fetch('your_module.wasm'),\n");
        content.push_str("     importObject\n");
        content.push_str("   );\n");
        content.push_str("   host.setMemory(wasmInstance.instance.exports.memory);\n");
        content.push_str("   wasmInstance.instance.exports.main();\n");
        content.push_str("   ```\n\n");
        
        content.push_str("## Available Functions\n\n");
        for interface in &bid.interfaces {
            content.push_str(&format!("### {}\n\n", interface.name));
            for method in &interface.methods {
                content.push_str(&format!("- `{}(", method.name));
                let param_strs: Vec<String> = method.params.iter().map(|p| {
                    format!("{}: {}", 
                            p.get_name().unwrap_or_else(|| "param".to_string()),
                            p.get_type())
                }).collect();
                content.push_str(&param_strs.join(", "));
                content.push_str(&format!(")` → `{}`\n", method.returns.type_name()));
            }
            content.push_str("\n");
        }
        
        Ok(content)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::bid::{BidInterface, BidMethod, BidParameter, BidTypeRef};
    use std::collections::HashMap;
    
    #[test]
    fn test_bid_type_to_wasm_type() {
        assert_eq!(WasmGenerator::bid_type_to_wasm_type("i32").unwrap(), "i32");
        assert_eq!(WasmGenerator::bid_type_to_wasm_type("string").unwrap(), "i32 i32");
        assert_eq!(WasmGenerator::bid_type_to_wasm_type("handle").unwrap(), "i64");
        assert_eq!(WasmGenerator::bid_type_to_wasm_type("void").unwrap(), "");
        
        assert!(WasmGenerator::bid_type_to_wasm_type("unknown").is_err());
    }
    
    #[test]
    fn test_generate_wasm_import() {
        let interface = BidInterface {
            name: "env.console".to_string(),
            box_type: Some("Console".to_string()),
            methods: vec![],
        };
        
        let mut param_map = HashMap::new();
        param_map.insert("string".to_string(), "msg".to_string());
        
        let method = BidMethod {
            name: "log".to_string(),
            params: vec![BidParameter {
                param_type: BidTypeRef::Named(param_map),
            }],
            returns: BidTypeRef::Simple("void".to_string()),
            effect: Some("io".to_string()),
        };
        
        let import = WasmGenerator::generate_wasm_import(&interface, &method).unwrap();
        assert!(import.contains("env.console.log"));
        assert!(import.contains("$env.console.log"));
        assert!(import.contains("param i32 i32")); // string = ptr + len
    }
}