import Icon from "../Icon/Icon";
import Link from "../Link/Link";
import Navigation from "../../Utils/Navigation";
import IconProp from "../../../Types/Icon/IconProp";
import React, { useMemo, useRef, useState, useEffect, } from "react";
/*
 * Icon color map carried over from the former NavBarMenuItem, extended with the
 * few colors the dashboard items use (yellow/red/teal) plus a glyph text color
 * so every product renders in its intended color. Unknown colors fall back to
 * indigo.
 */
const ICON_COLOR_CLASSES = {
    purple: {
        bg: "bg-purple-50",
        ring: "ring-purple-200",
        text: "text-purple-600",
    },
    blue: { bg: "bg-blue-50", ring: "ring-blue-200", text: "text-blue-600" },
    gray: { bg: "bg-gray-100", ring: "ring-gray-300", text: "text-gray-600" },
    amber: { bg: "bg-amber-50", ring: "ring-amber-200", text: "text-amber-600" },
    green: { bg: "bg-green-50", ring: "ring-green-200", text: "text-green-600" },
    cyan: { bg: "bg-cyan-50", ring: "ring-cyan-200", text: "text-cyan-600" },
    slate: { bg: "bg-slate-100", ring: "ring-slate-300", text: "text-slate-600" },
    indigo: {
        bg: "bg-indigo-50",
        ring: "ring-indigo-200",
        text: "text-indigo-600",
    },
    rose: { bg: "bg-rose-50", ring: "ring-rose-200", text: "text-rose-600" },
    violet: {
        bg: "bg-violet-50",
        ring: "ring-violet-200",
        text: "text-violet-600",
    },
    orange: {
        bg: "bg-orange-50",
        ring: "ring-orange-200",
        text: "text-orange-600",
    },
    stone: { bg: "bg-stone-100", ring: "ring-stone-300", text: "text-stone-600" },
    sky: { bg: "bg-sky-50", ring: "ring-sky-200", text: "text-sky-600" },
    emerald: {
        bg: "bg-emerald-50",
        ring: "ring-emerald-200",
        text: "text-emerald-600",
    },
    yellow: {
        bg: "bg-yellow-50",
        ring: "ring-yellow-200",
        text: "text-yellow-600",
    },
    red: { bg: "bg-red-50", ring: "ring-red-200", text: "text-red-600" },
    teal: { bg: "bg-teal-50", ring: "ring-teal-200", text: "text-teal-600" },
};
// Persist the handful of most-recently opened products across sessions.
const RECENT_STORAGE_KEY = "oneuptime-navbar-recent-products";
const RECENT_LIMIT = 5;
const readRecentRoutes = () => {
    try {
        if (typeof window === "undefined" || !window.localStorage) {
            return [];
        }
        const raw = window.localStorage.getItem(RECENT_STORAGE_KEY);
        if (!raw) {
            return [];
        }
        const parsed = JSON.parse(raw);
        if (!Array.isArray(parsed)) {
            return [];
        }
        return parsed.filter((value) => {
            return typeof value === "string";
        });
    }
    catch (_a) {
        // Ignore storage access / parse errors (e.g. private mode, bad JSON).
        return [];
    }
};
const writeRecentRoute = (routeString) => {
    try {
        if (typeof window === "undefined" || !window.localStorage) {
            return;
        }
        const existing = readRecentRoutes().filter((route) => {
            return route !== routeString;
        });
        const next = [routeString, ...existing].slice(0, RECENT_LIMIT);
        window.localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(next));
    }
    catch (_a) {
        // Ignore storage write errors (e.g. private mode, quota exceeded).
    }
};
const NavBarMenuModal = (props) => {
    const [query, setQuery] = useState("");
    const [activeIndex, setActiveIndex] = useState(0);
    // Drives the open transition (fade + scale-in) on the first paint.
    const [isShown, setIsShown] = useState(false);
    // Recently opened product routes, read once when the modal opens.
    const [recentRoutes] = useState(() => {
        return readRecentRoutes();
    });
    const inputRef = useRef(null);
    const cellRefs = useRef([]);
    const recentLabel = props.recentLabel || "Recent";
    // Show the OS-appropriate shortcut hint (⌘K on macOS, Ctrl K elsewhere).
    const shortcutLabel = useMemo(() => {
        if (typeof navigator === "undefined") {
            return "⌘K";
        }
        const platform = (navigator.platform ||
            navigator.userAgent ||
            "").toLowerCase();
        const isApplePlatform = platform.includes("mac") ||
            platform.includes("iphone") ||
            platform.includes("ipad") ||
            platform.includes("ipod");
        return isApplePlatform ? "⌘K" : "Ctrl K";
    }, []);
    // Filter items by the search query (title + description + category).
    const filteredItems = useMemo(() => {
        const normalizedQuery = query.trim().toLowerCase();
        if (!normalizedQuery) {
            return props.items;
        }
        return props.items.filter((item) => {
            const haystack = `${item.title} ${item.description} ${item.category || ""}`.toLowerCase();
            return haystack.includes(normalizedQuery);
        });
    }, [props.items, query]);
    /*
     * The "Recent" row: resolve stored routes to current items, skip the page
     * we're already on, and cap the count. Only shown while idle (no query).
     */
    const recentItems = useMemo(() => {
        if (recentRoutes.length === 0) {
            return [];
        }
        const itemByRoute = new Map();
        props.items.forEach((item) => {
            itemByRoute.set(item.route.toString(), item);
        });
        const resolved = [];
        recentRoutes.forEach((route) => {
            const item = itemByRoute.get(route);
            if (item && !Navigation.isStartWith(item.activeRoute || item.route)) {
                resolved.push(item);
            }
        });
        return resolved.slice(0, RECENT_LIMIT);
    }, [recentRoutes, props.items]);
    // Group filtered items by category, preserving first-seen order.
    const categoryGroups = useMemo(() => {
        const order = [];
        const byCategory = new Map();
        filteredItems.forEach((item) => {
            const category = item.category || "Other";
            if (!byCategory.has(category)) {
                byCategory.set(category, []);
                order.push(category);
            }
            byCategory.get(category).push(item);
        });
        return order.map((category) => {
            return { category, items: byCategory.get(category), isRecent: false };
        });
    }, [filteredItems]);
    /*
     * Final render order: a "Recent" group (idle only) followed by the category
     * groups, with every item stamped with its index in the flattened order so
     * keyboard navigation and cell refs stay in sync.
     */
    const displayGroups = useMemo(() => {
        const raw = [];
        if (!query.trim() && recentItems.length > 0) {
            raw.push({ category: recentLabel, items: recentItems, isRecent: true });
        }
        raw.push(...categoryGroups);
        let flatIndex = 0;
        return raw.map((group) => {
            const items = group.items.map((item) => {
                const entry = { item, flatIndex };
                flatIndex++;
                return entry;
            });
            return { category: group.category, items, isRecent: group.isRecent };
        });
    }, [query, recentItems, categoryGroups, recentLabel]);
    // Flattened list in render order — used for keyboard navigation.
    const flatItems = useMemo(() => {
        return displayGroups.reduce((acc, group) => {
            group.items.forEach((entry) => {
                acc.push(entry.item);
            });
            return acc;
        }, []);
    }, [displayGroups]);
    // Index of the product matching the current page (the "you are here" item).
    const currentFlatIndex = useMemo(() => {
        return flatItems.findIndex((item) => {
            return Navigation.isStartWith(item.activeRoute || item.route);
        });
    }, [flatItems]);
    // Play the open animation and focus the search box on mount.
    useEffect(() => {
        var _a;
        setIsShown(true);
        (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
    }, []);
    /*
     * When idle, pre-select the current page so the modal opens "where you are";
     * while searching, snap the selection to the first result.
     */
    useEffect(() => {
        if (query) {
            setActiveIndex(0);
            return;
        }
        setActiveIndex(currentFlatIndex >= 0 ? currentFlatIndex : 0);
    }, [query, currentFlatIndex]);
    // Keep the active cell scrolled into view as the selection moves.
    useEffect(() => {
        var _a;
        (_a = cellRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: "nearest" });
    }, [activeIndex]);
    // Record a selection and close the modal.
    const selectItem = (item) => {
        writeRecentRoute(item.route.toString());
        props.onClose();
    };
    // Wrap the portions of text that match the query in a highlight.
    const highlightMatch = (text) => {
        const needle = query.trim().toLowerCase();
        if (!needle) {
            return text;
        }
        const haystack = text.toLowerCase();
        const parts = [];
        let cursor = 0;
        let matchAt = haystack.indexOf(needle, cursor);
        let key = 0;
        while (matchAt !== -1) {
            if (matchAt > cursor) {
                parts.push(text.slice(cursor, matchAt));
            }
            parts.push(React.createElement("mark", { key: `m-${key}`, className: "rounded-sm bg-yellow-100 px-0.5 text-inherit" }, text.slice(matchAt, matchAt + needle.length)));
            key++;
            cursor = matchAt + needle.length;
            matchAt = haystack.indexOf(needle, cursor);
        }
        if (cursor < text.length) {
            parts.push(text.slice(cursor));
        }
        return parts;
    };
    const moveVertical = (direction) => {
        const cells = cellRefs.current;
        const active = cells[activeIndex] || null;
        if (!active) {
            return;
        }
        const activeRect = active.getBoundingClientRect();
        const activeCenterX = activeRect.left + activeRect.width / 2;
        // Find the nearest row in the requested direction.
        let targetTop = null;
        for (let i = 0; i < flatItems.length; i++) {
            const cell = cells[i] || null;
            if (!cell) {
                continue;
            }
            const top = cell.getBoundingClientRect().top;
            if (direction === "down" && top > activeRect.top + 1) {
                if (targetTop === null || top < targetTop) {
                    targetTop = top;
                }
            }
            if (direction === "up" && top < activeRect.top - 1) {
                if (targetTop === null || top > targetTop) {
                    targetTop = top;
                }
            }
        }
        if (targetTop === null) {
            return;
        }
        // Within that row, pick the cell whose horizontal center is closest.
        let bestIndex = activeIndex;
        let bestDistance = Number.POSITIVE_INFINITY;
        for (let i = 0; i < flatItems.length; i++) {
            const cell = cells[i] || null;
            if (!cell) {
                continue;
            }
            const rect = cell.getBoundingClientRect();
            if (Math.abs(rect.top - targetTop) <= 1) {
                const centerX = rect.left + rect.width / 2;
                const distance = Math.abs(centerX - activeCenterX);
                if (distance < bestDistance) {
                    bestDistance = distance;
                    bestIndex = i;
                }
            }
        }
        setActiveIndex(bestIndex);
    };
    const handleKeyDown = (event) => {
        if (event.key === "Escape") {
            event.preventDefault();
            props.onClose();
            return;
        }
        if (flatItems.length === 0) {
            return;
        }
        if (event.key === "ArrowRight") {
            event.preventDefault();
            setActiveIndex((index) => {
                return Math.min(index + 1, flatItems.length - 1);
            });
        }
        else if (event.key === "ArrowLeft") {
            event.preventDefault();
            setActiveIndex((index) => {
                return Math.max(index - 1, 0);
            });
        }
        else if (event.key === "ArrowDown") {
            event.preventDefault();
            moveVertical("down");
        }
        else if (event.key === "ArrowUp") {
            event.preventDefault();
            moveVertical("up");
        }
        else if (event.key === "Enter") {
            event.preventDefault();
            const item = flatItems[activeIndex];
            if (item) {
                selectItem(item);
                Navigation.navigate(item.route);
            }
        }
    };
    const keycapClass = "inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded border border-gray-200 bg-white px-1 text-[11px] font-medium text-gray-500 shadow-sm";
    return (React.createElement("div", { className: "relative z-50", role: "dialog", "aria-modal": "true", "aria-label": "Products menu" },
        React.createElement("div", { className: `fixed inset-0 bg-gray-900/50 backdrop-blur-sm transition-opacity duration-200 ${isShown ? "opacity-100" : "opacity-0"}` }),
        React.createElement("div", { className: "fixed inset-0 z-50 overflow-y-auto", onClick: props.onClose },
            React.createElement("div", { className: "flex min-h-full items-start justify-center p-4 sm:p-6" },
                React.createElement("div", { className: `relative mt-[6vh] flex w-full max-w-4xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl ring-1 ring-gray-900/5 transition-all duration-200 ease-out ${isShown
                        ? "translate-y-0 scale-100 opacity-100"
                        : "translate-y-2 scale-[0.98] opacity-0"}`, style: { maxHeight: "80vh" }, onClick: (event) => {
                        event.stopPropagation();
                    } },
                    React.createElement("div", { className: "flex items-center gap-3 border-b border-gray-100 px-5 py-4" },
                        React.createElement(Icon, { icon: IconProp.Search, className: "h-5 w-5 flex-shrink-0 text-gray-400" }),
                        React.createElement("input", { ref: inputRef, type: "text", role: "combobox", "aria-expanded": true, "aria-controls": "navbar-menu-listbox", "aria-activedescendant": flatItems.length > 0
                                ? `navbar-menu-option-${activeIndex}`
                                : undefined, "aria-label": props.searchPlaceholder || "Search products", autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, value: query, onChange: (event) => {
                                setQuery(event.target.value);
                            }, onKeyDown: handleKeyDown, placeholder: props.searchPlaceholder || "Search…", className: "flex-1 border-0 bg-transparent p-0 text-base text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-0" }),
                        query ? (React.createElement("button", { type: "button", "aria-label": "Clear search", onClick: () => {
                                var _a;
                                setQuery("");
                                (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
                            }, className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600" },
                            React.createElement(Icon, { icon: IconProp.Close, className: "h-4 w-4" }))) : (React.createElement("kbd", { className: "hidden items-center rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 text-xs font-medium text-gray-400 sm:inline-flex" }, shortcutLabel))),
                    React.createElement("div", { id: "navbar-menu-listbox", role: "listbox", "aria-label": "Products", className: "flex-1 overflow-y-auto overscroll-contain px-5 pb-5 pt-4" }, flatItems.length === 0 ? (React.createElement("div", { className: "flex flex-col items-center justify-center py-16 text-center" },
                        React.createElement("div", { className: "mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-gray-50 ring-1 ring-gray-100" },
                            React.createElement(Icon, { icon: IconProp.Search, className: "h-5 w-5 text-gray-400" })),
                        React.createElement("p", { className: "text-sm font-medium text-gray-900" }, props.noResultsText || "No results found."),
                        query && (React.createElement("p", { className: "mt-1 text-xs text-gray-500" },
                            "\u201C",
                            query,
                            "\u201D")))) : (displayGroups.map((group) => {
                        return (React.createElement("div", { key: group.category, className: "mb-6 last:mb-1" },
                            React.createElement("div", { className: "mb-2.5 flex items-center gap-1.5 px-1" },
                                group.isRecent && (React.createElement(Icon, { icon: IconProp.Clock, className: "h-3.5 w-3.5 text-gray-500" })),
                                React.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-[0.1em] text-gray-500" }, group.category)),
                            React.createElement("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3" }, group.items.map((entry) => {
                                const item = entry.item;
                                const flatIndex = entry.flatIndex;
                                const isActive = flatIndex === activeIndex;
                                const isCurrent = flatIndex === currentFlatIndex;
                                const colors = ICON_COLOR_CLASSES[item.iconColor || "indigo"] ||
                                    ICON_COLOR_CLASSES["indigo"];
                                return (React.createElement("div", { key: entry.flatIndex, id: `navbar-menu-option-${flatIndex}`, role: "option", "aria-selected": isActive, ref: (element) => {
                                        cellRefs.current[flatIndex] = element;
                                    }, onMouseMove: () => {
                                        setActiveIndex(flatIndex);
                                    } },
                                    React.createElement(Link, { to: item.route, onClick: () => {
                                            selectItem(item);
                                        }, className: `group flex h-full items-start gap-3 rounded-xl border p-3 text-left transition-colors duration-150 ${isActive
                                            ? "border-indigo-300 bg-indigo-50 shadow-sm"
                                            : isCurrent
                                                ? "border-indigo-100 bg-indigo-50/40"
                                                : "border-transparent"}` },
                                        React.createElement("div", { className: `flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg ${colors.bg} ring-1 ${colors.ring}` },
                                            React.createElement(Icon, { icon: item.icon, className: `h-5 w-5 transition-transform duration-150 group-hover:scale-110 ${colors.text}` })),
                                        React.createElement("div", { className: "min-w-0 flex-1" },
                                            React.createElement("p", { className: "flex items-center gap-1.5 text-sm font-medium text-gray-900" },
                                                React.createElement("span", { className: "truncate" }, highlightMatch(item.title)),
                                                isCurrent && (React.createElement("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 flex-shrink-0 rounded-full bg-indigo-500" }))),
                                            React.createElement("p", { className: "mt-0.5 text-xs leading-relaxed text-gray-500 line-clamp-2" }, highlightMatch(item.description))))));
                            }))));
                    }))),
                    props.footer && (React.createElement("div", { className: "flex items-center justify-between gap-4 border-t border-gray-100 bg-gray-50/80 px-5 py-3" },
                        React.createElement(Link, { to: props.footer.link, openInNewTab: true, className: "group flex min-w-0 items-center gap-3" },
                            React.createElement("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gray-900 transition-colors group-hover:bg-gray-700" },
                                React.createElement(Icon, { icon: IconProp.GitHub, className: "h-4 w-4 text-white" })),
                            React.createElement("div", { className: "min-w-0 text-left" },
                                React.createElement("p", { className: "flex items-center gap-1 text-sm font-medium text-gray-900" },
                                    React.createElement("span", { className: "truncate" }, props.footer.title),
                                    React.createElement(Icon, { icon: IconProp.ExternalLink, className: "h-3 w-3 flex-shrink-0 text-gray-400 opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-visible:opacity-100" })),
                                React.createElement("p", { className: "truncate text-xs text-gray-500" }, props.footer.description))),
                        props.keyboardHint && (React.createElement("div", { "aria-label": props.keyboardHint, className: "hidden flex-shrink-0 items-center gap-2 text-xs text-gray-400 md:flex" },
                            React.createElement("span", { className: "flex items-center gap-1" },
                                React.createElement("kbd", { className: keycapClass }, "\u2191"),
                                React.createElement("kbd", { className: keycapClass }, "\u2193")),
                            React.createElement("kbd", { className: keycapClass }, "\u21B5"),
                            React.createElement("kbd", { className: keycapClass }, "esc"))))))))));
};
export default NavBarMenuModal;
//# sourceMappingURL=NavBarMenuModal.js.map