import { Link } from '@inertiajs/react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';

import { useTranslation } from '@/contexts/LanguageContext';
import { CategorySummary, GameSummary } from '@/types/games';

import GameTile from './GameTile';

interface Props {
    category: CategorySummary & { games?: GameSummary[] };
    palette: { bg: string; text: string };
    // Override for virtual strips (Recent / Featured / Trending) that have
    // their own route. Real DB categories default to /games/category/{slug}.
    browseHref?: string;
}

export default function CategoryStrip({ category, palette, browseHref }: Props) {
    const { t } = useTranslation();
    const games = category.games ?? [];
    const href = browseHref ?? `/games/category/${category.slug}`;
    const scrollerRef = useRef<HTMLDivElement>(null);
    const [canLeft, setCanLeft] = useState(false);
    const [canRight, setCanRight] = useState(false);

    // Hero image fallback: virtual strips like Featured/Recent don't have a
    // category.image of their own. Reach into the loaded games and pick a
    // random thumbnail so the colored card has visual interest. useState's
    // lazy initializer runs exactly once on mount — keeping render pure
    // (Math.random in render trips the react-hooks/purity rule) and pinning
    // the pick across re-renders so the image doesn't flicker on hover or
    // scroll. Real DB categories that already have an image (or a
    // server-side game-thumbnail fallback from CategoryRepository) bypass
    // the random pick entirely.
    const [heroImage] = useState<string | null>(() => {
        if (category.image) return category.image;
        const candidates = games.filter((g) => g.thumbnail);
        if (candidates.length === 0) return null;
        return candidates[Math.floor(Math.random() * candidates.length)].thumbnail;
    });

    useEffect(() => {
        const el = scrollerRef.current;
        if (!el) return;

        const update = () => {
            const abs = Math.abs(el.scrollLeft);
            setCanLeft(abs > 4);
            setCanRight(abs + el.clientWidth < el.scrollWidth - 4);
        };

        update();
        el.addEventListener('scroll', update, { passive: true });
        const ro = new ResizeObserver(update);
        ro.observe(el);
        window.addEventListener('resize', update);

        return () => {
            el.removeEventListener('scroll', update);
            ro.disconnect();
            window.removeEventListener('resize', update);
        };
    }, [games.length]);

    // Hide strips that can't fill more than the first slide. With 4 tiles per
    // slide, anything ≤4 games has no "next page" to scroll to, which makes
    // the right arrow a dead control. Skip the strip entirely in that case.
    if (games.length < 5) return null;

    function scrollByPage(direction: 'left' | 'right') {
        const el = scrollerRef.current;
        if (!el) return;
        const isRtl = document.documentElement.dir === 'rtl';
        const forwardKey = isRtl ? 'left' : 'right';
        const delta = direction === forwardKey ? el.clientWidth : -el.clientWidth;
        el.scrollBy({ left: delta, behavior: 'smooth' });
    }

    function resetToStart() {
        const el = scrollerRef.current;
        if (!el) return;
        el.scrollTo({ left: 0, behavior: 'smooth' });
    }

    return (
        <section className="flex gap-2 mb-2">
            <div className="relative shrink-0">
                <Link
                    href={href}
                    className="group block w-[240px] sm:w-[300px] md:w-[380px] h-full relative overflow-hidden p-4 sm:p-5 rounded-md transition-transform duration-200 ease-out hover:scale-[1.02] hover:shadow-lg"
                    style={{ backgroundColor: palette.bg, color: palette.text }}
                >
                    {heroImage && (
                        <>
                            {/* Image fills the right ~75% of the card via bg-cover so
                                there are no letterboxed edges, then a CSS mask gradient
                                fades the image into the category color from left to
                                right (and softens the right edge too). The result is a
                                seamless blend instead of a hard rectangle of artwork on
                                top of a flat color block. Hover zooms the inner image
                                slightly without disturbing the mask. */}
                            <div
                                className="absolute inset-y-0 end-0 w-3/4 bg-cover bg-center bg-no-repeat transition-transform duration-500 ease-out group-hover:scale-110"
                                style={{
                                    backgroundImage: `url(${heroImage})`,
                                    WebkitMaskImage:
                                        'linear-gradient(to right, transparent 0%, black 45%, black 88%, transparent 100%)',
                                    maskImage:
                                        'linear-gradient(to right, transparent 0%, black 45%, black 88%, transparent 100%)',
                                }}
                                aria-hidden
                            />
                            {/* Subtle color tint over the image side keeps the palette
                                feel even on busy artwork, without obscuring the mascot. */}
                            <div
                                className="absolute inset-0 pointer-events-none"
                                style={{
                                    background: `linear-gradient(to right, ${palette.bg} 25%, ${palette.bg}40 60%, transparent 90%)`,
                                }}
                                aria-hidden
                            />
                        </>
                    )}
                    <div className="relative h-full flex flex-col justify-end">
                        {/*
                          * Width capped at ~9ch (≈ longest single category
                          * word like "Adventure" / "Strategy") with
                          * text-balance so 2-word names like "Featured
                          * Games" wrap cleanly at the space — never
                          * mid-word, since we don't enable break-words.
                          */}
                        <h2
                            className="text-xl sm:text-2xl md:text-3xl font-extrabold leading-tight max-w-[9ch] text-balance"
                            style={{ fontFamily: 'var(--font-display)' }}
                        >
                            {t(category.name)}
                        </h2>
                        <p className="text-xs sm:text-sm font-semibold opacity-90 mt-1">
                            {games.length === 1
                                ? t(':count game', { count: games.length.toLocaleString() })
                                : t(':count games', { count: games.length.toLocaleString() })}
                        </p>
                    </div>
                </Link>

                {/* Floating left arrow — pinned to the right edge of the
                    category title tile (y8-style), half overlapping the title
                    and half overlapping the slider gutter. A soft radial-
                    gradient halo behind the circle fades out into the title
                    tile and the first game, so the chevron reads cleanly
                    against either background. Shows only when the user has
                    scrolled off the first slide. */}
                {canLeft && (
                    <button
                        type="button"
                        onClick={() => scrollByPage('left')}
                        aria-label={t('Scroll :name left', { name: t(category.name) })}
                        className="hidden md:flex absolute end-0 top-1/2 translate-x-5 rtl:-translate-x-5 -translate-y-1/2 size-20 items-center justify-center z-10 transition-transform duration-200 ease-out hover:scale-110 active:scale-95 group/larrow"
                        style={{
                            background:
                                'radial-gradient(circle at center, var(--page-bg) 30%, transparent 70%)',
                        }}
                    >
                        <span className="flex size-10 items-center justify-center rounded-full bg-background/95 backdrop-blur-sm border text-foreground shadow-lg transition-all duration-200 ease-out group-hover/larrow:bg-background group-hover/larrow:shadow-xl">
                            <ChevronLeft className="size-5 transition-transform duration-200 group-hover/larrow:-translate-x-0.5 rtl:group-hover/larrow:translate-x-0.5 rtl:rotate-180" />
                        </span>
                    </button>
                )}
            </div>

            <div className="flex-1 min-w-0">
                <div
                    ref={scrollerRef}
                    className="flex gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain touch-pan-x scroll-smooth snap-x snap-mandatory [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]"
                >
                    {games.map((g) => (
                        <GameTile
                            key={g.id}
                            game={g}
                            // Tile count scales with viewport width matching
                            // y8.com's "wider → more per slide" pattern.
                            // We hold steady at 4 tiles through the common
                            // laptop range (md/lg/xl) so screenshots and
                            // designs stay consistent, then bump on 2xl
                            // (desktop monitors) and 3xl (ultrawide). Each
                            // calc subtracts (N-1) * gap-2 (8px) from 100%
                            // before dividing by N.
                            className="shrink-0 snap-start w-[calc((100%-16px)/3)] md:w-[calc((100%-24px)/4)] 2xl:w-[calc((100%-40px)/6)] 3xl:w-[calc((100%-72px)/10)]"
                        />
                    ))}
                </div>
            </div>

            {/* Right arrow — circular, in its own column to the right of the
                slider so it stays fully outside the games (unlike y8's which
                overlays the last game). Rewinds to start once the user reaches
                the end. */}
            <button
                type="button"
                onClick={canRight ? () => scrollByPage('right') : resetToStart}
                aria-label={canRight ? t('Scroll :name right', { name: t(category.name) }) : t('Reset :name slider', { name: t(category.name) })}
                className="shrink-0 hidden md:flex self-center size-10 items-center justify-center rounded-full bg-background/95 backdrop-blur-sm border text-foreground shadow-lg transition-all duration-200 ease-out hover:bg-background hover:scale-110 hover:shadow-xl active:scale-95 group/rarrow"
            >
                <ChevronRight className="size-5 transition-transform duration-200 group-hover/rarrow:translate-x-0.5 rtl:group-hover/rarrow:-translate-x-0.5 rtl:rotate-180" />
            </button>
        </section>
    );
}
