"use client"

import { useState, useCallback, useEffect, useRef } from "react"
import { useRouter } from "next/navigation"
import { useQueryClient } from "@tanstack/react-query"
import type {
	DocumentsWithMemoriesResponseSchema,
	SearchResponseSchema,
} from "@repo/validation/api"
import type { z } from "zod"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { useIsMobile } from "@hooks/use-mobile"
import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog"
import { SearchIcon, Settings, Home, Plus, Code2, Loader2 } from "lucide-react"
import { DocumentIcon } from "@/components/document-icon"
import { $fetch } from "@lib/api"

type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>
type DocumentWithMemories = DocumentsResponse["documents"][0]
type SearchResult = z.infer<typeof SearchResponseSchema>["results"][number]

type PaletteItem =
	| {
			kind: "action"
			id: string
			label: string
			icon: React.ReactNode
			action: () => void
	  }
	| { kind: "document"; doc: DocumentWithMemories }
	| { kind: "search-result"; result: SearchResult }

interface DocumentsCommandPaletteProps {
	open: boolean
	onOpenChange: (open: boolean) => void
	projectId: string
	novaContainerTags?: string[]
	onOpenDocument: (document: DocumentWithMemories) => void
	onAddMemory?: () => void
	onOpenIntegrations?: () => void
	initialSearch?: string
}

export function DocumentsCommandPalette({
	open,
	onOpenChange,
	projectId,
	novaContainerTags,
	onOpenDocument,
	onAddMemory,
	onOpenIntegrations,
	initialSearch = "",
}: DocumentsCommandPaletteProps) {
	const isMobile = useIsMobile()
	const router = useRouter()
	const queryClient = useQueryClient()
	const [search, setSearch] = useState("")
	const [selectedIndex, setSelectedIndex] = useState(0)
	const [cachedDocs, setCachedDocs] = useState<DocumentWithMemories[]>([])
	const [searchResults, setSearchResults] = useState<SearchResult[]>([])
	const [isSearching, setIsSearching] = useState(false)
	const inputRef = useRef<HTMLInputElement>(null)
	const listRef = useRef<HTMLDivElement>(null)
	const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
	const abortRef = useRef<AbortController | null>(null)

	const close = useCallback(
		(then?: () => void) => {
			onOpenChange(false)
			setSearch("")
			setSearchResults([])
			if (then) setTimeout(then, 0)
		},
		[onOpenChange],
	)

	const actions: PaletteItem[] = [
		{
			kind: "action",
			id: "home",
			label: "Go to Home",
			icon: <Home className="size-4 text-[#737373]" />,
			action: () => close(() => router.push("/")),
		},
		{
			kind: "action",
			id: "settings",
			label: "Go to Settings",
			icon: <Settings className="size-4 text-[#737373]" />,
			action: () => close(() => router.push("/settings")),
		},
		...(onAddMemory
			? [
					{
						kind: "action" as const,
						id: "add-memory",
						label: "Add Memory",
						icon: <Plus className="size-4 text-[#737373]" />,
						action: () => {
							close()
							onAddMemory()
						},
					},
				]
			: []),
		...(onOpenIntegrations
			? [
					{
						kind: "action" as const,
						id: "integrations",
						label: "Open Integrations",
						icon: <Code2 className="size-4 text-[#737373]" />,
						action: () => {
							close()
							onOpenIntegrations()
						},
					},
				]
			: []),
	]

	// Load cached docs when opening
	useEffect(() => {
		if (open) {
			const queryData = queryClient.getQueryData<{
				pages: DocumentsResponse[]
				pageParams: number[]
			}>(["documents-with-memories", projectId])

			if (queryData?.pages) {
				setCachedDocs(queryData.pages.flatMap((page) => page.documents ?? []))
			}
			setTimeout(() => inputRef.current?.focus(), 0)
			setSearch(initialSearch)
			setSelectedIndex(0)
			setSearchResults([])
		}
	}, [open, queryClient, projectId, initialSearch])

	// Debounced semantic search
	useEffect(() => {
		if (debounceRef.current) clearTimeout(debounceRef.current)
		if (abortRef.current) abortRef.current.abort()

		if (!search.trim()) {
			setSearchResults([])
			setIsSearching(false)
			return
		}

		setIsSearching(true)
		debounceRef.current = setTimeout(async () => {
			const controller = new AbortController()
			abortRef.current = controller

			try {
				const res = await $fetch("@post/search", {
					body: {
						q: search.trim(),
						limit: 10,
						containerTags:
							novaContainerTags ?? (projectId ? [projectId] : undefined),
						includeSummary: true,
					},
					signal: controller.signal,
				})
				if (!controller.signal.aborted && res.data) {
					setSearchResults(res.data.results)
				}
			} catch {
				// aborted or failed - ignore
			} finally {
				if (!controller.signal.aborted) setIsSearching(false)
			}
		}, 250)

		return () => {
			if (debounceRef.current) clearTimeout(debounceRef.current)
		}
	}, [search, projectId, novaContainerTags])

	// Build the item list
	const hasQuery = search.trim().length > 0
	const items: PaletteItem[] = []

	if (hasQuery) {
		for (const r of searchResults) {
			items.push({ kind: "search-result", result: r })
		}
		const q = search.toLowerCase()
		for (const a of actions) {
			if (a.kind === "action" && a.label.toLowerCase().includes(q))
				items.push(a)
		}
	} else {
		for (const doc of cachedDocs.slice(0, 10))
			items.push({ kind: "document", doc })
		for (const a of actions) items.push(a)
	}

	// Reset selection on items change
	useEffect(() => {
		setSelectedIndex(0)
	}, [search, searchResults.length])

	// Scroll selected into view
	useEffect(() => {
		listRef.current
			?.querySelector(`[data-index="${selectedIndex}"]`)
			?.scrollIntoView({ block: "nearest" })
	}, [selectedIndex])

	const handleSelect = useCallback(
		(item: PaletteItem) => {
			if (item.kind === "action") {
				item.action()
			} else if (item.kind === "document") {
				if (!item.doc.id) return
				onOpenDocument(item.doc)
				close()
			} else {
				// search result -> convert to DocumentWithMemories shape for the modal
				onOpenDocument({
					id: item.result.documentId,
					title: item.result.title,
					type: item.result.type,
					createdAt: item.result.createdAt as unknown as string,
					updatedAt: item.result.updatedAt as unknown as string,
					url: (item.result.metadata?.url as string) ?? null,
					content:
						item.result.content ?? item.result.chunks?.[0]?.content ?? null,
					summary: item.result.summary ?? null,
				} as unknown as DocumentWithMemories)
				close()
			}
		},
		[onOpenDocument, close],
	)

	const handleKeyDown = useCallback(
		(e: React.KeyboardEvent) => {
			if (e.key === "ArrowDown") {
				e.preventDefault()
				setSelectedIndex((i) => Math.min(i + 1, items.length - 1))
			} else if (e.key === "ArrowUp") {
				e.preventDefault()
				setSelectedIndex((i) => Math.max(i - 1, 0))
			} else if (e.key === "Enter") {
				e.preventDefault()
				const item = items[selectedIndex]
				if (item) handleSelect(item)
			}
		},
		[items, selectedIndex, handleSelect],
	)

	function renderItem(item: PaletteItem, index: number) {
		const isSelected = index === selectedIndex
		const baseClass = cn(
			"flex items-center gap-3 px-3 py-2.5 rounded-md cursor-pointer text-left transition-colors",
			isSelected
				? "bg-[#293952]/40"
				: "opacity-70 hover:opacity-100 hover:bg-[#293952]/40",
		)

		if (item.kind === "action") {
			return (
				<button
					key={item.id}
					type="button"
					data-index={index}
					onClick={() => handleSelect(item)}
					onMouseEnter={() => setSelectedIndex(index)}
					className={baseClass}
				>
					<div className="flex items-center justify-center size-5 shrink-0">
						{item.icon}
					</div>
					<p className="text-sm font-medium text-white">{item.label}</p>
				</button>
			)
		}

		const title = item.kind === "document" ? item.doc.title : item.result.title
		const type = item.kind === "document" ? item.doc.type : item.result.type
		const url =
			item.kind === "document"
				? item.doc.url
				: ((item.result.metadata?.url as string) ?? null)
		const date =
			item.kind === "document" ? item.doc.createdAt : item.result.createdAt
		const key = item.kind === "document" ? item.doc.id : item.result.documentId
		const snippet =
			item.kind === "search-result"
				? item.result.chunks?.find((c) => c.isRelevant)?.content
				: null

		return (
			<button
				key={key}
				type="button"
				data-index={index}
				onClick={() => handleSelect(item)}
				onMouseEnter={() => setSelectedIndex(index)}
				className={baseClass}
			>
				<div
					className="flex items-center justify-center size-5 rounded-md shrink-0"
					style={{
						background: "linear-gradient(180deg, #14161A 0%, #0D0F12 100%)",
						boxShadow:
							"inset 0px 1px 1px rgba(255,255,255,0.03), inset 0px -1px 1px rgba(0,0,0,0.1)",
					}}
				>
					<DocumentIcon type={type} url={url} className="size-4" />
				</div>
				<div className="flex-1 min-w-0">
					<div className="flex gap-1 justify-between items-center">
						<p className="text-sm font-medium text-white truncate">
							{title || "Untitled"}
						</p>
						<p className="text-xs text-[#737373] text-nowrap">
							{new Date(date).toLocaleDateString("en-US", {
								month: "short",
								day: "numeric",
							})}
						</p>
					</div>
					{snippet && (
						<p className="text-xs text-[#737373] truncate mt-0.5">
							{snippet.slice(0, 120)}
						</p>
					)}
				</div>
			</button>
		)
	}

	return (
		<Dialog open={open} onOpenChange={onOpenChange}>
			<DialogContent
				className={cn(
					"bg-[#1B1F24] flex flex-col p-0 gap-0 overflow-hidden top-[15%]! translate-y-0! scrollbar-thin border-none shadow-2xl",
					isMobile
						? "w-[calc(100vw-2rem)]! max-w-none! rounded-xl"
						: "w-[560px]! max-w-[560px]! rounded-xl",
					dmSansClassName(),
				)}
				style={{
					background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
					boxShadow: "0px 1.5px 20px 0px rgba(0,0,0,0.65)",
				}}
				showCloseButton={false}
				onKeyDown={handleKeyDown}
			>
				<DialogTitle className="sr-only">Search</DialogTitle>

				<div className="flex items-center gap-3 px-4 py-3">
					{isSearching ? (
						<Loader2 className="size-4 text-[#737373] shrink-0 animate-spin" />
					) : (
						<SearchIcon className="size-4 text-[#737373] shrink-0" />
					)}
					<input
						ref={inputRef}
						type="text"
						placeholder="Type to search your memories..."
						value={search}
						onChange={(e) => setSearch(e.target.value)}
						className={cn(
							"flex-1 bg-transparent text-white text-sm placeholder:text-[#737373] outline-none",
							dmSansClassName(),
						)}
					/>
				</div>

				<div
					ref={listRef}
					className="flex flex-col min-h-[200px] max-h-[400px] overflow-y-auto py-1.5 px-1.5"
				>
					{!hasQuery && cachedDocs.length > 0 && (
						<p className="px-3 pt-1 pb-1.5 text-[10px] uppercase tracking-wider text-[#737373]">
							Recent
						</p>
					)}
					{hasQuery && searchResults.length > 0 && (
						<p className="px-3 pt-1 pb-1.5 text-[10px] uppercase tracking-wider text-[#737373]">
							Results
						</p>
					)}
					{items
						.map((item, i) => ({ item, globalIndex: i }))
						.filter(({ item }) => item.kind !== "action")
						.map(({ item, globalIndex }) => renderItem(item, globalIndex))}

					{items.some((i) => i.kind === "action") && (
						<p className="px-3 pt-3 pb-1.5 text-[10px] uppercase tracking-wider text-[#737373]">
							Actions
						</p>
					)}
					{items
						.map((item, i) => ({ item, globalIndex: i }))
						.filter(({ item }) => item.kind === "action")
						.map(({ item, globalIndex }) => renderItem(item, globalIndex))}

					{hasQuery &&
						!isSearching &&
						searchResults.length === 0 &&
						items.every((i) => i.kind === "action") && (
							<div className="flex items-center justify-center py-12">
								<p className="text-[#737373] text-sm">No results found</p>
							</div>
						)}
				</div>

				<div className="flex items-center justify-between px-4 py-2.5 text-[11px] text-[#737373]">
					<div className="flex items-center gap-4">
						<span className="flex items-center gap-1.5">
							<span className="flex gap-0.5">
								<kbd className="px-1 py-0.5 rounded bg-[#14161A] border border-[#2E3033] text-[10px] font-medium">
									↑
								</kbd>
								<kbd className="px-1 py-0.5 rounded bg-[#14161A] border border-[#2E3033] text-[10px] font-medium">
									↓
								</kbd>
							</span>
							<span>Navigate</span>
						</span>
						<span className="flex items-center gap-1.5">
							<kbd className="px-1.5 py-0.5 rounded bg-[#14161A] border border-[#2E3033] text-[10px] font-medium">
								↵
							</kbd>
							<span>Open</span>
						</span>
						<span className="flex items-center gap-1.5">
							<kbd className="px-1.5 py-0.5 rounded bg-[#14161A] border border-[#2E3033] text-[10px] font-medium">
								Esc
							</kbd>
							<span>Close</span>
						</span>
					</div>
					{hasQuery && searchResults.length > 0 && (
						<span>{searchResults.length} results</span>
					)}
				</div>
			</DialogContent>
		</Dialog>
	)
}
