import type { LinkBookmark } from "@markone/core/bookmark"
import { Link } from "@tanstack/react-router"
import { createStore, useStore } from "zustand"
import { useEffect, useCallback, createContext, useRef, memo, useContext } from "react"
import { useMnemonics } from "~/hooks/use-mnemonics"
import { useBookmarkPageStore, ActiveDialog, LayoutMode } from "./-store"
import { Button } from "~/components/button"
import clsx from "clsx"
import { twMerge } from "tailwind-merge"
import { subscribeWithSelector } from "zustand/middleware"

enum BookmarkListItemAction {
	Open = "Open",
	Edit = "Edit",
	Delete = "Delete",
}

type SelectionChangeCallback = (bookmark: LinkBookmark) => void
type ItemActionCallback = (bookmark: LinkBookmark, action: BookmarkListItemAction) => void

interface BookmarkListProps {
	bookmarks: LinkBookmark[]
	selectedBookmarkId?: string
	alwaysExpandItem: boolean
	onSelectionChange?: SelectionChangeCallback
	onItemAction: (bookmark: LinkBookmark, action: BookmarkListItemAction) => void
	className?: string
}

interface CreateStoreOptions {
	bookmarks: LinkBookmark[]
	selectedBookmarkId?: string
	alwaysExpandItem: boolean
	onItemAction: ItemActionCallback
}

interface BookmarkListState {
	bookmarks: LinkBookmark[]
	selectedIndex: number
	selectedBookmarkId: string
	alwaysExpandItem: boolean
	isItemExpanded: boolean

	onItemAction: ItemActionCallback
	setBookmarks: (bookmarks: LinkBookmark[]) => void
	setSelectedIndex: (index: number) => void
	setSelectedBookmarkId: (id: string) => void
	setIsItemExpanded: (expanded: boolean) => void
}

type BookmarkListStore = ReturnType<typeof createBookmarkListStore>

const BookmarkListStoreContext = createContext<BookmarkListStore | null>(null)

function createBookmarkListStore({
	bookmarks,
	selectedBookmarkId,
	alwaysExpandItem,
	onItemAction,
}: CreateStoreOptions) {
	return createStore<BookmarkListState>()(
		subscribeWithSelector((set) => ({
			bookmarks,
			alwaysExpandItem,
			selectedIndex: selectedBookmarkId ? bookmarks.findIndex((bookmark) => bookmark.id === selectedBookmarkId) : 0,
			selectedBookmarkId: selectedBookmarkId ?? bookmarks[0].id,
			isItemExpanded: false,

			onItemAction,

			setBookmarks(bookmarks: LinkBookmark[]) {
				set({ bookmarks })
			},

			setSelectedIndex(index: number) {
				set({ selectedIndex: index })
			},

			setSelectedBookmarkId(id: string) {
				set({ selectedBookmarkId: id })
			},

			setIsItemExpanded(expanded: boolean) {
				set({ isItemExpanded: expanded })
			},
		})),
	)
}

function useBookmarkListStoreContext() {
	const store = useContext(BookmarkListStoreContext)
	if (!store) throw new Error("BookmarkListStoreContext not found")
	return store
}

function useBookmarkListStore<T>(selector: (state: BookmarkListState) => T): T {
	const store = useBookmarkListStoreContext()
	return useStore(store, selector)
}

function BookmarkList({
	bookmarks,
	selectedBookmarkId,
	alwaysExpandItem,
	onSelectionChange,
	onItemAction,
	className,
}: BookmarkListProps) {
	const storeRef = useRef<BookmarkListStore | null>(null)
	if (!storeRef.current) {
		storeRef.current = createBookmarkListStore({ bookmarks, selectedBookmarkId, alwaysExpandItem, onItemAction })
	}

	const setSelectedBookmarkId = useStore(storeRef.current, (state) => state.setSelectedBookmarkId)

	useEffect(() => {
		// biome-ignore lint/style/noNonNullAssertion: storeRef.current is already set above, so cant be null
		const store = storeRef.current!
		const unsub = store.subscribe(
			(state) => state,
			({ bookmarks, selectedIndex }) => {
				onSelectionChange?.(bookmarks[selectedIndex])
			},
			{
				equalityFn: (stateA, stateB) => stateA.selectedIndex === stateB.selectedIndex,
			},
		)
		return () => {
			unsub()
		}
	}, [onSelectionChange])

	useEffect(() => {
		// biome-ignore lint/style/noNonNullAssertion: storeRef.current is already set above, so cant be null
		const store = storeRef.current!
		if (selectedBookmarkId !== store.getState().selectedBookmarkId && selectedBookmarkId) {
			setSelectedBookmarkId(selectedBookmarkId)
		}
	}, [setSelectedBookmarkId, selectedBookmarkId])

	return (
		<BookmarkListStoreContext value={storeRef.current}>
			<_BookmarkList className={className} />
		</BookmarkListStoreContext>
	)
}

const _BookmarkList = memo(({ className }: { className?: string }) => {
	const store = useBookmarkListStoreContext()

	useMnemonics(
		{
			j: selectNextItem,
			ArrowDown: selectNextItem,

			k: selectPrevItem,
			ArrowUp: selectPrevItem,

			h: collapseItem,
			ArrowLeft: collapseItem,

			l: expandItem,
			ArrowRight: expandItem,

			d: deleteItem,

			Enter: openItem,
		},
		{
			ignore: useCallback(() => useBookmarkPageStore.getState().activeDialog !== ActiveDialog.None, []),
		},
	)

	function openItem() {
		const { bookmarks, selectedIndex, onItemAction } = store.getState()
		expandItem()
		onItemAction(bookmarks[selectedIndex], BookmarkListItemAction.Open)
	}

	function deleteItem() {
		const { bookmarks, selectedIndex, onItemAction } = store.getState()
		onItemAction(bookmarks[selectedIndex], BookmarkListItemAction.Delete)
	}

	function selectPrevItem() {
		const { bookmarks, selectedIndex, setSelectedBookmarkId } = store.getState()
		const prevIndex = selectedIndex - 1
		if (prevIndex >= 0) {
			setSelectedBookmarkId(bookmarks[prevIndex].id)
		}
	}

	function selectNextItem() {
		const { bookmarks, selectedIndex, setSelectedBookmarkId } = store.getState()
		const nextIndex = selectedIndex + 1
		if (nextIndex < bookmarks.length) {
			setSelectedBookmarkId(bookmarks[nextIndex].id)
		}
	}

	function expandItem() {
		store.getState().setIsItemExpanded(true)
	}

	function collapseItem() {
		const state = store.getState()
		if (!state.alwaysExpandItem) {
			store.getState().setIsItemExpanded(false)
		}
	}

	return (
		<ul className={twMerge("flex flex-col", className)}>
			<ListContainer />
		</ul>
	)
})

function ListContainer() {
	const bookmarks = useBookmarkListStore((state) => state.bookmarks)
	const selectedItemId = useBookmarkListStore((state) => state.selectedBookmarkId)

	return bookmarks.length === 0 ? (
		<p className="mt-2">You have not saved any bookmark!</p>
	) : (
		bookmarks.map((bookmark, i) => (
			<BookmarkListItem key={bookmark.id} index={i} bookmark={bookmark} selected={bookmark.id === selectedItemId} />
		))
	)
}

const BookmarkListItem = memo(
	({ bookmark, index, selected }: { bookmark: LinkBookmark; index: number; selected: boolean }) => {
		const url = new URL(bookmark.url)
		const store = useBookmarkListStoreContext()
		const alwaysExpandItem = useBookmarkListStore((state) => state.alwaysExpandItem)
		const isBookmarkItemExpanded = useBookmarkListStore((state) => state.alwaysExpandItem || state.isItemExpanded)
		const setIsItemExpanded = useBookmarkListStore((state) => state.setIsItemExpanded)
		const selectBookmark = useBookmarkPageStore((state) => state.selectBookmark)
		const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
		const onItemAction = useBookmarkListStore((state) => state.onItemAction)

		useEffect(() => {
			if (selected) {
				store.getState().setSelectedIndex(index)
			}
		}, [selected, index, store.getState])

		function deleteItem() {
			onItemAction(bookmark, BookmarkListItemAction.Delete)
		}

		function expandOrOpenPreview() {
			if (!selected) {
				selectBookmark(bookmark, index)
			}
			setIsItemExpanded(true)
			if (useBookmarkPageStore.getState().layoutMode === LayoutMode.SideBySide) {
				setBookmarkPreviewOpened(true)
			}
		}

		return (
			<li
				className={clsx("group flex flex-row justify-start py-2", {
					"bg-teal-600 text-stone-100": isBookmarkItemExpanded && selected,
					"text-teal-600": selected && !isBookmarkItemExpanded,
				})}
				onMouseEnter={() => {
					if (!isBookmarkItemExpanded) {
						selectBookmark(bookmark, index)
					}
				}}
			>
				<button
					type="button"
					disabled={alwaysExpandItem || !selected}
					className={clsx("select-none flex items-start font-bold hover:bg-teal-600 hover:text-stone-100", {
						invisible: !selected,
					})}
					onClick={() => {
						setIsItemExpanded(!isBookmarkItemExpanded)
						setBookmarkPreviewOpened(false)
					}}
				>
					<span className="sr-only">Options for this bookmark</span>
					<span>&nbsp;</span>
					<span className={isBookmarkItemExpanded ? "rotate-90" : ""}>&gt;</span>
					<span>&nbsp;</span>
				</button>
				<div className="flex flex-col w-full">
					<Link
						to={`/bookmarks/${bookmark.id}`}
						className={clsx("block w-full text-start font-bold", { underline: selected })}
						onClick={expandOrOpenPreview}
					>
						{bookmark.title}
					</Link>
					<p className="opacity-80 text-sm">{url.host}</p>
					{isBookmarkItemExpanded && selected ? (
						<div className="flex flex-col space-y-1 md:flex-row md:space-y-0 md:space-x-2 items-end justify-between pt-2">
							<p className="text-sm">#dev</p>
							<div className="flex space-x-2">
								<OpenBookmarkPreviewButton />
								<Button variant="light" className="text-sm">
									<span className="underline">E</span>dit
								</Button>
								<Button variant="light" className="text-sm" onClick={deleteItem}>
									<span className="underline">D</span>elete
								</Button>
								<span className="-ml-2">&nbsp;</span>
							</div>
						</div>
					) : null}
				</div>
			</li>
		)
	},
)

function OpenBookmarkPreviewButton() {
	const isBookmarkPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
	const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
	const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded)

	useEffect(() => {
		function onKeyDown(event: KeyboardEvent) {
			if (isBookmarkPreviewOpened && event.key === "c") {
				closePreview()
			} else if (!isBookmarkPreviewOpened && event.key === "o") {
				openPreview()
			}
		}

		window.addEventListener("keydown", onKeyDown)

		return () => {
			window.removeEventListener("keydown", onKeyDown)
		}
	}, [isBookmarkPreviewOpened])

	function closePreview() {
		setBookmarkPreviewOpened(false)
		setBookmarkItemExpanded(false)
	}

	function openPreview() {
		setBookmarkPreviewOpened(true)
	}

	return (
		<Button
			variant="light"
			className="text-sm"
			onClick={() => {
				if (isBookmarkPreviewOpened) {
					closePreview()
				} else {
					openPreview()
				}
			}}
		>
			{isBookmarkPreviewOpened ? (
				<>
					<span className="underline">C</span>lose
				</>
			) : (
				<>
					<span className="underline">O</span>pen
				</>
			)}
		</Button>
	)
}

export { BookmarkList, BookmarkListItemAction }
