"use client";

import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import Link from "next/link";

export interface FeatureCard {
  label?: string;
  title: string;
  description?: string;
  /** Resolved background image URL (optional — falls back to the gradient). */
  image?: string;
  /** Link target — clicking the card opens this (e.g. the blog post). */
  href?: string;
  /** Fallback background gradient classes (used when no image is set). */
  gradient: string;
  /** Label badge tint classes. */
  badgeTint: string;
}

/**
 * Scroll-left offset for each page-group's first card, clamped to the track's
 * maximum scroll. Shared by dot navigation AND the active-dot indicator so the
 * two always agree — and the last (possibly partial) group is reachable and
 * highlightable. `getBoundingClientRect` stays correct even as the active card
 * changes width.
 */
function pagePositionsOf(track: HTMLElement, pageCount: number, perPage: number): number[] {
  const max = Math.max(0, track.scrollWidth - track.clientWidth);
  const trackLeft = track.getBoundingClientRect().left;
  const items = Array.from(track.children) as HTMLElement[];
  const out: number[] = [];
  for (let p = 0; p < pageCount; p++) {
    const el = items[Math.min(p * perPage, items.length - 1)];
    if (!el) continue;
    const left = el.getBoundingClientRect().left - trackLeft + track.scrollLeft;
    out.push(Math.min(Math.max(0, left), max));
  }
  return out;
}

/**
 * Horizontal scrolling gallery (BCG-style). ALL cards live in one scroll track
 * — you scroll through them continuously; the active card widens, its title
 * slides up and its description fades in.
 *
 * - Desktop: hovering / focusing a card makes it active (it widens).
 * - Touch: the row scroll-snaps; the card centred in the viewport is active.
 *
 * Dots represent page-groups of `perPage` cards and reflect the scroll
 * position; clicking a dot (or an arrow) smoothly scrolls one group along.
 * There is no entrance animation — the row is simply there on load.
 */
export function ExpandingCards({
  cards,
  plainDescription = false,
  perPage = 3,
}: {
  cards: FeatureCard[];
  /** When true, the description is plain text (no dark panel) at 16px — used by the insights row. */
  plainDescription?: boolean;
  /** Cards per page-group (controls the dots and the arrow step). */
  perPage?: number;
}) {
  const trackRef = useRef<HTMLDivElement>(null);
  const [active, setActive] = useState(0);
  const [page, setPage] = useState(0);

  const pageCount = Math.ceil(cards.length / perPage);
  const single = cards.length === 1;

  // Reflect the scroll position in the dots, and (on touch) mark the centred
  // card as active so its description reveals.
  useEffect(() => {
    const track = trackRef.current;
    if (!track) return;

    let raf = 0;
    const update = () => {
      // Active dot = the page-group whose scroll position is nearest the
      // current scroll — i.e. exactly where clicking that dot would land.
      const positions = pagePositionsOf(track, pageCount, perPage);
      if (positions.length) {
        let bestP = 0;
        let bestDist = Infinity;
        positions.forEach((left, p) => {
          const dist = Math.abs(left - track.scrollLeft);
          if (dist < bestDist) {
            bestDist = dist;
            bestP = p;
          }
        });
        setPage(bestP);
      }

      if (window.innerWidth < 768) {
        const center = track.scrollLeft + track.clientWidth / 2;
        let best = 0;
        let bestDist = Infinity;
        Array.from(track.children).forEach((child, i) => {
          const el = child as HTMLElement;
          const elCenter = el.offsetLeft + el.offsetWidth / 2;
          const dist = Math.abs(elCenter - center);
          if (dist < bestDist) {
            bestDist = dist;
            best = i;
          }
        });
        setActive(best);
      }
    };
    const onScroll = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(update);
    };

    track.addEventListener("scroll", onScroll, { passive: true });
    update();
    return () => {
      track.removeEventListener("scroll", onScroll);
      cancelAnimationFrame(raf);
    };
  }, [pageCount, perPage]);

  // Smoothly scroll so the first card of page-group `p` aligns to the left —
  // using the same positions the active-dot indicator reads, so they agree.
  const goToPage = (p: number) => {
    const track = trackRef.current;
    if (!track) return;
    const left = pagePositionsOf(track, pageCount, perPage)[p];
    if (left == null) return;
    track.scrollTo({ left, behavior: "smooth" });
  };

  // Arrow step — one viewport (~`perPage` cards) at a time.
  const nudge = (dir: 1 | -1) => {
    const track = trackRef.current;
    if (!track) return;
    track.scrollBy({ left: dir * track.clientWidth, behavior: "smooth" });
  };

  // Click-and-drag to scroll (mouse only — touch scrolls natively). Snap is
  // disabled while dragging so the drag is free, then restored to re-align on
  // release. `moved` lets us swallow the click so a drag never opens a card.
  const drag = useRef({ down: false, startX: 0, startScroll: 0, moved: false });

  const onPointerDown = (e: React.PointerEvent) => {
    if (e.pointerType !== "mouse") return;
    const track = trackRef.current;
    if (!track) return;
    drag.current = { down: true, startX: e.clientX, startScroll: track.scrollLeft, moved: false };
    track.style.scrollSnapType = "none";
  };
  const onPointerMove = (e: React.PointerEvent) => {
    const d = drag.current;
    const track = trackRef.current;
    if (!d.down || !track) return;
    const dx = e.clientX - d.startX;
    if (Math.abs(dx) > 4) {
      // Only capture once we know this is a real drag, not a click — capturing
      // eagerly on pointerdown redirects the browser's click event to `track`
      // instead of the card's nested <a>, silently breaking navigation.
      if (!d.moved) track.setPointerCapture(e.pointerId);
      d.moved = true;
    }
    track.scrollLeft = d.startScroll - dx;
  };
  const endDrag = () => {
    if (!drag.current.down) return;
    drag.current.down = false;
    const track = trackRef.current;
    if (track) track.style.scrollSnapType = "";
  };
  const onClickCapture = (e: React.MouseEvent) => {
    if (drag.current.moved) {
      e.preventDefault();
      e.stopPropagation();
      drag.current.moved = false;
    }
  };

  return (
    <div className="relative">
      <div
        ref={trackRef}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={endDrag}
        onPointerLeave={endDrag}
        onClickCapture={onClickCapture}
        onDragStart={(e) => e.preventDefault()}
        className="no-scrollbar flex cursor-grab snap-x snap-mandatory select-none gap-4 overflow-x-auto pb-2 active:cursor-grabbing"
      >
        {cards.map((card, i) => {
          const isActive = i === active;

          // Widths drive the expand effect. On mobile every card is the same
          // width (the centred one reveals its description without growing); on
          // desktop the active card widens and the rest stay narrow. A single
          // card rests narrow and expands purely on hover.
          const widthCls = single
            ? "w-[82%] sm:w-[22rem] md:w-[20rem] md:hover:w-[34rem]"
            : `w-[82%] sm:w-[22rem] ${isActive ? "md:w-[34rem]" : "md:w-[20rem]"}`;
          const imageCls = single
            ? "scale-100 md:group-hover:scale-105"
            : isActive
              ? "scale-105"
              : "scale-100";
          const titleCls = single
            ? "top-[4.5rem] md:top-[calc(100%_-_7rem)] md:group-hover:top-[4.5rem]"
            : isActive
              ? "top-[4.5rem]"
              : "top-[calc(100%_-_7rem)]";
          const descCls = single
            ? "opacity-100 md:opacity-0 md:pointer-events-none md:group-hover:pointer-events-auto md:group-hover:opacity-100"
            : isActive
              ? "opacity-100"
              : "pointer-events-none opacity-0";

          return (
            <article
              key={`${card.title}-${i}`}
              onMouseEnter={() => setActive(i)}
              onFocus={() => setActive(i)}
              tabIndex={0}
              aria-expanded={single ? undefined : isActive}
              className={`group relative h-[26rem] shrink-0 snap-start cursor-pointer overflow-hidden rounded-[1.75rem] border border-line shadow-soft transition-[width] duration-500 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-leaf md:h-[30rem] ${widthCls}`}
            >
              {/* background — uploaded image or gradient fallback */}
              {card.image ? (
                <Image
                  src={card.image}
                  alt={card.title}
                  fill
                  sizes="(min-width: 768px) 34rem, 85vw"
                  // Eager-load: cards sit in a horizontal scroll track, and iOS
                  // WebKit often won't fire lazy-load for off-screen items in a
                  // scroller — leaving the card background blank (white text on
                  // white = invisible). The set is small, so eager is fine.
                  loading="eager"
                  className={`object-cover transition-transform duration-700 ease-out ${imageCls}`}
                />
              ) : (
                <div className={`absolute inset-0 ${card.gradient}`} />
              )}

              {/* readability scrim */}
              <div className="absolute inset-0 bg-gradient-to-b from-black/45 via-black/5 to-black/80" />

              {/* label badge */}
              {card.label ? (
                <span
                  className={`absolute left-5 top-5 z-20 inline-block rounded-full px-3.5 py-1.5 text-xs font-semibold text-white backdrop-blur ${card.badgeTint}`}
                >
                  {card.label}
                </span>
              ) : null}

              {/* Title — slides up under the label when active, else rests near
                  the bottom. Mobile-only complaint (narrow card wraps more
                  per title), so only mobile gets the more generous clamp;
                  desktop's original 3-line clamp is untouched — its wider
                  card already wraps far less per title anyway. */}
              <h3
                className={`absolute inset-x-0 z-10 line-clamp-5 sm:line-clamp-3 px-5 font-display text-xl font-bold leading-tight text-white drop-shadow transition-all duration-500 ease-in-out sm:px-6 sm:text-2xl ${titleCls}`}
              >
                {card.title}
              </h3>

              {/* Description — fades in on the active card; clamped on mobile. */}
              {card.description ? (
                <div
                  className={`absolute inset-x-0 bottom-0 z-10 p-5 transition-opacity duration-500 delay-100 sm:p-6 ${descCls}`}
                >
                  {plainDescription ? (
                    <p className="max-w-[26rem] text-left text-[15px] leading-relaxed tracking-normal text-white [text-shadow:0_1px_2px_rgba(0,0,0,0.6)] sm:text-base">
                      {card.description}
                    </p>
                  ) : (
                    <div className="max-w-sm rounded-2xl bg-black/35 p-4 backdrop-blur-sm sm:bg-black/30 sm:p-5">
                      <p className="text-sm leading-relaxed text-white/90">
                        {card.description}
                      </p>
                    </div>
                  )}
                </div>
              ) : null}

              {/* Stretched link — makes the whole card open the post. */}
              {card.href ? (
                <Link href={card.href} aria-label={card.title} className="absolute inset-0 z-30" />
              ) : null}
            </article>
          );
        })}
      </div>

      {/* Pagination dots — active = filled dot inside a ring, inactive = plain dot */}
      {pageCount > 1 ? (
        <div className="mt-7 flex items-center justify-end gap-2.5 pr-1">
          {Array.from({ length: pageCount }).map((_, i) => (
            <button
              key={i}
              type="button"
              onClick={() => goToPage(i)}
              aria-label={`Go to slide group ${i + 1}`}
              aria-current={i === page}
              className="relative flex h-[18px] w-[18px] items-center justify-center rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-ink/40"
            >
              {i === page ? (
                <span className="absolute inset-0 rounded-full border-2 border-black" />
              ) : null}
              <span
                className={`rounded-full bg-black transition-all duration-300 ${
                  i === page ? "h-[7px] w-[7px]" : "h-2 w-2"
                }`}
              />
            </button>
          ))}
        </div>
      ) : null}
    </div>
  );
}
