import { Form } from '@inertiajs/react';
import type { LucideIcon } from 'lucide-react';
import * as LucideIcons from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import CategoryController from '@/actions/App/Http/Controllers/CategoryController';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogFooter,
    DialogHeader,
    DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
    Select,
    SelectContent,
    SelectItem,
    SelectTrigger,
    SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import type {
    CategoryParentOption,
    CategoryRow,
    CategoryType,
} from '@/types/category';

type LucideIconOption = {
    name: string;
    Icon: LucideIcon;
};

const { ChevronDown, Search } = LucideIcons;

const SKIP_ICON_EXPORTS = new Set(['createLucideIcon', 'Icon', 'icons']);

function toKebabCase(value: string): string {
    return value
        .replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2')
        .replaceAll(/([A-Z])([A-Z][a-z])/g, '$1-$2')
        .toLowerCase();
}

function isSelectableLucideIcon(
    name: string,
    value: unknown,
): value is LucideIcon {
    if (
        SKIP_ICON_EXPORTS.has(name) ||
        name.startsWith('Lucide') ||
        name.endsWith('Icon')
    ) {
        return false;
    }

    return typeof value === 'object' && value !== null && '$$typeof' in value;
}

const LUCIDE_ICON_OPTIONS: LucideIconOption[] = [
    ...Object.entries({ ...LucideIcons })
        .filter(([name, value]) => isSelectableLucideIcon(name, value))
        .reduce((unique, [pascalName, Icon]) => {
            const name = toKebabCase(pascalName);

            if (!unique.has(name)) {
                unique.set(name, {
                    name,
                    Icon: Icon as LucideIcon,
                });
            }

            return unique;
        }, new Map<string, LucideIconOption>())
        .values(),
].sort((a, b) => a.name.localeCompare(b.name));

const LUCIDE_ICONS_BY_NAME = new Map(
    LUCIDE_ICON_OPTIONS.map((option) => [option.name, option]),
);

const MAX_VISIBLE_ICONS = 120;

type IconSearchSelectProps = {
    value: string | null;
    onChange: (icon: string | null) => void;
    error?: string;
};

function IconSearchSelect({ value, onChange, error }: IconSearchSelectProps) {
    const containerRef = useRef<HTMLDivElement>(null);
    const [isOpen, setIsOpen] = useState(false);
    const [search, setSearch] = useState('');

    const selectedIcon = value
        ? (LUCIDE_ICONS_BY_NAME.get(value) ?? null)
        : null;
    const SelectedIcon = selectedIcon?.Icon;

    const filteredIcons = useMemo(() => {
        const term = search.trim().toLowerCase().replaceAll(/\s+/g, '-');

        const matches =
            term === ''
                ? LUCIDE_ICON_OPTIONS
                : LUCIDE_ICON_OPTIONS.filter((option) =>
                      option.name.includes(term),
                  );

        return {
            items: matches.slice(0, MAX_VISIBLE_ICONS),
            total: matches.length,
        };
    }, [search]);

    useEffect(() => {
        if (!isOpen) {
            return;
        }

        const handlePointerDown = (event: MouseEvent) => {
            if (
                containerRef.current &&
                !containerRef.current.contains(event.target as Node)
            ) {
                setIsOpen(false);
                setSearch('');
            }
        };

        document.addEventListener('mousedown', handlePointerDown);

        return () => {
            document.removeEventListener('mousedown', handlePointerDown);
        };
    }, [isOpen]);

    return (
        <div className="grid gap-2">
            <input type="hidden" name="icon" value={value ?? ''} />
            <div ref={containerRef} className="relative">
                <Button
                    type="button"
                    variant="outline"
                    aria-expanded={isOpen}
                    aria-haspopup="listbox"
                    id="icon"
                    className={cn(
                        'h-9 w-full justify-between font-normal',
                        error && 'border-destructive',
                    )}
                    onClick={() => setIsOpen((previous) => !previous)}
                >
                    <span className="flex items-center gap-2 truncate">
                        {SelectedIcon ? (
                            <SelectedIcon className="size-4 shrink-0" />
                        ) : (
                            <Search className="size-4 shrink-0 text-muted-foreground" />
                        )}
                        {selectedIcon ? selectedIcon.name : 'Select an icon'}
                    </span>
                    <ChevronDown
                        className={cn(
                            'size-4 shrink-0 opacity-50 transition-transform',
                            isOpen && 'rotate-180',
                        )}
                    />
                </Button>

                {isOpen && (
                    <div className="mt-1 w-full rounded-md border bg-popover text-popover-foreground shadow-md">
                        <div className="border-b p-2">
                            <div className="relative">
                                <Search className="absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" />
                                <Input
                                    value={search}
                                    onChange={(event) =>
                                        setSearch(event.target.value)
                                    }
                                    placeholder="Search icons..."
                                    className="pl-8"
                                    autoFocus
                                />
                            </div>
                        </div>
                        <div className="flex items-center justify-between gap-2 border-b px-2 py-1.5">
                            <button
                                type="button"
                                className="rounded-sm px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground"
                                onClick={() => {
                                    onChange(null);
                                    setIsOpen(false);
                                    setSearch('');
                                }}
                            >
                                None
                            </button>
                            <span className="text-xs text-muted-foreground">
                                {filteredIcons.total > MAX_VISIBLE_ICONS
                                    ? `Showing ${filteredIcons.items.length} of ${filteredIcons.total}`
                                    : `${filteredIcons.total} icons`}
                            </span>
                        </div>
                        {filteredIcons.items.length > 0 ? (
                            <div
                                role="listbox"
                                className="grid max-h-52 grid-cols-8 gap-1 overflow-y-auto p-2"
                            >
                                {filteredIcons.items.map((option) => {
                                    const isSelected = option.name === value;
                                    const OptionIcon = option.Icon;

                                    return (
                                        <button
                                            key={option.name}
                                            type="button"
                                            role="option"
                                            title={option.name}
                                            aria-label={option.name}
                                            aria-selected={isSelected}
                                            className={cn(
                                                'flex size-8 items-center justify-center rounded-sm hover:bg-accent hover:text-accent-foreground',
                                                isSelected &&
                                                    'bg-accent text-accent-foreground',
                                            )}
                                            onClick={() => {
                                                onChange(option.name);
                                                setIsOpen(false);
                                                setSearch('');
                                            }}
                                        >
                                            <OptionIcon className="size-4" />
                                        </button>
                                    );
                                })}
                            </div>
                        ) : (
                            <p className="px-2 py-6 text-center text-sm text-muted-foreground">
                                No icons found.
                            </p>
                        )}
                    </div>
                )}
            </div>
            <InputError message={error} />
        </div>
    );
}

type CategoryFormDialogProps = {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    categoryType: CategoryType;
    parents: CategoryParentOption[];
    category?: CategoryRow | null;
};

type CategoryFormDialogFormProps = {
    category: CategoryRow | null;
    categoryType: CategoryType;
    parents: CategoryParentOption[];
    onOpenChange: (open: boolean) => void;
};

function CategoryFormDialogForm({
    category,
    categoryType,
    parents,
    onOpenChange,
}: CategoryFormDialogFormProps) {
    const isEditing = category !== null;
    const [parentId, setParentId] = useState<number | null>(
        category?.parent?.id ?? null,
    );
    const [icon, setIcon] = useState<string | null>(category?.icon ?? null);
    const [color, setColor] = useState(category?.color ?? '#6366F1');

    const formProps = isEditing
        ? CategoryController.update.form(category.id)
        : CategoryController.store.form();

    const parentOptions = parents.filter(
        (parent) => parent.id !== category?.id,
    );

    return (
        <Form
            {...formProps}
            options={{
                preserveScroll: true,
            }}
            onSuccess={() => onOpenChange(false)}
            className="space-y-4"
        >
            {({ processing, errors }) => (
                <>
                    {!isEditing && (
                        <input type="hidden" name="type" value={categoryType} />
                    )}

                    <div className="grid gap-2">
                        <Label htmlFor="name">Name</Label>
                        <Input
                            id="name"
                            name="name"
                            defaultValue={category?.name ?? ''}
                            required
                            placeholder="Category name"
                        />
                        <InputError message={errors.name} />
                    </div>

                    <div className="grid gap-2">
                        <Label htmlFor="parent_id">Parent category</Label>
                        <input
                            type="hidden"
                            name="parent_id"
                            value={parentId ?? ''}
                        />
                        <Select
                            value={parentId ? String(parentId) : 'none'}
                            onValueChange={(value) =>
                                setParentId(
                                    value === 'none' ? null : Number(value),
                                )
                            }
                        >
                            <SelectTrigger id="parent_id" className="w-full">
                                <SelectValue placeholder="None" />
                            </SelectTrigger>
                            <SelectContent>
                                <SelectItem value="none">None</SelectItem>
                                {parentOptions.map((parent) => (
                                    <SelectItem
                                        key={parent.id}
                                        value={String(parent.id)}
                                    >
                                        {parent.name}
                                    </SelectItem>
                                ))}
                            </SelectContent>
                        </Select>
                        <InputError message={errors.parent_id} />
                    </div>

                    <div className="grid gap-2">
                        <Label htmlFor="icon">Icon</Label>
                        <IconSearchSelect
                            value={icon}
                            onChange={setIcon}
                            error={errors.icon}
                        />
                    </div>

                    <div className="grid gap-2">
                        <Label htmlFor="color">Color</Label>
                        <input type="hidden" name="color" value={color} />
                        <div className="flex items-center gap-2">
                            <Input
                                id="color"
                                type="color"
                                value={color}
                                onChange={(event) =>
                                    setColor(event.target.value)
                                }
                                className="h-9 w-12 cursor-pointer p-1"
                            />
                            <Input
                                value={color}
                                onChange={(event) =>
                                    setColor(event.target.value)
                                }
                                placeholder="#6366F1"
                                maxLength={7}
                            />
                        </div>
                        <InputError message={errors.color} />
                    </div>

                    <DialogFooter>
                        <Button
                            type="button"
                            variant="outline"
                            onClick={() => onOpenChange(false)}
                        >
                            Cancel
                        </Button>
                        <Button type="submit" disabled={processing}>
                            {isEditing ? 'Save changes' : 'Create'}
                        </Button>
                    </DialogFooter>
                </>
            )}
        </Form>
    );
}

export function CategoryFormDialog({
    open,
    onOpenChange,
    categoryType,
    parents,
    category = null,
}: CategoryFormDialogProps) {
    const isEditing = category !== null;
    const formKey = category?.id ?? 'create';

    return (
        <Dialog open={open} onOpenChange={onOpenChange}>
            <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-lg">
                <DialogHeader>
                    <DialogTitle>
                        {isEditing ? 'Edit Category' : 'Create Category'}
                    </DialogTitle>
                    <DialogDescription>
                        {isEditing
                            ? 'Update the category details below.'
                            : 'Fill in the details to create a new category.'}
                    </DialogDescription>
                </DialogHeader>

                {open ? (
                    <CategoryFormDialogForm
                        key={formKey}
                        category={category}
                        categoryType={categoryType}
                        parents={parents}
                        onOpenChange={onOpenChange}
                    />
                ) : null}
            </DialogContent>
        </Dialog>
    );
}
