"use client";

import Link from "next/link";
import { useRef } from "react";
import { motion, useScroll, useTransform, type MotionValue } from "framer-motion";
import { useReducedMotionSafe } from "@/components/motion/use-reduced-motion-safe";
import { StaggerGrid } from "@/components/motion/StaggerGrid";
import { FOCUS_RING_CLASSES, cardClasses } from "@/components/ui/styles";
import { cn } from "@/lib/utils";

// Wavy top line — traced verbatim (transforms included) from
// `kolabora asset/assets/wavy line.svg`, recolored via currentColor.
function WavyLine({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 253 142" className={className} preserveAspectRatio="none" aria-hidden="true" focusable="false">
      <g transform="matrix(1,0,0,1,-22119.101888,-408.093953)">
        <g transform="matrix(0.876904,-0.480665,0.480665,0.876904,2897.476196,10532.824762)">
          <path
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
            d="M21655.505,484.383C21819.759,576.939 21804.577,382.957 21940.774,484.383"
          />
        </g>
      </g>
    </svg>
  );
}

// Diverging sweep — traced verbatim from
// `kolabora asset/assets/branching line.svg`: one long curve with two short
// branches peeling off, recolored via currentColor.
function BranchingLines({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 175 118" className={className} preserveAspectRatio="none" aria-hidden="true" focusable="false">
      <g transform="matrix(0.486866,-0.40513,0.600541,0.721701,11918.731859,8973.699642)">
        <path
          fill="none"
          stroke="currentColor"
          strokeWidth="2.5"
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M21655.505,484.383C21767.736,532.458 21881.765,531.321 21940.774,484.383"
        />
      </g>
      <g transform="matrix(1,0,0,1,-20,0)">
        <path
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M22881.39,502.949C22903.48,480.195 22916.585,469.277 22945.715,468.641"
        />
      </g>
      <g transform="matrix(1,0,0,1,-20,0)">
        <path
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M22873.135,510.107C22888.084,496.939 22902.076,482.714 22937.11,498.213"
        />
      </g>
    </svg>
  );
}

// Small filled node dot marking where a flow line "connects" — crisp SVG
// instead of a plain rounded <span>, so it stays sharp at any size and
// tints via the same currentColor as the lines around it.
function NodeDot({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 10 10" className={className} aria-hidden="true" focusable="false">
      <circle cx="5" cy="5" r="5" fill="currentColor" />
    </svg>
  );
}

// Small hollow node ring — the "unconnected" counterpart to NodeDot.
function NodeRing({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 10 10" className={className} aria-hidden="true" focusable="false">
      <circle cx="5" cy="5" r="3.5" fill="none" stroke="currentColor" strokeWidth="1.6" />
    </svg>
  );
}

// Three overlapping rings — same asset as AboutSection.tsx's RingsMark
// (traced from `kolabora asset/assets/circle.svg`), duplicated here rather
// than imported so this section stays self-contained, matching the
// K-mark-path duplication precedent already used between HeroBanner.tsx
// and AboutSection.tsx.
function TripleRing({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 98 39" className={className} aria-hidden="true" focusable="false">
      <g transform="matrix(1,0,0,1,-28927.36,-1305.89474)">
        <g transform="matrix(1,0,0,1,341,0)">
          <g transform="matrix(1.28,0,0,1.28,27611,1120.29474)">
            <path
              fill="currentColor"
              d="M800,145C808.279,145 815,151.721 815,160C815,168.279 808.279,175 800,175C791.721,175 785,168.279 785,160C785,151.721 791.721,145 800,145ZM800,147C792.825,147 787,152.825 787,160C787,167.175 792.825,173 800,173C807.175,173 813,167.175 813,160C813,152.825 807.175,147 800,147Z"
            />
          </g>
        </g>
        <g transform="matrix(1,0,0,1,341,0)">
          <g transform="matrix(1.28,0,0,1.28,27611,1120.29474)">
            <path
              fill="currentColor"
              d="M823,145C831.279,145 838,151.721 838,160C838,168.279 831.279,175 823,175C814.721,175 808,168.279 808,160C808,151.721 814.721,145 823,145ZM823,147C815.825,147 810,152.825 810,160C810,167.175 815.825,173 823,173C830.175,173 836,167.175 836,160C836,152.825 830.175,147 823,147Z"
            />
          </g>
        </g>
        <g transform="matrix(1,0,0,1,341,0)">
          <g transform="matrix(1.28,0,0,1.28,27611,1120.29474)">
            <path
              fill="currentColor"
              d="M777,145C785.279,145 792,151.721 792,160C792,168.279 785.279,175 777,175C768.721,175 762,168.279 762,160C762,151.721 768.721,145 777,145ZM777,147C769.825,147 764,152.825 764,160C764,167.175 769.825,173 777,173C784.175,173 790,167.175 790,160C790,152.825 784.175,147 777,147Z"
            />
          </g>
        </g>
      </g>
    </svg>
  );
}

// Concentric "orbit" ring cluster — echoes the same repeating-ring radar
// motif already used behind the Hero's K-mark (see .heroContours in
// HeroBanner.module.css's repeating-radial-gradient), reused here as
// discrete SVG rings instead of a single flat circle so it reads as a
// designed flourish rather than a bare outline.
function OrbitRings({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 200 200" className={className} aria-hidden="true" focusable="false">
      <circle cx="100" cy="100" r="98" fill="none" stroke="currentColor" strokeWidth="2" opacity="0.9" />
      <circle cx="100" cy="100" r="76" fill="none" stroke="currentColor" strokeWidth="1.4" opacity="0.5" />
      <circle cx="100" cy="100" r="54" fill="none" stroke="currentColor" strokeWidth="1" opacity="0.28" />
    </svg>
  );
}

/**
 * Recreates the "event-background.png" reference (orange flowing lines +
 * node dots/rings on the section's own cream background) as real markup
 * instead of a raster image. The wavy top line and diverging sweep are
 * official brand line assets (see `kolabora asset/assets/`); the node
 * markers and orbit cluster reuse the brand's circle motifs (the
 * `circle.svg` triple-ring asset, and the Hero's concentric-contour idea)
 * rather than a single bare stroke, so the "circle" side of the
 * composition feels as designed as the line side. All recolored via the
 * existing --color-kolabora-primary token.
 */
function EventBackgroundArt() {
  return (
    <div className="pointer-events-none absolute inset-0 overflow-hidden text-kolabora-primary" aria-hidden="true">
      <WavyLine className="absolute -top-4 left-0 h-24 w-full sm:h-36" />
      <BranchingLines className="absolute bottom-[8%] left-[4%] h-40 w-[60%] sm:h-56 sm:w-[50%]" />
      <NodeDot className="absolute bottom-[24%] left-[22%] h-2.5 w-2.5 sm:h-3 sm:w-3" />
      <TripleRing className="absolute bottom-[11%] left-[6%] h-3 w-8 opacity-70 sm:h-4 sm:w-10" />

      <OrbitRings className="absolute -bottom-[15%] -right-[10%] h-2/3 w-2/3 max-w-md sm:h-3/4 sm:w-3/4" />
      <NodeDot className="absolute bottom-[38%] right-[12%] h-2.5 w-2.5 sm:h-3 sm:w-3" />
      <NodeRing className="absolute bottom-[16%] right-[22%] h-2.5 w-2.5 sm:h-3 sm:w-3" />
    </div>
  );
}

export interface EventCardData {
  slug: string;
  title: string;
  category: string;
  date: string;
  location: string;
  /** null when no ticket price exists yet — the status badge is shown in its place instead of a "Coming soon" placeholder. */
  price: string | null;
  thumbnail?: string;
  /** Cream accent background instead of the default white card. */
  accent?: boolean;
  /** Coming Soon/Ongoing label (EVENT_STATUS_LABEL) — homepage only ever receives Published/Ongoing events. */
  statusLabel?: string;
}

const STATUS_BADGE_CLASSES: Record<string, string> = {
  Ongoing: "bg-green-600 text-white",
};

// TODO: wire to the published-events API (FR-010 Featured Events) once
// available — this hardcoded list is placeholder data only.
const DEFAULT_EVENTS: EventCardData[] = [
  {
    slug: "jakarta-jazz-festival-2026",
    title: "Jakarta Jazz Festival 2026",
    category: "Music",
    date: "12 September 2026",
    location: "JIExpo Kemayoran, Jakarta",
    price: "Mulai Rp350.000",
  },
  {
    slug: "startup-summit-bandung",
    title: "Startup Summit Bandung",
    category: "Business",
    date: "3 Oktober 2026",
    location: "Trans Convention Center, Bandung",
    price: "Mulai Rp150.000",
    accent: true,
  },
  {
    slug: "kolabora-food-festival",
    title: "Kolaborativ Food Festival",
    category: "Food & Culinary",
    date: "20 Oktober 2026",
    location: "Lapangan Banteng, Jakarta",
    price: "Mulai Rp75.000",
  },
  {
    slug: "yogyakarta-art-week",
    title: "Yogyakarta Art Week",
    category: "Arts & Culture",
    date: "8 November 2026",
    location: "Taman Budaya Yogyakarta",
    price: "Mulai Rp100.000",
  },
];

export interface EventStackSectionProps {
  heading?: string;
  subtitle?: string;
  events?: EventCardData[];
}

// Vertical layout (thumbnail on top, content below) — used by the mobile /
// reduced-motion static grid, unaffected by the desktop pinned-stack sizing.
function CardBody({ event }: { event: EventCardData }) {
  return (
    <>
      <div className="relative h-40 w-full bg-kolabora-neutral-dark/5">
        {event.thumbnail ? (
          // eslint-disable-next-line @next/next/no-img-element -- external/organizer-uploaded path, not yet on next/image allowlist
          <img src={event.thumbnail} alt={event.title} className="h-full w-full object-cover" />
        ) : (
          <div className="flex h-full items-center justify-center text-sm text-kolabora-neutral-dark/30">
            Kolaborativ
          </div>
        )}
        <span className="absolute left-3 top-3 rounded-full bg-kolabora-primary px-3 py-1 text-xs font-medium text-kolabora-neutral-white">
          {event.category}
        </span>
      </div>
      <div className="flex items-center justify-between gap-4 p-5">
        <div>
          <h3 className="font-heading text-lg font-medium text-kolabora-neutral-dark">{event.title}</h3>
          <p className="mt-1 text-sm text-kolabora-neutral-dark/60">
            {event.date} &middot; {event.location}
          </p>
          {event.price ? (
            <p className="mt-2 text-sm font-semibold text-kolabora-primary">{event.price}</p>
          ) : (
            event.statusLabel && (
              <span
                className={cn(
                  "mt-2 inline-block rounded-full px-3 py-1 text-xs font-medium",
                  STATUS_BADGE_CLASSES[event.statusLabel] ?? "bg-kolabora-neutral-dark text-white",
                )}
              >
                {event.statusLabel}
              </span>
            )
          )}
        </div>
        <span aria-hidden="true" className="shrink-0 text-2xl text-kolabora-neutral-dark">
          &rarr;
        </span>
      </div>
    </>
  );
}

// Two-column layout (thumbnail left, content right) — desktop pinned-stack
// cards only, which are much bigger than the mobile/static grid's cards.
function CardBodyLarge({ event }: { event: EventCardData }) {
  return (
    <div className="flex h-full flex-row">
      <div className="relative h-full w-[45%] shrink-0 overflow-hidden rounded-l-xl bg-kolabora-neutral-dark/5">
        {event.thumbnail ? (
          // eslint-disable-next-line @next/next/no-img-element -- external/organizer-uploaded path, not yet on next/image allowlist
          <img src={event.thumbnail} alt={event.title} className="h-full w-full object-cover" />
        ) : (
          <div className="flex h-full items-center justify-center text-sm text-kolabora-neutral-dark/30">
            Kolaborativ
          </div>
        )}
      </div>
      <div className="flex flex-1 flex-col justify-between p-8 sm:p-10">
        <div>
          <span className="inline-block rounded-full bg-kolabora-primary px-3 py-1 text-xs font-medium text-kolabora-neutral-white">
            {event.category}
          </span>
          <h3 className="font-heading mt-4 text-3xl font-semibold text-kolabora-neutral-dark sm:text-4xl">
            {event.title}
          </h3>
          <p className="mt-3 text-sm text-kolabora-neutral-dark/60">
            {event.date} &middot; {event.location}
          </p>
          {event.price ? (
            <p className="mt-3 text-lg font-semibold text-kolabora-primary">{event.price}</p>
          ) : (
            event.statusLabel && (
              <span
                className={cn(
                  "mt-3 inline-block rounded-full px-3 py-1 text-sm font-medium",
                  STATUS_BADGE_CLASSES[event.statusLabel] ?? "bg-kolabora-neutral-dark text-white",
                )}
              >
                {event.statusLabel}
              </span>
            )
          )}
        </div>
        <div className="mt-6 flex justify-end">
          {/* Plain span, not a nested Link — the whole card is already one
              big Link, and nesting an <a> inside an <a> is invalid HTML. */}
          <span className="inline-flex min-h-11 items-center gap-2 text-sm font-medium text-kolabora-neutral-dark">
            View Details <span aria-hidden="true">&rarr;</span>
          </span>
        </div>
      </div>
    </div>
  );
}

// Plain piecewise-linear interpolation, evaluated manually (rather than via
// useTransform's array-breakpoint overload) so clamping past the final
// breakpoint is unambiguous — holds at the last point's value, full stop.
function piecewise(v: number, points: Array<[number, number]>): number {
  if (v <= points[0][0]) return points[0][1];
  for (let i = 0; i < points.length - 1; i++) {
    const [x0, y0] = points[i];
    const [x1, y1] = points[i + 1];
    if (v <= x1) {
      const t = (v - x0) / (x1 - x0);
      return y0 + (y1 - y0) * t;
    }
  }
  return points[points.length - 1][1];
}

function StackCard({
  event,
  index,
  total,
  scrollYProgress,
}: {
  event: EventCardData;
  index: number;
  total: number;
  scrollYProgress: MotionValue<number>;
}) {
  // Each card gets a non-overlapping progress slice to enter in, then (if
  // it's not the last card) recedes — slightly scaled down and dimmed —
  // during the NEXT card's entrance slice, producing the deck-stack look.
  const unit = 1 / total;
  const enterStart = index * unit;
  const enterEnd = enterStart + unit * 0.6;
  const hasNext = index + 1 < total;
  const recedeStart = (index + 1) * unit;
  const recedeEnd = recedeStart + unit * 0.6;

  // Push distance must clear the card fully below the viewport before its
  // "top" offset is even factored in — with the bigger card size and the
  // smaller top-offset baseline (8vh + i*20px), 60vh wasn't enough (a card
  // could still peek in at the very bottom pre-entrance); 105vh comfortably
  // clears every index at any reasonable viewport height.
  const yPoints: Array<[number, number]> = [
    [enterStart, 105],
    [enterEnd, 0],
  ];
  const scalePoints: Array<[number, number]> = hasNext
    ? [
        [enterStart, 0.92],
        [enterEnd, 1],
        [recedeStart, 1],
        [recedeEnd, 0.95],
      ]
    : [
        [enterStart, 0.92],
        [enterEnd, 1],
      ];
  // Dimming for the recede effect (previous card, once the NEXT one starts
  // covering it) uses a brightness filter, NOT opacity. Opacity below 1
  // forces a new CSS stacking context on the card, which is what let the
  // pinned heading layer show through it — brightness dims the card's own
  // colors while it stays fully opaque, so nothing behind it can bleed in.
  const brightnessPoints: Array<[number, number]> = hasNext
    ? [
        [enterStart, 1],
        [enterEnd, 1],
        [recedeStart, 1],
        [recedeEnd, 0.96],
      ]
    : [
        [enterStart, 1],
        [enterEnd, 1],
      ];

  // No opacity transform at all: the card is always fully opaque. Its
  // "entrance" relies purely on translateY (60vh below to 0) combined with
  // the sticky stage's overflow:hidden, which clips it out of view while
  // off-screen — no fade needed for that either.
  const y = useTransform(scrollYProgress, (v) => `${piecewise(v, yPoints)}vh`);
  const scale = useTransform(scrollYProgress, (v) => piecewise(v, scalePoints));
  const filter = useTransform(scrollYProgress, (v) => `brightness(${piecewise(v, brightnessPoints)})`);

  return (
    <motion.div
      style={{
        y,
        scale,
        filter,
        // 15vh caps how high the settled card rises — still well above the
        // heading's top (~29-32vh across its scale range) so the title and
        // subtitle stay fully covered, but leaves breathing room above the
        // card instead of pinning it flush to the top of the viewport.
        top: `calc(15vh + ${index * 20}px)`,
        // Explicit stacking order (heading z-0, cards z-10+i) — later cards
        // arrive after and paint over earlier ones.
        zIndex: 10 + index,
      }}
      className="absolute left-1/2 h-[min(480px,62vh)] w-[min(920px,88vw)] -translate-x-1/2"
    >
      <Link
        href={`/events/${event.slug}`}
        className={cn(
          "block h-full overflow-hidden",
          cardClasses(),
          FOCUS_RING_CLASSES,
          event.accent ? "bg-kolabora-secondary" : "bg-kolabora-neutral-white",
        )}
      >
        <CardBodyLarge event={event} />
      </Link>
    </motion.div>
  );
}

function StaticEventGrid({
  heading,
  subtitle,
  events,
}: {
  heading: string;
  subtitle: string;
  events: EventCardData[];
}) {
  return (
    <section className="relative overflow-hidden bg-kolabora-secondary px-6 py-16">
      <EventBackgroundArt />

      <div className="relative z-10 mx-auto max-w-2xl text-center">
        <h2
          className="font-heading font-semibold text-kolabora-primary"
          style={{ fontSize: "clamp(2.5rem, 6vw, 5rem)" }}
        >
          {heading}
        </h2>
        <p className="mx-auto mt-4 max-w-md text-kolabora-neutral-dark/70">{subtitle}</p>
      </div>

      {/* StaggerGrid self-checks prefers-reduced-motion, so this renders
          statically for the reduced-motion caller below and staggered-in
          for the mobile (non-reduced-motion) fallback caller — no separate
          animateIn flag needed here anymore. */}
      <StaggerGrid className="relative z-10 mx-auto mt-10 grid max-w-6xl gap-6 sm:grid-cols-2 lg:grid-cols-3">
        {events.map((event) => (
          <Link
            key={event.slug}
            href={`/events/${event.slug}`}
            className={cn(
              "block overflow-hidden",
              cardClasses(),
              FOCUS_RING_CLASSES,
              event.accent ? "bg-kolabora-secondary" : "bg-kolabora-neutral-white",
            )}
          >
            <CardBody event={event} />
          </Link>
        ))}
      </StaggerGrid>
    </section>
  );
}

function PinnedStack({
  heading,
  subtitle,
  events,
}: {
  heading: string;
  subtitle: string;
  events: EventCardData[];
}) {
  const sectionRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: sectionRef,
    offset: ["start start", "end end"],
  });

  // No Y transform — the heading's resting position is fixed purely via
  // pt-[22vh] below, so there's nothing to drift. Scale/opacity are the
  // only scroll-driven changes, shrinking it in place (transform-origin
  // center top) as cards stack over it.
  const headingScale = useTransform(scrollYProgress, [0, 0.5], [1, 0.88]);
  const headingOpacity = useTransform(scrollYProgress, [0, 0.5], [1, 0.45]);

  return (
    <div ref={sectionRef} className="relative hidden md:block" style={{ height: `${100 + events.length * 50}vh` }}>
      <div className="sticky top-0 flex h-screen items-center justify-center overflow-hidden bg-kolabora-secondary">
        {/* Same background as the static grid above — z-0 like the pinned
            heading below (DOM order puts this first, so it paints behind
            the heading; StackCard is explicitly z-10+index, well above both). */}
        <EventBackgroundArt />

        {/* Pinned heading layer. pt-[33vh] (measured empirically — pt tracks
            ~1:1 with the rendered title's top position) puts the title's
            vertical center at ~40% viewport height, matching the reference
            screenshot. The sticky stage itself already stacks below the
            sticky navbar natively (both use top:0, so the later sticky
            element sticks to the bottom of the earlier one), so this
            padding doesn't need to separately account for navbar height. */}
        <motion.div
          style={{ scale: headingScale, opacity: headingOpacity, transformOrigin: "center top" }}
          className="pointer-events-none absolute inset-0 z-0 flex flex-col items-center justify-start px-6 pt-[33vh] text-center"
        >
          <h2
            className="font-heading font-semibold text-kolabora-primary"
            style={{ fontSize: "clamp(2.5rem, 6vw, 6rem)" }}
          >
            {heading}
          </h2>
          <p className="mt-4 max-w-md text-kolabora-neutral-dark/70">{subtitle}</p>
        </motion.div>

        {events.map((event, i) => (
          <StackCard key={event.slug} event={event} index={i} total={events.length} scrollYProgress={scrollYProgress} />
        ))}
      </div>
    </div>
  );
}

/**
 * Scroll-driven "sticky stack" section (incredibles.dev-style): the heading
 * pins and recedes into the background while event cards scroll in and
 * stack over it one by one. Purely scroll-linked (Framer Motion
 * useScroll/useTransform reading scroll position) — never intercepts wheel
 * or touch input, so normal scrolling always keeps working.
 *
 * Disabled below md (simple fade-up grid instead — a pinned multi-screen
 * section doesn't translate well to small viewports) and under
 * prefers-reduced-motion (fully static, no transforms or animations).
 */
export function EventStackSection({
  heading = "On Going Event",
  subtitle = "Don't miss out on the best lineup of events waiting for you.",
  events = DEFAULT_EVENTS,
}: EventStackSectionProps) {
  const reduceMotion = useReducedMotionSafe();

  if (reduceMotion) {
    return <StaticEventGrid heading={heading} subtitle={subtitle} events={events} />;
  }

  return (
    <>
      <PinnedStack heading={heading} subtitle={subtitle} events={events} />
      <div className="md:hidden">
        <StaticEventGrid heading={heading} subtitle={subtitle} events={events} />
      </div>
    </>
  );
}
