use icons::ChevronRight;
use leptos::context::Provider;
use leptos::prelude::*;
use leptos_ui::clx;
use tw_merge::*;
use wasm_bindgen::JsCast;

use crate::components::hooks::use_random::use_random_id_for;

/// Programmatically close any open context menu.
pub fn close_context_menu() {
    let Some(document) = window().document() else {
        return;
    };
    let Some(menu) = document.query_selector("[data-target='target__context'][data-state='open']").ok().flatten()
    else {
        return;
    };
    let _ = menu.set_attribute("data-state", "closed");
    if let Some(el) = menu.dyn_ref::<web_sys::HtmlElement>() {
        let _ = el.style().set_property("pointer-events", "none");
    }
}

mod components {
    use super::*;
    clx! {ContextMenuLabel, span, "px-2 py-1.5 text-sm font-medium data-inset:pl-8", "mb-1"}
    clx! {ContextMenuGroup, ul, "group"}
    clx! {ContextMenuItem, li, "inline-flex gap-2 items-center w-full rounded-sm px-2 py-1.5 text-sm no-underline transition-colors duration-200 text-popover-foreground hover:bg-accent hover:text-accent-foreground [&_svg:not([class*='size-'])]:size-4"}
    clx! {ContextMenuSubContent, ul, "context__menu_sub_content", "rounded-md border bg-card shadow-lg p-1 absolute z-[100] min-w-[160px] opacity-0 invisible translate-x-[-8px] transition-all duration-200 ease-out pointer-events-none"}
    clx! {ContextMenuLink, a, "w-full inline-flex gap-2 items-center"}
}

pub use components::*;

#[component]
pub fn ContextMenuAction(
    children: Children,
    #[prop(optional, into)] class: String,
    #[prop(optional, into)] aria_selected: Option<Signal<bool>>,
    #[prop(optional, into)] href: Option<String>,
) -> impl IntoView {
    let _ctx = expect_context::<ContextMenuContext>();

    let class = tw_merge!(
        "inline-flex gap-2 items-center w-full text-sm text-left transition-colors duration-200 focus:outline-none focus-visible:outline-none text-popover-foreground [&_svg:not([class*='size-'])]:size-4",
        class
    );

    let aria_selected_attr = move || aria_selected.map(|s| s.get()).unwrap_or(false).to_string();

    if let Some(href) = href {
        view! {
            <a
                data-name="ContextMenuAction"
                class=class
                href=href
                aria-selected=aria_selected_attr
                data-context-close="true"
            >
                {children()}
            </a>
        }
        .into_any()
    } else {
        view! {
            <button
                type="button"
                data-name="ContextMenuAction"
                class=class
                data-context-close="true"
                aria-selected=aria_selected_attr
            >
                {children()}
            </button>
        }
        .into_any()
    }
}

#[component]
pub fn ContextMenuHoldAction(
    children: Children,
    #[prop(into)] on_hold_complete: Callback<()>,
    #[prop(optional, into)] class: String,
    #[prop(default = 1000)] hold_duration: u64,
) -> impl IntoView {
    let is_holding = RwSignal::new(false);
    let progress = RwSignal::new(0.0);
    
    let on_mousedown = move |_| {
        is_holding.set(true);
        progress.set(0.0);
    };
    
    let on_mouseup = move |_| {
        is_holding.set(false);
        progress.set(0.0);
    };

    Effect::new(move |_| {
        if is_holding.get() {
            let start_time = js_sys::Date::now();
            let duration = hold_duration as f64;
            
            leptos::task::spawn_local(async move {
                while is_holding.get_untracked() {
                    let now = js_sys::Date::now();
                    let elapsed = now - start_time;
                    let p = (elapsed / duration).min(1.0);
                    progress.set(p * 100.0);
                    
                    if p >= 1.0 {
                        on_hold_complete.run(());
                        is_holding.set(false);
                        close_context_menu();
                        break;
                    }
                    gloo_timers::future::TimeoutFuture::new(16).await; // ~60fps
                }
            });
        }
    });

    let class = tw_merge!(
        "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors overflow-hidden",
        class
    );

    view! {
        <div
            class=class
            on:mousedown=on_mousedown
            on:mouseup=on_mouseup
            on:mouseleave=on_mouseup
            on:touchstart=move |_| on_mousedown(web_sys::MouseEvent::new("mousedown").unwrap())
            on:touchend=move |_| on_mouseup(web_sys::MouseEvent::new("mouseup").unwrap())
        >
            // Progress background
            <div 
                class="absolute inset-y-0 left-0 bg-destructive/20 transition-all duration-75 ease-linear pointer-events-none"
                style=move || format!("width: {}%;", progress.get())
            />
            <span class="relative z-10 flex items-center gap-2 w-full">
                {children()}
            </span>
        </div>
    }.into_any()
}

#[derive(Clone)]
struct ContextMenuContext {
    target_id: String,
}

#[component]
pub fn ContextMenu(children: Children) -> impl IntoView {
    let context_target_id = use_random_id_for("context");

    let ctx = ContextMenuContext { target_id: context_target_id.clone() };

    view! {
        <Provider value=ctx>
            <style>
                "
                /* Submenu Styles */
                .context__menu_sub_content {
                    position: absolute;
                    inset-inline-start: calc(100% + 8px);
                    inset-block-start: -4px;
                    z-index: 100;
                    min-inline-size: 160px;
                    opacity: 0;
                    visibility: hidden;
                    transform: translateX(-8px);
                    transition: all 0.2s ease-out;
                    pointer-events: none;
                }
                
                .context__menu_sub_trigger:hover .context__menu_sub_content {
                    opacity: 1;
                    visibility: visible;
                    transform: translateX(0);
                    pointer-events: auto;
                }
                "
            </style>

            <div data-name="ContextMenu" class="contents">
                {children()}
            </div>
        </Provider>
    }
}

/// Wrapper that triggers the context menu on right-click.
/// The `on_open` callback is triggered when the context menu opens (right-click).
#[component]
pub fn ContextMenuTrigger(
    children: Children,
    #[prop(optional, into)] class: String,
    #[prop(optional)] on_open: Option<Callback<()>>,
) -> impl IntoView {
    let ctx = expect_context::<ContextMenuContext>();
    let trigger_class = tw_merge!("contents", class);

    view! {
        <div
            class=trigger_class
            data-name="ContextMenuTrigger"
            data-context-trigger=ctx.target_id
            on:contextmenu=move |e: web_sys::MouseEvent| {
                if let Some(cb) = on_open {
                    cb.run(());
                }
            }
        >
            {children()}
        </div>
    }
}

/// Content of the context menu that appears on right-click.
/// The `on_close` callback is triggered when the menu closes (click outside, ESC key, or action click).
#[component]
pub fn ContextMenuContent(
    children: Children,
    #[prop(optional, into)] class: String,
    #[prop(optional)] on_close: Option<Callback<()>>,
) -> impl IntoView {
    let ctx = expect_context::<ContextMenuContext>();

    let base_classes = "fixed z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100";

    let class = tw_merge!(base_classes, class);

    let target_id_for_script = ctx.target_id.clone();

    view! {
        <div
            data-name="ContextMenuContent"
            class=class
            // Listen for custom 'contextmenuclose' event dispatched by JS when menu closes
            on:contextmenuclose=move |_: web_sys::CustomEvent| {
                if let Some(cb) = on_close {
                    cb.run(());
                }
            }
            id=ctx.target_id
            data-target="target__context"
            data-state="closed"
            style="pointer-events: none;"
        >
            {children()}
        </div>

        <script>
            {format!(
                r#"
                (function() {{
                    const setupContextMenu = () => {{
                        const menu = document.querySelector('#{}');
                        const trigger = document.querySelector('[data-context-trigger="{}"]');

                        if (!menu || !trigger) {{
                            setTimeout(setupContextMenu, 50);
                            return;
                        }}

                        if (menu.hasAttribute('data-initialized')) {{
                            return;
                        }}
                        menu.setAttribute('data-initialized', 'true');

                        let isOpen = false;

                        const updatePosition = (x, y) => {{
                            const menuRect = menu.getBoundingClientRect();
                            const viewportHeight = window.innerHeight;
                            const viewportWidth = window.innerWidth;

                            // Calculate position, ensuring menu stays within viewport
                            let left = x;
                            let top = y;

                            // Adjust if menu would go off right edge
                            if (x + menuRect.width > viewportWidth) {{
                                left = x - menuRect.width;
                            }}

                            // Adjust if menu would go off bottom edge
                            if (y + menuRect.height > viewportHeight) {{
                                top = y - menuRect.height;
                            }}

                            menu.style.left = `${{left}}px`;
                            menu.style.top = `${{top}}px`;
                            menu.style.transformOrigin = 'top left';
                        }};

                        const openMenu = (x, y) => {{
                            isOpen = true;

                            // Close any other open context menus
                            const allMenus = document.querySelectorAll('[data-target="target__context"]');
                            allMenus.forEach(m => {{
                                if (m !== menu && m.getAttribute('data-state') === 'open') {{
                                    m.setAttribute('data-state', 'closed');
                                    m.style.pointerEvents = 'none';
                                }}
                            }});

                            menu.setAttribute('data-state', 'open');
                            menu.style.visibility = 'hidden';
                            menu.style.pointerEvents = 'auto';

                            // Force reflow
                            menu.offsetHeight;

                            updatePosition(x, y);
                            menu.style.visibility = 'visible';

                            // Lock scroll
                            if (window.ScrollLock) {{
                                window.ScrollLock.lock();
                            }}

                            setTimeout(() => {{
                                document.addEventListener('click', handleClickOutside);
                                document.addEventListener('contextmenu', handleContextOutside);
                            }}, 0);
                        }};

                        const closeMenu = () => {{
                            isOpen = false;
                            menu.setAttribute('data-state', 'closed');
                            menu.style.pointerEvents = 'none';
                            document.removeEventListener('click', handleClickOutside);
                            document.removeEventListener('contextmenu', handleContextOutside);

                            // Dispatch custom event for Leptos to listen to
                            menu.dispatchEvent(new CustomEvent('contextmenuclose', {{ bubbles: false }}));

                            if (window.ScrollLock) {{
                                window.ScrollLock.unlock(200);
                            }}
                        }};

                        const handleClickOutside = (e) => {{
                            if (!menu.contains(e.target)) {{
                                closeMenu();
                            }}
                        }};

                        const handleContextOutside = (e) => {{
                            if (!trigger.contains(e.target)) {{
                                closeMenu();
                            }}
                        }};

                        // Right-click on trigger
                        trigger.addEventListener('contextmenu', (e) => {{
                            e.preventDefault();
                            e.stopPropagation();

                            if (isOpen) {{
                                closeMenu();
                            }}
                            openMenu(e.clientX, e.clientY);
                        }});

                        // Close when action is clicked
                        const actions = menu.querySelectorAll('[data-context-close]');
                        actions.forEach(action => {{
                            action.addEventListener('click', () => {{
                                closeMenu();
                            }});
                        }});

                        // Handle ESC key
                        document.addEventListener('keydown', (e) => {{
                            if (e.key === 'Escape' && isOpen) {{
                                e.preventDefault();
                                closeMenu();
                            }}
                        }});
                    }};

                    if (document.readyState === 'loading') {{
                        document.addEventListener('DOMContentLoaded', setupContextMenu);
                    }} else {{
                        setupContextMenu();
                    }}
                }})();
                "#,
                target_id_for_script,
                target_id_for_script,
            )}
        </script>
    }.into_any()
}

#[component]
pub fn ContextMenuSub(children: Children) -> impl IntoView {
    clx! {ContextMenuSubRoot, li, "context__menu_sub_trigger", " relative inline-flex relative gap-2 items-center py-1.5 px-2 w-full text-sm no-underline rounded-sm transition-colors duration-200 cursor-pointer text-popover-foreground [&_svg:not([class*='size-'])]:size-4 hover:bg-accent hover:text-accent-foreground"}

    view! { <ContextMenuSubRoot>{children()}</ContextMenuSubRoot> }
}

#[component]
pub fn ContextMenuSubTrigger(children: Children, #[prop(optional, into)] class: String) -> impl IntoView {
    let class = tw_merge!("flex items-center justify-between w-full", class);

    view! {
        <span data-name="ContextMenuSubTrigger" class=class>
            <span class="flex gap-2 items-center">{children()}</span>
            <ChevronRight class="opacity-70 size-4" />
        </span>
    }
}

#[component]
pub fn ContextMenuSubItem(children: Children, #[prop(optional, into)] class: String) -> impl IntoView {
    let class = tw_merge!(
        "inline-flex gap-2 items-center w-full rounded-sm px-3 py-2 text-sm transition-all duration-150 ease text-popover-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer hover:translate-x-[2px]",
        class
    );

    view! {
        <li data-name="ContextMenuSubItem" class=class data-context-close="true">
            {children()}
        </li>
    }
}