import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './LogoLoop.css';

const ANIMATION_CONFIG = {
    SMOOTH_TAU: 0.25,
    MIN_COPIES: 2,
    COPY_HEADROOM: 2,
};

const toCssLength = (value) =>
    typeof value === 'number' ? `${value}px` : value ?? undefined;

const cx = (...parts) => parts.filter(Boolean).join(' ');

function useResizeObserver(callback, elements, deps) {
    useEffect(() => {
        if (!window.ResizeObserver) {
            const handler = () => callback();
            window.addEventListener('resize', handler);
            callback();
            return () => window.removeEventListener('resize', handler);
        }
        const observers = elements.map((ref) => {
            if (!ref.current) return null;
            const observer = new ResizeObserver(callback);
            observer.observe(ref.current);
            return observer;
        });
        callback();
        return () => {
            observers.forEach((o) => o?.disconnect());
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, deps);
}

function useImageLoader(seqRef, onLoad, deps) {
    useEffect(() => {
        const images = seqRef.current?.querySelectorAll('img') ?? [];
        if (images.length === 0) {
            onLoad();
            return;
        }
        let remaining = images.length;
        const handleLoad = () => {
            remaining -= 1;
            if (remaining === 0) onLoad();
        };
        images.forEach((img) => {
            if (img.complete) {
                handleLoad();
            } else {
                img.addEventListener('load', handleLoad, { once: true });
                img.addEventListener('error', handleLoad, { once: true });
            }
        });
        return () => {
            images.forEach((img) => {
                img.removeEventListener('load', handleLoad);
                img.removeEventListener('error', handleLoad);
            });
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, deps);
}

function useAnimationLoop(trackRef, targetVelocity, seqWidth, seqHeight, isHovered, hoverSpeed, isVertical) {
    const rafRef = useRef(null);
    const lastTsRef = useRef(null);
    const offsetRef = useRef(0);
    const velocityRef = useRef(0);

    useEffect(() => {
        const track = trackRef.current;
        if (!track) return;

        const prefersReduced =
            typeof window !== 'undefined' &&
            window.matchMedia &&
            window.matchMedia('(prefers-reduced-motion: reduce)').matches;

        const seqSize = isVertical ? seqHeight : seqWidth;

        if (seqSize > 0) {
            offsetRef.current = ((offsetRef.current % seqSize) + seqSize) % seqSize;
            track.style.transform = isVertical
                ? `translate3d(0, ${-offsetRef.current}px, 0)`
                : `translate3d(${-offsetRef.current}px, 0, 0)`;
        }

        if (prefersReduced) {
            track.style.transform = 'translate3d(0, 0, 0)';
            return () => {
                lastTsRef.current = null;
            };
        }

        const animate = (ts) => {
            if (lastTsRef.current === null) lastTsRef.current = ts;
            const dt = Math.max(0, ts - lastTsRef.current) / 1000;
            lastTsRef.current = ts;

            const target = isHovered && hoverSpeed !== undefined ? hoverSpeed : targetVelocity;
            const easing = 1 - Math.exp(-dt / ANIMATION_CONFIG.SMOOTH_TAU);
            velocityRef.current += (target - velocityRef.current) * easing;

            if (seqSize > 0) {
                let next = offsetRef.current + velocityRef.current * dt;
                next = ((next % seqSize) + seqSize) % seqSize;
                offsetRef.current = next;
                track.style.transform = isVertical
                    ? `translate3d(0, ${-offsetRef.current}px, 0)`
                    : `translate3d(${-offsetRef.current}px, 0, 0)`;
            }
            rafRef.current = requestAnimationFrame(animate);
        };

        rafRef.current = requestAnimationFrame(animate);
        return () => {
            if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
            rafRef.current = null;
            lastTsRef.current = null;
        };
    }, [trackRef, targetVelocity, seqWidth, seqHeight, isHovered, hoverSpeed, isVertical]);
}

export default function LogoLoop({
    logos,
    speed = 60,
    direction = 'left',
    width = '100%',
    logoHeight = 36,
    gap = 32,
    pauseOnHover = true,
    hoverSpeed,
    fadeOut = true,
    fadeOutColor,
    scaleOnHover = true,
    ariaLabel = 'Skill logos',
    className,
    style,
}) {
    const containerRef = useRef(null);
    const trackRef = useRef(null);
    const seqRef = useRef(null);

    const [seqWidth, setSeqWidth] = useState(0);
    const [seqHeight, setSeqHeight] = useState(0);
    const [copyCount, setCopyCount] = useState(ANIMATION_CONFIG.MIN_COPIES);
    const [isHovered, setIsHovered] = useState(false);

    const effectiveHoverSpeed = useMemo(() => {
        if (hoverSpeed !== undefined) return hoverSpeed;
        if (pauseOnHover === true) return 0;
        if (pauseOnHover === false) return undefined;
        return 0;
    }, [hoverSpeed, pauseOnHover]);

    const isVertical = direction === 'up' || direction === 'down';

    const targetVelocity = useMemo(() => {
        const magnitude = Math.abs(speed);
        const dirMul = isVertical
            ? direction === 'up'
                ? 1
                : -1
            : direction === 'left'
                ? 1
                : -1;
        const speedMul = speed < 0 ? -1 : 1;
        return magnitude * dirMul * speedMul;
    }, [speed, direction, isVertical]);

    const updateDimensions = useCallback(() => {
        const containerWidth = containerRef.current?.clientWidth ?? 0;
        const seqRect = seqRef.current?.getBoundingClientRect?.();
        const sw = seqRect?.width ?? 0;
        const sh = seqRect?.height ?? 0;
        if (isVertical) {
            const parentH = containerRef.current?.parentElement?.clientHeight ?? 0;
            if (containerRef.current && parentH > 0) {
                const h = Math.ceil(parentH);
                if (containerRef.current.style.height !== `${h}px`)
                    containerRef.current.style.height = `${h}px`;
            }
            if (sh > 0) {
                setSeqHeight(Math.ceil(sh));
                const viewport = containerRef.current?.clientHeight ?? parentH ?? sh;
                const copies = Math.ceil(viewport / sh) + ANIMATION_CONFIG.COPY_HEADROOM;
                setCopyCount(Math.max(ANIMATION_CONFIG.MIN_COPIES, copies));
            }
        } else if (sw > 0) {
            setSeqWidth(Math.ceil(sw));
            const copies = Math.ceil(containerWidth / sw) + ANIMATION_CONFIG.COPY_HEADROOM;
            setCopyCount(Math.max(ANIMATION_CONFIG.MIN_COPIES, copies));
        }
    }, [isVertical]);

    useResizeObserver(updateDimensions, [containerRef, seqRef], [logos, gap, logoHeight, isVertical]);
    useImageLoader(seqRef, updateDimensions, [logos, gap, logoHeight, isVertical]);
    useAnimationLoop(trackRef, targetVelocity, seqWidth, seqHeight, isHovered, effectiveHoverSpeed, isVertical);

    const cssVars = useMemo(() => ({
        '--logoloop-gap': `${gap}px`,
        '--logoloop-logoHeight': `${logoHeight}px`,
        ...(fadeOutColor && { '--logoloop-fadeColor': fadeOutColor }),
    }), [gap, logoHeight, fadeOutColor]);

    const containerStyle = useMemo(() => ({
        width: isVertical
            ? toCssLength(width) === '100%'
                ? undefined
                : toCssLength(width)
            : toCssLength(width) ?? '100%',
        ...cssVars,
        ...style,
    }), [width, cssVars, style, isVertical]);

    const handleEnter = useCallback(() => {
        if (effectiveHoverSpeed !== undefined) setIsHovered(true);
    }, [effectiveHoverSpeed]);
    const handleLeave = useCallback(() => {
        if (effectiveHoverSpeed !== undefined) setIsHovered(false);
    }, [effectiveHoverSpeed]);

    const renderItem = (item, key) => {
        const isNode = 'node' in item;
        const inner = isNode ? (
            <span
                className={cx('logoloop__node', scaleOnHover && 'logoloop__node--scale')}
                aria-hidden={!!item.href && !item.ariaLabel}
            >
                {item.node}
            </span>
        ) : (
            <img
                className={cx('logoloop__img', scaleOnHover && 'logoloop__img--scale')}
                src={item.src}
                srcSet={item.srcSet}
                sizes={item.sizes}
                width={item.width}
                height={item.height}
                alt={item.alt ?? ''}
                title={item.title}
                loading="lazy"
                decoding="async"
                draggable={false}
            />
        );
        const itemAriaLabel = isNode ? item.ariaLabel ?? item.title : item.alt ?? item.title;
        const wrapper = item.href ? (
            <a
                className="logoloop__link"
                href={item.href}
                aria-label={itemAriaLabel || 'logo link'}
                target="_blank"
                rel="noreferrer noopener"
            >
                {inner}
            </a>
        ) : (
            inner
        );
        return (
            <li
                className={cx('logoloop__item', scaleOnHover && 'logoloop__item--scalable')}
                key={key}
                role="listitem"
            >
                {wrapper}
            </li>
        );
    };

    const lists = useMemo(
        () =>
            Array.from({ length: copyCount }, (_, i) => (
                <ul
                    className={cx('logoloop__list', isVertical && 'logoloop__list--vertical')}
                    key={`copy-${i}`}
                    role="list"
                    aria-hidden={i > 0}
                    ref={i === 0 ? seqRef : undefined}
                >
                    {logos.map((it, idx) => renderItem(it, `${i}-${idx}`))}
                </ul>
            )),
        // eslint-disable-next-line react-hooks/exhaustive-deps
        [copyCount, logos, isVertical, scaleOnHover],
    );

    return (
        <div
            ref={containerRef}
            className={cx('logoloop', isVertical && 'logoloop--vertical', className)}
            style={containerStyle}
            role="region"
            aria-label={ariaLabel}
        >
            {fadeOut && (
                <>
                    <div
                        aria-hidden
                        className={cx('logoloop__fade', isVertical ? 'logoloop__fade--top' : 'logoloop__fade--left')}
                    />
                    <div
                        aria-hidden
                        className={cx('logoloop__fade', isVertical ? 'logoloop__fade--bottom' : 'logoloop__fade--right')}
                    />
                </>
            )}
            <div
                className={cx('logoloop__track', isVertical && 'logoloop__track--vertical')}
                ref={trackRef}
                onMouseEnter={handleEnter}
                onMouseLeave={handleLeave}
            >
                {lists}
            </div>
        </div>
    );
}
