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

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

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

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

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

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

	onItemAction: ItemActionCallback
	setBookmarks: (bookmarks: Bookmark[]) => 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) {
	let _selectedBookmarkId = selectedBookmarkId
	if (!_selectedBookmarkId && bookmarks.length > 0) {
		_selectedBookmarkId = bookmarks[0].id
	}

	return createStore<BookmarkListState>()(
		subscribeWithSelector((set) => ({
			bookmarks,
			alwaysExpandItem,
			selectedIndex: selectedBookmarkId ? bookmarks.findIndex((bookmark) => bookmark.id === selectedBookmarkId) : 0,
			selectedBookmarkId: _selectedBookmarkId ?? "",
			isItemExpanded: false,

			onItemAction,

			setBookmarks(bookmarks: Bookmark[]) {
				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!
		store.getState().setBookmarks(bookmarks)
	}, [bookmarks])

	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: Bookmark; 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 onItemAction = useBookmarkListStore((state) => state.onItemAction)

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

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

		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,
				})}
			>
				<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)
					}}
				>
					<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 })}
					>
						{bookmark.title}
					</Link>
					<p className="opacity-80 text-sm">{url.host}</p>
					{isBookmarkItemExpanded && selected ? (
						<>
							<BookmarkTagList bookmark={bookmark} />
							<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">
								<div className="flex space-x-2">
									<Button variant="light" className="text-sm">
										<span>COPY LINK</span>
									</Button>
									<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 BookmarkTagList({ bookmark }: { bookmark: Bookmark }) {
	const { data: tags, status } = useBookmarkTags(bookmark)
	switch (status) {
		case "pending":
			return <LoadingSpinner />
		case "success":
			return <p className="my-2 text-sm">{tags.map((tag) => `#${tag.name}`).join(" ")}</p>
		case "error":
			return null
	}
}

export { BookmarkList, BookmarkListItemAction }
