"use client";

import { LucideMapPin } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { Input } from "@/components/ui/input";
import {
	type GoogleMapsLocationCapability,
	type GoogleMapsSuggestion,
	getGoogleMapsLocationCapability,
	getGoogleMapsPlaceLabel,
	getLocationAutocompletePlaceholder,
} from "@/lib/google-maps";
import { cn } from "@/lib/utils";

interface LocationAutocompleteProps {
	capability?: GoogleMapsLocationCapability;
	className?: string;
	id: string;
	onChange: (value: string) => void;
	value: string;
}

const SEARCH_HELP_TEXT = "Search Google Maps or keep typing a custom location.";
const SEARCH_UNAVAILABLE_TEXT =
	"Google Maps search is unavailable right now. You can still type a location.";

export const LocationAutocomplete = ({
	capability,
	className,
	id,
	onChange,
	value,
}: LocationAutocompleteProps) => {
	const resolvedCapability = capability ?? getGoogleMapsLocationCapability();

	if (!resolvedCapability.enabled) {
		return (
			<ManualLocationInput
				className={className}
				id={id}
				onChange={onChange}
				placeholder={getLocationAutocompletePlaceholder(resolvedCapability)}
				value={value}
			/>
		);
	}

	return (
		<ServerLocationInput
			capability={resolvedCapability}
			className={className}
			id={id}
			onChange={onChange}
			value={value}
		/>
	);
};

const ManualLocationInput = ({
	className,
	id,
	onChange,
	placeholder,
	value,
}: Omit<LocationAutocompleteProps, "capability"> & { placeholder: string }) => {
	return (
		<div className="space-y-1.5" data-location-mode="manual">
			<div className="relative">
				<LucideMapPin className="absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/50" />
				<Input
					className={cn("pl-8", className)}
					id={id}
					onChange={(event) => onChange(event.target.value)}
					placeholder={placeholder}
					value={value}
				/>
			</div>
		</div>
	);
};

const ServerLocationInput = ({
	capability,
	className,
	id,
	onChange,
	value,
}: Omit<LocationAutocompleteProps, "capability"> & {
	capability: GoogleMapsLocationCapability;
}) => {
	const [hasSearchError, setHasSearchError] = useState(false);
	const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
	const [suggestions, setSuggestions] = useState<GoogleMapsSuggestion[]>([]);
	const sessionTokenRef = useRef<string | null>(null);
	const skipNextLookupRef = useRef(false);

	const helperText = useMemo(() => {
		return hasSearchError ? SEARCH_UNAVAILABLE_TEXT : SEARCH_HELP_TEXT;
	}, [hasSearchError]);

	useEffect(() => {
		if (skipNextLookupRef.current) {
			skipNextLookupRef.current = false;
			return;
		}

		const query = value.trim();
		if (!query || hasSearchError) {
			setSuggestions([]);
			setIsLoadingSuggestions(false);
			return;
		}

		if (!sessionTokenRef.current) {
			sessionTokenRef.current = crypto.randomUUID();
		}

		const controller = new AbortController();
		setIsLoadingSuggestions(true);

		void fetch(
			`/api/location-autocomplete?input=${encodeURIComponent(query)}&sessionToken=${encodeURIComponent(sessionTokenRef.current)}`,
			{ signal: controller.signal },
		)
			.then(async (response) => {
				if (!response.ok) {
					throw new Error("Autocomplete request failed");
				}

				return (await response.json()) as {
					suggestions?: GoogleMapsSuggestion[];
				};
			})
			.then((payload) => {
				setSuggestions(payload.suggestions ?? []);
			})
			.catch((error) => {
				if (controller.signal.aborted) {
					return;
				}

				console.error("Location autocomplete failed", error);
				setHasSearchError(true);
				setSuggestions([]);
			})
			.finally(() => {
				if (!controller.signal.aborted) {
					setIsLoadingSuggestions(false);
				}
			});

		return () => controller.abort();
	}, [hasSearchError, value]);

	const handleSuggestionSelect = (suggestion: GoogleMapsSuggestion) => {
		const nextValue = getGoogleMapsPlaceLabel({
			formattedAddress: suggestion.formattedAddress,
			name: suggestion.text,
			predictionText: suggestion.text,
		});

		if (!nextValue) {
			return;
		}

		skipNextLookupRef.current = true;
		sessionTokenRef.current = null;
		setSuggestions([]);
		onChange(nextValue);
	};

	const showSuggestions = suggestions.length > 0 && !hasSearchError;

	return (
		<div className="space-y-1.5" data-location-mode="google-maps-server">
			<div className="relative">
				<LucideMapPin className="absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/50" />
				<Input
					aria-autocomplete="list"
					aria-expanded={showSuggestions}
					className={cn("pl-8", className)}
					id={id}
					onChange={(event) => {
						setHasSearchError(false);
						onChange(event.target.value);
					}}
					placeholder={getLocationAutocompletePlaceholder(capability)}
					role="combobox"
					value={value}
				/>
				{showSuggestions ? (
					<div className="absolute left-0 right-0 top-[calc(100%+0.375rem)] z-20 rounded-[10px] bg-popover p-1.5 shadow-lg">
						<div className="space-y-1" role="listbox">
							{suggestions.map((suggestion) => {
								const suggestionKey =
									suggestion.placeId || suggestion.text || "suggestion";

								return (
									<div key={suggestionKey}>
										<button
											className="w-full rounded-[6px] px-3 py-2 text-left text-sm text-foreground transition hover:bg-accent hover:text-accent-foreground"
											onClick={() => handleSuggestionSelect(suggestion)}
											type="button"
										>
											{suggestion.text || "Use this place"}
										</button>
									</div>
								);
							})}
						</div>
					</div>
				) : null}
			</div>
			<p className="text-xs text-muted-foreground">{helperText}</p>
			{isLoadingSuggestions ? (
				<p className="text-xs text-muted-foreground">
					Loading place suggestions…
				</p>
			) : null}
		</div>
	);
};
