//! WASI-compatible bindings for Hypen Engine
//!
//! This module provides a C-compatible FFI interface for non-JavaScript WASM runtimes
//! like Go (wazero, wasmtime-go), Python (wasmtime, wasmer), Rust (wasmtime, wasmer),
//! and other languages that can embed WASM via WASI.
//!
//! ## Design
//!
//! Unlike the JS bindings which use wasm-bindgen and JavaScript types, this module uses:
//! - `extern "C"` functions with `#[no_mangle]` for stable ABI
//! - JSON strings for complex data exchange (passed as ptr+len pairs)
//! - A callback registration system using function table indices
//! - Memory allocation helpers for the host to allocate/free memory
//!
//! ## Usage Pattern
//!
//! 1. Host allocates memory using `wasi_alloc`
//! 2. Host writes JSON data to allocated memory
//! 3. Host calls engine functions with (ptr, len) pairs
//! 4. Engine processes and returns JSON via allocated memory
//! 5. Host reads result and frees memory using `wasi_free`
//!
//! For callbacks (patches, actions), the engine writes to a buffer and the host
//! polls for updates or uses WASI's event system.

use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::sync::Arc;

use crate::{
    dispatch::Action,
    ir::{ast_to_ir, ast_to_ir_node, IRNode},
    lifecycle::{Module, ModuleInstance},
    reconcile::{reconcile_ir, reconcile_ir_node, create_ir_node_tree, InstanceTree, Patch},
    reactive::{DependencyGraph, Scheduler},
    ir::ComponentRegistry,
};

use super::ffi::{extract_changed_paths, FfiResult, ModuleConfig, ActionPayload, SparseStateUpdate};

/// Global engine instance for WASI (single-threaded)
/// WASI runtimes are typically single-threaded, so we use thread-local storage
thread_local! {
    static ENGINE: RefCell<Option<WasiEngine>> = RefCell::new(None);
    static PATCH_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
    static ACTION_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::new());
}

/// Internal engine state for WASI
struct WasiEngine {
    component_registry: ComponentRegistry,
    module: Option<ModuleInstance>,
    tree: InstanceTree,
    dependencies: DependencyGraph,
    scheduler: Scheduler,
    revision: u64,
    root_ir_node: Option<IRNode>,
    /// Registered action names that we should capture
    registered_actions: Vec<String>,
}

impl WasiEngine {
    fn new() -> Self {
        Self {
            component_registry: ComponentRegistry::new(),
            module: None,
            tree: InstanceTree::new(),
            dependencies: DependencyGraph::new(),
            scheduler: Scheduler::new(),
            revision: 0,
            root_ir_node: None,
            registered_actions: Vec::new(),
        }
    }
}

// ============================================================================
// Memory Management
// ============================================================================

/// Allocate memory in the WASM linear memory
/// Returns a pointer to the allocated memory, or 0 on failure
#[no_mangle]
pub extern "C" fn wasi_alloc(size: usize) -> *mut u8 {
    let mut buf = Vec::with_capacity(size);
    let ptr = buf.as_mut_ptr();
    std::mem::forget(buf);
    ptr
}

/// Free memory previously allocated with wasi_alloc
#[no_mangle]
pub extern "C" fn wasi_free(ptr: *mut u8, size: usize) {
    if !ptr.is_null() {
        unsafe {
            let _ = Vec::from_raw_parts(ptr, 0, size);
        }
    }
}

/// Get the length of a null-terminated string
#[no_mangle]
pub extern "C" fn wasi_strlen(ptr: *const c_char) -> usize {
    if ptr.is_null() {
        return 0;
    }
    unsafe { CStr::from_ptr(ptr).to_bytes().len() }
}

// ============================================================================
// Engine Lifecycle
// ============================================================================

/// Initialize the engine
/// Returns 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_init() -> i32 {
    ENGINE.with(|engine| {
        *engine.borrow_mut() = Some(WasiEngine::new());
    });
    0
}

/// Destroy the engine and free resources
#[no_mangle]
pub extern "C" fn hypen_destroy() {
    ENGINE.with(|engine| {
        *engine.borrow_mut() = None;
    });
    PATCH_BUFFER.with(|buf| buf.borrow_mut().clear());
    ACTION_BUFFER.with(|buf| buf.borrow_mut().clear());
}

/// Get the current revision number
#[no_mangle]
pub extern "C" fn hypen_get_revision() -> u64 {
    ENGINE.with(|engine| {
        engine
            .borrow()
            .as_ref()
            .map(|e| e.revision)
            .unwrap_or(0)
    })
}

// ============================================================================
// Rendering
// ============================================================================

/// Render Hypen DSL source code
///
/// # Arguments
/// * `source_ptr` - Pointer to UTF-8 source string
/// * `source_len` - Length of source string in bytes
///
/// # Returns
/// 0 on success, non-zero error code on failure
/// Error details can be retrieved with `hypen_get_last_error`
#[no_mangle]
pub extern "C" fn hypen_render_source(source_ptr: *const u8, source_len: usize) -> i32 {
    let source = match ptr_to_str(source_ptr, source_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        let engine = match engine_ref.as_mut() {
            Some(e) => e,
            None => return 2,
        };

        match hypen_parser::parse_component(source) {
            Ok(component) => {
                let ir_node = ast_to_ir_node(&component);
                engine.root_ir_node = Some(ir_node.clone());
                render_internal(engine, &ir_node);
                0
            }
            Err(_) => 3,
        }
    })
}

/// Render into a specific parent node (for lazy routing)
///
/// # Arguments
/// * `source_ptr/len` - Hypen DSL source to render
/// * `parent_id_ptr/len` - Parent node ID string
/// * `state_ptr/len` - JSON state object (or empty for null)
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_render_into(
    source_ptr: *const u8,
    source_len: usize,
    parent_id_ptr: *const u8,
    parent_id_len: usize,
    state_ptr: *const u8,
    state_len: usize,
) -> i32 {
    let source = match ptr_to_str(source_ptr, source_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };
    let parent_id_str = match ptr_to_str(parent_id_ptr, parent_id_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };
    let state: serde_json::Value = if state_len == 0 {
        serde_json::Value::Null
    } else {
        match ptr_to_str(state_ptr, state_len) {
            Ok(s) => serde_json::from_str(s).unwrap_or(serde_json::Value::Null),
            Err(_) => serde_json::Value::Null,
        }
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        let engine = match engine_ref.as_mut() {
            Some(e) => e,
            None => return 2,
        };

        let component = match hypen_parser::parse_component(source) {
            Ok(c) => c,
            Err(_) => return 3,
        };

        let ir_node = ast_to_ir_node(&component);
        let expanded = engine.component_registry.expand_ir_node(&ir_node);

        // Find parent node
        let parent_id = match engine.tree.iter()
            .find(|(id, _)| format!("{:?}", id) == parent_id_str)
            .map(|(id, _)| id)
        {
            Some(id) => id,
            None => return 4,
        };

        let mut patches = Vec::new();

        let parent_has_children = engine.tree.get(parent_id)
            .map(|node| !node.children.is_empty())
            .unwrap_or(false);

        if parent_has_children {
            if let Some(parent_node) = engine.tree.get(parent_id) {
                if let Some(&first_child_id) = parent_node.children.front() {
                    reconcile_ir_node(
                        &mut engine.tree,
                        first_child_id,
                        &expanded,
                        &state,
                        &mut patches,
                        &mut engine.dependencies,
                    );
                }
            }
        } else {
            create_ir_node_tree(
                &mut engine.tree,
                &expanded,
                Some(parent_id),
                &state,
                &mut patches,
                false,
                &mut engine.dependencies,
            );
        }

        emit_patches_internal(&patches);
        engine.revision += 1;
        0
    })
}

/// Static null value to avoid cloning state when no module exists
static NULL_STATE: serde_json::Value = serde_json::Value::Null;

/// Internal render helper
fn render_internal(engine: &mut WasiEngine, ir_node: &IRNode) {
    let expanded = engine.component_registry.expand_ir_node(ir_node);

    // Get state reference (no clone needed - reconcile only borrows)
    let state: &serde_json::Value = engine
        .module
        .as_ref()
        .map(|m| m.get_state())
        .unwrap_or(&NULL_STATE);

    engine.dependencies.clear();

    let patches = reconcile_ir(
        &mut engine.tree,
        &expanded,
        None,
        state,
        &mut engine.dependencies,
    );

    emit_patches_internal(&patches);
    engine.revision += 1;
}

// ============================================================================
// State Management
// ============================================================================

/// Update state with a JSON patch
///
/// # Arguments
/// * `patch_ptr/len` - JSON object with state changes
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_update_state(patch_ptr: *const u8, patch_len: usize) -> i32 {
    let patch_str = match ptr_to_str(patch_ptr, patch_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    let patch: serde_json::Value = match serde_json::from_str(patch_str) {
        Ok(v) => v,
        Err(_) => return 2,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        let engine = match engine_ref.as_mut() {
            Some(e) => e,
            None => return 3,
        };

        let changed_paths = extract_changed_paths(&patch);

        if let Some(module) = &mut engine.module {
            module.update_state(patch);
        }

        let mut affected_nodes = Vec::new();
        for path in &changed_paths {
            affected_nodes.extend(engine.dependencies.get_affected_nodes(path));
        }

        for &node_id in &affected_nodes {
            engine.scheduler.mark_dirty(node_id);
        }

        render_dirty_internal(engine);
        0
    })
}

/// Update state with sparse path-value pairs (more efficient for large state)
///
/// # Arguments
/// * `update_ptr/len` - JSON object with { paths: string[], values: object }
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_update_state_sparse(update_ptr: *const u8, update_len: usize) -> i32 {
    let update_str = match ptr_to_str(update_ptr, update_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    let update: SparseStateUpdate = match serde_json::from_str(update_str) {
        Ok(v) => v,
        Err(_) => return 2,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        let engine = match engine_ref.as_mut() {
            Some(e) => e,
            None => return 3,
        };

        if let Some(module) = &mut engine.module {
            module.update_state_sparse(&update.paths, &update.values);
        }

        let mut affected_nodes = Vec::new();
        for path in &update.paths {
            affected_nodes.extend(engine.dependencies.get_affected_nodes(path));
        }

        for &node_id in &affected_nodes {
            engine.scheduler.mark_dirty(node_id);
        }

        render_dirty_internal(engine);
        0
    })
}

fn render_dirty_internal(engine: &mut WasiEngine) {
    let patches = crate::render::render_dirty_nodes(
        &mut engine.scheduler,
        &mut engine.tree,
        engine.module.as_ref(),
    );

    if !patches.is_empty() {
        emit_patches_internal(&patches);
        engine.revision += 1;
    }
}

// ============================================================================
// Module Management
// ============================================================================

/// Set the module configuration
///
/// # Arguments
/// * `config_ptr/len` - JSON ModuleConfig object
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_set_module(config_ptr: *const u8, config_len: usize) -> i32 {
    let config_str = match ptr_to_str(config_ptr, config_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    let config: ModuleConfig = match serde_json::from_str(config_str) {
        Ok(v) => v,
        Err(_) => return 2,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        let engine = match engine_ref.as_mut() {
            Some(e) => e,
            None => return 3,
        };

        let module = Module::new(&config.name)
            .with_actions(config.actions)
            .with_state_keys(config.state_keys);

        let instance = ModuleInstance::new(module, config.initial_state);
        engine.module = Some(instance);
        0
    })
}

// ============================================================================
// Actions
// ============================================================================

/// Register an action handler (the name will be captured when dispatched)
///
/// # Arguments
/// * `name_ptr/len` - Action name string
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_register_action(name_ptr: *const u8, name_len: usize) -> i32 {
    let name = match ptr_to_str(name_ptr, name_len) {
        Ok(s) => s.to_string(),
        Err(_) => return 1,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        if let Some(e) = engine_ref.as_mut() {
            e.registered_actions.push(name);
            0
        } else {
            2
        }
    })
}

/// Dispatch an action
///
/// # Arguments
/// * `action_ptr/len` - JSON ActionPayload object { name, payload }
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_dispatch_action(action_ptr: *const u8, action_len: usize) -> i32 {
    let action_str = match ptr_to_str(action_ptr, action_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    let action_payload: ActionPayload = match serde_json::from_str(action_str) {
        Ok(v) => v,
        Err(_) => return 2,
    };

    ENGINE.with(|engine| {
        let engine_ref = engine.borrow();
        let engine = match engine_ref.as_ref() {
            Some(e) => e,
            None => return 3,
        };

        // Check if this action is registered and write to action buffer
        if engine.registered_actions.contains(&action_payload.name) {
            let action = Action::new(&action_payload.name).with_payload(action_payload.payload);
            ACTION_BUFFER.with(|buf| {
                if let Ok(json) = serde_json::to_vec(&action) {
                    *buf.borrow_mut() = json;
                }
            });
        }
        0
    })
}

// ============================================================================
// Component Registration
// ============================================================================

/// Register a primitive element type (skips component resolution)
///
/// # Arguments
/// * `name_ptr/len` - Primitive name string (e.g., "Text", "Button")
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_register_primitive(name_ptr: *const u8, name_len: usize) -> i32 {
    let name = match ptr_to_str(name_ptr, name_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        if let Some(e) = engine_ref.as_mut() {
            e.component_registry.register_primitive(name);
            0
        } else {
            2
        }
    })
}

/// Register a component from source
///
/// # Arguments
/// * `name_ptr/len` - Component name
/// * `source_ptr/len` - Hypen DSL source
/// * `path_ptr/len` - Source file path
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_register_component(
    name_ptr: *const u8,
    name_len: usize,
    source_ptr: *const u8,
    source_len: usize,
    path_ptr: *const u8,
    path_len: usize,
) -> i32 {
    let name = match ptr_to_str(name_ptr, name_len) {
        Ok(s) => s.to_string(),
        Err(_) => return 1,
    };
    let source = match ptr_to_str(source_ptr, source_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };
    let path = match ptr_to_str(path_ptr, path_len) {
        Ok(s) => s.to_string(),
        Err(_) => return 1,
    };

    ENGINE.with(|engine| {
        let mut engine_ref = engine.borrow_mut();
        let engine = match engine_ref.as_mut() {
            Some(e) => e,
            None => return 2,
        };

        match hypen_parser::parse_component(source) {
            Ok(component_spec) => {
                let ir_element = ast_to_ir(&component_spec);
                let component = crate::ir::Component::new(name, move |_props| ir_element.clone())
                    .with_source_path(&path);
                engine.component_registry.register(component);
                0
            }
            Err(_) => 3,
        }
    })
}

// ============================================================================
// Buffer Access (for retrieving patches and actions)
// ============================================================================

/// Get the size of the patch buffer
#[no_mangle]
pub extern "C" fn hypen_get_patches_len() -> usize {
    PATCH_BUFFER.with(|buf| buf.borrow().len())
}

/// Copy patches to the provided buffer
/// Returns the number of bytes copied
#[no_mangle]
pub extern "C" fn hypen_get_patches(out_ptr: *mut u8, out_len: usize) -> usize {
    PATCH_BUFFER.with(|buf| {
        let patches = buf.borrow();
        let copy_len = patches.len().min(out_len);
        if copy_len > 0 && !out_ptr.is_null() {
            unsafe {
                std::ptr::copy_nonoverlapping(patches.as_ptr(), out_ptr, copy_len);
            }
        }
        copy_len
    })
}

/// Clear the patch buffer (call after processing patches)
#[no_mangle]
pub extern "C" fn hypen_clear_patches() {
    PATCH_BUFFER.with(|buf| buf.borrow_mut().clear());
}

/// Get the size of the action buffer
#[no_mangle]
pub extern "C" fn hypen_get_action_len() -> usize {
    ACTION_BUFFER.with(|buf| buf.borrow().len())
}

/// Copy the pending action to the provided buffer
/// Returns the number of bytes copied
#[no_mangle]
pub extern "C" fn hypen_get_action(out_ptr: *mut u8, out_len: usize) -> usize {
    ACTION_BUFFER.with(|buf| {
        let action = buf.borrow();
        let copy_len = action.len().min(out_len);
        if copy_len > 0 && !out_ptr.is_null() {
            unsafe {
                std::ptr::copy_nonoverlapping(action.as_ptr(), out_ptr, copy_len);
            }
        }
        copy_len
    })
}

/// Clear the action buffer (call after processing the action)
#[no_mangle]
pub extern "C" fn hypen_clear_action() {
    ACTION_BUFFER.with(|buf| buf.borrow_mut().clear());
}

// ============================================================================
// Utility Functions
// ============================================================================

/// Clear the engine tree
#[no_mangle]
pub extern "C" fn hypen_clear_tree() {
    ENGINE.with(|engine| {
        if let Some(e) = engine.borrow_mut().as_mut() {
            e.tree.clear();
        }
    });
}

/// Parse Hypen DSL and return AST as JSON
/// The result is written to the patch buffer (reused for convenience)
///
/// # Returns
/// 0 on success, non-zero on error
#[no_mangle]
pub extern "C" fn hypen_parse_to_json(source_ptr: *const u8, source_len: usize) -> i32 {
    let source = match ptr_to_str(source_ptr, source_len) {
        Ok(s) => s,
        Err(_) => return 1,
    };

    match hypen_parser::parse_component(source) {
        Ok(component) => {
            match serde_json::to_vec_pretty(&component) {
                Ok(json) => {
                    PATCH_BUFFER.with(|buf| *buf.borrow_mut() = json);
                    0
                }
                Err(_) => 2,
            }
        }
        Err(_) => 3,
    }
}

// ============================================================================
// Internal Helpers
// ============================================================================

/// Convert a pointer + length to a string slice
fn ptr_to_str<'a>(ptr: *const u8, len: usize) -> Result<&'a str, ()> {
    if ptr.is_null() {
        return Err(());
    }
    unsafe {
        let slice = std::slice::from_raw_parts(ptr, len);
        std::str::from_utf8(slice).map_err(|_| ())
    }
}

/// Write patches to the buffer
fn emit_patches_internal(patches: &[Patch]) {
    if let Ok(json) = serde_json::to_vec(patches) {
        PATCH_BUFFER.with(|buf| *buf.borrow_mut() = json);
    }
}
