//! mindmap shell 的 node view / runtime mount / event bridge。
//!
//! 本模块承接 mindmap block 的完整 NodeView 实现：
//! - 类型定义（MindmapShellOptions 等）
//! - Rust Leptos shell 组件（MindmapShell）
//! - 工具栏、侧栏、导航栏子组件
//! - simplemindmap event bridge（CustomEvent 派发）
//! - mount / unmount 注册表
//!
//! #[wasm_bindgen] 薄包装器保留在 lib.rs 中。

use leptos::ev;
use leptos::mount::mount_to;
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use serde_wasm_bindgen;
use std::any::Any;
use std::collections::HashMap;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{CustomEvent, CustomEventInit, EventTarget, HtmlElement, HtmlInputElement};

use crate::editor_runtime::mount::next_mount_id;

thread_local! {
    static MOUNTED_MINDMAP_SHELLS: std::cell::RefCell<HashMap<u32, MountedMindmapShell>> = std::cell::RefCell::new(HashMap::new());
}

pub(crate) const MINDMAP_SHELL_ACTION_EVENT: &str = "mnote:mindmap-shell:action";
pub(crate) const MINDMAP_SHELL_PANEL_EVENT: &str = "mnote:mindmap-shell:panel";
pub(crate) const MINDMAP_SHELL_MINIMAP_EVENT: &str = "mnote:mindmap-shell:minimap";
pub(crate) const MINDMAP_SHELL_ZOOM_EVENT: &str = "mnote:mindmap-shell:zoom";
pub(crate) const MINDMAP_SHELL_TOOLBAR_OVERFLOW_EVENT: &str =
    "mnote:mindmap-shell:toolbar-overflow";
pub(crate) struct MountedMindmapShell {
    // 保留 Leptos mount handle，避免 mindmap shell 被提前释放。
    #[allow(dead_code)]
    mount_handle: Box<dyn Any>,
}
#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellAction {
    id: String,
    label: String,
    #[serde(default)]
    long_label: Option<String>,
    icon: String,
    #[serde(default)]
    icon_key: Option<String>,
    #[serde(default)]
    priority: Option<u32>,
    #[serde(default)]
    overflow_group: Option<String>,
    #[serde(default)]
    cluster: Option<String>,
    disabled: bool,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellToolbarGroup {
    id: String,
    label: String,
    actions: Vec<MindmapShellAction>,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellSidebarPanel {
    id: String,
    kind: String,
    label: String,
    icon: String,
    active: bool,
    body_title: String,
    body_caption: String,
    #[serde(default)]
    options: Vec<MindmapShellSidebarOption>,
    #[serde(default)]
    body_items: Vec<String>,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellSidebarOption {
    id: String,
    label: String,
    action_id: Option<String>,
    value: Value,
    control_type: String,
    #[serde(default)]
    preview: Option<String>,
    #[serde(default)]
    description: Option<String>,
    #[serde(default)]
    readonly: bool,
    compat_path: Option<String>,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellNavigator {
    word_count: u32,
    node_count: u32,
    zoom_percent: u32,
    readonly: bool,
    #[serde(default)]
    minimap_open: bool,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellToolbarOverflowState {
    available_width: Option<f64>,
    #[serde(default)]
    visible_action_ids: Vec<String>,
    #[serde(default)]
    overflow_action_ids: Vec<String>,
    #[serde(default)]
    more_open: bool,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellFullscreenState {
    mode: String,
    is_fullscreen: bool,
    target: String,
    #[serde(default = "default_true")]
    api_available: bool,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellSidebarState {
    trigger_visible: bool,
    panel_open: bool,
    active_panel_id: Option<String>,
    drawer_width: u32,
    collapsed_by_toggle: bool,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellNavigatorState {
    search_open: bool,
    minimap_open: bool,
    readonly: bool,
    zoom_percent: u32,
    mouse_behavior: String,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellInteractionState {
    chrome_visibility: String,
    toolbar_overflow: MindmapShellToolbarOverflowState,
    fullscreen: MindmapShellFullscreenState,
    sidebar: MindmapShellSidebarState,
    navigator: MindmapShellNavigatorState,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellOptions {
    mindmap_id: String,
    toolbar_groups: Vec<MindmapShellToolbarGroup>,
    sidebar_panels: Vec<MindmapShellSidebarPanel>,
    navigator: MindmapShellNavigator,
    shell: MindmapShellInteractionState,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellActionPayload<'a> {
    action_id: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    value: Option<&'a Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    compat_path: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    source: Option<&'a str>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellPanelPayload<'a> {
    event: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    panel_id: Option<&'a str>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellMinimapPayload {
    open: bool,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellZoomPayload {
    percent: u32,
}

fn default_true() -> bool {
    true
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MindmapShellToolbarOverflowPayload {
    more_open: bool,
}

fn dispatch_mindmap_shell_event<T>(target: &EventTarget, event_name: &'static str, payload: &T)
where
    T: Serialize,
{
    let Ok(detail_value) = serde_wasm_bindgen::to_value(payload) 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);
    }
}

fn render_mindmap_toolbar_action(
    action: MindmapShellAction,
    class_name: &'static str,
    source: &'static str,
    action_target: EventTarget,
) -> impl IntoView {
    let action_id = action.id.clone();
    let action_label_title = action
        .long_label
        .clone()
        .filter(|label| !label.is_empty())
        .unwrap_or_else(|| action.label.clone());
    let action_label_aria = action_label_title.clone();
    let action_label_text = action.label.clone();
    let action_icon_text = action.icon.clone();
    let action_icon_key = action.icon_key.clone().unwrap_or_default();
    let action_overflow_group = action.overflow_group.clone().unwrap_or_default();
    let action_cluster = action.cluster.clone().unwrap_or_default();
    let action_priority = action.priority.unwrap_or(u32::MAX).to_string();
    let action_disabled = action.disabled;

    view! {
        <button
            type="button"
            class=class_name
            data-testid=format!("mindmap-schema-toolbar-action-{}", action_id)
            data-mindmap-action-id=action.id.clone()
            data-icon-key=action_icon_key
            data-overflow-group=action_overflow_group
            data-toolbar-cluster=action_cluster
            data-priority=action_priority
            title=action_label_title
            aria-label=action_label_aria
            disabled=action_disabled
            on:click=move |_| {
                dispatch_mindmap_shell_event(
                    &action_target,
                    MINDMAP_SHELL_ACTION_EVENT,
                    &MindmapShellActionPayload {
                        action_id: &action_id,
                        value: None,
                        compat_path: None,
                        source: Some(source),
                    },
                );
            }
        >
            <span class="mnote-mindmap-tool-icon" aria-hidden="true">{action_icon_text}</span>
            <span class="mnote-mindmap-tool-label">{action_label_text}</span>
        </button>
    }
}

fn render_mindmap_sidebar_option(
    option: MindmapShellSidebarOption,
    action_target: EventTarget,
) -> impl IntoView {
    let option_id = option.id.clone();
    let option_label = option.label.clone();
    let option_control_type = option.control_type.clone();
    let action_id = option.action_id.clone();
    let option_value = option.value.clone();
    let compat_path = option.compat_path.clone();
    let option_preview = option.preview.clone().unwrap_or_default();
    let option_description = option.description.clone().unwrap_or_default();
    let option_readonly = option.readonly;
    let option_action_id_attr = action_id.clone().unwrap_or_default();

    if option_readonly || action_id.is_none() {
        return view! {
            <div
                class="mnote-mindmap-side-option mnote-mindmap-side-option-readonly"
                data-testid=format!("mindmap-schema-sidebar-option-{}", option_id)
                data-mindmap-sidebar-option-id=option.id
                data-control-type=option_control_type.clone()
                data-readonly="true"
            >
                <span class="mnote-mindmap-side-option-label">{option_label}</span>
                {if option_description.is_empty() {
                    ().into_any()
                } else {
                    view! { <span class="mnote-mindmap-side-option-description">{option_description}</span> }.into_any()
                }}
            </div>
        }
        .into_any();
    }

    let button_class = if option_control_type == "layoutCard" {
        "mnote-mindmap-side-option mnote-mindmap-side-option-layout-card"
    } else if option_control_type == "swatch" {
        "mnote-mindmap-side-option mnote-mindmap-side-option-swatch"
    } else if option_control_type == "treeItem" {
        "mnote-mindmap-side-option mnote-mindmap-side-option-tree-item"
    } else {
        "mnote-mindmap-side-option"
    };

    view! {
        <button
            type="button"
            class=button_class
            data-testid=format!("mindmap-schema-sidebar-option-{}", option_id)
            data-mindmap-sidebar-option-id=option.id
            data-mindmap-action-id=option_action_id_attr
            data-control-type=option_control_type.clone()
            on:click=move |_| {
                if let Some(action_id) = action_id.as_deref() {
                    dispatch_mindmap_shell_event(
                        &action_target,
                        MINDMAP_SHELL_ACTION_EVENT,
                        &MindmapShellActionPayload {
                            action_id,
                            value: Some(&option_value),
                            compat_path: compat_path.as_deref(),
                            source: Some("sidebar"),
                        },
                    );
                }
            }
        >
            {if option_control_type == "swatch" {
                view! {
                    <span class="mnote-mindmap-side-option-swatch-chip" style=format!("background:{};", option_preview.clone())></span>
                }
                .into_any()
            } else if option_control_type == "layoutCard" {
                view! {
                    <span class="mnote-mindmap-side-option-preview-card">{option_preview.clone()}</span>
                }
                .into_any()
            } else {
                ().into_any()
            }}
            <span class="mnote-mindmap-side-option-label">{option_label}</span>
            {if option_description.is_empty() {
                ().into_any()
            } else {
                view! { <span class="mnote-mindmap-side-option-description">{option_description}</span> }.into_any()
            }}
        </button>
    }
    .into_any()
}

#[component]
pub(crate) fn MindmapShell(
    options: MindmapShellOptions,
    event_target: EventTarget,
) -> impl IntoView {
    let toolbar_groups = options.toolbar_groups.clone();
    let sidebar_panels = options.sidebar_panels.clone();
    let navigator = options.navigator.clone();
    let shell = options.shell.clone();
    let minimap_open = shell.navigator.minimap_open;
    let search_open = shell.navigator.search_open;
    let sidebar_panel_open = shell.sidebar.panel_open;
    let sidebar_trigger_visible = shell.sidebar.trigger_visible;
    let active_panel_id = shell.sidebar.active_panel_id.clone();
    let sidebar_drawer_width = shell.sidebar.drawer_width.to_string();
    let chrome_visibility = shell.chrome_visibility.clone();
    let fullscreen_mode = shell.fullscreen.mode.clone();
    let fullscreen_target = shell.fullscreen.target.clone();
    let navigator_mouse_behavior = shell.navigator.mouse_behavior.clone();
    let toolbar_available_width = shell
        .toolbar_overflow
        .available_width
        .map(|width| width.round().to_string())
        .unwrap_or_else(|| "".to_string());
    let chrome_visible = shell.chrome_visibility == "visible";
    let toolbar_visible_actions = shell.toolbar_overflow.visible_action_ids.join(",");
    let toolbar_overflow_actions = shell.toolbar_overflow.overflow_action_ids.join(",");
    let sidebar_drawer_width_value = shell.sidebar.drawer_width;
    let navigator_right_offset = if chrome_visible && sidebar_panel_open && sidebar_trigger_visible
    {
        sidebar_drawer_width_value + 112
    } else if chrome_visible && sidebar_trigger_visible {
        108
    } else {
        28
    };
    let navigator_right_style = format!("right: {}px;", navigator_right_offset);
    let minimap_right_style = format!("right: {}px;", navigator_right_offset);
    let mut primary_toolbar_actions: Vec<MindmapShellAction> = Vec::new();
    let mut overflow_toolbar_actions: Vec<MindmapShellAction> = Vec::new();
    let mut file_toolbar_actions: Vec<MindmapShellAction> = Vec::new();

    for group in toolbar_groups.into_iter() {
        match group.id.as_str() {
            "overflow" => overflow_toolbar_actions.extend(group.actions),
            "file" => file_toolbar_actions.extend(group.actions),
            _ => primary_toolbar_actions.extend(group.actions),
        }
    }
    primary_toolbar_actions.sort_by_key(|action| action.priority.unwrap_or(u32::MAX));
    overflow_toolbar_actions.sort_by_key(|action| action.priority.unwrap_or(u32::MAX));
    file_toolbar_actions.sort_by_key(|action| action.priority.unwrap_or(u32::MAX));
    let has_toolbar_overflow = !overflow_toolbar_actions.is_empty();
    let toolbar_more_open = shell.toolbar_overflow.more_open && has_toolbar_overflow;

    let action_target = event_target.clone();
    let panel_target = event_target;

    view! {
        <div
            class="mnote-mindmap-rust-shell"
            data-testid="mindmap-rust-shell"
            data-ui-shell-source="leptos-rust-shell"
            data-mnote-mindmap-id=options.mindmap_id
            data-chrome-visibility=chrome_visibility
            data-toolbar-available-width=toolbar_available_width
            data-toolbar-visible-actions=toolbar_visible_actions
            data-toolbar-overflow-actions=toolbar_overflow_actions
            data-toolbar-more-open=toolbar_more_open.to_string()
            data-fullscreen-mode=fullscreen_mode
            data-fullscreen-active=shell.fullscreen.is_fullscreen.to_string()
            data-fullscreen-target=fullscreen_target
            data-fullscreen-api-available=shell.fullscreen.api_available.to_string()
            data-sidebar-trigger-visible=sidebar_trigger_visible.to_string()
            data-sidebar-panel-open=sidebar_panel_open.to_string()
            data-sidebar-active-panel=active_panel_id.unwrap_or_default()
            data-sidebar-drawer-width=sidebar_drawer_width
            data-sidebar-collapsed-by-toggle=shell.sidebar.collapsed_by_toggle.to_string()
            data-navigator-search-open=shell.navigator.search_open.to_string()
            data-navigator-minimap-open=shell.navigator.minimap_open.to_string()
            data-navigator-readonly=shell.navigator.readonly.to_string()
            data-navigator-zoom-percent=shell.navigator.zoom_percent.to_string()
            data-navigator-mouse-behavior=navigator_mouse_behavior
        >
            {if chrome_visible {
            view! { <div
                class="mnote-mindmap-command-toolbar mnote-mindmap-schema-toolbar"
                data-testid="mindmap-schema-toolbar"
                data-ui-source="leptos-rust-shell"
                data-schema-source="mindmapDefaultUiSchema"
            >
                <div class="mnote-mindmap-toolbar-primary" data-testid="mindmap-schema-toolbar-groups">
                    <div class="mnote-mindmap-toolbar-cluster mnote-mindmap-toolbar-cluster-main" data-testid="mindmap-schema-toolbar-cluster-main">
                        {primary_toolbar_actions
                            .into_iter()
                            .map(|action| render_mindmap_toolbar_action(action, "mnote-mindmap-tool", "toolbar", action_target.clone()))
                            .collect_view()}
                    </div>
                    {if has_toolbar_overflow {
                        let overflow_toggle_target = action_target.clone();
                        let overflow_menu_actions = overflow_toolbar_actions.clone();
                        view! {
                            <div class="mnote-mindmap-toolbar-more" data-testid="mindmap-schema-toolbar-more">
                                <button
                                    type="button"
                                    class="mnote-mindmap-tool mnote-mindmap-toolbar-more-button"
                                    data-testid="mindmap-schema-toolbar-action-more"
                                    data-mindmap-action-id="more"
                                    data-open=toolbar_more_open.to_string()
                                    aria-haspopup="menu"
                                    aria-expanded=toolbar_more_open.to_string()
                                    title="更多"
                                    aria-label="更多"
                                    on:click=move |_| {
                                        dispatch_mindmap_shell_event(
                                            &overflow_toggle_target,
                                            MINDMAP_SHELL_TOOLBAR_OVERFLOW_EVENT,
                                            &MindmapShellToolbarOverflowPayload { more_open: !toolbar_more_open },
                                        );
                                    }
                                >
                                    <span class="mnote-mindmap-tool-icon" aria-hidden="true">"⋯"</span>
                                    <span class="mnote-mindmap-tool-label">"更多"</span>
                                </button>
                                {if toolbar_more_open {
                                    view! {
                                        <div class="mnote-mindmap-toolbar-more-menu" data-testid="mindmap-schema-toolbar-more-menu" role="menu">
                                            {overflow_menu_actions
                                                .into_iter()
                                                .map(|action| render_mindmap_toolbar_action(action, "mnote-mindmap-tool mnote-mindmap-toolbar-more-item", "toolbar", action_target.clone()))
                                                .collect_view()}
                                        </div>
                                    }.into_any()
                                } else {
                                    ().into_any()
                                }}
                            </div>
                        }.into_any()
                    } else {
                        ().into_any()
                    }}
                    <div class="mnote-mindmap-toolbar-cluster mnote-mindmap-toolbar-cluster-file" data-testid="mindmap-schema-toolbar-cluster-file">
                        {file_toolbar_actions
                            .into_iter()
                            .map(|action| render_mindmap_toolbar_action(action, "mnote-mindmap-tool", "toolbar", action_target.clone()))
                            .collect_view()}
                    </div>
                </div>
            </div> }.into_any()
            } else {
                ().into_any()
            }}
            {if chrome_visible {
            view! { <aside
                class="mnote-mindmap-side-panel mnote-mindmap-schema-sidebar"
                data-testid="mindmap-schema-sidebar"
                data-ui-source="leptos-rust-shell"
                data-schema-source="mindmapDefaultUiSchema"
                data-trigger-visible=sidebar_trigger_visible.to_string()
                data-panel-open=sidebar_panel_open.to_string()
                data-collapsed-by-toggle=shell.sidebar.collapsed_by_toggle.to_string()
            >
                {if sidebar_trigger_visible {
                    view! {
                        <div class="mnote-mindmap-side-rail" data-testid="mindmap-schema-sidebar-rail">
                            <button
                                type="button"
                                class="mnote-mindmap-side-rail-handle"
                                data-testid="mindmap-schema-sidebar-hide-handle"
                                aria-label="隐藏侧栏"
                                title="隐藏侧栏"
                                on:click={
                                    let panel_target = panel_target.clone();
                                    move |_| {
                                        dispatch_mindmap_shell_event(
                                            &panel_target,
                                            MINDMAP_SHELL_PANEL_EVENT,
                                            &MindmapShellPanelPayload {
                                                event: "hideTrigger",
                                                panel_id: None,
                                            },
                                        );
                                    }
                                }
                            >
                                "›"
                            </button>
                            {sidebar_panels
                                .iter()
                                .cloned()
                                .map(|panel| {
                                    let panel_id = panel.id.clone();
                                    let panel_kind = panel.kind.clone();
                                    let panel_target = panel_target.clone();
                                    view! {
                                        <button
                                            type="button"
                                            class="mnote-mindmap-side-tab"
                                            data-testid=format!("mindmap-schema-sidebar-tab-{}", panel_id)
                                            data-mindmap-sidebar-panel-id=panel.id.clone()
                                            data-sidebar-kind=panel_kind
                                            data-active=panel.active.to_string()
                                            on:click=move |_| {
                                                dispatch_mindmap_shell_event(
                                                    &panel_target,
                                                    MINDMAP_SHELL_PANEL_EVENT,
                                                    &MindmapShellPanelPayload {
                                                        event: "togglePanel",
                                                        panel_id: Some(&panel_id),
                                                    },
                                                );
                                            }
                                        >
                                            <span class="mnote-mindmap-side-icon">{panel.icon}</span>
                                            <span class="mnote-mindmap-side-tab-label">{panel.label}</span>
                                        </button>
                                    }
                                })
                                .collect_view()}
                        </div>
                    }
                    .into_any()
                } else {
                    view! {
                        <button
                            type="button"
                            class="mnote-mindmap-side-restore-handle"
                            data-testid="mindmap-schema-sidebar-restore-handle"
                            aria-label="恢复侧栏"
                            title="恢复侧栏"
                            on:click={
                                let panel_target = panel_target.clone();
                                move |_| {
                                    dispatch_mindmap_shell_event(
                                        &panel_target,
                                        MINDMAP_SHELL_PANEL_EVENT,
                                        &MindmapShellPanelPayload {
                                            event: "restoreTrigger",
                                            panel_id: None,
                                        },
                                    );
                                }
                            }
                        >
                            "‹"
                        </button>
                    }
                    .into_any()
                }}
                {if sidebar_panel_open {
                    sidebar_panels
                        .iter()
                        .find(|panel| panel.active)
                        .or_else(|| sidebar_panels.first())
                        .cloned()
                        .map(|panel| view! {
                        <div
                            class="mnote-mindmap-side-body mnote-mindmap-side-drawer"
                            data-testid="mindmap-schema-sidebar-drawer"
                            data-sidebar-panel-id=panel.id.clone()
                            data-sidebar-kind=panel.kind.clone()
                            style=format!("width: {}px;", sidebar_drawer_width_value)
                        >
                            <div class="mnote-mindmap-side-drawer-header">
                                <div class="mnote-mindmap-side-drawer-heading">
                                    <strong data-testid="mindmap-schema-sidebar-title">{panel.body_title}</strong>
                                    <span>{panel.body_caption}</span>
                                </div>
                                <button
                                    type="button"
                                    class="mnote-mindmap-side-drawer-close"
                                    data-testid="mindmap-schema-sidebar-close"
                                    aria-label="关闭侧栏"
                                    title="关闭侧栏"
                                    on:click={
                                        let panel_target = panel_target.clone();
                                        move |_| {
                                            dispatch_mindmap_shell_event(
                                                &panel_target,
                                                MINDMAP_SHELL_PANEL_EVENT,
                                                &MindmapShellPanelPayload {
                                                    event: "closeDrawer",
                                                    panel_id: None,
                                                },
                                            );
                                        }
                                    }
                                >
                                    "×"
                                </button>
                            </div>
                            <div class="mnote-mindmap-side-options" data-testid=format!("mindmap-schema-sidebar-options-{}", panel.id)>
                                {panel.options
                                    .into_iter()
                                    .map(|option| render_mindmap_sidebar_option(option, action_target.clone()))
                                    .collect_view()}
                            </div>
                            <ol class="mnote-mindmap-side-outline" data-testid=format!("mindmap-schema-sidebar-outline-{}", panel.id)>
                                {panel.body_items
                                    .into_iter()
                                    .map(|item| view! { <li>{item}</li> })
                                    .collect_view()}
                            </ol>
                        </div>
                        }.into_any())
                        .unwrap_or_else(|| ().into_any())
                } else {
                    ().into_any()
                }}
            </aside> }.into_any()
            } else {
                ().into_any()
            }}
            <div
                class="mnote-mindmap-count"
                data-testid="mindmap-schema-count"
                data-ui-source="leptos-rust-shell"
                data-schema-source="mindmapDefaultUiSchema"
            >
                <span data-testid="mindmap-schema-navigator-stats">{format!("字数 {}", navigator.word_count)}</span>
                <span>{format!("节点 {}", navigator.node_count)}</span>
            </div>
            {if chrome_visible {
            view! { <div
                class="mnote-mindmap-bottom-bar mnote-mindmap-schema-navigator"
                data-testid="mindmap-schema-navigator"
                data-ui-source="leptos-rust-shell"
                data-schema-source="mindmapDefaultUiSchema"
                style=navigator_right_style
            >
                <div class="mnote-mindmap-schema-navigator-group">
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-centerRoot" data-mindmap-action-id="centerRoot" title="回根节点" aria-label="回根节点" on:click={
                        let action_target = action_target.clone();
                        move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                            action_id: "centerRoot",
                            value: None,
                            compat_path: None,
                            source: None,
                        })
                    }><span class="material-symbols-outlined" data-icon="home" aria-hidden="true"></span></button>
                    <div class="mnote-mindmap-schema-navigator-search-wrap">
                        <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-search" data-mindmap-action-id="search" data-active=search_open.to_string() title="搜索" aria-label="搜索" on:click={
                            let action_target = action_target.clone();
                            move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                                action_id: "search",
                                value: None,
                                compat_path: None,
                                source: None,
                            })
                        }><span class="material-symbols-outlined" data-icon="search" aria-hidden="true"></span></button>
                        {if search_open {
                            view! {
                                <input
                                    data-testid="mindmap-schema-navigator-search"
                                    data-mindmap-action-id="search"
                                    placeholder="搜索"
                                    aria-label="搜索"
                                    on:input={
                                        let action_target = action_target.clone();
                                        move |event| {
                                            let value = event_target_value(&event);
                                            let value = json!(value);
                                            dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                                                action_id: "search",
                                                value: Some(&value),
                                                compat_path: None,
                                                source: None,
                                            })
                                        }
                                    }
                                    on:keydown={
                                        let action_target = action_target.clone();
                                        move |event: ev::KeyboardEvent| {
                                            if event.key() == "Escape" {
                                                let value = json!(false);
                                                dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                                                    action_id: "search",
                                                    value: Some(&value),
                                                    compat_path: None,
                                                    source: None,
                                                });
                                            }
                                        }
                                    }
                                />
                            }.into_any()
                        } else {
                            ().into_any()
                        }}
                    </div>
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-minimap" data-active=minimap_open.to_string() title="小地图" aria-label="小地图" on:click={
                        let action_target = action_target.clone();
                        move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_MINIMAP_EVENT, &MindmapShellMinimapPayload { open: !minimap_open })
                    }><span class="material-symbols-outlined" data-icon="account_tree" aria-hidden="true"></span></button>
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-settings" data-active=(sidebar_panel_open && sidebar_trigger_visible).to_string() title="设置" aria-label="设置" on:click={
                        let action_target = action_target.clone();
                        let active_panel_id = active_panel_id.clone();
                        move |_| {
                            let event = if sidebar_trigger_visible { "togglePanel" } else { "restoreTrigger" };
                            dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_PANEL_EVENT, &MindmapShellPanelPayload {
                                event,
                                panel_id: active_panel_id.as_deref(),
                            })
                        }
                    }><span class="mnote-mindmap-tool-icon" aria-hidden="true">"⚙"</span></button>
                </div>
                <div class="mnote-mindmap-schema-navigator-group">
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-zoomOut" data-mindmap-action-id="zoomOut" title="缩小" aria-label="缩小" on:click={
                        let action_target = action_target.clone();
                        move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                            action_id: "zoomOut",
                            value: None,
                            compat_path: None,
                            source: None,
                        })
                    }><span class="mnote-mindmap-tool-icon" aria-hidden="true">"－"</span></button>
                    <input
                        class="mnote-mindmap-schema-navigator-zoom"
                        data-testid="mindmap-schema-navigator-zoom-input"
                        value=format!("{}%", navigator.zoom_percent)
                        aria-label="缩放百分比"
                        on:change={
                            let action_target = action_target.clone();
                            move |event| {
                                let raw = event_target_value(&event);
                                let normalized = raw.replace('%', "").trim().to_string();
                                if let Ok(percent) = normalized.parse::<u32>() {
                                    dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ZOOM_EVENT, &MindmapShellZoomPayload { percent });
                                } else if let Some(target) = event.target() {
                                    if let Ok(input) = target.dyn_into::<HtmlInputElement>() {
                                        input.set_value(&format!("{}%", navigator.zoom_percent));
                                    }
                                }
                            }
                        }
                        on:keydown=move |event: ev::KeyboardEvent| {
                            if event.key() == "Escape" {
                                if let Some(target) = event.target() {
                                    if let Ok(input) = target.dyn_into::<HtmlInputElement>() {
                                        input.set_value(&format!("{}%", navigator.zoom_percent));
                                        input.blur().ok();
                                    }
                                }
                            }
                        }
                    />
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-zoomIn" data-mindmap-action-id="zoomIn" title="放大" aria-label="放大" on:click={
                        let action_target = action_target.clone();
                        move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                            action_id: "zoomIn",
                            value: None,
                            compat_path: None,
                            source: None,
                        })
                    }><span class="material-symbols-outlined" data-icon="add" aria-hidden="true"></span></button>
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-fullscreenCanvas" data-mindmap-action-id="fullscreenCanvas" data-active=shell.fullscreen.is_fullscreen.to_string() data-disabled-reason=if shell.fullscreen.api_available { "" } else { "fullscreen-unavailable" } disabled=!shell.fullscreen.api_available title=if shell.fullscreen.is_fullscreen { "退出全屏" } else { "全屏查看" } aria-label=if shell.fullscreen.is_fullscreen { "退出全屏" } else { "全屏查看" } on:click={
                        let action_target = action_target.clone();
                        move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                            action_id: if shell.fullscreen.is_fullscreen { "exitFullscreen" } else { "fullscreenCanvas" },
                            value: None,
                            compat_path: None,
                            source: None,
                        })
                    }><span class="mnote-mindmap-tool-icon" aria-hidden="true">{if shell.fullscreen.is_fullscreen { "⤫" } else { "⛶" }}</span></button>
                    <button type="button" class="mnote-mindmap-schema-navigator-button" data-testid="mindmap-schema-navigator-action-readonly" data-mindmap-action-id="readonly" data-active=navigator.readonly.to_string() title=if navigator.readonly { "切回编辑" } else { "切换只读" } aria-label=if navigator.readonly { "切回编辑" } else { "切换只读" } on:click={
                        let action_target = action_target;
                        move |_| dispatch_mindmap_shell_event(&action_target, MINDMAP_SHELL_ACTION_EVENT, &MindmapShellActionPayload {
                            action_id: "readonly",
                            value: None,
                            compat_path: None,
                            source: None,
                        })
                    }><span class="mnote-mindmap-tool-icon" aria-hidden="true">{if navigator.readonly { "锁" } else { "开" }}</span></button>
                </div>
            </div> }.into_any()
            } else {
                ().into_any()
            }}
            {if minimap_open {
            view! {
                <div
                    class="mnote-mindmap-minimap"
                    data-testid="mindmap-schema-minimap"
                    data-ui-source="leptos-rust-shell"
                    style=minimap_right_style
                >
                        <div class="mnote-mindmap-minimap-map">
                            <span></span>
                            <span></span>
                            <span></span>
                        </div>
                        <div class="mnote-mindmap-minimap-viewport" data-testid="mindmap-schema-minimap-viewport"></div>
                    </div>
                }.into_any()
            } else {
                ().into_any()
            }}
        </div>
    }
}

fn take_mindmap_shell_handle(id: u32) -> Option<MountedMindmapShell> {
    MOUNTED_MINDMAP_SHELLS.with(|registry| registry.borrow_mut().remove(&id))
}

pub(crate) fn mount_mindmap_shell_impl(
    container: JsValue,
    options: JsValue,
) -> Result<u32, JsValue> {
    console_error_panic_hook::set_once();
    let options = serde_wasm_bindgen::from_value::<MindmapShellOptions>(options)?;
    let target = container
        .dyn_into::<HtmlElement>()
        .map_err(|_| JsValue::from_str("mindmap shell mount 目标必须是 HTML 元素"))?;
    let mount_id = next_mount_id();
    let event_target: EventTarget = target.clone().into();
    let handle = mount_to(target, move || {
        view! {
            <MindmapShell options=options event_target=event_target />
        }
    });
    MOUNTED_MINDMAP_SHELLS.with(|registry| {
        registry.borrow_mut().insert(
            mount_id,
            MountedMindmapShell {
                mount_handle: Box::new(handle),
            },
        );
    });
    Ok(mount_id)
}

pub(crate) fn unmount_mindmap_shell_impl(mount_id: u32) -> Result<(), JsValue> {
    if take_mindmap_shell_handle(mount_id).is_some() {
        Ok(())
    } else {
        Err(JsValue::from_str("找不到对应的 mindmap shell 挂载句柄"))
    }
}
