//! UniFFI bindings for Hypen Engine
//!
//! This module provides native bindings for Kotlin, Swift, Python, and Ruby
//! via Mozilla's UniFFI framework.
//!
//! ## Building for Kotlin
//!
//! ```bash
//! # Build the native library
//! cargo build --release --features uniffi
//!
//! # Generate Kotlin bindings
//! cargo run --features uniffi --bin uniffi-bindgen generate \
//!     --library target/release/libhypen_engine.so \
//!     --language kotlin \
//!     --out-dir ../hypen-kotlin/src/main/kotlin
//! ```

use std::sync::{Arc, Mutex};

use crate::{
    ir::{ast_to_ir, ast_to_ir_node, ComponentRegistry, IRNode},
    lifecycle::{Module, ModuleInstance},
    reactive::{DependencyGraph, Scheduler},
    reconcile::{reconcile_ir, InstanceTree, Patch as InternalPatch},
};

// UniFFI scaffolding is set up in lib.rs

/// Version information
#[uniffi::export]
pub fn version() -> String {
    env!("CARGO_PKG_VERSION").to_string()
}

/// Patch types for DOM operations
#[derive(Debug, Clone, uniffi::Enum)]
pub enum PatchType {
    Create,
    SetProp,
    RemoveProp,
    SetText,
    Insert,
    Move,
    Remove,
}

/// A patch represents a single DOM operation
#[derive(Debug, Clone, uniffi::Record)]
pub struct Patch {
    pub patch_type: PatchType,
    pub id: String,
    pub element_type: Option<String>,
    pub props_json: Option<String>,
    pub name: Option<String>,
    pub value_json: Option<String>,
    pub text: Option<String>,
    pub parent_id: Option<String>,
    pub before_id: Option<String>,
}

impl From<InternalPatch> for Patch {
    fn from(p: InternalPatch) -> Self {
        match p {
            InternalPatch::Create {
                id,
                element_type,
                props,
            } => Patch {
                patch_type: PatchType::Create,
                id,
                element_type: Some(element_type),
                props_json: Some(serde_json::to_string(&props).unwrap_or_default()),
                name: None,
                value_json: None,
                text: None,
                parent_id: None,
                before_id: None,
            },
            InternalPatch::SetProp { id, name, value } => Patch {
                patch_type: PatchType::SetProp,
                id,
                element_type: None,
                props_json: None,
                name: Some(name),
                value_json: Some(serde_json::to_string(&value).unwrap_or_default()),
                text: None,
                parent_id: None,
                before_id: None,
            },
            InternalPatch::RemoveProp { id, name } => Patch {
                patch_type: PatchType::RemoveProp,
                id,
                element_type: None,
                props_json: None,
                name: Some(name),
                value_json: None,
                text: None,
                parent_id: None,
                before_id: None,
            },
            InternalPatch::SetText { id, text } => Patch {
                patch_type: PatchType::SetText,
                id,
                element_type: None,
                props_json: None,
                name: None,
                value_json: None,
                text: Some(text),
                parent_id: None,
                before_id: None,
            },
            InternalPatch::Insert {
                parent_id,
                id,
                before_id,
            } => Patch {
                patch_type: PatchType::Insert,
                id,
                element_type: None,
                props_json: None,
                name: None,
                value_json: None,
                text: None,
                parent_id: Some(parent_id),
                before_id,
            },
            InternalPatch::Move {
                parent_id,
                id,
                before_id,
            } => Patch {
                patch_type: PatchType::Move,
                id,
                element_type: None,
                props_json: None,
                name: None,
                value_json: None,
                text: None,
                parent_id: Some(parent_id),
                before_id,
            },
            InternalPatch::Remove { id } => Patch {
                patch_type: PatchType::Remove,
                id,
                element_type: None,
                props_json: None,
                name: None,
                value_json: None,
                text: None,
                parent_id: None,
                before_id: None,
            },
        }
    }
}

/// Action dispatched from UI
#[derive(Debug, Clone, uniffi::Record)]
pub struct Action {
    pub name: String,
    pub payload_json: Option<String>,
}

/// Module configuration
#[derive(Debug, Clone, uniffi::Record)]
pub struct ModuleConfig {
    pub name: String,
    pub actions: Vec<String>,
    pub state_keys: Vec<String>,
    pub initial_state_json: String,
}

/// Component definition for registration
#[derive(Debug, Clone, uniffi::Record)]
pub struct ComponentDef {
    pub name: String,
    pub source: String,
    pub path: String,
}

/// Error type for engine operations
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum HypenError {
    #[error("Parse error: {0}")]
    ParseError(String),
    #[error("Render error: {0}")]
    RenderError(String),
    #[error("State error: {0}")]
    StateError(String),
    #[error("Action error: {0}")]
    ActionError(String),
    #[error("Component error: {0}")]
    ComponentError(String),
    #[error("Initialization error: {0}")]
    InitializationError(String),
}

impl From<crate::error::EngineError> for HypenError {
    fn from(err: crate::error::EngineError) -> Self {
        match err {
            crate::error::EngineError::ParseError { message, .. } => {
                HypenError::ParseError(message)
            }
            crate::error::EngineError::ComponentNotFound(name) => HypenError::ComponentError(name),
            crate::error::EngineError::RenderError(msg) => HypenError::RenderError(msg),
            crate::error::EngineError::ActionNotFound(name) => {
                HypenError::ActionError(format!("No handler registered for action: {}", name))
            }
            crate::error::EngineError::StateError(msg) => HypenError::StateError(msg),
            crate::error::EngineError::ExpressionError(msg) => {
                HypenError::RenderError(format!("Expression error: {}", msg))
            }
        }
    }
}

/// Import information returned to SDK hosts (Kotlin, Swift, etc.)
#[derive(Debug, Clone, uniffi::Record)]
pub struct ImportInfo {
    /// Component names being imported (e.g., ["Button", "Card"])
    pub names: Vec<String>,
    /// Source path (e.g., "./components/ui" or "https://cdn.example.com/ui")
    pub source_path: String,
    /// Source type: "local" or "url"
    pub source_type: String,
}

/// Internal engine state
struct EngineState {
    component_registry: ComponentRegistry,
    module: Option<ModuleInstance>,
    tree: InstanceTree,
    dependencies: DependencyGraph,
    scheduler: Scheduler,
    revision: u64,
    root_ir_node: Option<IRNode>,
    registered_actions: Vec<String>,
    pending_actions: Vec<Action>,
    /// Imports from the last rendered document (for SDK to query)
    pending_imports: Vec<ImportInfo>,
}

/// The main Hypen engine interface
#[derive(uniffi::Object)]
pub struct HypenEngine {
    state: Mutex<EngineState>,
}

#[uniffi::export]
impl HypenEngine {
    /// Create a new engine instance
    #[uniffi::constructor]
    pub fn new() -> Result<Arc<Self>, HypenError> {
        Ok(Arc::new(Self {
            state: Mutex::new(EngineState {
                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(),
                pending_actions: Vec::new(),
                pending_imports: Vec::new(),
            }),
        }))
    }

    /// Parse Hypen DSL and return AST as JSON
    pub fn parse_to_json(&self, source: String) -> Result<String, HypenError> {
        match hypen_parser::parse_component(&source) {
            Ok(component) => serde_json::to_string_pretty(&component)
                .map_err(|e| HypenError::ParseError(e.to_string())),
            Err(errors) => Err(HypenError::ParseError(format!("{:?}", errors))),
        }
    }

    /// Render Hypen DSL source and return patches
    /// Supports documents with import statements — call get_pending_imports() after
    /// to retrieve imports for resolution by the host SDK.
    pub fn render_source(&self, source: String) -> Result<Vec<Patch>, HypenError> {
        let mut state = self
            .state
            .lock()
            .map_err(|e| HypenError::RenderError(e.to_string()))?;

        let doc = hypen_parser::parse_document(&source)
            .map_err(|e| HypenError::ParseError(format!("{:?}", e)))?;

        // Store imports for SDK to query
        state.pending_imports = doc
            .imports
            .iter()
            .map(|imp| {
                let (source_path, source_type) = match &imp.source {
                    hypen_parser::ImportSource::Local(p) => (p.clone(), "local".to_string()),
                    hypen_parser::ImportSource::Url(u) => (u.clone(), "url".to_string()),
                };
                ImportInfo {
                    names: imp
                        .imported_names()
                        .into_iter()
                        .map(|s| s.to_string())
                        .collect(),
                    source_path,
                    source_type,
                }
            })
            .collect();

        let component = doc
            .components
            .first()
            .ok_or_else(|| HypenError::ParseError("No component found in source".to_string()))?;

        let ir_node = ast_to_ir_node(component);
        state.root_ir_node = Some(ir_node.clone());

        let expanded = state.component_registry.expand_ir_node(&ir_node);

        // Clone state to release the immutable borrow
        let module_state: serde_json::Value = state
            .module
            .as_ref()
            .map(|m| m.get_state().clone())
            .unwrap_or(serde_json::Value::Null);

        // Destructure to get separate mutable borrows of each field
        let EngineState {
            tree,
            dependencies,
            revision,
            ..
        } = &mut *state;

        dependencies.clear();

        let patches = reconcile_ir(tree, &expanded, None, &module_state, dependencies);

        *revision += 1;

        Ok(patches.into_iter().map(Patch::from).collect())
    }

    /// Update engine state with a JSON patch
    pub fn update_state(&self, state_json: String) -> Result<Vec<Patch>, HypenError> {
        let patch: serde_json::Value =
            serde_json::from_str(&state_json).map_err(|e| HypenError::StateError(e.to_string()))?;

        let mut state = self
            .state
            .lock()
            .map_err(|e| HypenError::StateError(e.to_string()))?;

        let changed_paths = extract_changed_paths(&patch);

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

        // Destructure to get separate mutable borrows
        let EngineState {
            dependencies,
            scheduler,
            tree,
            module,
            revision,
            ..
        } = &mut *state;

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

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

        let patches = crate::render::render_dirty_nodes(scheduler, tree, module.as_ref());

        if !patches.is_empty() {
            *revision += 1;
        }

        Ok(patches.into_iter().map(Patch::from).collect())
    }

    /// Set module configuration
    pub fn set_module(&self, config: ModuleConfig) {
        if let Ok(mut state) = self.state.lock() {
            let initial_state: serde_json::Value =
                serde_json::from_str(&config.initial_state_json).unwrap_or(serde_json::Value::Null);

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

            let instance = ModuleInstance::new(module, initial_state);
            state.module = Some(instance);
        }
    }

    /// Register an action handler name
    pub fn register_action(&self, action_name: String) {
        if let Ok(mut state) = self.state.lock() {
            state.registered_actions.push(action_name);
        }
    }

    /// Dispatch an action (queued for polling)
    pub fn dispatch_action(
        &self,
        action_name: String,
        payload_json: Option<String>,
    ) -> Result<(), HypenError> {
        let mut state = self
            .state
            .lock()
            .map_err(|e| HypenError::ActionError(e.to_string()))?;

        if state.registered_actions.contains(&action_name) {
            state.pending_actions.push(Action {
                name: action_name,
                payload_json,
            });
        }

        Ok(())
    }

    /// Get pending actions (clears the queue)
    pub fn get_pending_actions(&self) -> Vec<Action> {
        if let Ok(mut state) = self.state.lock() {
            std::mem::take(&mut state.pending_actions)
        } else {
            Vec::new()
        }
    }

    /// Get pending imports from the last rendered document (clears the queue)
    /// Call this after render_source() to discover which components need to be resolved.
    /// For each import, use register_component() to provide the resolved component source.
    pub fn get_pending_imports(&self) -> Vec<ImportInfo> {
        if let Ok(mut state) = self.state.lock() {
            std::mem::take(&mut state.pending_imports)
        } else {
            Vec::new()
        }
    }

    /// Register a primitive element type
    pub fn register_primitive(&self, name: String) {
        if let Ok(mut state) = self.state.lock() {
            state.component_registry.register_primitive(&name);
        }
    }

    /// Register a component from source
    pub fn register_component(&self, component: ComponentDef) -> Result<(), HypenError> {
        let mut state = self
            .state
            .lock()
            .map_err(|e| HypenError::ComponentError(e.to_string()))?;

        let component_spec = hypen_parser::parse_component(&component.source)
            .map_err(|e| HypenError::ParseError(format!("{:?}", e)))?;

        let ir_element = ast_to_ir(&component_spec);
        let comp = crate::ir::Component::new(component.name, move |_props| ir_element.clone())
            .with_source_path(&component.path);

        state.component_registry.register(comp);

        Ok(())
    }

    /// Clear the render tree
    pub fn clear_tree(&self) {
        if let Ok(mut state) = self.state.lock() {
            state.tree.clear();
        }
    }

    /// Get the current revision number
    pub fn get_revision(&self) -> u64 {
        self.state.lock().map(|s| s.revision).unwrap_or(0)
    }
}

/// Extract changed paths from a state patch JSON value
fn extract_changed_paths(patch: &serde_json::Value) -> Vec<String> {
    let mut paths = Vec::new();
    extract_paths_recursive(patch, String::new(), &mut paths);
    paths
}

fn extract_paths_recursive(value: &serde_json::Value, prefix: String, paths: &mut Vec<String>) {
    match value {
        serde_json::Value::Object(map) => {
            for (key, val) in map {
                let path = if prefix.is_empty() {
                    key.clone()
                } else {
                    format!("{}.{}", prefix, key)
                };
                paths.push(path.clone());
                extract_paths_recursive(val, path, paths);
            }
        }
        _ => {}
    }
}
