import type { LinkBookmark } from "@markone/core/bookmark"
import { createFileRoute, useNavigate } from "@tanstack/react-router"
import clsx from "clsx"
import { useEffect, useId, useState } from "react"
import { create } from "zustand"
import { fetchApi, useAuthenticatedQuery, BadRequestError, ApiErrorCode } from "~/api"
import { useCreateBookmark, useDeleteBookmark } from "~/bookmark/api"
import { Button } from "~/components/button"
import { Dialog, DialogActionRow, DialogBody, DialogTitle } from "~/components/dialog"
import { Message, MessageVariant } from "~/components/message"
import { FormField } from "~/components/form-field"
import { LoadingSpinner } from "~/components/loading-spinner"
import { useMnemonics } from "~/hooks/use-mnemonics"
import { useLogOut } from "~/auth"

const LAYOUT_MODE = {
	popup: "popup",
	sideBySide: "side-by-side",
} as const
type LayoutMode = (typeof LAYOUT_MODE)[keyof typeof LAYOUT_MODE]

enum ActiveDialog {
	None = "None",
	AddBookmark = "AddBookmark",
	DeleteBookmark = "DeleteBookmark",
}

interface BookmarkPageState {
	bookmarkCount: number
	selectedBookmarkId: string
	selectedBookmarkIndex: number
	isBookmarkItemExpanded: boolean
	isBookmarkPreviewOpened: boolean
	bookmarkToBeDeleted: LinkBookmark | null
	layoutMode: LayoutMode
	activeDialog: ActiveDialog

	setActiveDialog: (dialog: ActiveDialog) => void
	setBookmarkItemExpanded: (isExpanded: boolean) => void
	setBookmarkPreviewOpened: (isOpened: boolean) => void
	setLayoutMode: (mode: LayoutMode) => void
	selectBookmark: (bookmark: LinkBookmark, index: number) => void
	reconcileSelection: (bookmarks: LinkBookmark[]) => void
	markBookmarkForDeletion: (bookmark: LinkBookmark | null) => void
}

const useBookmarkPageStore = create<BookmarkPageState>()((set, get) => ({
	bookmarkCount: 0,
	bookmarks: [],
	selectedBookmarkId: "",
	selectedBookmarkIndex: 0,
	isBookmarkItemExpanded: false,
	isBookmarkPreviewOpened: false,
	bookmarkToBeDeleted: null,
	layoutMode: LAYOUT_MODE.popup,
	activeDialog: ActiveDialog.None,

	setActiveDialog(dialog: ActiveDialog) {
		set({ activeDialog: dialog })
	},

	setBookmarkItemExpanded(isExpanded: boolean) {
		set({ isBookmarkItemExpanded: isExpanded })
	},

	setBookmarkPreviewOpened(isOpened: boolean) {
		set({ isBookmarkPreviewOpened: isOpened })
	},

	setLayoutMode(mode: LayoutMode) {
		set({ layoutMode: mode })
	},

	selectBookmark(bookmark: LinkBookmark, index: number) {
		set({ selectedBookmarkId: bookmark.id, selectedBookmarkIndex: index })
	},

	reconcileSelection(bookmarks: LinkBookmark[]) {
		const { selectedBookmarkId, selectedBookmarkIndex } = get()
		const newIndex = bookmarks.findIndex((bookmark) => bookmark.id === selectedBookmarkId)
		if (newIndex !== selectedBookmarkIndex) {
			if (newIndex >= 0) {
				set({ selectedBookmarkIndex: newIndex })
			} else if (selectedBookmarkIndex >= bookmarks.length - 1) {
				set({ selectedBookmarkIndex: bookmarks.length - 1 })
			} else if (selectedBookmarkIndex === 0) {
				set({ selectedBookmarkIndex: 0 })
			} else {
				set({ selectedBookmarkIndex: selectedBookmarkIndex + 1 })
			}
		}
	},

	markBookmarkForDeletion(bookmark: LinkBookmark | null) {
		set({ bookmarkToBeDeleted: bookmark })
	},
}))

function Page() {
	const setLayoutMode = useBookmarkPageStore((state) => state.setLayoutMode)

	useEffect(() => {
		function mediaQueryListener(this: MediaQueryList) {
			if (this.matches) {
				setLayoutMode(LAYOUT_MODE.sideBySide)
			} else {
				setLayoutMode(LAYOUT_MODE.popup)
			}
		}

		const q = window.matchMedia("(width >= 64rem)")
		q.addEventListener("change", mediaQueryListener)

		mediaQueryListener.call(q)

		return () => {
			q.removeEventListener("change", mediaQueryListener)
		}
	}, [setLayoutMode])

	return (
		<div className="relative">
			<Main>
				<div className="flex flex-col md:flex-row justify-center py-16 lg:py-32">
					<header className="mb-4 md:mb-0 md:mr-16 text-start">
						<h1 className="font-bold text-start">
							<span className="invisible md:hidden">&gt;&nbsp;</span>
							YOUR BOOKMARKS
						</h1>
					</header>
					<BookmarkListSection />
				</div>
				<BookmarkPreview />
			</Main>
			<ActionBar />
			<PageDialog />
		</div>
	)
}

function Main({ children }: React.PropsWithChildren) {
	const isPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
	const layoutMode = useBookmarkPageStore((state) => state.layoutMode)

	return (
		<main
			className={clsx(
				"px-4 lg:px-8 2xl:px-0 grid flex justify-center relative w-full",
				isPreviewOpened && layoutMode === LAYOUT_MODE.sideBySide ? "grid-cols-2" : "grid-cols-1",
			)}
		>
			{children}
		</main>
	)
}

function PageDialog() {
	const dialog = useBookmarkPageStore((state) => state.activeDialog)
	switch (dialog) {
		case ActiveDialog.None:
			return null
		case ActiveDialog.AddBookmark:
			return <AddBookmarkDialog />
		case ActiveDialog.DeleteBookmark:
			return <DeleteBookmarkDialog />
	}
}

function DeleteBookmarkDialog() {
	// biome-ignore lint/style/noNonNullAssertion: this cannot be null when delete bookmark dialog is visible
	const bookmark = useBookmarkPageStore((state) => state.bookmarkToBeDeleted!)
	const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog)
	const markBookmarkForDeletion = useBookmarkPageStore((state) => state.markBookmarkForDeletion)
	const deleteBookmarkMutation = useDeleteBookmark()

	useMnemonics(
		{
			y: proceed,
			n: cancel,
		},
		{ active: true },
	)

	async function proceed() {
		try {
			await deleteBookmarkMutation.mutateAsync({ bookmark })
			setActiveDialog(ActiveDialog.None)
			markBookmarkForDeletion(null)
		} catch (error) {
			console.error(error)
		}
	}

	function cancel() {
		setActiveDialog(ActiveDialog.None)
		markBookmarkForDeletion(null)
	}

	function body() {
		switch (deleteBookmarkMutation.status) {
			case "pending":
				return (
					<p>
						Deleting <LoadingSpinner />
					</p>
				)
			case "idle":
				return (
					<p>
						The bookmark titled{" "}
						<strong>
							<em>"{bookmark.title}"</em>
						</strong>{" "}
						will be deleted. Proceed?
					</p>
				)
			case "error":
				return <p className="text-red-500">Failed to delete the bookmark!</p>
		}
	}

	function title() {
		switch (deleteBookmarkMutation.status) {
			case "pending":
				return "PLEASE WAIT"
			case "idle":
				return "CONFIRM"
			case "error":
				return "ERROR OCCURRED"
		}
	}

	return (
		<Dialog>
			<DialogTitle>{title()}</DialogTitle>
			<DialogBody>{body()}</DialogBody>
			<DialogActionRow>
				<Button disabled={deleteBookmarkMutation.isPending} onClick={proceed}>
					{deleteBookmarkMutation.isError ? "Retry" : "Proceed"} (y)
				</Button>
				<Button disabled={deleteBookmarkMutation.isPending} onClick={cancel}>
					Cancel (n)
				</Button>
			</DialogActionRow>
		</Dialog>
	)
}

function AddBookmarkDialog() {
	const [isWebsiteUnreachable, setIsWebsiteUnreachable] = useState(false)
	const createBookmarkMutation = useCreateBookmark()
	const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog)
	const formId = useId()

	useMnemonics(
		{
			c: cancel,
		},
		{ active: true },
	)

	async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
		event.preventDefault()

		const formData = new FormData(event.currentTarget)
		const url = formData.get("link")
		if (url && typeof url === "string") {
			try {
				await createBookmarkMutation.mutateAsync({ url, force: isWebsiteUnreachable })
			} catch (error) {
				if (error instanceof BadRequestError && error.code === ApiErrorCode.WebsiteUnreachable) {
					setIsWebsiteUnreachable(true)
				}
			}
		}
	}

	function cancel() {
		setActiveDialog(ActiveDialog.None)
	}

	function message() {
		if (createBookmarkMutation.isPending) {
			return (
				<p>
					Loading <LoadingSpinner />
				</p>
			)
		}
		if (isWebsiteUnreachable) {
			return (
				<Message variant={MessageVariant.Warning} className="px-4">
					The link does not seem to be reachable. Click "SAVE" to save anyways.
				</Message>
			)
		}
		if (createBookmarkMutation.status === "error") {
			return (
				<Message variant={MessageVariant.Error} className="px-4">
					An error occurred when saving bookmark
				</Message>
			)
		}
		return null
	}

	return (
		<Dialog>
			<DialogTitle>NEW BOOKMARK</DialogTitle>
			<DialogBody>
				{message()}
				<form id={formId} className="px-8" onSubmit={onSubmit}>
					<FormField
						type="text"
						name="link"
						label="LINK"
						className="w-full"
						labelClassName="bg-stone-300 dark:bg-stone-800"
					/>
				</form>
			</DialogBody>
			<DialogActionRow>
				<Button type="submit" disabled={createBookmarkMutation.isPending} form={formId}>
					SAVE
				</Button>
				<Button type="button" disabled={createBookmarkMutation.isPending} onClick={cancel}>
					<span className="underline">C</span>ANCEL
				</Button>
			</DialogActionRow>
		</Dialog>
	)
}

function BookmarkListSection() {
	const { data: bookmarks, status } = useAuthenticatedQuery(["bookmarks"], () =>
		fetchApi("/bookmarks").then((res) => res.json()),
	)

	switch (status) {
		case "pending":
			return (
				<p className="container max-w-2xl">
					Loading&nbsp;
					<LoadingSpinner />
				</p>
			)
		case "success":
			if (bookmarks) {
				return <BookmarkList bookmarks={bookmarks} />
			}
			return null
		default:
			return null
	}
}

function BookmarkList({ bookmarks }: { bookmarks: LinkBookmark[] }) {
	const reconcileSelection = useBookmarkPageStore((state) => state.reconcileSelection)

	useEffect(() => {
		reconcileSelection(bookmarks)
	}, [bookmarks, reconcileSelection])

	useEffect(() => {
		function onKeyDown(event: KeyboardEvent) {
			const state = useBookmarkPageStore.getState()

			switch (event.key) {
				case "ArrowDown": {
					const nextIndex = state.selectedBookmarkIndex + 1
					if (nextIndex < bookmarks.length) {
						state.selectBookmark(bookmarks[nextIndex], nextIndex)
					}
					break
				}
				case "ArrowUp": {
					const prevIndex = state.selectedBookmarkIndex - 1
					if (prevIndex >= 0) {
						state.selectBookmark(bookmarks[prevIndex], prevIndex)
					}
					break
				}
				case "ArrowLeft":
					state.setBookmarkItemExpanded(false)
					break
				case "ArrowRight":
					state.setBookmarkItemExpanded(true)
					break
				default:
					break
			}
		}

		window.addEventListener("keydown", onKeyDown)

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

	return (
		<div className="flex flex-col container max-w-2xl -mt-2">
			{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} />)
			)}
		</div>
	)
}

function BookmarkPreview() {
	const isVisible = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
	const layoutMode = useBookmarkPageStore((state) => state.layoutMode)

	if (!isVisible) {
		return null
	}

	return (
		<div
			className={clsx(
				"h-screen flex justify-center items-center border-l border-stone-700 dark:border-stone-300 flex dark:bg-stone-900",
				{
					"absolute inset-0 border-l-0": layoutMode === LAYOUT_MODE.popup,
				},
			)}
		>
			<p>Content here</p>
		</div>
	)
}

function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index: number }) {
	const url = new URL(bookmark.url)
	const selectedBookmarkIndex = useBookmarkPageStore((state) => state.selectedBookmarkIndex)
	const isBookmarkItemExpanded = useBookmarkPageStore((state) => state.isBookmarkItemExpanded)
	const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog)
	const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded)
	const selectBookmark = useBookmarkPageStore((state) => state.selectBookmark)
	const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
	const markBookmarkForDeletion = useBookmarkPageStore((state) => state.markBookmarkForDeletion)
	const isSelected = selectedBookmarkIndex === index

	useMnemonics(
		{
			d: deleteItem,
		},
		{ active: isSelected },
	)

	function deleteItem() {
		if (isSelected) {
			markBookmarkForDeletion(bookmark)
			setActiveDialog(ActiveDialog.DeleteBookmark)
		}
	}

	function expandOrOpenPreview() {
		setBookmarkItemExpanded(true)
		if (useBookmarkPageStore.getState().layoutMode === LAYOUT_MODE.sideBySide) {
			console.log(useBookmarkPageStore.getState().layoutMode)
			setBookmarkPreviewOpened(true)
		}
	}

	return (
		<div
			className={clsx("group flex flex-row justify-start py-2", {
				"bg-teal-600 text-stone-100": isBookmarkItemExpanded && isSelected,
				"text-teal-600": isSelected && !isBookmarkItemExpanded,
			})}
			onMouseEnter={() => {
				if (!isBookmarkItemExpanded) {
					selectBookmark(bookmark, index)
				}
			}}
		>
			<button
				type="button"
				disabled={!isSelected}
				className={clsx("select-none flex items-start font-bold hover:bg-teal-600 hover:text-stone-100", {
					invisible: !isSelected,
				})}
				onClick={() => {
					setBookmarkItemExpanded(!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">
				<button type="button" className="text-start font-bold" onClick={expandOrOpenPreview}>
					{bookmark.title}
				</button>
				<p className="opacity-80 text-sm">{url.host}</p>
				{isBookmarkItemExpanded && isSelected ? (
					<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 #devops #devops #devops #devops #devops #devops</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>
		</div>
	)
}

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>
	)
}

function ActionBar() {
	const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog)

	useMnemonics(
		{
			a: addBookmark,
		},
		{ active: true },
	)

	function addBookmark() {
		setActiveDialog(ActiveDialog.AddBookmark)
	}

	return (
		<div className="fixed z-10 bottom-0 left-0 right-0 border-t-1 flex flex-row justify-center py-4 space-x-4">
			<Button onClick={addBookmark}>
				<span className="underline">A</span>DD
			</Button>
			<Button>
				<span className="underline">S</span>EARCH
			</Button>
			<LogOutButton />
		</div>
	)
}

function LogOutButton() {
	const logOutMutation = useLogOut()
	const navigate = useNavigate()

	function logOut() {
		logOutMutation.mutate()
		navigate({ to: "/", replace: true })
	}

	return (
		<Button disabled={logOutMutation.isPending} onClick={logOut}>
			LOG OUT
		</Button>
	)
}

export const Route = createFileRoute("/bookmarks")({
	component: Page,
})
