//! Command Palette — keyboard-driven navigation overlay (Cmd+K / Ctrl+K).
//!
//! WASM-only: SSR renders an empty fragment so hydration sees zero DOM nodes
//! from this component on both sides.

use leptos::prelude::*;

/// Global command palette triggered by Cmd+K / Ctrl+K.
///
/// Renders nothing in SSR. On the client, registers a document-level `keydown`
/// listener that toggles the overlay. Menu items are loaded from the menu
/// registry for the current language and filtered as the user types.
#[component]
pub fn CommandPalette() -> impl IntoView {
    #[cfg(not(target_arch = "wasm32"))]
    {
        view! { <></> }
    }

    #[cfg(target_arch = "wasm32")]
    {
        use leptos::html::Input;
        use leptos::portal::Portal;
        use rustelo_core_lib::state::use_current_language;
        use rustelo_core_lib::utils::nav;
        use wasm_bindgen::closure::Closure;
        use wasm_bindgen::JsCast as _;

        let show = use_context::<crate::navigation::command_palette::CommandPaletteOpen>()
            .map(|c| c.0)
            .unwrap_or_else(|| RwSignal::new(false));

        let query = RwSignal::new(String::new());
        let selected_idx = RwSignal::new(0usize);
        let lang_signal = use_current_language();

        let extra_items_ctx =
            use_context::<crate::navigation::command_palette::CommandPaletteExtraItems>();

        // Both signals are Copy — captured by move into items_memo for reactive
        // filtering.
        let deny_paths_signal = use_context::<RwSignal<Vec<String>>>();
        let auth_user_signal = use_context::<rustelo_core_lib::state::AuthState>().map(|a| a.user);

        let items_memo = Memo::new(move |_| {
            let lang = lang_signal.get();

            // Nav menu items (non-external)
            let mut items: Vec<(String, String)> =
                rustelo_core_lib::load_reactive_menu_for_language(&lang)
                    .unwrap_or_default()
                    .into_iter()
                    .filter(|item| !item.is_external.unwrap_or(false))
                    .map(|item| {
                        let label = item
                            .label
                            .get_for_language(&lang)
                            .cloned()
                            .unwrap_or_else(|| item.route.clone());
                        let route = item.get_route_for_language(&lang).to_string();
                        (label, route)
                    })
                    .collect();

            // Footer menu items (non-external, non-auth)
            if let Ok(footer) = rustelo_core_lib::load_reactive_footer_for_language(&lang) {
                for item in footer.menu {
                    if !item.is_external.unwrap_or(false) && !item.auth_required {
                        let label = item
                            .label
                            .get_for_language(&lang)
                            .cloned()
                            .unwrap_or_else(|| item.route.clone());
                        items.push((label, item.route));
                    }
                }
            }

            // Static application pages injected by the website client at build time
            if let Some(extra) = extra_items_ctx {
                for &(label, route) in extra.0 {
                    items.push((label.to_string(), route.to_string()));
                }
            }

            // Dedup by route — preserve first occurrence order
            let mut seen = std::collections::HashSet::new();
            items.retain(|(_, route)| seen.insert(route.clone()));

            // Filter auth-required routes for unauthenticated users.
            // deny_paths comes from the RBAC WebSocket — updates reactively on policy
            // reload.
            let is_authed = auth_user_signal.map(|s| s.get().is_some()).unwrap_or(false);
            if !is_authed {
                if let Some(deny_sig) = deny_paths_signal {
                    let deny = deny_sig.get();
                    items.retain(|(_, route)| {
                        !deny.iter().any(|pattern| {
                            if let Some(prefix) = pattern.strip_suffix("/*") {
                                route == prefix || route.starts_with(&format!("{prefix}/"))
                            } else {
                                route.as_str() == pattern.as_str()
                            }
                        })
                    });
                }
            }

            items
        });

        let filtered = Memo::new(move |_| {
            let q = query.get().to_lowercase();
            let all = items_memo.get();
            if q.is_empty() {
                return all;
            }
            all.into_iter()
                .filter(|(label, route)| {
                    label.to_lowercase().contains(&q) || route.to_lowercase().contains(&q)
                })
                .collect()
        });

        // Reset selection to first item whenever the query changes.
        Effect::new(move |_| {
            query.get();
            selected_idx.set(0);
        });

        let set_path_ctx = use_context::<WriteSignal<String>>();

        // NodeRef for focusing the search input when the palette opens.
        let input_ref = NodeRef::<Input>::new();

        // Reset state and focus input on every open, covering all open paths
        // (Cmd+K and FloatingPill button) without relying on `autofocus`.
        Effect::new(move |_| {
            if show.get() {
                query.set(String::new());
                selected_idx.set(0);
                if let Some(input) = input_ref.get() {
                    let _ = input.focus();
                }
            }
        });

        // Document-level keydown: Cmd+K toggle, Esc close, arrows navigate, Enter
        // select.
        {
            let closure = Closure::wrap(Box::new(move |e: web_sys::KeyboardEvent| {
                let key = e.key();

                if !show.get_untracked() {
                    if (key == "k" || key == "K") && (e.meta_key() || e.ctrl_key()) {
                        e.prevent_default();
                        show.set(true);
                    }
                    return;
                }

                match key.as_str() {
                    "Escape" => show.set(false),
                    "k" if e.meta_key() || e.ctrl_key() => {
                        e.prevent_default();
                        show.set(false);
                    }
                    "ArrowDown" => {
                        e.prevent_default();
                        let max = filtered.get_untracked().len().saturating_sub(1);
                        selected_idx.update(|v| *v = (*v + 1).min(max));
                    }
                    "ArrowUp" => {
                        e.prevent_default();
                        selected_idx.update(|v| *v = v.saturating_sub(1));
                    }
                    "Enter" => {
                        e.prevent_default();
                        let items = filtered.get_untracked();
                        let idx = selected_idx.get_untracked();
                        if let Some((_, route)) = items.get(idx) {
                            if let Some(sp) = set_path_ctx {
                                nav::anchor_navigate(sp, route);
                            }
                            show.set(false);
                        }
                    }
                    _ => {}
                }
            }) as Box<dyn FnMut(_)>);

            if let Some(window) = web_sys::window() {
                if let Some(document) = window.document() {
                    let _ = document.add_event_listener_with_callback(
                        "keydown",
                        closure.as_ref().unchecked_ref(),
                    );
                }
            }
            closure.forget();
        }

        // Portal mounts into document.body outside the SSR hydration stream.
        // display:none guarantees zero pointer-events when closed, eliminating
        // any z-index race with FloatingPill (z-9990) regardless of async timing.
        view! {
            <Portal>
                <div
                    style=move || if show.get() {
                        "position:fixed;inset:0;z-index:9998;background:rgba(0,0,0,0.5);backdrop-filter:blur(4px);"
                    } else {
                        "display:none;"
                    }
                    on:click=move |_| show.set(false)
                />
                <div
                    class="rounded bg-gray-800 text-white dark:bg-gray-300 dark:text-gray-900"
                    style=move || if show.get() {
                        "position:fixed;z-index:9999;top:20vh;left:50%;transform:translateX(-50%);width:min(calc(100vw - 2rem),32rem);pointer-events:none;"
                    } else {
                        "display:none;"
                    }
                >
                    <div style="pointer-events:auto;background:var(--b1,#ffffff);border:1px solid var(--b3,#d1d5db);border-radius:1rem;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5);overflow:hidden;color:var(--bc,#1f2937);">
                        <div style="display:flex;align-items:center;gap:0.75rem;padding:0.75rem 1rem;border-bottom:1px solid var(--b3,#d1d5db);">
                            <svg style="width:1rem;height:1rem;flex-shrink:0;opacity:0.4;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0"/>
                            </svg>
                            <input
                                type="text"
                                id="command-palette-search"
                                name="command-palette-search"
                                node_ref=input_ref
                                style="flex:1;background:transparent;outline:none;font-size:0.875rem;color:inherit;border:0;min-width:0;"
                                placeholder="Search..."
                                prop:value=move || query.get()
                                on:input=move |ev| query.set(event_target_value(&ev))
                            />
                            <kbd style="font-size:0.75rem;opacity:0.4;border:1px solid var(--b3,#d1d5db);border-radius:0.25rem;padding:0.125rem 0.375rem;font-family:monospace;flex-shrink:0;">{"esc"}</kbd>
                        </div>
                        <ul style="max-height:18rem;overflow-y:auto;padding:0.25rem 0;margin:0;list-style:none;">
                            {move || {
                                let items = filtered.get();
                                let sel = selected_idx.get();
                                if items.is_empty() {
                                    view! {
                                        <li style="padding:1.5rem 1rem;font-size:0.875rem;opacity:0.5;text-align:center;">
                                            {"No results"}
                                        </li>
                                    }.into_any()
                                } else {
                                    items
                                        .into_iter()
                                        .enumerate()
                                        .map(|(idx, (label, route))| {
                                            let route_nav = route.clone();
                                            let sp = set_path_ctx;
                                            let is_sel = idx == sel;
                                            let btn_style = if is_sel {
                                                "display:flex;align-items:center;gap:0.75rem;width:100%;padding:0.625rem 1rem;font-size:0.875rem;text-align:left;border:0;border-radius:0.5rem;cursor:pointer;color:inherit;background:rgba(99,102,241,0.18);"
                                            } else {
                                                "display:flex;align-items:center;gap:0.75rem;width:100%;padding:0.625rem 1rem;font-size:0.875rem;text-align:left;border:0;border-radius:0.5rem;cursor:pointer;color:inherit;background:transparent;"
                                            };
                                            view! {
                                                <li style="margin:2px 4px;">
                                                    <button
                                                        type="button"
                                                        style=btn_style
                                                        on:click=move |_| {
                                                            if let Some(sp) = sp {
                                                                nav::anchor_navigate(sp, &route_nav);
                                                            }
                                                            show.set(false);
                                                        }
                                                    >
                                                        <svg style="width:0.875rem;height:0.875rem;flex-shrink:0;opacity:0.4;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
                                                        </svg>
                                                        <span>{label}</span>
                                                    </button>
                                                </li>
                                            }
                                        })
                                        .collect_view()
                                        .into_any()
                                }
                            }}
                        </ul>
                        <div style="padding:0.5rem 1rem;border-top:1px solid var(--b3,#d1d5db);display:flex;gap:1rem;font-size:0.75rem;opacity:0.5;">
                            <span>
                                <kbd style="border:1px solid var(--b3,#d1d5db);border-radius:0.25rem;padding:0 0.25rem;font-family:monospace;">{"↑↓"}</kbd>
                                {" navigate"}
                            </span>
                            <span>
                                <kbd style="border:1px solid var(--b3,#d1d5db);border-radius:0.25rem;padding:0 0.25rem;font-family:monospace;">{"↵"}</kbd>
                                {" select"}
                            </span>
                            <span>
                                <kbd style="border:1px solid var(--b3,#d1d5db);border-radius:0.25rem;padding:0 0.25rem;font-family:monospace;">{"esc"}</kbd>
                                {" close"}
                            </span>
                        </div>
                    </div>
                </div>
            </Portal>
        }
    }
}
