import { GitBranch, Package, Search, Server, User, X } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { searchAPI } from "../utils/api";
const GlobalSearch = () => {
const [query, setQuery] = useState("");
const [results, setResults] = useState(null);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const searchRef = useRef(null);
const inputRef = useRef(null);
const navigate = useNavigate();
// Debounce search
const debounceTimerRef = useRef(null);
const performSearch = useCallback(async (searchQuery) => {
if (!searchQuery || searchQuery.trim().length === 0) {
setResults(null);
setIsOpen(false);
return;
}
setIsLoading(true);
try {
const response = await searchAPI.global(searchQuery);
setResults(response.data);
setIsOpen(true);
setSelectedIndex(-1);
} catch (error) {
console.error("Search error:", error);
setResults(null);
} finally {
setIsLoading(false);
}
}, []);
const handleInputChange = (e) => {
const value = e.target.value;
setQuery(value);
// Clear previous timer
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
// Set new timer
debounceTimerRef.current = setTimeout(() => {
performSearch(value);
}, 300);
};
const handleClear = () => {
// Clear debounce timer to prevent any pending searches
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
setQuery("");
setResults(null);
setIsOpen(false);
setSelectedIndex(-1);
inputRef.current?.focus();
};
const handleResultClick = (result) => {
// Navigate based on result type
switch (result.type) {
case "host":
navigate(`/hosts/${result.id}`);
break;
case "package":
navigate(`/packages/${result.id}`);
break;
case "repository":
navigate(`/repositories/${result.id}`);
break;
case "user":
// Users don't have detail pages, so navigate to settings
navigate("/settings/users");
break;
default:
break;
}
// Close dropdown and clear
handleClear();
};
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
if (searchRef.current && !searchRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
// Keyboard navigation
const flattenedResults = [];
if (results) {
if (results.hosts?.length > 0) {
flattenedResults.push({ type: "header", label: "Hosts" });
flattenedResults.push(...results.hosts);
}
if (results.packages?.length > 0) {
flattenedResults.push({ type: "header", label: "Packages" });
flattenedResults.push(...results.packages);
}
if (results.repositories?.length > 0) {
flattenedResults.push({ type: "header", label: "Repositories" });
flattenedResults.push(...results.repositories);
}
if (results.users?.length > 0) {
flattenedResults.push({ type: "header", label: "Users" });
flattenedResults.push(...results.users);
}
}
const navigableResults = flattenedResults.filter((r) => r.type !== "header");
const handleKeyDown = (e) => {
if (!isOpen || !results) return;
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setSelectedIndex((prev) =>
prev < navigableResults.length - 1 ? prev + 1 : prev,
);
break;
case "ArrowUp":
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : -1));
break;
case "Enter":
e.preventDefault();
if (selectedIndex >= 0 && navigableResults[selectedIndex]) {
handleResultClick(navigableResults[selectedIndex]);
}
break;
case "Escape":
e.preventDefault();
setIsOpen(false);
setSelectedIndex(-1);
break;
default:
break;
}
};
// Get icon for result type
const getResultIcon = (type) => {
switch (type) {
case "host":
return