//! 编辑器 runtime 与宿主之间的事件桥接 helper。
//!
//! 本模块只承接 envelope dispatch、listener registry 和低耦合 host command
//! listener 安装逻辑；信号读写和编辑器命令处理仍留在 `lib.rs`。
use std::{any::Any, collections::HashMap};

use leptos::mount::UnmountHandle;
use serde::Serialize;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{CustomEvent, CustomEventInit, Event, EventTarget};

use crate::editor_runtime::bridge_dispatch::HostCommandKind;
use crate::editor_runtime::bridge_events::{
    BridgeEnvelope, ChangePayload, HostCommandEnvelope, HostStatusPayload, ReadyPayload,
    StatePayload, CHANGE_EVENT, COMMAND_EVENT, EVENT_PREFIX, PROTOCOL, READY_EVENT, RUNTIME_NAME,
    RUNTIME_VERSION, SELECTION_EVENT, STATE_EVENT, STATUS_EVENT,
};
use crate::editor_runtime::dom_selection::SelectionPayload;

pub(crate) struct MountedRuntimeListener {
    target: EventTarget,
    listener: Closure<dyn FnMut(Event)>,
}

pub(crate) struct MountedRuntime {
    listeners: Vec<MountedRuntimeListener>,
    // 保留 Leptos mount handle，避免挂载内容被提前释放。
    #[allow(dead_code)]
    mount_handle: Box<dyn Any>,
}

impl Drop for MountedRuntime {
    fn drop(&mut self) {
        for listener in &self.listeners {
            let _ = listener.target.remove_event_listener_with_callback(
                COMMAND_EVENT,
                listener.listener.as_ref().unchecked_ref(),
            );
        }
    }
}

thread_local! {
    static MOUNTED_HANDLES: std::cell::RefCell<HashMap<u32, MountedRuntime>> = std::cell::RefCell::new(HashMap::new());
    static PENDING_RUNTIME_LISTENERS: std::cell::RefCell<HashMap<u32, Vec<MountedRuntimeListener>>> = std::cell::RefCell::new(HashMap::new());
}

pub(crate) fn dispatch_runtime_event<T>(
    target: Option<EventTarget>,
    event_name: &'static str,
    payload: &T,
) where
    T: Serialize,
{
    let Some(target) = target else {
        return;
    };
    let envelope = BridgeEnvelope {
        protocol: PROTOCOL,
        runtime: RUNTIME_NAME,
        version: RUNTIME_VERSION,
        source: EVENT_PREFIX,
        event: event_name,
        payload,
    };
    let Ok(detail_value) = serde_wasm_bindgen::to_value(&envelope) else {
        return;
    };

    let init = CustomEventInit::new();
    init.set_detail(&detail_value);
    if let Ok(event) = CustomEvent::new_with_event_init_dict(event_name, &init) {
        let _ = target.dispatch_event(&event);
    }
}

fn dispatch_custom_event_to_target<T>(target: &EventTarget, event_name: &'static str, payload: &T)
where
    T: Serialize,
{
    let envelope = BridgeEnvelope {
        protocol: PROTOCOL,
        runtime: RUNTIME_NAME,
        version: RUNTIME_VERSION,
        source: EVENT_PREFIX,
        event: event_name,
        payload,
    };
    let Ok(detail_value) = serde_wasm_bindgen::to_value(&envelope) else {
        return;
    };

    let init = CustomEventInit::new();
    init.set_detail(&detail_value);
    init.set_bubbles(true);
    if let Ok(event) = CustomEvent::new_with_event_init_dict(event_name, &init) {
        let _ = target.dispatch_event(&event);
    }
}

pub(crate) fn dispatch_ready_event_to_target(target: &EventTarget, payload: &ReadyPayload) {
    dispatch_custom_event_to_target(target, READY_EVENT, payload);
}

pub(crate) fn dispatch_change_event_to_target(target: &EventTarget, payload: &ChangePayload) {
    dispatch_custom_event_to_target(target, CHANGE_EVENT, payload);
}

pub(crate) fn dispatch_state_event_to_target(target: &EventTarget, payload: &StatePayload) {
    dispatch_custom_event_to_target(target, STATE_EVENT, payload);
}

pub(crate) fn dispatch_status_event_to_target(target: &EventTarget, payload: &HostStatusPayload) {
    dispatch_custom_event_to_target(target, STATUS_EVENT, payload);
}

pub(crate) fn dispatch_selection_event_to_target(target: &EventTarget, payload: &SelectionPayload) {
    dispatch_custom_event_to_target(target, SELECTION_EVENT, payload);
}

pub(crate) fn register_unmount_handle<M: Any + leptos::prelude::Mountable + 'static>(
    id: u32,
    target: EventTarget,
    listener: Closure<dyn FnMut(Event)>,
    handle: UnmountHandle<M>,
) {
    let mut listeners = vec![MountedRuntimeListener { target, listener }];
    PENDING_RUNTIME_LISTENERS.with(|registry| {
        if let Some(mut pending) = registry.borrow_mut().remove(&id) {
            listeners.append(&mut pending);
        }
    });
    MOUNTED_HANDLES.with(|registry| {
        registry.borrow_mut().insert(
            id,
            MountedRuntime {
                listeners,
                mount_handle: Box::new(handle),
            },
        );
    });
}

pub(crate) fn register_runtime_listener(
    id: u32,
    target: EventTarget,
    listener: Closure<dyn FnMut(Event)>,
) {
    MOUNTED_HANDLES.with(|registry| {
        if let Some(runtime) = registry.borrow_mut().get_mut(&id) {
            runtime
                .listeners
                .push(MountedRuntimeListener { target, listener });
        } else {
            PENDING_RUNTIME_LISTENERS.with(|pending_registry| {
                pending_registry
                    .borrow_mut()
                    .entry(id)
                    .or_default()
                    .push(MountedRuntimeListener { target, listener });
            });
        }
    });
}

pub(crate) fn take_unmount_handle(id: u32) -> Option<MountedRuntime> {
    PENDING_RUNTIME_LISTENERS.with(|registry| {
        registry.borrow_mut().remove(&id);
    });
    MOUNTED_HANDLES.with(|registry| registry.borrow_mut().remove(&id))
}

pub(crate) fn build_command_listener(mount_id: u32) -> Closure<dyn FnMut(Event)> {
    Closure::<dyn FnMut(Event)>::wrap(Box::new(move |event: Event| {
        let Some(custom_event) = event.dyn_ref::<CustomEvent>() else {
            return;
        };
        let detail = custom_event.detail();
        let Ok(envelope) = serde_wasm_bindgen::from_value::<HostCommandEnvelope>(detail) else {
            return;
        };
        if envelope.protocol.as_deref() != Some(PROTOCOL) {
            return;
        }

        let Some(payload) = envelope.payload else {
            return;
        };
        let Some(command_kind) = HostCommandKind::from_payload(&payload) else {
            return;
        };

        let _ = mount_id;
        let _ = command_kind;
    }))
}
