import { api } from "@fileone/convex/_generated/api"
import type { Doc } from "@fileone/convex/_generated/dataModel"
import type { DirectoryItem } from "@fileone/convex/model/directories"
import { useMutation } from "@tanstack/react-query"
import { Link } from "@tanstack/react-router"
import {
	type ColumnDef,
	flexRender,
	getCoreRowModel,
	type Row,
	useReactTable,
} from "@tanstack/react-table"
import { useMutation as useContextMutation, useQuery } from "convex/react"
import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai"
import { CheckIcon, TextCursorInputIcon, TrashIcon, XIcon } from "lucide-react"
import { useEffect, useId, useRef } from "react"
import { toast } from "sonner"
import { DirectoryIcon } from "@/components/icons/directory-icon"
import { Checkbox } from "@/components/ui/checkbox"
import {
	ContextMenu,
	ContextMenuContent,
	ContextMenuItem,
	ContextMenuTrigger,
} from "@/components/ui/context-menu"
import {
	Table,
	TableBody,
	TableCell,
	TableHead,
	TableHeader,
	TableRow,
} from "@/components/ui/table"
import { TextFileIcon } from "../components/icons/text-file-icon"
import { Button } from "../components/ui/button"
import { LoadingSpinner } from "../components/ui/loading-spinner"
import { withDefaultOnError } from "../lib/error"
import { cn } from "../lib/utils"
import {
	contextMenuTargeItemAtom,
	itemBeingRenamedAtom,
	newItemKindAtom,
	optimisticDeletedItemsAtom,
} from "./state"

function formatFileSize(bytes: number): string {
	if (bytes === 0) return "0 B"

	const k = 1024
	const sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
	const i = Math.floor(Math.log(bytes) / Math.log(k))

	return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
}

const columns: ColumnDef<DirectoryItem>[] = [
	{
		id: "select",
		header: ({ table }) => (
			<Checkbox
				checked={table.getIsAllPageRowsSelected()}
				onCheckedChange={(value) =>
					table.toggleAllPageRowsSelected(!!value)
				}
				aria-label="Select all"
			/>
		),
		cell: ({ row }) => (
			<Checkbox
				checked={row.getIsSelected()}
				onCheckedChange={row.getToggleSelectedHandler()}
				aria-label="Select row"
			/>
		),
		enableSorting: false,
		enableHiding: false,
		size: 24,
	},
	{
		header: "Name",
		accessorKey: "doc.name",
		cell: ({ row }) => {
			switch (row.original.kind) {
				case "file":
					return <FileNameCell initialName={row.original.doc.name} />
				case "directory":
					return <DirectoryNameCell directory={row.original.doc} />
			}
		},
		size: 1000,
	},
	{
		header: "Size",
		accessorKey: "size",
		cell: ({ row }) => {
			switch (row.original.kind) {
				case "file":
					return <div>{formatFileSize(row.original.doc.size)}</div>
				case "directory":
					return <div className="font-mono">-</div>
			}
		},
	},
	{
		header: "Created At",
		accessorKey: "createdAt",
		cell: ({ row }) => {
			return (
				<div>
					{new Date(row.original.doc.createdAt).toLocaleString()}
				</div>
			)
		},
	},
]

export function FileTable({ path }: { path: string }) {
	return (
		<FileTableContextMenu>
			<div className="w-full">
				<FileTableContent path={path} />
			</div>
		</FileTableContextMenu>
	)
}

export function FileTableContextMenu({
	children,
}: {
	children: React.ReactNode
}) {
	const store = useStore()
	const target = useAtomValue(contextMenuTargeItemAtom)
	const setOptimisticDeletedItems = useSetAtom(optimisticDeletedItemsAtom)
	const moveToTrashMutation = useContextMutation(api.files.moveToTrash)
	const setItemBeingRenamed = useSetAtom(itemBeingRenamedAtom)
	const { mutate: moveToTrash } = useMutation({
		mutationFn: moveToTrashMutation,
		onMutate: ({ itemId }) => {
			setOptimisticDeletedItems((prev) => new Set([...prev, itemId]))
		},
		onSuccess: (itemId) => {
			setOptimisticDeletedItems((prev) => {
				const newSet = new Set(prev)
				newSet.delete(itemId)
				return newSet
			})
			toast.success("Moved to trash")
		},
	})

	const handleRename = () => {
		const selectedItem = store.get(contextMenuTargeItemAtom)
		if (selectedItem) {
			setItemBeingRenamed({
				kind: selectedItem.kind,
				originalItem: selectedItem,
				name: selectedItem.doc.name,
			})
		}
	}

	const handleDelete = () => {
		const selectedItem = store.get(contextMenuTargeItemAtom)
		if (selectedItem) {
			moveToTrash({
				kind: selectedItem.kind,
				itemId: selectedItem.doc._id,
			})
		}
	}

	return (
		<ContextMenu>
			<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
			{target && (
				<ContextMenuContent>
					<ContextMenuItem onClick={handleRename}>
						<TextCursorInputIcon />
						Rename
					</ContextMenuItem>
					<ContextMenuItem onClick={handleDelete}>
						<TrashIcon />
						Move to trash
					</ContextMenuItem>
				</ContextMenuContent>
			)}
		</ContextMenu>
	)
}

export function FileTableContent({ path }: { path: string }) {
	const directory = useQuery(api.files.fetchDirectoryContent, { path })
	const optimisticDeletedItems = useAtomValue(optimisticDeletedItemsAtom)
	const setContextMenuTargetItem = useSetAtom(contextMenuTargeItemAtom)
	const store = useStore()

	const handleRowContextMenu = (
		row: Row<DirectoryItem>,
		_event: React.MouseEvent,
	) => {
		const target = store.get(contextMenuTargeItemAtom)
		if (target === row.original) {
			setContextMenuTargetItem(null)
		} else {
			selectRow(row)
			setContextMenuTargetItem(row.original)
		}
	}

	const table = useReactTable({
		data: directory || [],
		columns,
		getCoreRowModel: getCoreRowModel(),
		enableRowSelection: true,
		enableGlobalFilter: true,
		globalFilterFn: (row, _columnId, _filterValue, _addMeta) => {
			return !optimisticDeletedItems.has(row.original.doc._id)
		},
		getRowId: (row) => row.doc._id,
	})

	const selectRow = (row: Row<DirectoryItem>) => {
		console.log("row.getIsSelected()", row.getIsSelected())
		if (!row.getIsSelected()) {
			table.toggleAllPageRowsSelected(false)
			row.toggleSelected(true)
		}
	}

	if (!directory) {
		return null
	}

	return (
		<div className="overflow-hidden">
			<Table>
				<TableHeader>
					{table.getHeaderGroups().map((headerGroup) => (
						<TableRow className="px-4" key={headerGroup.id}>
							{headerGroup.headers.map((header) => (
								<TableHead
									className="first:pl-4 last:pr-4"
									key={header.id}
									style={{ width: header.getSize() }}
								>
									{header.isPlaceholder
										? null
										: flexRender(
												header.column.columnDef.header,
												header.getContext(),
											)}
								</TableHead>
							))}
						</TableRow>
					))}
				</TableHeader>
				<TableBody>
					{table.getRowModel().rows?.length ? (
						table.getRowModel().rows.map((row) => (
							<TableRow
								key={row.id}
								data-state={row.getIsSelected() && "selected"}
								onClick={() => {
									selectRow(row)
								}}
								onContextMenu={(e) => {
									handleRowContextMenu(row, e)
								}}
							>
								{row.getVisibleCells().map((cell) => (
									<TableCell
										className="first:pl-4 last:pr-4"
										key={cell.id}
										style={{ width: cell.column.getSize() }}
									>
										{flexRender(
											cell.column.columnDef.cell,
											cell.getContext(),
										)}
									</TableCell>
								))}
							</TableRow>
						))
					) : (
						<NoResultsRow />
					)}
					<NewItemRow />
				</TableBody>
			</Table>
		</div>
	)
}

function NoResultsRow() {
	const newItemKind = useAtomValue(newItemKindAtom)
	if (newItemKind) {
		return null
	}
	return (
		<TableRow>
			<TableCell colSpan={columns.length} className="text-center">
				No results.
			</TableCell>
		</TableRow>
	)
}

function NewItemRow() {
	const inputRef = useRef<HTMLInputElement>(null)
	const newItemFormId = useId()
	const [newItemKind, setNewItemKind] = useAtom(newItemKindAtom)
	const { mutate: createDirectory, isPending } = useMutation({
		mutationFn: useContextMutation(api.files.createDirectory),
		onSuccess: () => {
			setNewItemKind(null)
		},
		onError: withDefaultOnError(() => {
			setTimeout(() => {
				inputRef.current?.focus()
			}, 1)
		}),
	})

	useEffect(() => {
		if (newItemKind) {
			setTimeout(() => {
				inputRef.current?.focus()
			}, 1)
		}
	}, [newItemKind])

	if (!newItemKind) {
		return null
	}

	const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
		event.preventDefault()

		const formData = new FormData(event.currentTarget)
		const itemName = formData.get("itemName") as string

		if (itemName) {
			createDirectory({ name: itemName })
		} else {
			toast.error("Please enter a name.")
		}
	}

	const clearNewItemKind = () => {
		// setItemBeingAdded(null)
		setNewItemKind(null)
	}

	return (
		<TableRow className={cn("align-middle", { "opacity-50": isPending })}>
			<TableCell />
			<TableCell className="p-0">
				<div className="flex items-center gap-2 px-2 py-1 h-full">
					{isPending ? (
						<LoadingSpinner className="size-6" />
					) : (
						<DirectoryIcon />
					)}
					<form
						className="w-full"
						id={newItemFormId}
						onSubmit={onSubmit}
					>
						<input
							ref={inputRef}
							type="text"
							name="itemName"
							defaultValue={newItemKind}
							disabled={isPending}
							className="w-full h-8 px-2 bg-transparent border border-input rounded-sm outline-none focus:border-primary focus:ring-1 focus:ring-primary"
						/>
					</form>
				</div>
			</TableCell>
			<TableCell />
			<TableCell align="right" className="space-x-2 p-1">
				{!isPending ? (
					<>
						<Button
							type="button"
							form={newItemFormId}
							variant="ghost"
							size="icon"
							onClick={clearNewItemKind}
						>
							<XIcon />
						</Button>
						<Button type="submit" form={newItemFormId} size="icon">
							<CheckIcon />
						</Button>
					</>
				) : null}
			</TableCell>
		</TableRow>
	)
}

function DirectoryNameCell({ directory }: { directory: Doc<"directories"> }) {
	return (
		<div className="flex w-full items-center gap-2">
			<DirectoryIcon className="size-4" />
			<Link className="hover:underline" to={`/files/${directory.path}`}>
				{directory.name}
			</Link>
		</div>
	)
}

function FileNameCell({ initialName }: { initialName: string }) {
	return (
		<div className="flex w-full items-center gap-2">
			<TextFileIcon className="size-4" />
			{initialName}
		</div>
	)
}
