import { useEffect, useMemo, useState } from "react";
import {
	Dialog,
	DialogContent,
	DialogHeader,
	DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
	Select,
	SelectContent,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import {
	ChevronDown,
	ChevronUp,
	Link2,
	Pencil,
	Plus,
	Trash2,
	Split,
} from "lucide-react";
import {
	useCreateWidgetReference,
	useDeleteWidgetInstance,
	useDeleteWidgetReference,
	useDetachWidgetReference,
	useSaveWidgetInstance,
	useUpdateWidgetReference,
	useWidgetInstances,
	useWidgetReferences,
} from "../hooks/useWidgets";
import { useServiceInstances } from "../hooks/useServices";
import { useTasks } from "../hooks/useSettings";
import { useIsMobile } from "../hooks/useIsMobile";
import { SheetForm } from "@/components/ui/sheet-form";
import type { WidgetInstance, WidgetInstanceInput } from "../types";
import {
	BUILTIN_WIDGETS,
	SERVICE_REGISTRY,
	type ServiceWidgetBinding,
} from "../integrations/registry";

interface Props {
	open: boolean;
	onClose: () => void;
	/** When set, scope the dialog to a specific service instance's widgets. */
	serviceId?: string;
	/** When set, enable widget references ("Add existing") for this dashboard scope. */
	dashboardScope?: string;
	/** When set, auto-open in edit mode for this widget id (instead of the list view). */
	editWidgetId?: string;
}

interface Draft {
	id?: string;
	serviceId: string | null;
	widgetKind: string;
	title: string;
	config: Record<string, unknown>;
	enabled: boolean;
	sortOrder: number;
}

function Field({
	label,
	htmlFor,
	helper,
	children,
}: {
	label: string;
	htmlFor: string;
	helper?: string;
	children: React.ReactNode;
}) {
	return (
		<div className="flex flex-col gap-1.5">
			<Label htmlFor={htmlFor}>{label}</Label>
			{children}
			{helper ? (
				<p className="text-xs text-muted-foreground">{helper}</p>
			) : null}
		</div>
	);
}

function bindingLabel(serviceId: string | null, widgetKind: string): string {
	if (serviceId === null)
		return BUILTIN_WIDGETS[widgetKind]?.name ?? widgetKind;
	return widgetKind;
}

function WidgetConfigEditor({
	binding,
	isTaskOutput,
	config,
	onChange,
	tasks,
}: {
	binding: ServiceWidgetBinding | undefined;
	isTaskOutput: boolean;
	config: Record<string, unknown>;
	onChange: (config: Record<string, unknown>) => void;
	tasks: { id: string; name: string; enabled: boolean }[];
}) {
	// SSH task output gets a dedicated task picker; everything else gets a
	// generic text field per top-level schema property.
	if (isTaskOutput) {
		return (
			<Field label="Saved task" htmlFor="widget-task-id">
				<Select
					value={String(config.task_id ?? "")}
					onValueChange={(v) => onChange({ ...config, task_id: v })}
				>
					<SelectTrigger id="widget-task-id">
						<SelectValue placeholder="Select a task" />
					</SelectTrigger>
					<SelectContent>
						{tasks
							.filter((t) => t.enabled)
							.map((t) => (
								<SelectItem key={t.id} value={t.id}>
									{t.name}
								</SelectItem>
							))}
					</SelectContent>
				</Select>
			</Field>
		);
	}

	const properties = binding
		? Object.entries(
				(
					binding.configSchema as
						| { properties?: Record<string, unknown> }
						| undefined
				)?.properties ?? {},
			)
		: [];

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

	return (
		<div className="flex flex-col gap-3">
			{properties.map(([key, schema]) => {
				const isNumber =
					(schema as { type?: string }).type === "integer" ||
					(schema as { type?: string }).type === "number";
				// Use a multi-line textarea for fields that tend to hold complex
				// multi-line values (PromQL, text blocks, etc.). The widget kind's
				// config schema can opt in via `format: "textarea"`; the well-known
				// `query` field is treated as textarea by default.
				const schemaFormat = (schema as { format?: string }).format;
				const isTextarea = schemaFormat === "textarea" || key === "query";
				return (
					<Field
						key={key}
						label={key}
						htmlFor={`widget-cfg-${key}`}
						helper={(schema as { description?: string }).description}
					>
						{isTextarea ? (
							<Textarea
								id={`widget-cfg-${key}`}
								rows={4}
								className="resize-y font-mono text-xs"
								value={String(config[key] ?? "")}
								onChange={(e) => onChange({ ...config, [key]: e.target.value })}
							/>
						) : (
							<Input
								id={`widget-cfg-${key}`}
								type={isNumber ? "number" : "text"}
								value={String(config[key] ?? "")}
								onChange={(e) =>
									onChange({
										...config,
										[key]: isNumber
											? e.target.value === ""
												? undefined
												: Number(e.target.value)
											: e.target.value,
									})
								}
							/>
						)}
					</Field>
				);
			})}
		</div>
	);
}

export function WidgetConfigDialog({
	open,
	onClose,
	serviceId,
	dashboardScope,
	editWidgetId,
}: Props) {
	// When editing a dashboard (no serviceId), scope to dashboard-only widgets
	// (service_id IS NULL) so service-scoped widgets don't leak into the list.
	const { data: instances = [] } = useWidgetInstances(
		serviceId,
		!serviceId && dashboardScope ? "dashboard" : undefined,
	);
	const { data: services = [] } = useServiceInstances();
	const { data: tasks = [] } = useTasks();
	const saveWidget = useSaveWidgetInstance();
	const deleteWidget = useDeleteWidgetInstance();
	const { data: references = [] } = useWidgetReferences(dashboardScope);
	const createRef = useCreateWidgetReference();
	const deleteRef = useDeleteWidgetReference();
	const detachRef = useDetachWidgetReference();
	const updateRef = useUpdateWidgetReference();
	const { data: allWidgets = [] } = useWidgetInstances();
	const [showExisting, setShowExisting] = useState(false);
	const [existingSearch, setExistingSearch] = useState("");

	const [draft, setDraft] = useState<Draft | null>(null);
	const [draftBaseline, setDraftBaseline] = useState<Draft | null>(null);
	// When opened via editWidgetId, closing the edit should close the dialog
	// entirely (not fall back to the list view).
	const directEdit = Boolean(editWidgetId);

	// When editWidgetId is set and the dialog opens, auto-enter edit mode for
	// that widget (instead of showing the list view).
	useEffect(() => {
		if (open && editWidgetId) {
			// Search both owned widgets and referenced widgets.
			const target =
				instances.find((w) => w.id === editWidgetId) ??
				references.find((r) => r.widget.id === editWidgetId)?.widget;
			if (target) {
				startEdit(target);
			}
		}
	}, [open, editWidgetId, instances, references]);

	function startAddBuiltIn(kind: string) {
		const binding = BUILTIN_WIDGETS[kind];
		setDraft({
			serviceId: serviceId ?? null,
			widgetKind: kind,
			title: binding?.name ?? kind,
			config: { ...(binding?.defaultConfig ?? {}) },
			enabled: true,
			sortOrder: 0,
		});
	}

	function startAddService(serviceId: string, kind: string) {
		const binding = SERVICE_REGISTRY[
			services.find((s) => s.id === serviceId)?.service_type ?? ""
		]?.widgets.find((w) => w.kind === kind);
		setDraft({
			serviceId,
			widgetKind: kind,
			title: binding?.name ?? kind,
			config: { ...(binding?.defaultConfig ?? {}) },
			enabled: true,
			sortOrder: 0,
		});
	}

	function startEdit(instance: WidgetInstance) {
		const d: Draft = {
			id: instance.id,
			serviceId: instance.service_id,
			widgetKind: instance.widget_kind,
			title: instance.title,
			config: instance.config,
			enabled: instance.enabled,
			sortOrder: instance.sort_order,
		};
		setDraft(d);
		setDraftBaseline(d);
	}

	function reset() {
		// If we were opened via direct edit, closing should close the dialog
		// entirely, not fall back to the list view.
		if (directEdit) {
			onClose();
			return;
		}
		setDraft(null);
		setDraftBaseline(null);
	}

	async function saveDraft() {
		if (!draft) return;
		const input: WidgetInstanceInput = {
			id: draft.id ?? null,
			service_id: draft.serviceId,
			widget_kind: draft.widgetKind,
			title: draft.title,
			config: draft.config,
			enabled: draft.enabled,
			sort_order: draft.sortOrder,
		};
		await saveWidget.mutateAsync(input);
		reset();
	}

	async function toggleEnabled(instance: WidgetInstance) {
		await saveWidget.mutateAsync({
			id: instance.id,
			service_id: instance.service_id,
			widget_kind: instance.widget_kind,
			title: instance.title,
			config: instance.config,
			enabled: !instance.enabled,
			sort_order: instance.sort_order,
		});
	}

	async function moveInstance(index: number, direction: -1 | 1) {
		const targetIndex = index + direction;
		if (targetIndex < 0 || targetIndex >= combinedWidgets.length) return;
		// Swap the two items in a copy, then renumber ALL items by their new
		// index position (index * 10). This guarantees the sort_order values
		// change even when both items previously shared the same value (e.g. 0).
		const reordered = [...combinedWidgets];
		const tmp = reordered[index];
		reordered[index] = reordered[targetIndex];
		reordered[targetIndex] = tmp;
		// Sequential (not Promise.all) to avoid cache-invalidation race.
		for (let i = 0; i < reordered.length; i++) {
			const item = reordered[i];
			const newSortOrder = i * 10;
			const refId = (item as { _ref_id?: string })._ref_id;
			if (refId) {
				await updateRef.mutateAsync({
					referenceId: refId,
					sortOrder: newSortOrder,
				});
			} else {
				await saveWidget.mutateAsync({ ...item, sort_order: newSortOrder });
			}
		}
	}

	async function removeInstance(instance: WidgetInstance) {
		await deleteWidget.mutateAsync(instance.id);
	}

	// Build a combined view of owned widgets + references for display.
	const owned = [...instances].sort(
		(a, b) => a.sort_order - b.sort_order || a.created_at - b.created_at,
	);
	const refs = references.map((r) => ({
		...r.widget,
		_ref_id: r.id,
		_is_reference: true as const,
	}));
	const combinedWidgets = [...owned, ...refs].sort(
		(a, b) => a.sort_order - b.sort_order || a.created_at - b.created_at,
	);

	const referencedWidgetIds = new Set(references.map((r) => r.widget_id));

	// Available widgets for the "Add existing" picker: all widgets not already
	// on this dashboard (owned or referenced).
	const availableWidgets = useMemo(() => {
		const onDashboard = new Set([
			...instances.map((w) => w.id),
			...referencedWidgetIds,
		]);
		const search = existingSearch.toLowerCase().trim();
		return allWidgets
			.filter((w) => !onDashboard.has(w.id))
			.filter(
				(w) =>
					!search ||
					w.title.toLowerCase().includes(search) ||
					w.widget_kind.toLowerCase().includes(search),
			);
	}, [allWidgets, instances, referencedWidgetIds, existingSearch]);

	async function handleAddReference(widgetId: string) {
		await createRef.mutateAsync({
			dashboard_scope: dashboardScope!,
			widget_id: widgetId,
		});
	}

	async function handleRemoveReference(refId: string) {
		await deleteRef.mutateAsync(refId);
	}

	async function handleDetach(refId: string) {
		await detachRef.mutateAsync(refId);
	}

	function handleClose(next: boolean) {
		if (!next) {
			reset();
			onClose();
		}
	}

	const draftBinding = draft
		? draft.serviceId
			? SERVICE_REGISTRY[
					services.find((s) => s.id === draft.serviceId)?.service_type ?? ""
				]?.widgets.find((w) => w.kind === draft.widgetKind)
			: BUILTIN_WIDGETS[draft.widgetKind]
		: undefined;
	const isMobile = useIsMobile();
	const isTaskOutput =
		draft?.serviceId !== null &&
		services.find((s) => s.id === draft?.serviceId)?.service_type ===
			"ssh_tasks";

	// The draft body (Title/SortOrder/Enabled/config editor) is shared between
	// the Dialog (desktop) and SheetForm (mobile). On mobile the inline
	// Back/Save buttons are omitted because the SheetForm footer provides them.
	const draftBody = draft ? (
		<div className="flex flex-col gap-4">
			<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
				<Field label="Title" htmlFor="widget-title">
					<Input
						id="widget-title"
						value={draft.title}
						onChange={(e) => setDraft({ ...draft, title: e.target.value })}
					/>
				</Field>
				<Field label="Sort order" htmlFor="widget-sort-order">
					<Input
						id="widget-sort-order"
						type="number"
						value={String(draft.sortOrder)}
						onChange={(e) =>
							setDraft({
								...draft,
								sortOrder: e.target.value === "" ? 0 : Number(e.target.value),
							})
						}
					/>
				</Field>
			</div>
			<div className="flex items-center gap-2">
				<Switch
					id="widget-enabled"
					className="mobile-touch-target"
					checked={draft.enabled}
					onCheckedChange={(checked) =>
						setDraft({ ...draft, enabled: checked })
					}
				/>
				<Label htmlFor="widget-enabled">Enabled</Label>
			</div>
			<WidgetConfigEditor
				binding={draftBinding}
				isTaskOutput={!!isTaskOutput}
				config={draft.config}
				onChange={(config) => setDraft({ ...draft, config })}
				tasks={tasks}
			/>
			{!isMobile ? (
				<div className="flex justify-end gap-2">
					<Button
						variant="outline"
						onClick={reset}
						className="mobile-touch-target"
					>
						Back
					</Button>
					<Button
						onClick={saveDraft}
						disabled={saveWidget.isPending}
						className="mobile-touch-target"
					>
						Save widget
					</Button>
				</div>
			) : null}
		</div>
	) : (
		<div className="flex flex-col gap-4">
			{combinedWidgets.length === 0 ? (
				<Alert>
					<AlertDescription>No widgets yet. Add one below.</AlertDescription>
				</Alert>
			) : (
				<div className="flex flex-col gap-2">
					{combinedWidgets.map((instance, index) => {
						const serviceName = instance.service_id
							? services.find((s) => s.id === instance.service_id)?.name
							: "Built-in";
						const isRef =
							(instance as { _is_reference?: boolean })._is_reference === true;
						const refId = (instance as { _ref_id?: string })._ref_id;
						return (
							<div
								key={instance.id}
								className="flex items-center gap-2 rounded border p-2"
							>
								<div className="flex flex-1 flex-col gap-1">
									<div className="flex items-center gap-2">
										<span className="font-medium">{instance.title}</span>
										{isRef ? (
											<Badge variant="secondary">
												<Link2 className="mr-1 h-3 w-3" />
												linked
											</Badge>
										) : null}
										<Badge variant="outline">
											{bindingLabel(instance.service_id, instance.widget_kind)}
										</Badge>
										{serviceName ? (
											<span className="text-xs text-muted-foreground">
												{serviceName}
											</span>
										) : null}
										{!instance.enabled ? (
											<Badge variant="secondary">disabled</Badge>
										) : null}
									</div>
								</div>
								<div className="flex items-center gap-1">
									<Button
										variant="ghost"
										size="icon"
										className="mobile-touch-target h-8 w-8"
										disabled={index === 0}
										onClick={() => moveInstance(index, -1)}
									>
										<ChevronUp className="h-4 w-4" />
									</Button>
									<Button
										variant="ghost"
										size="icon"
										className="mobile-touch-target h-8 w-8"
										disabled={index === combinedWidgets.length - 1}
										onClick={() => moveInstance(index, 1)}
									>
										<ChevronDown className="h-4 w-4" />
									</Button>
									{!isRef ? (
										<Switch
											className="mobile-touch-target"
											checked={instance.enabled}
											onCheckedChange={() => toggleEnabled(instance)}
											aria-label={`Toggle ${instance.title}`}
										/>
									) : null}
									{!isRef ? (
										<Button
											variant="ghost"
											size="icon"
											className="mobile-touch-target h-8 w-8"
											onClick={() => startEdit(instance)}
										>
											<Pencil className="h-4 w-4" />
										</Button>
									) : null}
									{isRef && refId ? (
										<Button
											variant="ghost"
											size="icon"
											className="mobile-touch-target h-8 w-8"
											title="Make an independent copy"
											onClick={() => handleDetach(refId)}
										>
											<Split className="h-4 w-4" />
										</Button>
									) : null}
									<Button
										variant="ghost"
										size="icon"
										className="mobile-touch-target h-8 w-8 text-destructive"
										title={
											isRef ? "Remove from this dashboard" : "Delete widget"
										}
										onClick={() =>
											isRef && refId
												? handleRemoveReference(refId)
												: removeInstance(instance)
										}
									>
										<Trash2 className="h-4 w-4" />
									</Button>
								</div>
							</div>
						);
					})}
				</div>
			)}

			{dashboardScope ? (
				<div className="flex flex-col gap-2">
					<Button
						variant="outline"
						size="sm"
						className="w-fit"
						onClick={() => setShowExisting(!showExisting)}
					>
						<Link2 className="mr-1 h-3 w-3" />
						{showExisting ? "Hide" : "Add existing widget"}
					</Button>
					{showExisting ? (
						<div className="flex flex-col gap-2">
							<Input
								placeholder="Search widgets..."
								value={existingSearch}
								onChange={(e) => setExistingSearch(e.target.value)}
							/>
							{availableWidgets.length === 0 ? (
								<p className="text-xs text-muted-foreground">
									No widgets available to reuse.
								</p>
							) : (
								<div className="flex flex-col gap-1">
									{availableWidgets.map((w) => {
										const owner = w.service_id
											? services.find((s) => s.id === w.service_id)?.name
											: "Dashboard";
										return (
											<div
												key={w.id}
												className="flex items-center gap-2 rounded border p-2"
											>
												<div className="flex flex-1 flex-col">
													<span className="text-sm font-medium">{w.title}</span>
													<span className="text-xs text-muted-foreground">
														{bindingLabel(w.service_id, w.widget_kind)} ·{" "}
														{owner}
													</span>
												</div>
												<Button
													variant="outline"
													size="sm"
													className="mobile-touch-target"
													onClick={() => handleAddReference(w.id)}
												>
													<Plus className="mr-1 h-3 w-3" />
													Add
												</Button>
											</div>
										);
									})}
								</div>
							)}
						</div>
					) : null}
				</div>
			) : null}

			<div className="flex flex-col gap-2">
				<p className="text-sm font-medium">Add widget</p>
				<div className="flex flex-wrap gap-2">
					{Object.values(BUILTIN_WIDGETS).map((b) => (
						<Button
							key={b.kind}
							variant="outline"
							size="sm"
							className="mobile-touch-target"
							onClick={() => startAddBuiltIn(b.kind)}
						>
							<Plus className="mr-1 h-3 w-3" />
							{b.name}
						</Button>
					))}
					{services
						.filter((s) => s.enabled)
						// When scoped to a service Overview, only show widgets for THAT
						// service instance's type (not all services' widgets).
						.filter((s) => !serviceId || s.id === serviceId)
						.flatMap((s) =>
							(SERVICE_REGISTRY[s.service_type]?.widgets ?? []).map((w) => (
								<Button
									key={`${s.id}:${w.kind}`}
									variant="outline"
									size="sm"
									className="mobile-touch-target"
									onClick={() => startAddService(s.id, w.kind)}
								>
									<Plus className="mr-1 h-3 w-3" />
									{w.name} · {s.name}
								</Button>
							)),
						)}
				</div>
				<p className="text-xs text-muted-foreground">
					Configure services on their service pages to unlock more widgets.
				</p>
			</div>
		</div>
	);

	const dialogTitle = draft
		? draft.id
			? "Edit widget"
			: "Add widget"
		: "Dashboard widgets";

	if (isMobile) {
		return (
			<SheetForm
				open={open}
				onOpenChange={(next) => {
					if (!next) handleClose(next);
				}}
				title={dialogTitle}
				onSave={draft ? saveDraft : () => handleClose(false)}
				onCancel={draft ? reset : () => handleClose(false)}
				saveLabel={draft ? "Save widget" : "Done"}
				isPending={draft ? saveWidget.isPending : false}
				isDirty={
					draft !== null && draftBaseline !== null
						? JSON.stringify(draft) !== JSON.stringify(draftBaseline)
						: draft !== null && draft?.id === undefined
				}
			>
				<div className="flex flex-col gap-4">{draftBody}</div>
			</SheetForm>
		);
	}

	return (
		<Dialog open={open} onOpenChange={handleClose}>
			<DialogContent className="sm:max-w-2xl">
				<DialogHeader>
					<DialogTitle>{dialogTitle}</DialogTitle>
				</DialogHeader>
				{draftBody}
			</DialogContent>
		</Dialog>
	);
}
