"use client";

import type {
	Announcements,
	DndContextProps,
	DragEndEvent,
	DragOverEvent,
	DragStartEvent,
} from "@dnd-kit/core";
import {
	closestCenter,
	DndContext,
	DragOverlay,
	KeyboardSensor,
	MouseSensor,
	TouchSensor,
	useDroppable,
	useSensor,
	useSensors,
} from "@dnd-kit/core";
import { arrayMove, SortableContext, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { GripHorizontal } from "lucide-react";
import {
	createContext,
	type HTMLAttributes,
	type ReactNode,
	useContext,
	useState,
} from "react";
import { createPortal } from "react-dom";
import tunnel from "tunnel-rat";
import { Card } from "~/components/ui/card";
import { ScrollArea, ScrollBar } from "~/components/ui/scroll-area";
import { cn } from "~/lib/utils";

//https://www.shadcn.io/components/data/kanban#installation
const t = tunnel();

export type { DragEndEvent } from "@dnd-kit/core";

type KanbanItemProps = {
	id: string;
	name: string;
	column: string;
} & Record<string, unknown>;

type KanbanColumnProps = {
	id: string;
	name: string;
} & Record<string, unknown>;

type KanbanContextProps<
	T extends KanbanItemProps = KanbanItemProps,
	C extends KanbanColumnProps = KanbanColumnProps,
> = {
	columns: C[];
	data: T[];
	activeCardId: string | null;
};

const KanbanContext = createContext<KanbanContextProps>({
	columns: [],
	data: [],
	activeCardId: null,
});

export type KanbanBoardProps = {
	id: string;
	children: ReactNode;
	className?: string;
};

export const KanbanBoard = ({ id, children, className }: KanbanBoardProps) => {
	const { isOver, setNodeRef } = useDroppable({
		id,
	});

	return (
		<div
			className={cn(
				"flex size-full min-h-40 flex-col divide-y overflow-hidden rounded-md border bg-secondary text-xs shadow-sm ring-2 transition-all",
				isOver ? "ring-primary" : "ring-transparent",
				className,
			)}
			ref={setNodeRef}
		>
			{children}
		</div>
	);
};

export type KanbanCardProps<T extends KanbanItemProps = KanbanItemProps> = T & {
	children?: ReactNode;
	className?: string;
	cardOnClick?: () => void;
};

export const KanbanCard = <T extends KanbanItemProps = KanbanItemProps>({
	id,
	name,
	children,
	className,
	cardOnClick,
}: KanbanCardProps<T>) => {
	const {
		attributes,
		listeners,
		setNodeRef,
		setActivatorNodeRef,
		transition,
		transform,
		isDragging,
	} = useSortable({
		id,
	});
	const { activeCardId } = useContext(KanbanContext) as KanbanContextProps;

	const style = {
		transition,
		transform: CSS.Transform.toString(transform),
	};

	return (
		<>
			<div style={style} {...attributes} ref={setNodeRef}>
				<Card
					className={cn(
						"relative flex w-full cursor-pointer flex-col gap-0 rounded-md py-0 shadow-sm", // Removed default padding to handle it manually
						isDragging && "opacity-30",
						className,
					)}
					onClick={cardOnClick}
				>
					<div
						{...listeners}
						className={cn(
							"flex h-6 w-full cursor-grab items-center justify-center rounded-t-md bg-muted/50 transition-colors hover:bg-muted",
							isDragging && "cursor-grabbing",
						)}
						ref={setActivatorNodeRef}
					>
						<GripHorizontal className="text-muted-foreground" />
					</div>

					<div className="p-3">
						{children ?? <p className="m-0 font-medium text-sm">{name}</p>}
					</div>
				</Card>
			</div>

			{activeCardId === id && (
				<t.In>
					<Card
						className={cn(
							"relative flex w-full flex-col gap-0 rounded-md py-0 shadow-sm ring-2 ring-primary",
							className,
						)}
					>
						<div className="flex h-6 w-full cursor-grabbing items-center justify-center rounded-t-md bg-muted/50">
							<GripHorizontal className="text-muted-foreground" />
						</div>
						<div className="p-3">
							{children ?? <p className="m-0 font-medium text-sm">{name}</p>}
						</div>
					</Card>
				</t.In>
			)}
		</>
	);
};

export type KanbanCardsProps<T extends KanbanItemProps = KanbanItemProps> =
	Omit<HTMLAttributes<HTMLDivElement>, "children" | "id"> & {
		children: (item: T) => ReactNode;
		id: string;
	};

export const KanbanCards = <T extends KanbanItemProps = KanbanItemProps>({
	children,
	className,
	...props
}: KanbanCardsProps<T>) => {
	const { data } = useContext(KanbanContext) as KanbanContextProps<T>;
	const filteredData = data.filter((item) => item.column === props.id);
	const items = filteredData.map((item) => item.id);

	return (
		<ScrollArea className="overflow-hidden">
			<SortableContext items={items}>
				<div
					className={cn("flex flex-grow flex-col gap-2 p-2", className)}
					{...props}
				>
					{filteredData.map(children)}
				</div>
			</SortableContext>
			<ScrollBar orientation="vertical" />
		</ScrollArea>
	);
};

export type KanbanHeaderProps = HTMLAttributes<HTMLDivElement>;

export const KanbanHeader = ({ className, ...props }: KanbanHeaderProps) => (
	<div className={cn("m-0 p-2 font-semibold text-sm", className)} {...props} />
);

export type KanbanProviderProps<
	T extends KanbanItemProps = KanbanItemProps,
	C extends KanbanColumnProps = KanbanColumnProps,
> = Omit<DndContextProps, "children"> & {
	children: (column: C) => ReactNode;
	className?: string;
	columns: C[];
	data: T[];
	onDataChange?: (data: T[]) => void;
	onDragStart?: (event: DragStartEvent) => void;
	onDragEnd?: (event: DragEndEvent) => void;
	onDragOver?: (event: DragOverEvent) => void;
};

export const KanbanProvider = <
	T extends KanbanItemProps = KanbanItemProps,
	C extends KanbanColumnProps = KanbanColumnProps,
>({
	children,
	onDragStart,
	onDragEnd,
	onDragOver,
	className,
	columns,
	data,
	onDataChange,
	...props
}: KanbanProviderProps<T, C>) => {
	const [activeCardId, setActiveCardId] = useState<string | null>(null);

	const sensors = useSensors(
		useSensor(MouseSensor),
		useSensor(TouchSensor),
		useSensor(KeyboardSensor),
	);

	const handleDragStart = (event: DragStartEvent) => {
		const card = data.find((item) => item.id === event.active.id);
		if (card) {
			setActiveCardId(event.active.id as string);
		}
		onDragStart?.(event);
	};

	const handleDragOver = (event: DragOverEvent) => {
		const { active, over } = event;

		if (!over) {
			return;
		}

		const activeItem = data.find((item) => item.id === active.id);
		const overItem = data.find((item) => item.id === over.id);

		if (!activeItem) {
			return;
		}

		const activeColumn = activeItem.column;
		const overColumn =
			overItem?.column ||
			columns.find((col) => col.id === over.id)?.id ||
			columns[0]?.id;

		if (!overColumn) {
			return;
		}

		if (activeColumn !== overColumn) {
			let newData = [...data];
			const activeIndex = newData.findIndex((item) => item.id === active.id);
			const overIndex = newData.findIndex((item) => item.id === over.id);

			if (newData[activeIndex] === undefined) {
				return;
			}
			newData[activeIndex].column = overColumn; // Now TypeScript knows this is a string
			newData = arrayMove(newData, activeIndex, overIndex);

			onDataChange?.(newData);
		}

		onDragOver?.(event);
	};

	const handleDragEnd = (event: DragEndEvent) => {
		setActiveCardId(null);

		onDragEnd?.(event);

		const { active, over } = event;

		if (!over || active.id === over.id) {
			return;
		}

		let newData = [...data];

		const oldIndex = newData.findIndex((item) => item.id === active.id);
		const newIndex = newData.findIndex((item) => item.id === over.id);

		newData = arrayMove(newData, oldIndex, newIndex);

		onDataChange?.(newData);
	};

	const announcements: Announcements = {
		onDragStart({ active }) {
			const { name, column } = data.find((item) => item.id === active.id) ?? {};

			return `Picked up the card "${name}" from the "${column}" column`;
		},
		onDragOver({ active, over }) {
			const { name } = data.find((item) => item.id === active.id) ?? {};
			const newColumn = columns.find((column) => column.id === over?.id)?.name;

			return `Dragged the card "${name}" over the "${newColumn}" column`;
		},
		onDragEnd({ active, over }) {
			const { name } = data.find((item) => item.id === active.id) ?? {};
			const newColumn = columns.find((column) => column.id === over?.id)?.name;

			return `Dropped the card "${name}" into the "${newColumn}" column`;
		},
		onDragCancel({ active }) {
			const { name } = data.find((item) => item.id === active.id) ?? {};

			return `Cancelled dragging the card "${name}"`;
		},
	};

	return (
		<KanbanContext.Provider value={{ columns, data, activeCardId }}>
			<DndContext
				accessibility={{ announcements }}
				collisionDetection={closestCenter}
				onDragEnd={handleDragEnd}
				onDragOver={handleDragOver}
				onDragStart={handleDragStart}
				sensors={sensors}
				{...props}
			>
				<div
					className={cn(
						"grid size-full auto-cols-fr grid-flow-col gap-3",
						className,
					)}
				>
					{columns.map((column) => children(column))}
				</div>
				{typeof window !== "undefined" &&
					createPortal(
						<DragOverlay>
							<t.Out />
						</DragOverlay>,
						document.body,
					)}
			</DndContext>
		</KanbanContext.Provider>
	);
};
