"use client";

import {
	type QueryClient,
	queryOptions,
	useMutation,
	useQuery,
	useQueryClient,
} from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import {
	ChevronDown,
	LayoutGrid,
	LayoutList,
	Minimize2,
	RefreshCw,
} from "lucide-react";
import { useRef, useState, useSyncExternalStore } from "react";
import { createProject } from "@/app/_main/projects/-create-project";
import { ProjectsGridCard } from "@/app/_main/projects/-projects-grid-card";
import Loading from "@/app/-loading";
import { HNavBar, HNavBarText, VStack } from "@/components/layout";
import { SearchBox } from "@/components/SearchBox";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import {
	Dialog,
	DialogContent,
	DialogFooter,
	DialogTitle,
} from "@/components/ui/dialog";
import {
	DropdownMenu,
	DropdownMenuContent,
	DropdownMenuItem,
	DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Progress } from "@/components/ui/progress";
import {
	Tooltip,
	TooltipContent,
	TooltipTrigger,
} from "@/components/ui/tooltip";
import { assertNever } from "@/lib/assert-never";
import {
	commands,
	type TauriRestoreProjectFromBackupProgress,
} from "@/lib/bindings";
import { callAsyncCommand } from "@/lib/call-async-command";
import { isFindKey, useDocumentEvent } from "@/lib/events";
import { useProjectUpdateInProgress } from "@/lib/global-events";
import { tc, tt } from "@/lib/i18n";
import { toastError, toastSuccess, toastThrownError } from "@/lib/toast";
import { ProjectsTableCard } from "./-projects-list-card";

export const Route = createFileRoute("/_main/projects/")({
	component: Page,
});

const environmentProjects = queryOptions({
	queryKey: ["environmentProjects"],
	queryFn: commands.environmentProjects,
});

function Page() {
	const result = useQuery(environmentProjects);
	const [search, setSearch] = useState("");

	const viewModeQuery = useQuery({
		initialData: "List",
		queryKey: ["environmentGetProjectViewMode"],
		queryFn: async () => {
			return await commands.environmentProjectViewMode();
		},
	});

	const queryClient = useQueryClient();

	const setViewModeMutation = useMutation({
		mutationFn: async (value: string) => {
			await commands.environmentSetProjectViewMode(value);
		},
		onMutate: async (value: string) => {
			await queryClient.setQueryData(["environmentGetProjectViewMode"], value);
		},
		onSuccess: async () => {
			await queryClient.invalidateQueries({
				queryKey: ["environmentGetProjectViewMode"],
			});
		},
	});

	const viewMode = viewModeQuery.data ?? true;

	const setViewMode = (value: string) => {
		setViewModeMutation.mutate(value);
	};

	const startCreateProject = () => void createProject();

	const loading = result.isFetching;

	return (
		<VStack>
			<ProjectViewHeader
				startCreateProject={startCreateProject}
				isLoading={loading}
				search={search}
				setSearch={setSearch}
				viewMode={viewMode}
				setViewMode={setViewMode}
			/>
			<main className="shrink overflow-hidden flex w-full h-full">
				{result.status === "pending" ? (
					<Card className="w-full shadow-none overflow-hidden p-4">
						<Loading loadingText={tc("general:loading...")} />
					</Card>
				) : result.status === "error" ? (
					<Card className="w-full shadow-none overflow-hidden p-4">
						{tc("projects:error:load error", { msg: result.error.message })}
					</Card>
				) : viewMode === "List" ? (
					<ProjectsTableCard
						projects={result.data}
						search={search}
						loading={loading}
					/>
				) : viewMode === "Grid" ? (
					<ProjectsGridCard
						projects={result.data}
						search={search}
						loading={loading}
					/>
				) : (
					<ProjectsTableCard
						projects={result.data}
						search={search}
						loading={loading}
					/>
				)}
			</main>
			<RestoreBackupProgressHost />
		</VStack>
	);
}

type RestoreBackupProgressState = {
	progress: TauriRestoreProjectFromBackupProgress;
	minimized: boolean;
	promise: Promise<
		| "NoFileSelected"
		| "InvalidSelection"
		| "AlreadyExists"
		| "AlreadyAdded"
		| "Successful"
		| "cancelled"
	>;
};

let restoreBackupProgressState: RestoreBackupProgressState | null = null;
const restoreBackupProgressListeners = new Set<() => void>();

function emitRestoreBackupProgress() {
	for (const listener of restoreBackupProgressListeners) listener();
}

function setRestoreBackupProgressState(
	state: RestoreBackupProgressState | null,
) {
	restoreBackupProgressState = state;
	emitRestoreBackupProgress();
}

function subscribeRestoreBackupProgress(listener: () => void) {
	restoreBackupProgressListeners.add(listener);
	return () => restoreBackupProgressListeners.delete(listener);
}

function getRestoreBackupProgressSnapshot() {
	return restoreBackupProgressState;
}

function minimizeRestoreBackupProgress() {
	if (restoreBackupProgressState == null) return;
	setRestoreBackupProgressState({
		...restoreBackupProgressState,
		minimized: true,
	});
}

function restoreRestoreBackupProgress() {
	if (restoreBackupProgressState == null) return;
	setRestoreBackupProgressState({
		...restoreBackupProgressState,
		minimized: false,
	});
}

async function restoreProjectFromBackup(queryClient: QueryClient) {
	if (restoreBackupProgressState != null) {
		restoreRestoreBackupProgress();
		return;
	}

	const [_, commandPromise] = callAsyncCommand(
		commands.environmentRestoreProjectFromBackup,
		[],
		(progress) => {
			const prev = restoreBackupProgressState;
			if (prev == null) return;
			if (prev.progress.proceed > progress.proceed) return;
			setRestoreBackupProgressState({
				...prev,
				progress,
			});
		},
	);

	const promise = commandPromise.finally(() => {
		setRestoreBackupProgressState(null);
	});

	setRestoreBackupProgressState({
		progress: {
			proceed: 0,
			total: 1,
			last_proceed: tt("projects:dialog:restore backup waiting"),
		},
		minimized: false,
		promise,
	});

	const result = await promise;

	switch (result) {
		case "NoFileSelected":
		case "cancelled":
			// no-op
			break;
		case "InvalidSelection":
			toastError(tt("general:toast:invalid directory"));
			break;
		case "AlreadyExists":
		case "AlreadyAdded":
			toastError(tt("projects:toast:project already exists"));
			break;
		case "Successful":
			toastSuccess(tt("projects:toast:project restored from backup"));
			break;
		default:
			assertNever(result);
	}

	await queryClient.invalidateQueries(environmentProjects);
}

function RestoreBackupProgressHost() {
	const state = useSyncExternalStore(
		subscribeRestoreBackupProgress,
		getRestoreBackupProgressSnapshot,
	);

	if (state == null) return null;

	return (
		<>
			<Dialog
				open={!state.minimized}
				onOpenChange={(open) => {
					if (!open) minimizeRestoreBackupProgress();
				}}
			>
				<DialogContent
					onEscapeKeyDown={(event) => {
						event.preventDefault();
						minimizeRestoreBackupProgress();
					}}
				>
					<DialogTitle>
						{tc("projects:dialog:restore backup header")}
					</DialogTitle>
					<div>
						<p>{tc("projects:dialog:restoring backup...")}</p>
						<p>
							{tc("projects:dialog:proceed k/n", {
								count: state.progress.proceed,
								total: state.progress.total,
							})}
						</p>
						<p className={"overflow-hidden w-full whitespace-pre"}>
							{state.progress.last_proceed}
						</p>
						<Progress
							value={state.progress.proceed}
							max={state.progress.total}
						/>
						<p>{tc("projects:do not close")}</p>
					</div>
					<DialogFooter className={"gap-2"}>
						<Button disabled>{tc("general:button:cancel")}</Button>
						<Button className="gap-2" onClick={minimizeRestoreBackupProgress}>
							<Minimize2 className="size-4" />
							{tc("projects:manage:progress:minimize")}
						</Button>
					</DialogFooter>
				</DialogContent>
			</Dialog>
			{state.minimized && (
				<Button
					className="fixed bottom-4 right-4 z-50 gap-2 shadow-2xl"
					onClick={restoreRestoreBackupProgress}
				>
					<RefreshCw className="size-4 animate-spin" />
					{tc("projects:dialog:backup restore")}
				</Button>
			)}
		</>
	);
}

function ProjectViewHeader({
	startCreateProject,
	isLoading,
	search,
	setSearch,
	viewMode,
	setViewMode,
}: {
	startCreateProject?: () => void;
	isLoading?: boolean;
	search: string;
	setSearch: (search: string) => void;
	viewMode: string;
	setViewMode: (viewMode: string) => void;
}) {
	const queryClient = useQueryClient();
	const addProjectWithPicker = useMutation({
		mutationFn: async () => await commands.environmentAddProjectWithPicker(),
		onSuccess: (result) => {
			switch (result) {
				case "NoFolderSelected":
					// no-op
					break;
				case "InvalidSelection":
					toastError(tt("general:toast:invalid directory"));
					break;
				case "Successful":
					toastSuccess(tt("projects:toast:project added"));
					break;
				case "AlreadyAdded":
					toastError(tt("projects:toast:project already exists"));
					break;
				default:
					assertNever(result);
			}
		},
		onError: (e) => {
			console.error("Error adding project", e);
			toastThrownError(e);
		},
		onSettled: async () => {
			await queryClient.invalidateQueries(environmentProjects);
		},
	});

	const startRestoreProjectFromBackup = () => {
		void restoreProjectFromBackup(queryClient);
	};

	const inProgress = useProjectUpdateInProgress();

	const searchRef = useRef<HTMLInputElement>(null);

	useDocumentEvent(
		"keydown",
		(e) => {
			if (isFindKey(e)) {
				searchRef.current?.focus();
			}
		},
		[],
	);

	isLoading = isLoading || inProgress;

	return (
		<HNavBar
			className="shrink-0"
			leading={
				<>
					<HNavBarText>{tc("projects")}</HNavBarText>
					<Tooltip>
						<TooltipTrigger asChild>
							<Button
								className={"compact:h-10 compact:w-10"}
								variant={"ghost"}
								size={"icon"}
								onClick={() =>
									queryClient.invalidateQueries(environmentProjects)
								}
								disabled={isLoading}
							>
								{isLoading ? (
									<RefreshCw className="w-5 h-5 animate-spin" />
								) : (
									<RefreshCw className={"w-5 h-5"} />
								)}
							</Button>
						</TooltipTrigger>
						<TooltipContent>{tc("projects:tooltip:refresh")}</TooltipContent>
					</Tooltip>

					<SearchBox
						className={"w-max grow compact:h-10"}
						value={search}
						onChange={(e) => setSearch(e.target.value)}
						ref={searchRef}
					/>

					<Button
						className={"compact:h-10"}
						variant={"ghost"}
						onClick={() => {
							if (viewMode === "List") {
								setViewMode("Grid");
							} else {
								setViewMode("List");
							}
						}}
					>
						{viewMode === "List" ? (
							<>
								<LayoutList className={"w-5 h-5"} />
								<p className="ml-2">{tc("projects:list view")}</p>
							</>
						) : viewMode === "Grid" ? (
							<>
								<LayoutGrid className={"w-5 h-5"} />
								<p className="ml-2">{tc("projects:grid view")}</p>
							</>
						) : (
							<>
								<LayoutList className={"w-5 h-5"} />
								<p className="ml-2">{tc("projects:list view")}</p>
							</>
						)}
					</Button>
				</>
			}
			trailing={
				<DropdownMenu>
					<div className={"flex divide-x"}>
						<Button
							className={"rounded-r-none pl-4 pr-3 compact:h-10"}
							onClick={startCreateProject}
							variant="emphasis"
						>
							{tc("projects:create new project")}
						</Button>
						<DropdownMenuTrigger
							asChild
							className={"rounded-l-none pl-2 pr-2 compact:h-10"}
						>
							<Button variant="emphasis">
								<ChevronDown className={"w-4 h-4"} />
							</Button>
						</DropdownMenuTrigger>
					</div>
					<DropdownMenuContent>
						<DropdownMenuItem onClick={() => addProjectWithPicker.mutate()}>
							{tc("projects:add existing project")}
						</DropdownMenuItem>
						<DropdownMenuItem onClick={startRestoreProjectFromBackup}>
							{tc("projects:restore from backup")}
						</DropdownMenuItem>
					</DropdownMenuContent>
				</DropdownMenu>
			}
		/>
	);
}
