"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
    CheckIcon,
    ImageOffIcon,
    Loader2Icon,
    SparklesIcon,
    XIcon,
} from "lucide-react";
import { toast } from "sonner";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import type { GalleryPhoto } from "@/types/admin/gallery";

interface GalleryGridProps {
    initialPhotos?: GalleryPhoto[];
}

type PickedFilter = "all" | "picked" | "unpicked";
type ColorFilter =
    | "all"
    | "black"
    | "blue"
    | "green"
    | "light"
    | "orange"
    | "purple"
    | "red"
    | "yellow";

const PER_PAGE = 24;
const COLUMNS_COUNT = 4;

const colorFilters: Array<{ label: string; value: ColorFilter }> = [
    { label: "All colors", value: "all" },
    { label: "Light", value: "light" },
    { label: "Black", value: "black" },
    { label: "Red", value: "red" },
    { label: "Orange", value: "orange" },
    { label: "Yellow", value: "yellow" },
    { label: "Green", value: "green" },
    { label: "Blue", value: "blue" },
    { label: "Purple", value: "purple" },
];

function getImageUrl(photo: GalleryPhoto): string {
    const urls = photo.urls as Record<string, string> | undefined;
    return urls?.small ?? urls?.regular ?? urls?.thumb ?? urls?.raw ?? "";
}

function getFullImageUrl(photo: GalleryPhoto): string {
    const urls = photo.urls as Record<string, string> | undefined;
    return photo.oss_url ?? urls?.full ?? urls?.regular ?? urls?.raw ?? "";
}

function getUserName(photo: GalleryPhoto): string {
    const user = photo.user_info as Record<string, unknown> | undefined;
    return (user?.name as string) || (user?.username as string) || "Unknown";
}

function getPhotoAspectRatio(photo: GalleryPhoto): number {
    if (photo.width && photo.height && photo.height > 0) {
        return photo.width / photo.height;
    }
    return 1.5;
}

function distributeToColumns(
    photos: GalleryPhoto[],
    columnCount: number,
): GalleryPhoto[][] {
    const columns: GalleryPhoto[][] = Array.from(
        { length: columnCount },
        () => [],
    );
    const columnHeights = Array(columnCount).fill(0);

    for (const photo of photos) {
        const shortestColIndex = columnHeights.indexOf(
            Math.min(...columnHeights),
        );

        columns[shortestColIndex].push(photo);
        columnHeights[shortestColIndex] += 1 / getPhotoAspectRatio(photo);
    }

    return columns;
}

export function GalleryGrid({ initialPhotos = [] }: GalleryGridProps) {
    const [columns, setColumns] = useState<GalleryPhoto[][]>([]);
    const [allPhotos, setAllPhotos] = useState<GalleryPhoto[]>(initialPhotos);
    const [page, setPage] = useState(1);
    const [loading, setLoading] = useState(false);
    const [hasMore, setHasMore] = useState(true);
    const [error, setError] = useState<string | null>(null);
    const [selectedPhoto, setSelectedPhoto] = useState<GalleryPhoto | null>(
        null,
    );
    const [lightboxVisible, setLightboxVisible] = useState(false);
    const [loadedImages, setLoadedImages] = useState<Set<string>>(new Set());
    const [pickedFilter, setPickedFilter] = useState<PickedFilter>("all");
    const [colorFilter, setColorFilter] = useState<ColorFilter>("all");
    const [pickingIds, setPickingIds] = useState<Set<string>>(new Set());
    const observerRef = useRef<IntersectionObserver | null>(null);
    const sentinelRef = useRef<HTMLDivElement | null>(null);
    const loadingRef = useRef(loading);
    const hasMoreRef = useRef(hasMore);

    useEffect(() => {
        loadingRef.current = loading;
    }, [loading]);

    useEffect(() => {
        hasMoreRef.current = hasMore;
    }, [hasMore]);

    useEffect(() => {
        setColumns(distributeToColumns(allPhotos, COLUMNS_COUNT));
    }, [allPhotos]);

    const fetchPhotos = useCallback(
        async (targetPage: number, replace = false) => {
            if (loadingRef.current || (!replace && !hasMoreRef.current)) {
                return;
            }

            setLoading(true);
            setError(null);

            try {
                const params = new URLSearchParams({
                    page: String(targetPage),
                    per_page: String(PER_PAGE),
                });

                if (pickedFilter === "picked") {
                    params.set("picked", "true");
                } else if (pickedFilter === "unpicked") {
                    params.set("picked", "false");
                }

                if (colorFilter !== "all") {
                    params.set("color_family", colorFilter);
                }

                const res = await fetch(
                    `/api/admin/gallery-photos?${params.toString()}`,
                );
                const json = await res.json();

                if (!json.ok) {
                    throw new Error(json.error || "Failed to fetch photos.");
                }

                const newPhotos: GalleryPhoto[] = json.data;

                setAllPhotos((prev) =>
                    replace || targetPage === 1
                        ? newPhotos
                        : [...prev, ...newPhotos],
                );
                setHasMore(json.pagination.hasMore);
                setPage(targetPage);
            } catch (err) {
                setError(err instanceof Error ? err.message : "Unknown error.");
            } finally {
                setLoading(false);
            }
        },
        [colorFilter, pickedFilter],
    );

    useEffect(() => {
        hasMoreRef.current = true;
        setHasMore(true);
        setPage(1);
        setAllPhotos([]);
        setLoadedImages(new Set());
        void fetchPhotos(1, true);
    }, [fetchPhotos]);

    useEffect(() => {
        observerRef.current?.disconnect();

        observerRef.current = new IntersectionObserver(
            (entries) => {
                if (
                    entries[0]?.isIntersecting &&
                    hasMoreRef.current &&
                    !loadingRef.current
                ) {
                    void fetchPhotos(page + 1);
                }
            },
            { rootMargin: "200px" },
        );

        if (sentinelRef.current) {
            observerRef.current.observe(sentinelRef.current);
        }

        return () => {
            observerRef.current?.disconnect();
        };
    }, [fetchPhotos, page]);

    const replacePhoto = useCallback(
        (updated: GalleryPhoto) => {
            setAllPhotos((prev) => {
                const shouldRemoveFromCurrentFilter =
                    (pickedFilter === "picked" && !updated.is_picked) ||
                    (pickedFilter === "unpicked" && updated.is_picked);

                if (shouldRemoveFromCurrentFilter) {
                    return prev.filter(
                        (photo) => photo.unsplash_id !== updated.unsplash_id,
                    );
                }

                return prev.map((photo) =>
                    photo.unsplash_id === updated.unsplash_id ? updated : photo,
                );
            });
            setSelectedPhoto((photo) =>
                photo?.unsplash_id === updated.unsplash_id ? updated : photo,
            );
        },
        [pickedFilter],
    );

    const togglePicked = async (photo: GalleryPhoto) => {
        const nextPicked = !photo.is_picked;
        const toastId = toast.loading(
            nextPicked ? "Picking photo..." : "Unpicking photo...",
        );

        setPickingIds((prev) => new Set(prev).add(photo.unsplash_id));
        setError(null);

        try {
            const res = await fetch("/api/admin/gallery-photos", {
                method: "PATCH",
                headers: {
                    "content-type": "application/json",
                },
                body: JSON.stringify({
                    unsplashId: photo.unsplash_id,
                    isPicked: nextPicked,
                }),
            });
            const json = await res.json().catch(() => null);

            if (!res.ok || !json?.ok) {
                throw new Error(
                    json?.error ||
                        `Failed to update pick status (${res.status}).`,
                );
            }

            replacePhoto(json.data);
            toast.success(nextPicked ? "Photo picked" : "Photo unpicked", {
                id: toastId,
                description: json.data.oss_sync_error || undefined,
            });
        } catch (err) {
            const message =
                err instanceof Error ? err.message : "Unknown pick error.";
            setError(message);
            toast.error("Pick request failed", {
                id: toastId,
                description: message,
            });
        } finally {
            setPickingIds((prev) => {
                const next = new Set(prev);
                next.delete(photo.unsplash_id);
                return next;
            });
        }
    };

    const handlePhotoClick = (photo: GalleryPhoto) => {
        setSelectedPhoto(photo);
        requestAnimationFrame(() => {
            setLightboxVisible(true);
        });
    };

    const handleCloseModal = () => {
        setLightboxVisible(false);
        setTimeout(() => {
            setSelectedPhoto(null);
        }, 250);
    };

    const handleImageLoad = (unsplashId: string) => {
        setLoadedImages((prev) => new Set(prev).add(unsplashId));
    };

    const totalPickedOnPage = useMemo(
        () => allPhotos.filter((photo) => photo.is_picked).length,
        [allPhotos],
    );

    const renderPhotoCard = (
        photo: GalleryPhoto,
        colIndex: number,
        itemIndex: number,
    ) => {
        const isLoaded = loadedImages.has(photo.unsplash_id);
        const isPicking = pickingIds.has(photo.unsplash_id);
        const staggerDelay = ((colIndex * 3 + itemIndex) % 12) * 60;

        return (
            <div
                key={`${colIndex}-${photo.unsplash_id}`}
                className="group animate-photo-enter mb-4 cursor-pointer opacity-0"
                style={{
                    animationDelay: `${staggerDelay}ms`,
                    animationFillMode: "forwards",
                }}
                onClick={() => handlePhotoClick(photo)}
            >
                <div
                    className={`bg-muted ease-out-quart relative overflow-hidden rounded-lg shadow-sm transition-all duration-250 hover:shadow-lg ${
                        isPicking ? "opacity-70" : "opacity-100"
                    }`}
                >
                    <div
                        className="absolute inset-0 transition-opacity duration-500"
                        style={{
                            backgroundColor: photo.color || "#f0f0f0",
                            opacity: isLoaded ? 0 : 1,
                        }}
                    />
                    {/* eslint-disable-next-line @next/next/no-img-element */}
                    <img
                        src={getImageUrl(photo)}
                        alt={
                            photo.alt_description ||
                            photo.description ||
                            "Unsplash photo"
                        }
                        loading="lazy"
                        onLoad={() => handleImageLoad(photo.unsplash_id)}
                        className={`ease-out-quart h-auto w-full object-cover transition-all duration-250 group-hover:scale-[1.03] ${
                            isLoaded ? "opacity-100" : "opacity-0"
                        }`}
                        style={{
                            transitionProperty: "opacity, transform",
                        }}
                    />
                    <div className="absolute top-2 left-2 z-20 flex items-center gap-1">
                        {photo.is_picked && (
                            <Badge className="bg-primary text-primary-foreground">
                                <SparklesIcon />
                                Picked
                            </Badge>
                        )}
                        {photo.oss_url && (
                            <Badge variant="secondary">
                                <CheckIcon />
                                OSS
                            </Badge>
                        )}
                    </div>
                    <div className="absolute top-2 right-2 z-20 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
                        <Button
                            size="sm"
                            variant={photo.is_picked ? "secondary" : "default"}
                            disabled={isPicking}
                            onPointerDown={(event) => {
                                event.stopPropagation();
                            }}
                            onClick={(event) => {
                                event.preventDefault();
                                event.stopPropagation();
                                void togglePicked(photo);
                            }}
                        >
                            {isPicking ? (
                                <Loader2Icon className="animate-spin" />
                            ) : photo.is_picked ? (
                                <XIcon />
                            ) : (
                                <SparklesIcon />
                            )}
                            {photo.is_picked ? "Unpick" : "Pick"}
                        </Button>
                    </div>
                    <div className="ease-out-quart pointer-events-none absolute inset-0 z-10 flex items-end bg-linear-to-t from-black/60 via-black/0 to-transparent p-3 opacity-0 transition-opacity duration-250 group-hover:opacity-100">
                        <div className="ease-out-quart translate-y-2 text-white transition-transform duration-250 group-hover:translate-y-0">
                            <p className="truncate text-xs font-medium">
                                {getUserName(photo)}
                            </p>
                            <div className="mt-1 flex flex-wrap gap-1 text-xs text-white/80">
                                {photo.likes ? (
                                    <span>{photo.likes} likes</span>
                                ) : null}
                                {photo.color_family ? (
                                    <span>{photo.color_family}</span>
                                ) : null}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    };

    return (
        <div className="flex flex-1 flex-col">
            <div className="flex flex-col gap-3 border-b px-4 py-3 lg:flex-row lg:items-center lg:justify-between lg:px-6">
                <div className="flex flex-wrap items-center gap-2">
                    <Button
                        variant={pickedFilter === "all" ? "default" : "outline"}
                        onClick={() => setPickedFilter("all")}
                    >
                        All
                    </Button>
                    <Button
                        variant={
                            pickedFilter === "picked" ? "default" : "outline"
                        }
                        onClick={() => setPickedFilter("picked")}
                    >
                        <SparklesIcon />
                        Picked
                    </Button>
                    <Button
                        variant={
                            pickedFilter === "unpicked" ? "default" : "outline"
                        }
                        onClick={() => setPickedFilter("unpicked")}
                    >
                        <ImageOffIcon />
                        Unpicked
                    </Button>
                </div>
                <div className="flex flex-wrap items-center gap-2">
                    <select
                        value={colorFilter}
                        onChange={(event) =>
                            setColorFilter(event.target.value as ColorFilter)
                        }
                        className="border-input bg-background focus-visible:border-ring focus-visible:ring-ring/50 h-8 rounded-lg border px-2.5 text-sm transition-colors outline-none focus-visible:ring-3"
                    >
                        {colorFilters.map((filter) => (
                            <option key={filter.value} value={filter.value}>
                                {filter.label}
                            </option>
                        ))}
                    </select>
                    <Badge variant="outline">
                        {allPhotos.length} loaded / {totalPickedOnPage} picked
                    </Badge>
                </div>
            </div>

            <div className="flex gap-4 px-4 py-4">
                {columns.map((columnPhotos, colIndex) => (
                    <div key={colIndex} className="min-w-0 flex-1">
                        {columnPhotos.map((photo, itemIndex) =>
                            renderPhotoCard(photo, colIndex, itemIndex),
                        )}
                    </div>
                ))}
            </div>

            {loading && (
                <div className="flex justify-center py-8">
                    <div className="animate-pulse-subtle text-muted-foreground flex items-center gap-2">
                        <div className="border-primary size-5 animate-spin rounded-full border-2 border-t-transparent" />
                        <span className="text-sm">Loading more...</span>
                    </div>
                </div>
            )}

            {error && (
                <div className="flex justify-center py-8">
                    <div className="text-center">
                        <p className="mb-2 text-sm text-red-500">{error}</p>
                        <Button
                            variant="outline"
                            onClick={() => void fetchPhotos(page, page === 1)}
                        >
                            Retry
                        </Button>
                    </div>
                </div>
            )}

            {!hasMore && allPhotos.length > 0 && (
                <div className="flex justify-center py-8">
                    <p className="text-muted-foreground text-sm">
                        No more photos
                    </p>
                </div>
            )}

            {!loading && allPhotos.length === 0 && !error && (
                <div className="flex flex-1 items-center justify-center">
                    <div className="text-center">
                        <p className="text-muted-foreground text-lg">
                            No photos found
                        </p>
                        <p className="text-muted-foreground mt-1 text-sm">
                            Run the sync script to populate the gallery.
                        </p>
                    </div>
                </div>
            )}

            <div ref={sentinelRef} className="h-4" />

            {selectedPhoto && (
                <div
                    className={`ease-out-quart fixed inset-0 z-50 flex items-center justify-center p-4 transition-all duration-250 ${
                        lightboxVisible
                            ? "bg-black/90 opacity-100"
                            : "bg-black/0 opacity-0"
                    }`}
                    onClick={handleCloseModal}
                >
                    <div
                        className={`ease-out-quart relative flex max-h-[90vh] w-full max-w-5xl flex-col items-center transition-all duration-250 ${
                            lightboxVisible
                                ? "scale-100 opacity-100"
                                : "scale-95 opacity-0"
                        }`}
                        onClick={(event) => event.stopPropagation()}
                    >
                        {/* eslint-disable-next-line @next/next/no-img-element */}
                        <img
                            src={getFullImageUrl(selectedPhoto)}
                            alt={
                                selectedPhoto.alt_description ||
                                selectedPhoto.description ||
                                "Unsplash photo"
                            }
                            className="max-h-[85vh] max-w-full rounded-lg object-contain"
                        />
                        <div className="mt-4 text-center text-white">
                            <div className="flex items-center justify-center gap-2">
                                <p className="text-sm font-medium">
                                    {getUserName(selectedPhoto)}
                                </p>
                                {selectedPhoto.is_picked && (
                                    <Badge>
                                        <SparklesIcon />
                                        Picked
                                    </Badge>
                                )}
                            </div>
                            {selectedPhoto.description && (
                                <p className="mt-1 max-w-xl text-sm text-white/70">
                                    {selectedPhoto.description}
                                </p>
                            )}
                            {selectedPhoto.oss_sync_error && (
                                <p className="mt-2 max-w-xl text-xs text-red-200">
                                    {selectedPhoto.oss_sync_error}
                                </p>
                            )}
                        </div>
                        <div className="absolute top-2 right-2 flex gap-2">
                            <Button
                                variant={
                                    selectedPhoto.is_picked
                                        ? "secondary"
                                        : "default"
                                }
                                disabled={pickingIds.has(
                                    selectedPhoto.unsplash_id,
                                )}
                                onPointerDown={(event) => {
                                    event.stopPropagation();
                                }}
                                onClick={(event) => {
                                    event.preventDefault();
                                    event.stopPropagation();
                                    void togglePicked(selectedPhoto);
                                }}
                            >
                                {pickingIds.has(selectedPhoto.unsplash_id) ? (
                                    <Loader2Icon className="animate-spin" />
                                ) : selectedPhoto.is_picked ? (
                                    <XIcon />
                                ) : (
                                    <SparklesIcon />
                                )}
                                {selectedPhoto.is_picked ? "Unpick" : "Pick"}
                            </Button>
                            <Button
                                size="icon"
                                variant="secondary"
                                onClick={handleCloseModal}
                            >
                                <XIcon />
                            </Button>
                        </div>
                    </div>
                </div>
            )}
        </div>
    );
}
