import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type MouseEvent as ReactMouseEvent,
  type PointerEvent as ReactPointerEvent,
} from "react";
import { useNavigate } from "react-router-dom";
import { assetUrl, getJSON, itemsOrEmpty, readJSONCache } from "../api";
import { langQuery, useI18n, type Lang } from "../i18n";

const FIGMA_ASSET_BASE = "/assets/ark-library/figma";

export const officialRecommendationCoverFallbacks = [
  `${FIGMA_ASSET_BASE}/official-recommendation-cover.png`,
] as const;

type BannerSlide = {
  id: string;
  mobile: string;
  desktop: string;
  alt: string;
  linkUrl?: string;
};

type BannerApiItem = {
  id: number | string;
  imageUrl: string;
  linkUrl?: string;
  sortOrder?: number;
};

type BannerApiResponse = {
  items?: BannerApiItem[] | null;
};

const AUTOPLAY_MS = 3000;
const RESUME_AFTER_INTERACTION_MS = 8000;
const publicMenuOpenChangeEvent = "ark:public-menu-open-change";

function bannerLangParam(lang: Lang): string {
  return langQuery(lang).toLowerCase();
}

/**
 * If `linkUrl` points inside this app (a relative path, or an absolute URL on
 * the same origin), return the router path so we can navigate client-side —
 * e.g. `/browse?post=<id>` triggers the stream's scroll-to-post without a full
 * reload. External URLs return null and fall back to a plain `<a href>`.
 */
function internalPath(linkUrl: string): string | null {
  try {
    if (linkUrl.startsWith("//")) return null;
    if (linkUrl.startsWith("/")) return linkUrl;
    const url = new URL(linkUrl, window.location.origin);
    if (url.origin !== window.location.origin) return null;
    return `${url.pathname}${url.search}${url.hash}`;
  } catch {
    return null;
  }
}

function toSlides(items: BannerApiItem[]): BannerSlide[] {
  return [...items]
    .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
    .filter((item) => item.imageUrl)
    .map((item) => {
      const imageUrl = assetUrl(item.imageUrl);
      return {
        id: String(item.id),
        mobile: imageUrl,
        desktop: imageUrl,
        alt: "",
        linkUrl: item.linkUrl || undefined,
      };
    });
}

export function FigmaBanner() {
  const { lang } = useI18n();
  const navigate = useNavigate();
  const [slides, setSlides] = useState<BannerSlide[]>([]);
  const scrollerRef = useRef<HTMLDivElement>(null);
  // Set when a mouse drag just moved the carousel, so the synthetic `click`
  // that follows pointerup doesn't navigate the slide's link.
  const suppressClickRef = useRef(false);
  const [activeIndex, setActiveIndex] = useState(0);
  const [autoplayPaused, setAutoplayPaused] = useState(false);
  const [publicMenuOpen, setPublicMenuOpen] = useState(false);
  const resumeTimerRef = useRef<number | null>(null);
  const dragStateRef = useRef<{
    pointerId: number;
    startX: number;
    startScrollLeft: number;
    moved: boolean;
  } | null>(null);
  const hasMultiple = slides.length > 1;

  useEffect(() => {
    let cancelled = false;
    const bannersUrl = `/api/banners?lang=${bannerLangParam(lang)}`;
    setActiveIndex(0);

    const cachedBanners = readJSONCache<BannerApiResponse>(bannersUrl);
    if (cachedBanners) setSlides(toSlides(itemsOrEmpty(cachedBanners.items)));

    getJSON<BannerApiResponse>(bannersUrl)
      .then((res) => {
        if (cancelled) return;
        setSlides(toSlides(itemsOrEmpty(res.items)));
      })
      .catch(() => {
        if (!cancelled && !cachedBanners) setSlides([]);
      });
    return () => {
      cancelled = true;
    };
  }, [lang]);

  const goTo = useCallback((index: number, behavior: ScrollBehavior) => {
    const scroller = scrollerRef.current;
    if (!scroller) return;
    const target = scroller.children[index] as HTMLElement | undefined;
    if (!target) return;
    scroller.scrollTo({ left: target.offsetLeft, behavior });
  }, []);

  const pauseAutoplay = useCallback(() => {
    setAutoplayPaused(true);
    if (resumeTimerRef.current !== null) {
      window.clearTimeout(resumeTimerRef.current);
    }
    resumeTimerRef.current = window.setTimeout(() => {
      setAutoplayPaused(false);
      resumeTimerRef.current = null;
    }, RESUME_AFTER_INTERACTION_MS);
  }, []);

  useEffect(
    () => () => {
      if (resumeTimerRef.current !== null) {
        window.clearTimeout(resumeTimerRef.current);
      }
    },
    [],
  );

  useEffect(() => {
    const handlePublicMenuOpenChange = (event: Event) => {
      setPublicMenuOpen(Boolean((event as CustomEvent<boolean>).detail));
    };

    window.addEventListener(
      publicMenuOpenChangeEvent,
      handlePublicMenuOpenChange,
    );
    return () => {
      window.removeEventListener(
        publicMenuOpenChangeEvent,
        handlePublicMenuOpenChange,
      );
    };
  }, []);

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

    const handleScroll = () => {
      const width = scroller.clientWidth;
      if (width === 0) return;
      const next = Math.round(scroller.scrollLeft / width);
      setActiveIndex((prev) => (prev === next ? prev : next));
    };

    handleScroll();
    scroller.addEventListener("scroll", handleScroll, { passive: true });
    return () => scroller.removeEventListener("scroll", handleScroll);
  }, [slides.length]);

  useEffect(() => {
    if (!hasMultiple || autoplayPaused || publicMenuOpen) return;
    const timer = window.setInterval(() => {
      setActiveIndex((prev) => {
        const next = (prev + 1) % slides.length;
        goTo(next, "smooth");
        return next;
      });
    }, AUTOPLAY_MS);
    return () => window.clearInterval(timer);
  }, [hasMultiple, autoplayPaused, publicMenuOpen, slides.length, goTo]);

  const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (event.pointerType !== "mouse") return;
    const scroller = scrollerRef.current;
    if (!scroller) return;
    dragStateRef.current = {
      pointerId: event.pointerId,
      startX: event.clientX,
      startScrollLeft: scroller.scrollLeft,
      moved: false,
    };
    // Don't capture the pointer yet: capturing on press makes the browser
    // dispatch the following `click` to the scroller instead of the slide's
    // <a>, swallowing the link. Capture only once a real drag begins (below).
    pauseAutoplay();
  };

  const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = dragStateRef.current;
    if (!drag || drag.pointerId !== event.pointerId) return;
    const scroller = scrollerRef.current;
    if (!scroller) return;
    const dx = event.clientX - drag.startX;
    if (!drag.moved && Math.abs(dx) > 4) {
      drag.moved = true;
      scroller.setPointerCapture(event.pointerId);
      scroller.style.scrollSnapType = "none";
    }
    if (drag.moved) {
      scroller.scrollLeft = drag.startScrollLeft - dx;
    }
  };

  const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = dragStateRef.current;
    if (!drag || drag.pointerId !== event.pointerId) return;
    const scroller = scrollerRef.current;
    dragStateRef.current = null;
    if (!scroller) return;
    if (scroller.hasPointerCapture(event.pointerId)) {
      scroller.releasePointerCapture(event.pointerId);
    }
    if (drag.moved) {
      // Swallow the click that the browser fires right after a drag-release.
      suppressClickRef.current = true;
      window.setTimeout(() => {
        suppressClickRef.current = false;
      }, 0);
      const width = scroller.clientWidth || 1;
      const nearest = Math.round(scroller.scrollLeft / width);
      const clamped = Math.max(0, Math.min(slides.length - 1, nearest));
      scroller.style.scrollSnapType = "";
      goTo(clamped, "smooth");
    }
  };

  const handleSlideClick = (
    event: ReactMouseEvent<HTMLAnchorElement>,
    linkUrl: string,
  ) => {
    if (suppressClickRef.current) {
      suppressClickRef.current = false;
      event.preventDefault();
      return;
    }
    // Let modified clicks (new tab / window) and external links use default
    // browser behavior; route same-app links through the SPA so the stream's
    // `?post=<id>` scroll-to-post runs without a full page reload.
    if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
      return;
    }
    const path = internalPath(linkUrl);
    if (!path) return;
    event.preventDefault();
    navigate(path);
  };

  if (slides.length === 0) return null;

  const pagination = hasMultiple ? (
    <div
      className="flex items-center justify-center gap-1.5 md:gap-2"
      role="tablist"
      aria-label="Banner pagination"
    >
      {slides.map((slide, index) => {
        const active = index === activeIndex;
        return (
          <button
            key={slide.id}
            type="button"
            role="tab"
            aria-selected={active}
            aria-label={`Go to slide ${index + 1}`}
            onClick={() => {
              pauseAutoplay();
              setActiveIndex(index);
              goTo(index, "smooth");
            }}
            className={`h-1.5 rounded-full transition-all ${
              active
                ? "w-6 bg-ark-gold"
                : "w-1.5 bg-[#7C7C7C] hover:bg-white/50"
            }`}
          />
        );
      })}
    </div>
  ) : null;

  return (
    <div className="relative md:mx-auto md:max-w-[680px] lg:max-w-[900px] xl:max-w-[1120px]">
      <div
        ref={scrollerRef}
        onPointerDown={handlePointerDown}
        onPointerMove={handlePointerMove}
        onPointerUp={endDrag}
        onPointerCancel={endDrag}
        onTouchStart={pauseAutoplay}
        className="flex cursor-grab snap-x snap-mandatory overflow-x-auto overflow-y-hidden scroll-smooth select-none touch-pan-x [-ms-overflow-style:none] [scrollbar-width:none] active:cursor-grabbing [&::-webkit-scrollbar]:hidden"
        role="region"
        aria-roledescription="carousel"
        aria-label="ARK Library banner"
      >
        {slides.map((slide, index) => {
          const image = (
            <picture className="block aspect-video w-full overflow-hidden bg-black md:rounded-xl">
              <source media="(max-width: 767px)" srcSet={slide.mobile} />
              <img
                src={slide.desktop}
                alt={slide.alt}
                className="pointer-events-none h-full w-full object-cover"
                width={1280}
                height={720}
                loading={index === 0 ? "eager" : "lazy"}
                decoding="async"
                draggable={false}
              />
            </picture>
          );

          return (
            <div
              key={slide.id}
              className="relative w-full shrink-0 snap-start"
              role="group"
              aria-roledescription="slide"
              aria-label={`${index + 1} / ${slides.length}`}
            >
              {slide.linkUrl ? (
                <a
                  href={slide.linkUrl}
                  className="block"
                  rel="noreferrer"
                  onClick={(event) => handleSlideClick(event, slide.linkUrl!)}
                >
                  {image}
                </a>
              ) : (
                image
              )}
            </div>
          );
        })}
      </div>

      {hasMultiple ? (
        <>
          <div className="pointer-events-none absolute inset-x-0 bottom-0 flex h-[30px] items-center justify-center bg-gradient-to-b from-[#14131900] to-[#141319] md:hidden">
            <div className="pointer-events-auto">{pagination}</div>
          </div>
          <div className="mt-3 hidden md:block">{pagination}</div>
        </>
      ) : null}
    </div>
  );
}
