use crate::{
    dispatch::{Action, ActionDispatcher},
    error::EngineError,
    ir::{ComponentRegistry, Element, IRNode},
    lifecycle::{ModuleInstance, ResourceCache},
    reactive::{DependencyGraph, Scheduler},
    reconcile::{reconcile_ir, InstanceTree, Patch},
    state::StateChange,
};

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

/// Callback type for rendering patches to the platform
pub type RenderCallback = Box<dyn Fn(&[Patch]) + Send + Sync>;

/// The main Hypen engine that orchestrates reactive UI rendering.
///
/// # Architecture: Single Active Module
///
/// Each `Engine` instance has one "active" module at a time for state bindings.
/// When DSL templates reference `${state.xxx}`, they resolve against the active
/// module's state.
///
/// ## Multi-module Applications
///
/// Multi-module apps (e.g., AppState + StatsScreen) work as follows:
///
/// 1. **TypeScript SDK layer** manages multiple `HypenModuleInstance` objects,
///    each with its own Proxy-based state tracking.
///
/// 2. **Cross-module communication** uses `HypenGlobalContext`:
///    ```typescript
///    // In StatsScreen's action handler:
///    const app = context.getModule<AppState>("app");
///    app.setState({ showStats: true });
///    ```
///
/// 3. **State binding scope**: Each module's template binds to its own state.
///    You cannot reference another module's state directly in templates.
///    Use props or callbacks instead:
///    ```hypen
///    // StatsScreen receives app data via props, not state binding
///    StatsView(data: @props.appStats)
///    ```
///
/// ## Limitation
///
/// The engine's state binding model (`${state.xxx}`) is scoped to a single module.
/// Cross-module state access requires explicit prop passing or action dispatching.
/// This keeps the reactive graph simple but requires careful data flow design.
pub struct Engine {
    /// Component registry for expanding custom components
    component_registry: ComponentRegistry,

    /// Current module instance (provides state for `${state.xxx}` bindings)
    module: Option<ModuleInstance>,

    /// Instance tree (virtual tree, no platform objects)
    tree: InstanceTree,

    /// Reactive dependency graph
    dependencies: DependencyGraph,

    /// Scheduler for dirty nodes
    scheduler: Scheduler,

    /// Action dispatcher
    actions: ActionDispatcher,

    /// Resource cache
    resources: ResourceCache,

    /// Callback to send patches to the platform renderer
    render_callback: Option<RenderCallback>,

    /// Revision counter for remote UI
    revision: u64,
}

impl Engine {
    pub fn new() -> Self {
        Self {
            component_registry: ComponentRegistry::new(),
            module: None,
            tree: InstanceTree::new(),
            dependencies: DependencyGraph::new(),
            scheduler: Scheduler::new(),
            actions: ActionDispatcher::new(),
            resources: ResourceCache::new(),
            render_callback: None,
            revision: 0,
        }
    }

    /// Register a custom component
    pub fn register_component(&mut self, component: crate::ir::Component) {
        self.component_registry.register(component);
    }

    /// Set the component resolver for dynamic component loading
    /// The resolver receives (component_name, context_path) and should return
    /// ResolvedComponent { source, path } or None
    pub fn set_component_resolver<F>(&mut self, resolver: F)
    where
        F: Fn(&str, Option<&str>) -> Option<crate::ir::ResolvedComponent> + Send + Sync + 'static,
    {
        self.component_registry
            .set_resolver(std::sync::Arc::new(resolver));
    }

    /// Set the module instance
    pub fn set_module(&mut self, module: ModuleInstance) {
        self.module = Some(module);
    }

    /// Set the render callback
    pub fn set_render_callback<F>(&mut self, callback: F)
    where
        F: Fn(&[Patch]) + Send + Sync + 'static,
    {
        self.render_callback = Some(Box::new(callback));
    }

    /// Register an action handler
    pub fn on_action<F>(&mut self, action_name: impl Into<String>, handler: F)
    where
        F: Fn(&Action) + Send + Sync + 'static,
    {
        self.actions.on(action_name, handler);
    }

    /// Render an element tree (initial render or full re-render)
    pub fn render(&mut self, element: &Element) {
        // Convert Element to IRNode for reconciliation
        let ir_node = IRNode::Element(element.clone());

        // Expand components (registry needs mutable access for lazy loading)
        let expanded = self.component_registry.expand_ir_node(&ir_node);

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

        // Clear dependency graph before rebuilding
        self.dependencies.clear();

        // Reconcile and generate patches (dependencies are collected during tree construction)
        let patches = reconcile_ir(
            &mut self.tree,
            &expanded,
            None,
            state,
            &mut self.dependencies,
        );

        // Send patches to renderer
        self.emit_patches(patches);

        self.revision += 1;
    }

    /// Handle a state change notification from the module host
    /// The host keeps the actual state - we just need to know what changed
    pub fn notify_state_change(&mut self, change: &StateChange) {
        // Find affected nodes based on changed paths
        let mut affected_nodes = indexmap::IndexSet::new();
        for path in change.paths() {
            affected_nodes.extend(self.dependencies.get_affected_nodes(path));
        }

        // Mark affected nodes as dirty
        self.scheduler
            .mark_many_dirty(affected_nodes.iter().copied());

        // Re-render dirty subtrees
        // Note: The host will be asked to provide values during rendering via a callback
        self.render_dirty();
    }

    /// Convenience method for backward compatibility / JSON patches
    pub fn update_state(&mut self, state_patch: serde_json::Value) {
        // Update module state FIRST before rendering
        if let Some(module) = &mut self.module {
            module.update_state(state_patch.clone());
        }

        // Then notify and render with the new state
        let change = StateChange::from_json(&state_patch);
        self.notify_state_change(&change);
    }

    /// Dispatch an action
    pub fn dispatch_action(&mut self, action: Action) -> Result<(), EngineError> {
        self.actions.dispatch(&action)
    }

    /// Render only dirty nodes
    fn render_dirty(&mut self) {
        let patches = crate::render::render_dirty_nodes(
            &mut self.scheduler,
            &mut self.tree,
            self.module.as_ref(),
        );

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

    /// Send patches to the renderer
    fn emit_patches(&self, patches: Vec<Patch>) {
        if let Some(ref callback) = self.render_callback {
            callback(&patches);
        }
    }

    /// Get the current revision number
    pub fn revision(&self) -> u64 {
        self.revision
    }

    /// Get access to the component registry
    pub fn component_registry(&self) -> &ComponentRegistry {
        &self.component_registry
    }

    /// Get mutable access to the component registry
    pub fn component_registry_mut(&mut self) -> &mut ComponentRegistry {
        &mut self.component_registry
    }

    /// Get access to the resource cache
    pub fn resources(&self) -> &ResourceCache {
        &self.resources
    }

    /// Get mutable access to the resource cache
    pub fn resources_mut(&mut self) -> &mut ResourceCache {
        &mut self.resources
    }
}

impl Default for Engine {
    fn default() -> Self {
        Self::new()
    }
}
