import { useHttp, usePage } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';
import { useDebounce } from 'use-debounce';
import {
    Combobox,
    ComboboxContent,
    ComboboxEmpty,
    ComboboxInput,
    ComboboxItem,
    ComboboxList,
} from '@/components/ui/combobox';
import { index as callesIndex, store as callesStore } from '@/routes/calles';
import type { User } from '@/types/auth';

type Calle = { id: number; nombre: string };

/** Roles que pueden crear calles (coincide con el middleware de `calles.store`). */
const CALLE_CREATE_ROLES = new Set<User['role']>([
    'jefe-de-brigada',
    'administrador',
]);

/**
 * Combobox de calles con búsqueda server-side y creación on-demand.
 *
 * - Filtrado del backend (`GET /calles?nombre=`), por eso `filter={null}` y
 *   `autoComplete="none"`.
 * - Al montar con `value` resuelve la calle por id (`GET /calles?id=`) para
 *   precargar su nombre. Al escribir, busca por nombre (debounce 300ms).
 * - `inputValue` es la única fuente de verdad del input; `lastSyncedRef`
 *   sincroniza su texto con la calle seleccionada solo cuando `value` cambia
 *   externamente, sin pisar lo que el usuario escribe.
 * - Opción "Añadir" solo para roles con permiso de escritura.
 * - DOS instancias de `useHttp` (GET/POST) para que el campo `nombre` del POST
 *   no contamine los GET. Son estables entre renders (Inertia v3).
 */
export function CalleCombobox({
    value,
    onChange,
    error,
    placeholder = 'Buscar calle…',
}: {
    value: number | null;
    onChange: (id: number | null) => void;
    error?: string;
    placeholder?: string;
}) {
    const httpGet = useHttp<Record<string, never>, Calle[]>({});
    const httpPost = useHttp<{ nombre: string }, Calle>({ nombre: '' });

    const [items, setItems] = useState<Calle[]>([]);
    const [loading, setLoading] = useState(false);
    const [inputValue, setInputValue] = useState('');
    const [debouncedInput] = useDebounce(inputValue, 300);

    // Evita races: solo aplica la última respuesta.
    const seqRef = useRef(0);
    const trimmed = debouncedInput.trim();

    // Un solo efecto decide qué pedir según el input y la selección.
    // biome-ignore lint/correctness/useExhaustiveDependencies: httpGet is stable across renders (Inertia v3 useHttp)
    useEffect(() => {
        const url =
            trimmed !== ''
                ? callesIndex.url({ query: { nombre: trimmed } })
                : value != null
                  ? callesIndex.url({ query: { id: value } })
                  : null;

        if (url == null) {
            setItems([]);
            return;
        }

        const seq = ++seqRef.current;
        setLoading(true);
        httpGet.get(url, {
            onSuccess: (response: unknown) => {
                if (seq !== seqRef.current) {
                    return;
                }
                setItems((response as Calle[]) ?? []);
            },
            onError: () => {
                if (seq !== seqRef.current) {
                    return;
                }
                setItems([]);
            },
            onFinish: () => {
                if (seq !== seqRef.current) {
                    return;
                }
                setLoading(false);
            },
        });
    }, [trimmed, value]);

    // Sincroniza el input con el nombre de la calle seleccionada SOLO cuando
    // `value` cambia (no en cada llegada de resultados, para no pisar lo
    // escrito). Reintenta cuando llegan items que contienen el valor.
    const lastSyncedRef = useRef<number | null | undefined>(undefined);
    useEffect(() => {
        if (value === lastSyncedRef.current) {
            return;
        }
        if (value == null) {
            setInputValue('');
            lastSyncedRef.current = value;
            return;
        }
        const selected = items.find((it) => it.id === value);
        if (selected) {
            setInputValue(selected.nombre);
            lastSyncedRef.current = value;
        }
    }, [value, items]);

    const user = usePage().props.auth?.user;
    const canCreate = user?.role != null && CALLE_CREATE_ROLES.has(user.role);

    const trimmedQuery = inputValue.trim();
    const existsExact = items.some(
        (it) => it.nombre.toLowerCase() === trimmedQuery.toLowerCase(),
    );
    const showCreateOption =
        canCreate && trimmedQuery.length > 0 && !existsExact;

    const selectedItem =
        value != null ? (items.find((it) => it.id === value) ?? null) : null;

    const createCalle = (nombre: string): Promise<Calle | null> =>
        new Promise((resolve) => {
            httpPost.setData('nombre', nombre);
            httpPost.post(callesStore.url(), {
                onSuccess: (response: unknown) => {
                    const created = response as Calle | undefined;
                    if (
                        !created ||
                        typeof created.id !== 'number' ||
                        typeof created.nombre !== 'string'
                    ) {
                        resolve(null);
                        return;
                    }
                    setItems((prev) =>
                        prev.some((c) => c.id === created.id)
                            ? prev
                            : [created, ...prev],
                    );
                    resolve(created);
                },
                onError: () => resolve(null),
            });
        });

    const handleCreate = async () => {
        const created = await createCalle(trimmedQuery);
        if (!created) {
            toast.error('No se pudo crear la calle (¿ya existe?).');
            return;
        }
        onChange(created.id);
        setInputValue(created.nombre);
        lastSyncedRef.current = created.id;
        toast.success(`Calle “${created.nombre}” creada.`);
    };

    return (
        <div className="flex flex-col gap-1">
            <Combobox<Calle>
                value={selectedItem}
                onValueChange={(item) => {
                    const v = item as Calle | null;
                    onChange(v ? v.id : null);
                    setInputValue(v ? v.nombre : '');
                    lastSyncedRef.current = v ? v.id : null;
                }}
                inputValue={inputValue}
                onInputValueChange={(q) => setInputValue(q)}
                items={items}
                filter={null}
                autoComplete="none"
                isItemEqualToValue={(a, b) => a?.id === b?.id}
                itemToStringLabel={(c: Calle) => c.nombre}
            >
                <ComboboxInput
                    className="w-full!"
                    placeholder={loading ? 'Cargando…' : placeholder}
                    showTrigger
                />
                <ComboboxContent>
                    <ComboboxList>
                        {items.map((item) => (
                            <ComboboxItem key={item.id} value={item}>
                                {item.nombre}
                            </ComboboxItem>
                        ))}
                    </ComboboxList>
                    {showCreateOption ? (
                        <button
                            type="button"
                            onClick={handleCreate}
                            className="flex w-full items-center gap-2 border-t bg-muted/40 px-2 py-2 text-left text-sm font-medium hover:bg-muted"
                        >
                            <Plus className="size-4" />
                            Agregar “{trimmedQuery}”
                        </button>
                    ) : null}
                    <ComboboxEmpty>Sin resultados</ComboboxEmpty>
                </ComboboxContent>
            </Combobox>
            {error ? (
                <span className="text-xs text-destructive">{error}</span>
            ) : null}
        </div>
    );
}
