import { Check, ChevronsUpDown, Tag as TagIcon, X } from "lucide-react";
import type React from "react";
import {
	forwardRef,
	type KeyboardEvent,
	useEffect,
	useRef,
	useState,
} from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Command, CommandGroup, CommandItem } from "@/components/ui/command";
import {
	Popover,
	PopoverContent,
	PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";

/**
 * Tag Input component for adding and managing tags
 *
 * @example
 * ```tsx
 * <TagInput
 *   placeholder="Add tags..."
 *   tags={tags}
 *   onTagsChange={setTags}
 *   suggestions={[
 *     { value: 'fiction', label: 'Fiction' },
 *     { value: 'historical', label: 'Historical' }
 *   ]}
 * />
 * ```
 */

export type TagItem = {
	/**
	 * Unique identifier for the tag
	 */
	value: string;
	/**
	 * Display text for the tag
	 */
	label: string;
	/**
	 * Optional color or variant for the tag
	 */
	variant?: string;
	/**
	 * Optional icon for the tag
	 */
	icon?: React.ReactNode;
	/**
	 * Optional description of the tag
	 */
	description?: string;
	/**
	 * Whether the tag is disabled/readonly
	 */
	disabled?: boolean;
	/**
	 * Any additional properties
	 */
	[key: string]: any;
};

export interface TagInputProps
	extends Omit<
		React.InputHTMLAttributes<HTMLInputElement>,
		"value" | "onChange"
	> {
	/**
	 * Array of tag items
	 */
	tags: TagItem[];
	/**
	 * Callback when tags change
	 */
	onTagsChange: (tags: TagItem[]) => void;
	/**
	 * Optional array of tag suggestions
	 */
	suggestions?: TagItem[];
	/**
	 * Maximum number of tags allowed
	 */
	maxTags?: number;
	/**
	 * Minimum length for new tag values
	 */
	minLength?: number;
	/**
	 * Maximum length for new tag values
	 */
	maxLength?: number;
	/**
	 * Whether to allow duplicate tags
	 */
	allowDuplicates?: boolean;
	/**
	 * Whether to validate tag values
	 */
	validate?: (value: string) => boolean | string;
	/**
	 * Whether to convert tag input to lowercase
	 */
	lowercase?: boolean;
	/**
	 * Whether to allow creating new tags
	 */
	allowNew?: boolean;
	/**
	 * Whether the input is disabled
	 */
	disabled?: boolean;
	/**
	 * Whether the component should display in read-only mode
	 */
	readOnly?: boolean;
	/**
	 * Button text for adding tags
	 */
	addButtonText?: string;
	/**
	 * Placeholder text when no tags are present
	 */
	emptyPlaceholder?: string;
	/**
	 * Custom class for the container
	 */
	containerClassName?: string;
	/**
	 * Custom class for each tag
	 */
	tagClassName?: string;
	/**
	 * Custom renderer for tags
	 */
	tagRenderer?: (
		tag: TagItem,
		index: number,
		onRemove: () => void,
	) => React.ReactNode;
}

// Define permitted badge variants to match the Badge component
type BadgeVariant = "default" | "secondary" | "destructive" | "outline";

export const TagInput = forwardRef<HTMLInputElement, TagInputProps>(
	(
		{
			tags = [],
			onTagsChange,
			suggestions = [],
			maxTags,
			minLength = 1,
			maxLength = 50,
			allowDuplicates = false,
			validate,
			lowercase = false,
			allowNew = true,
			disabled = false,
			readOnly = false,
			addButtonText = "Add",
			placeholder = "Add a tag...",
			emptyPlaceholder = "No tags added",
			containerClassName,
			tagClassName,
			tagRenderer,
			className,
			...props
		},
		ref,
	) => {
		const [inputValue, setInputValue] = useState("");
		const [suggestionsOpen, setSuggestionsOpen] = useState(false);
		const [inputError, setInputError] = useState<string | null>(null);
		const [filteredSuggestions, setFilteredSuggestions] = useState(suggestions);
		const inputRef = useRef<HTMLInputElement>(null);

		// Create an internal ref that doesn't get forwarded
		const _internalRef = useRef<HTMLInputElement | null>(null);

		// Focus the input when clicking the container
		const focusInput = () => {
			if (!disabled && !readOnly && inputRef.current) {
				inputRef.current.focus();
			}
		};

		// Filter suggestions based on input value
		useEffect(() => {
			if (inputValue) {
				const filtered = suggestions.filter(
					(suggestion) =>
						suggestion.label.toLowerCase().includes(inputValue.toLowerCase()) &&
						(allowDuplicates ||
							!tags.some((tag) => tag.value === suggestion.value)),
				);
				setFilteredSuggestions(filtered);
			} else {
				// Show all available suggestions when input is empty
				setFilteredSuggestions(
					suggestions.filter(
						(suggestion) =>
							allowDuplicates ||
							!tags.some((tag) => tag.value === suggestion.value),
					),
				);
			}
		}, [inputValue, suggestions, tags, allowDuplicates]);

		// Add a tag
		const addTag = (tag: TagItem) => {
			if (disabled || readOnly) return;

			// Check maximum tags
			if (maxTags && tags.length >= maxTags) {
				setInputError(`Maximum ${maxTags} tags allowed`);
				return;
			}

			// Check for duplicates if not allowed
			if (!allowDuplicates && tags.some((t) => t.value === tag.value)) {
				setInputError("This tag already exists");
				return;
			}

			// Add the tag and reset input
			onTagsChange([...tags, tag]);
			setInputValue("");
			setInputError(null);

			// Focus back on input
			inputRef.current?.focus();
		};

		// Remove a tag
		const removeTag = (indexToRemove: number) => {
			if (disabled || readOnly) return;

			onTagsChange(tags.filter((_, index) => index !== indexToRemove));
			setInputError(null);

			// Focus back on input
			inputRef.current?.focus();
		};

		// Create a new tag from input
		const createNewTag = () => {
			if (!inputValue.trim() || !allowNew || disabled || readOnly) return;

			// Format the value
			let value = inputValue.trim();
			if (lowercase) {
				value = value.toLowerCase();
			}

			// Validate length
			if (value.length < minLength) {
				setInputError(`Tag must be at least ${minLength} characters`);
				return;
			}

			if (value.length > maxLength) {
				setInputError(`Tag must be at most ${maxLength} characters`);
				return;
			}

			// Custom validation
			if (validate) {
				const validationResult = validate(value);
				if (typeof validationResult === "string") {
					setInputError(validationResult);
					return;
				} else if (validationResult === false) {
					setInputError("Invalid tag");
					return;
				}
			}

			// Create and add the tag
			addTag({ value, label: value });
		};

		// Handle keyboard input
		const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
			// Clear error on new input
			if (inputError) {
				setInputError(null);
			}

			if (e.key === "Enter") {
				e.preventDefault();
				createNewTag();
			} else if (e.key === "Backspace" && !inputValue && tags.length > 0) {
				removeTag(tags.length - 1);
			} else if (e.key === "," || e.key === ";") {
				e.preventDefault();
				createNewTag();
			}
		};

		// Custom tag renderer or default
		const renderTag = (tag: TagItem, index: number) => {
			if (tagRenderer) {
				return tagRenderer(tag, index, () => removeTag(index));
			}

			return (
				<Badge
					key={`${tag.value}-${index}`}
					variant={(tag.variant as BadgeVariant) || "default"}
					className={cn(
						"m-1 max-w-full truncate",
						tag.disabled && "opacity-60",
						tagClassName,
					)}
				>
					<div className="flex items-center max-w-full">
						{tag.icon && <span className="mr-1 flex-shrink-0">{tag.icon}</span>}
						<span className="truncate">{tag.label}</span>
						{!readOnly && !tag.disabled && (
							<Button
								variant="ghost"
								size="sm"
								onClick={() => removeTag(index)}
								className="h-4 w-4 p-0 ml-1"
								disabled={disabled}
							>
								<X className="h-3 w-3" />
								<span className="sr-only">Remove {tag.label}</span>
							</Button>
						)}
					</div>
				</Badge>
			);
		};

		return (
			<div className="space-y-2">
				<div
					className={cn(
						"flex flex-wrap items-center border rounded-md px-3 py-2 focus-within:ring-1 focus-within:ring-ring focus-within:border-input",
						disabled && "opacity-60 cursor-not-allowed bg-muted",
						readOnly && "bg-muted/50 cursor-default",
						inputError && "border-destructive focus-within:ring-destructive",
						containerClassName,
					)}
					onClick={focusInput}
				>
					{/* Render existing tags */}
					{tags.length > 0 ? (
						<div className="flex flex-wrap -m-1">
							{tags.map((tag, index) => renderTag(tag, index))}
						</div>
					) : (
						<div className="text-muted-foreground text-sm py-1.5">
							{emptyPlaceholder}
						</div>
					)}

					{/* Render input */}
					{!readOnly && (
						<div className="flex flex-1 items-center min-w-[180px]">
							<input
								ref={ref}
								type="text"
								value={inputValue}
								onChange={(e) => setInputValue(e.target.value)}
								onKeyDown={handleKeyDown}
								placeholder={placeholder}
								className={cn(
									"flex-1 bg-transparent border-0 outline-none focus:outline-none focus:ring-0 p-1.5 text-sm",
									disabled && "cursor-not-allowed",
									className,
								)}
								disabled={
									disabled || (maxTags !== undefined && tags.length >= maxTags)
								}
								{...props}
							/>

							{/* Suggestions dropdown */}
							{suggestions.length > 0 && (
								<Popover
									open={suggestionsOpen}
									onOpenChange={setSuggestionsOpen}
								>
									<PopoverTrigger asChild>
										<Button
											variant="ghost"
											size="sm"
											className="h-7 w-7 p-0 ml-1"
											disabled={disabled || readOnly}
										>
											<ChevronsUpDown className="h-4 w-4" />
											<span className="sr-only">Show suggestions</span>
										</Button>
									</PopoverTrigger>
									<PopoverContent className="p-0 w-[200px]" align="start">
										<Command>
											<CommandGroup heading="Suggestions">
												{filteredSuggestions.length > 0 ? (
													filteredSuggestions.map((suggestion) => (
														<CommandItem
															key={suggestion.value}
															onSelect={() => {
																addTag(suggestion);
																setSuggestionsOpen(false);
															}}
														>
															<div className="flex items-center">
																{suggestion.icon || (
																	<TagIcon className="mr-2 h-4 w-4" />
																)}
																<span className="ml-2">{suggestion.label}</span>
															</div>
															<Check
																className={cn(
																	"ml-auto h-4 w-4",
																	tags.some(
																		(tag) => tag.value === suggestion.value,
																	)
																		? "opacity-100"
																		: "opacity-0",
																)}
															/>
														</CommandItem>
													))
												) : (
													<div className="px-2 py-3 text-sm text-muted-foreground text-center">
														No suggestions available
													</div>
												)}
											</CommandGroup>
										</Command>
									</PopoverContent>
								</Popover>
							)}

							{/* Add button */}
							{allowNew && inputValue && (
								<Button
									type="button"
									variant="ghost"
									size="sm"
									onClick={createNewTag}
									disabled={disabled || readOnly}
									className="h-7 p-1 px-2 ml-1"
								>
									{addButtonText}
								</Button>
							)}
						</div>
					)}
				</div>

				{/* Error message */}
				{inputError && (
					<p className="text-destructive text-xs mt-1">{inputError}</p>
				)}
			</div>
		);
	},
);

TagInput.displayName = "TagInput";

export default TagInput;
