import type { DragEndEvent } from "@dnd-kit/core";
import {
	DndContext,
	KeyboardSensor,
	PointerSensor,
	TouchSensor,
	useSensor,
	useSensors,
} from "@dnd-kit/core";
import {
	arrayMove,
	SortableContext,
	sortableKeyboardCoordinates,
	useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Search as SearchIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useFetcher, useLoaderData } from "react-router";
import { SendouButton } from "~/components/elements/Button";
import { Input } from "~/components/Input";
import { MainSlotIcon } from "~/components/icons/MainSlot";
import { SideSlotIcon } from "~/components/icons/SideSlot";
import { Placeholder } from "~/components/Placeholder";
import type { Tables } from "~/db/tables";
import {
	ALL_WIDGETS,
	defaultStoredWidget,
	findWidgetById,
} from "~/features/user-page/core/widgets/portfolio";
import { getWidgetFormSchema } from "~/features/user-page/core/widgets/widget-form-schemas";
import { USER } from "~/features/user-page/user-page-constants";
import { useHydrated } from "~/hooks/useHydrated";
import { action } from "../actions/u.$identifier.edit-widgets.server";
import { WidgetSettingsForm } from "../components/WidgetSettingsForm";
import { loader } from "../loaders/u.$identifier.edit-widgets.server";
import styles from "./u.$identifier.edit-widgets.module.css";

export { action, loader };

export default function EditWidgetsPage() {
	const { t } = useTranslation(["user", "common"]);
	const data = useLoaderData<typeof loader>();
	const isHydrated = useHydrated();
	const fetcher = useFetcher();

	const [selectedWidgets, setSelectedWidgets] = useState<
		Array<Tables["UserWidget"]["widget"]>
	>(data.currentWidgets);
	const [expandedWidgetId, setExpandedWidgetId] = useState<string | null>(null);
	const [pendingScrollWidgetId, setPendingScrollWidgetId] = useState<
		string | null
	>(null);

	const mainWidgets = selectedWidgets.filter((w) => {
		const def = findWidgetById(w.id);
		return def?.slot === "main";
	});

	const sideWidgets = selectedWidgets.filter((w) => {
		const def = findWidgetById(w.id);
		return def?.slot === "side";
	});

	const sensors = useSensors(
		useSensor(PointerSensor),
		useSensor(TouchSensor, {
			activationConstraint: {
				delay: 200,
				tolerance: 5,
			},
		}),
		useSensor(KeyboardSensor, {
			coordinateGetter: sortableKeyboardCoordinates,
		}),
	);

	const handleDragStart = () => {
		setExpandedWidgetId(null);
	};

	const handleDragEnd = (event: DragEndEvent) => {
		const { active, over } = event;

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

		const oldIndex = selectedWidgets.findIndex((w) => w.id === active.id);
		const newIndex = selectedWidgets.findIndex((w) => w.id === over.id);

		setSelectedWidgets(arrayMove(selectedWidgets, oldIndex, newIndex));
	};

	const addWidget = (widgetId: string) => {
		const widget = findWidgetById(widgetId);
		if (!widget) return;

		const currentCount =
			widget.slot === "main" ? mainWidgets.length : sideWidgets.length;
		const maxCount =
			widget.slot === "main" ? USER.MAX_MAIN_WIDGETS : USER.MAX_SIDE_WIDGETS;

		if (currentCount >= maxCount) return;

		const newWidget = defaultStoredWidget(widgetId);

		setSelectedWidgets([...selectedWidgets, newWidget]);

		const widgetDef = findWidgetById(widgetId);
		if (widgetDef && "schema" in widgetDef) {
			setExpandedWidgetId(widgetId);
		}
	};

	const removeWidget = (widgetId: string) => {
		setSelectedWidgets(selectedWidgets.filter((w) => w.id !== widgetId));
		setExpandedWidgetId((prev) => (prev === widgetId ? null : prev));
	};

	const handleSubmit = () => {
		const invalidWidgetIds = computeInvalidWidgetIds(selectedWidgets);
		const firstInvalid = selectedWidgets.find((w) =>
			invalidWidgetIds.has(w.id),
		);
		if (firstInvalid) {
			setExpandedWidgetId(firstInvalid.id);
			setPendingScrollWidgetId(firstInvalid.id);
			return;
		}

		fetcher.submit(
			{ widgets: selectedWidgets } as unknown as Record<string, string>,
			{ method: "post", encType: "application/json" },
		);
	};

	const handleSettingsChange = (widgetId: string, settings: any) => {
		setSelectedWidgets(
			selectedWidgets.map((w) => (w.id === widgetId ? { ...w, settings } : w)),
		);
	};

	const toggleExpanded = (widgetId: string) => {
		setExpandedWidgetId((prev) => (prev === widgetId ? null : widgetId));
	};

	useEffect(() => {
		if (!pendingScrollWidgetId) return;
		if (expandedWidgetId !== pendingScrollWidgetId) return;
		const panel = document.querySelector<HTMLDivElement>(
			`[data-widget-settings="${pendingScrollWidgetId}"]`,
		);
		scrollToFirstWidgetError(panel);
		setPendingScrollWidgetId(null);
	}, [pendingScrollWidgetId, expandedWidgetId]);

	if (!isHydrated) {
		return <Placeholder />;
	}

	return (
		<div className={styles.container}>
			<header className={styles.header}>
				<h1>{t("user:widgets.editTitle")}</h1>
				<div className={styles.actions}>
					<SendouButton onPress={handleSubmit}>
						{t("common:actions.save")}
					</SendouButton>
				</div>
			</header>

			<div className={styles.content}>
				<div className={styles.grid}>
					<section className={styles.selected}>
						<DndContext
							sensors={sensors}
							onDragStart={handleDragStart}
							onDragEnd={handleDragEnd}
						>
							<SelectedWidgetsList
								mainWidgets={mainWidgets}
								sideWidgets={sideWidgets}
								onRemoveWidget={removeWidget}
								onSettingsChange={handleSettingsChange}
								expandedWidgetId={expandedWidgetId}
								onToggleExpanded={toggleExpanded}
							/>
						</DndContext>
					</section>

					<section className={styles.available}>
						<h2>{t("user:widgets.available")}</h2>
						<AvailableWidgetsList
							selectedWidgets={selectedWidgets}
							mainWidgets={mainWidgets}
							sideWidgets={sideWidgets}
							onAddWidget={addWidget}
						/>
					</section>
				</div>
			</div>
		</div>
	);
}

interface AvailableWidgetsListProps {
	selectedWidgets: Array<Tables["UserWidget"]["widget"]>;
	mainWidgets: Array<Tables["UserWidget"]["widget"]>;
	sideWidgets: Array<Tables["UserWidget"]["widget"]>;
	onAddWidget: (widgetId: string) => void;
}

function AvailableWidgetsList({
	selectedWidgets,
	mainWidgets,
	sideWidgets,
	onAddWidget,
}: AvailableWidgetsListProps) {
	const { t } = useTranslation(["user"]);
	const [searchValue, setSearchValue] = useState("");

	const widgetsByCategory = ALL_WIDGETS;
	const categoryKeys = (
		Object.keys(widgetsByCategory) as Array<keyof typeof widgetsByCategory>
	).sort((a, b) => a.localeCompare(b));

	const searchLower = searchValue.toLowerCase();

	return (
		<div>
			<Input
				className={styles.searchInput}
				icon={<SearchIcon className={styles.searchIcon} />}
				value={searchValue}
				onChange={(e) => setSearchValue(e.target.value)}
				placeholder={t("user:widgets.search")}
			/>
			{categoryKeys.map((category) => {
				const filteredWidgets = widgetsByCategory[category]!.filter((widget) =>
					(t(`user:widget.${widget.id}` as const) as string)
						.toLowerCase()
						.includes(searchLower),
				);

				if (filteredWidgets.length === 0) return null;

				return (
					<div key={category} className={styles.categoryGroup}>
						<div className={styles.categoryTitle}>
							{t(`user:widgets.category.${category}`)}
						</div>
						{filteredWidgets.map((widget) => {
							const isSelected = selectedWidgets.some(
								(w) => w.id === widget.id,
							);
							const currentCount =
								widget.slot === "main"
									? mainWidgets.length
									: sideWidgets.length;
							const maxCount =
								widget.slot === "main"
									? USER.MAX_MAIN_WIDGETS
									: USER.MAX_SIDE_WIDGETS;
							const isMaxReached = currentCount >= maxCount;

							return (
								<div key={widget.id} className={styles.widgetCard}>
									<div className={styles.widgetHeader}>
										<span className={styles.widgetName}>
											{t(`user:widget.${widget.id}` as const)}
										</span>
										<SendouButton
											size="miniscule"
											variant="outlined"
											onPress={() => onAddWidget(widget.id)}
											isDisabled={isSelected || isMaxReached}
										>
											{t("user:widgets.add")}
										</SendouButton>
									</div>
									<div className={styles.widgetFooter}>
										<div className={styles.widgetSlot}>
											{widget.slot === "main" ? (
												<>
													<MainSlotIcon size={16} />
													<span>{t("user:widgets.main")}</span>
												</>
											) : (
												<>
													<SideSlotIcon size={16} />
													<span>{t("user:widgets.side")}</span>
												</>
											)}
										</div>
										<div className="text-xs font-bold">{"//"}</div>
										<div className={styles.widgetDescription}>
											{t(
												`user:widgets.description.${widget.id}` as const,
												widgetDescriptionParams(widget.id),
											)}
										</div>
									</div>
								</div>
							);
						})}
					</div>
				);
			})}
		</div>
	);
}

interface SelectedWidgetsListProps {
	mainWidgets: Array<Tables["UserWidget"]["widget"]>;
	sideWidgets: Array<Tables["UserWidget"]["widget"]>;
	onRemoveWidget: (widgetId: string) => void;
	onSettingsChange: (widgetId: string, settings: any) => void;
	expandedWidgetId: string | null;
	onToggleExpanded: (widgetId: string) => void;
}

function SelectedWidgetsList({
	mainWidgets,
	sideWidgets,
	onRemoveWidget,
	onSettingsChange,
	expandedWidgetId,
	onToggleExpanded,
}: SelectedWidgetsListProps) {
	const { t } = useTranslation(["user"]);

	return (
		<div className={styles.selectedWidgetsList}>
			<div className={styles.slotSection}>
				<div className={styles.slotHeader}>
					<span className="stack horizontal xs">
						<MainSlotIcon size={24} /> {t("user:widgets.mainSlot")}
					</span>
					<span className={styles.slotCount}>
						{mainWidgets.length}/{USER.MAX_MAIN_WIDGETS}
					</span>
				</div>
				<SortableContext items={mainWidgets.map((w) => w.id)}>
					<div className={styles.widgetList}>
						{mainWidgets.length === 0 ? (
							<div className={styles.empty}>
								{t("user:widgets.add")} {t("user:widgets.mainSlot")}
							</div>
						) : (
							mainWidgets.map((widget) => (
								<DraggableWidgetItem
									key={widget.id}
									widget={widget}
									onRemove={onRemoveWidget}
									onSettingsChange={onSettingsChange}
									isExpanded={expandedWidgetId === widget.id}
									onToggleExpanded={onToggleExpanded}
								/>
							))
						)}
					</div>
				</SortableContext>
			</div>

			<div className={styles.slotSection}>
				<div className={styles.slotHeader}>
					<span className="stack horizontal xs">
						<SideSlotIcon size={24} /> {t("user:widgets.sideSlot")}
					</span>
					<span className={styles.slotCount}>
						{sideWidgets.length}/{USER.MAX_SIDE_WIDGETS}
					</span>
				</div>
				<SortableContext items={sideWidgets.map((w) => w.id)}>
					<div className={styles.widgetList}>
						{sideWidgets.length === 0 ? (
							<div className={styles.empty}>
								{t("user:widgets.add")} {t("user:widgets.sideSlot")}
							</div>
						) : (
							sideWidgets.map((widget) => (
								<DraggableWidgetItem
									key={widget.id}
									widget={widget}
									onRemove={onRemoveWidget}
									onSettingsChange={onSettingsChange}
									isExpanded={expandedWidgetId === widget.id}
									onToggleExpanded={onToggleExpanded}
								/>
							))
						)}
					</div>
				</SortableContext>
			</div>
		</div>
	);
}

interface DraggableWidgetItemProps {
	widget: Tables["UserWidget"]["widget"];
	onRemove: (widgetId: string) => void;
	onSettingsChange: (widgetId: string, settings: any) => void;
	isExpanded: boolean;
	onToggleExpanded: (widgetId: string) => void;
}

function DraggableWidgetItem({
	widget,
	onRemove,
	onSettingsChange,
	isExpanded,
	onToggleExpanded,
}: DraggableWidgetItemProps) {
	const { t } = useTranslation(["user", "common"]);

	const {
		attributes,
		listeners,
		setNodeRef,
		transform,
		transition,
		isDragging,
	} = useSortable({ id: widget.id });

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

	const widgetDef = findWidgetById(widget.id);
	const hasSettings = widgetDef && "schema" in widgetDef;

	return (
		<div
			ref={setNodeRef}
			style={style}
			className={`${styles.draggableWidget} ${isDragging ? styles.isDragging : ""}`}
			{...attributes}
		>
			<div className={styles.widgetHeader}>
				<span className={styles.widgetName} {...listeners}>
					☰ {t(`user:widget.${widget.id}` as const)}
				</span>
				<div className={styles.widgetActions}>
					{hasSettings ? (
						<SendouButton
							size="miniscule"
							variant="outlined"
							onPress={() => onToggleExpanded(widget.id)}
						>
							{isExpanded
								? t("common:actions.hide")
								: t("common:actions.settings")}
						</SendouButton>
					) : null}
					<SendouButton
						size="miniscule"
						variant="minimal-destructive"
						onPress={() => onRemove(widget.id)}
					>
						{t("user:widgets.remove")}
					</SendouButton>
				</div>
			</div>

			{isExpanded && hasSettings ? (
				<div data-widget-settings={widget.id} className={styles.widgetSettings}>
					<WidgetSettingsForm
						widget={widget}
						onSettingsChange={onSettingsChange}
					/>
				</div>
			) : null}
		</div>
	);
}

const WIDGET_DESCRIPTION_PARAMS: Record<string, Record<string, unknown>> = {
	"game-badges": { max: USER.GAME_BADGES_MAX },
	"game-badges-small": { max: USER.GAME_BADGES_SMALL_MAX },
};

function widgetDescriptionParams(widgetId: string) {
	return WIDGET_DESCRIPTION_PARAMS[widgetId];
}

function scrollToFirstWidgetError(container: HTMLDivElement | null) {
	const target = container?.querySelector<HTMLElement>('[id$="-error"]');
	target?.scrollIntoView({ behavior: "smooth", block: "center" });
}

function computeInvalidWidgetIds(
	widgets: Array<Tables["UserWidget"]["widget"]>,
): Set<string> {
	const invalid = new Set<string>();
	for (const widget of widgets) {
		const schema = getWidgetFormSchema(widget.id);
		if (!schema) continue;
		if (!schema.safeParse(widget.settings ?? {}).success) {
			invalid.add(widget.id);
		}
	}
	return invalid;
}
