"use client";

import { ReactNode, useEffect, useRef, useSyncExternalStore } from "react";
import Image from "next/image";
import { motion, useSpring, Variants } from "framer-motion";
import { C } from "./palette";

export { C };

const viewport = { once: true, amount: 0.2 } as const;

/* Respect the OS "reduce motion" setting — entrances collapse to a plain fade
   so the page stays usable for anyone who gets motion sickness. */
const REDUCE_QUERY = "(prefers-reduced-motion: reduce)";

function subscribeReduced(onChange: () => void) {
  const mql = window.matchMedia(REDUCE_QUERY);
  mql.addEventListener("change", onChange);
  return () => mql.removeEventListener("change", onChange);
}

export function useReducedMotion() {
  return useSyncExternalStore(
    subscribeReduced,
    () => window.matchMedia(REDUCE_QUERY).matches,
    () => false, // server render: assume motion is fine, corrected on hydrate
  );
}

/* Shared smooth easing — a soft, premium ease-out curve reused across reveals. */
const smoothEase = [0.22, 1, 0.36, 1] as const;

/* stagger container variants */
export const stagger: Variants = {
  hidden: {},
  show: { transition: { staggerChildren: 0.11, delayChildren: 0.06 } },
};
export const riseItem: Variants = {
  hidden: { opacity: 0, y: 34, scale: 0.96 },
  show: {
    opacity: 1,
    y: 0,
    scale: 1,
    transition: { duration: 0.7, ease: smoothEase },
  },
};

/* ------- Section ------- */
type SectionProps = {
  id: string;
  children: ReactNode;
  variant?: "cream" | "deep" | "night" | "plain";
  className?: string;
  /** optional full-bleed background image layered over the variant colour */
  bgImage?: string;
  /** Vertical weight. Drives the page's pacing — not every beat deserves a
      full screen. `punch` is a short interstitial, `epic` is an over-tall
      statement moment. */
  size?: "punch" | "normal" | "full" | "epic";
  /** Content width. `bleed` lets a section run edge-to-edge instead of sitting
      in the same 6xl column as everything else. */
  width?: "narrow" | "default" | "wide" | "bleed";
  /** Vertical anchoring of the content within the section. */
  align?: "center" | "start" | "end";
};

const SIZE = {
  punch: "min-h-[42vh] py-14",
  normal: "min-h-[70vh] py-16",
  full: "min-h-[calc(100vh-40px)] py-20",
  epic: "min-h-[125vh] py-28",
} as const;

const WIDTH = {
  narrow: "max-w-3xl",
  default: "max-w-6xl",
  wide: "max-w-[1600px]",
  bleed: "max-w-none",
} as const;

const ALIGN = {
  center: "justify-center",
  start: "justify-start",
  end: "justify-end",
} as const;

export function Section({
  id,
  children,
  variant = "deep",
  className = "",
  bgImage,
  size = "full",
  width = "default",
  align = "center",
}: SectionProps) {
  const bg =
    variant === "cream"
      ? "bg-cream text-[#0a0a0a]"
      : variant === "night"
        ? "bg-night text-white"
        : variant === "plain"
          ? "bg-[#120a1f] text-white"
          : "bg-deep text-white";
  return (
    <section
      id={id}
      className={`relative m-5 flex flex-col overflow-hidden rounded-[20px] px-6 sm:px-10 lg:px-20 ${SIZE[size]} ${ALIGN[align]} ${bg} ${className}`}
    >
      {bgImage && (
        <div
          aria-hidden="true"
          className="pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat"
          style={{ backgroundImage: `url(${bgImage})` }}
        />
      )}
      {/* Scrim — keeps copy legible over the busy artwork. Darkest through the
          centre column where the text sits, lighter at the edges so the props
          stay visible. */}
      {bgImage && (
        <div
          aria-hidden="true"
          className="pointer-events-none absolute inset-0"
          style={{
            background:
              "linear-gradient(90deg, rgba(10,4,20,0.45) 0%, rgba(10,4,20,0.78) 30%, rgba(10,4,20,0.78) 70%, rgba(10,4,20,0.45) 100%)",
          }}
        />
      )}
      <div className={`relative mx-auto w-full ${WIDTH[width]}`}>{children}</div>
    </section>
  );
}

/* ------- Reveal (fade/slide up on scroll) ------- */
export function Reveal({
  children,
  className = "",
  delay = 0,
  y = 26,
}: {
  children: ReactNode;
  className?: string;
  delay?: number;
  y?: number;
}) {
  return (
    <motion.div
      className={className}
      initial={{ opacity: 0, y }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={viewport}
      transition={{ duration: 0.7, ease: smoothEase, delay }}
    >
      {children}
    </motion.div>
  );
}

/* ------- useScrollPass -------
   Calls back with 0..1 as an element travels from entering the bottom of the
   viewport to leaving the top.

   Deliberately NOT framer-motion's useScroll: this app drives scrolling with
   Lenis, and useScroll's progress stays pinned at 0 the whole way through
   (verified — a Parallax on it never left its initial offset). A rAF loop is
   agnostic to how scroll is driven. Gated on an IntersectionObserver so the
   loop only spins while the element is actually near the viewport. */
function useScrollPass(
  ref: React.RefObject<HTMLElement | null>,
  onProgress: (p: number) => void,
  enabled = true,
) {
  const cb = useRef(onProgress);
  useEffect(() => {
    cb.current = onProgress;
  }, [onProgress]);

  useEffect(() => {
    const el = ref.current;
    if (!el || !enabled) return;
    let frame = 0;
    let last = -1;

    const paint = () => {
      const r = el.getBoundingClientRect();
      const span = window.innerHeight + r.height;
      const p = span > 0 ? (window.innerHeight - r.top) / span : 0;
      const clamped = Math.min(1, Math.max(0, p));
      if (clamped !== last) {
        last = clamped;
        cb.current(clamped);
      }
      frame = requestAnimationFrame(paint);
    };

    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && !frame) frame = requestAnimationFrame(paint);
        else if (!entry.isIntersecting && frame) {
          cancelAnimationFrame(frame);
          frame = 0;
        }
      },
      { rootMargin: "200px 0px" },
    );
    io.observe(el);
    return () => {
      io.disconnect();
      if (frame) cancelAnimationFrame(frame);
    };
  }, [ref, enabled]);
}

/* ------- Parallax (scroll-linked drift) -------
   Unlike Reveal, this stays tied to scroll position the whole time the element
   is on screen, so the page has depth rather than just fading things in.
   `speed` is how far it travels, in px, across the full pass. */
export function Parallax({
  children,
  className = "",
  speed = 80,
}: {
  children: ReactNode;
  className?: string;
  speed?: number;
}) {
  const outer = useRef<HTMLDivElement>(null);
  const inner = useRef<HTMLDivElement>(null);
  const reduced = useReducedMotion();

  useScrollPass(
    outer,
    (p) => {
      const el = inner.current;
      if (el) el.style.transform = `translate3d(0, ${(0.5 - p) * 2 * speed}px, 0)`;
    },
    !reduced,
  );

  return (
    <div ref={outer} className={className}>
      <div ref={inner} className="will-change-transform">
        {children}
      </div>
    </div>
  );
}

/* ------- Marquee (continuous horizontal scroll) -------
   A funky, always-moving band. Doubles its children so the loop is seamless. */
export function Marquee({
  children,
  className = "",
  duration = 22,
  reverse = false,
}: {
  children: ReactNode;
  className?: string;
  duration?: number;
  reverse?: boolean;
}) {
  return (
    <div className={`marquee-viewport relative flex overflow-hidden ${className}`}>
      <div
        className="marquee-track flex w-max shrink-0 items-center gap-6 pr-6"
        data-reverse={reverse ? "true" : undefined}
        style={{ ["--marquee-duration" as string]: `${duration}s` }}
      >
        {/* Rendered twice — the second copy is what the loop scrolls into. */}
        {children}
        <span aria-hidden className="contents">
          {children}
        </span>
      </div>
    </div>
  );
}

/* ------- SplitWords -------
   Headline that assembles word by word, each one rising and unblurring from
   its own mask. The mask is what sells it — words emerge from behind a hard
   edge rather than just fading. */
export function SplitWords({
  text,
  className = "",
  delay = 0,
  stagger = 0.055,
}: {
  text: string;
  className?: string;
  delay?: number;
  stagger?: number;
}) {
  const reduced = useReducedMotion();
  const words = text.split(" ");
  return (
    <span className={className}>
      {words.map((w, i) => (
        // Real space between words so the text stays one readable string for
        // screen readers, copy-paste and crawlers — the mask is presentation.
        <span key={`${w}-${i}`}>
          <span className="inline-block overflow-hidden pb-[0.12em] align-bottom">
            <motion.span
              className="inline-block will-change-transform"
              initial={reduced ? { opacity: 0 } : { y: "110%", opacity: 0, rotate: 4 }}
              whileInView={reduced ? { opacity: 1 } : { y: "0%", opacity: 1, rotate: 0 }}
              viewport={viewport}
              transition={{
                duration: 0.85,
                ease: smoothEase,
                delay: delay + i * stagger,
              }}
            >
              {w}
            </motion.span>
          </span>
          {i < words.length - 1 ? " " : null}
        </span>
      ))}
    </span>
  );
}

/* ------- ScrollLitText -------
   Long statement where each word lights up as it passes the middle of the
   viewport — the copy reads itself as you scroll. */
export function ScrollLitText({
  text,
  className = "",
  dim = "rgba(255,255,255,0.16)",
  lit = "#ffffff",
}: {
  text: string;
  className?: string;
  dim?: string;
  lit?: string;
}) {
  const ref = useRef<HTMLParagraphElement>(null);
  const reduced = useReducedMotion();
  const words = text.split(" ");

  /* The pass is remapped so the line is fully lit by the time it reaches the
     middle of the screen, rather than only at the very top. */
  useScrollPass(
    ref,
    (p) => {
      const el = ref.current;
      if (!el) return;
      const spans = el.querySelectorAll<HTMLElement>("[data-lit-word]");
      const eased = Math.min(1, Math.max(0, (p - 0.15) / 0.45));
      spans.forEach((s, i) => {
        const t = Math.min(1, Math.max(0, eased * spans.length - i));
        s.style.color = t > 0.5 ? lit : dim;
        s.style.opacity = String(0.35 + t * 0.65);
      });
    },
    !reduced,
  );

  return (
    <p ref={ref} className={className}>
      {words.map((w, i) => (
        <span key={`${w}-${i}`}>
          <span
            data-lit-word
            className="inline-block transition-colors duration-200"
            style={{ color: reduced ? lit : dim }}
          >
            {w}
          </span>
          {i < words.length - 1 ? " " : null}
        </span>
      ))}
    </p>
  );
}

/* ------- Magnetic -------
   Pulls toward the cursor on hover and springs back on leave. Pointer-driven,
   so it's skipped on touch and under reduced-motion. */
export function Magnetic({
  children,
  className = "",
  strength = 0.35,
}: {
  children: ReactNode;
  className?: string;
  strength?: number;
}) {
  const ref = useRef<HTMLSpanElement>(null);
  const reduced = useReducedMotion();
  const x = useSpring(0, { stiffness: 260, damping: 18 });
  const y = useSpring(0, { stiffness: 260, damping: 18 });

  const onMove = (e: React.PointerEvent) => {
    if (reduced || e.pointerType !== "mouse" || !ref.current) return;
    const r = ref.current.getBoundingClientRect();
    x.set((e.clientX - (r.left + r.width / 2)) * strength);
    y.set((e.clientY - (r.top + r.height / 2)) * strength);
  };
  const reset = () => {
    x.set(0);
    y.set(0);
  };

  return (
    <motion.span
      ref={ref}
      className={`inline-block ${className}`}
      style={{ x, y }}
      onPointerMove={onMove}
      onPointerLeave={reset}
    >
      {children}
    </motion.span>
  );
}

/* ------- Group (stagger children that use riseItem) ------- */
export function Group({
  children,
  className = "",
}: {
  children: ReactNode;
  className?: string;
}) {
  return (
    <motion.div
      className={className}
      variants={stagger}
      initial="hidden"
      whileInView="show"
      viewport={{ once: true, amount: 0.15 }}
    >
      {children}
    </motion.div>
  );
}

/* ------- Pill ------- */
export function Pill({
  children,
  bg = C.pink,
  text = "#fff",
}: {
  children: ReactNode;
  bg?: string;
  text?: string;
}) {
  return (
    <motion.span
      className="story-pill nb inline-flex items-center gap-2.5 rounded-full px-6 py-3 text-lg font-extrabold uppercase tracking-wide sm:px-8 sm:py-4 sm:text-2xl"
      style={{ background: bg, color: text }}
      initial={{ opacity: 0, scale: 0.8, y: 12 }}
      whileInView={{ opacity: 1, scale: 1, y: 0 }}
      viewport={viewport}
      transition={{ type: "spring", stiffness: 320, damping: 15 }}
      whileHover={{ rotate: -2, scale: 1.04 }}
    >
      {children}
    </motion.span>
  );
}

/* ------- Card (scroll-reveal + neobrutalist hover) ------- */
export function Card({
  children,
  bg = "#fff",
  text = "#0a0a0a",
  className = "",
  rounded = "rounded-2xl",
  delay = 0,
  group = false,
}: {
  children: ReactNode;
  bg?: string;
  text?: string;
  className?: string;
  rounded?: string;
  delay?: number;
  /** set true when rendered inside a <Group> so stagger drives the entrance */
  group?: boolean;
}) {
  const entrance = group
    ? { variants: riseItem }
    : {
        initial: { opacity: 0, y: 30, scale: 0.96 },
        whileInView: { opacity: 1, y: 0, scale: 1 },
        viewport,
        transition: { duration: 0.7, ease: smoothEase, delay },
      };
  return (
    <motion.div
      className={`nb ${rounded} ${className}`}
      style={{ background: bg, color: text }}
      {...entrance}
      whileHover={{
        y: -6,
        x: -3,
        boxShadow: "11px 13px 0 0 #0a0a0a",
        transition: { type: "spring", stiffness: 400, damping: 15 },
      }}
    >
      {children}
    </motion.div>
  );
}

/* ------- Chip ------- */
export function Chip({
  children,
  bg = C.lime,
  text = "#0a0a0a",
  delay = 0,
}: {
  children: ReactNode;
  bg?: string;
  text?: string;
  delay?: number;
}) {
  return (
    <motion.span
      className="nb-sm inline-flex items-center gap-1.5 rounded-xl px-3 py-2 text-sm font-extrabold sm:px-4"
      style={{ background: bg, color: text }}
      initial={{ opacity: 0, scale: 0.85, y: 10 }}
      whileInView={{ opacity: 1, scale: 1, y: 0 }}
      viewport={viewport}
      transition={{ type: "spring", stiffness: 300, damping: 16, delay }}
      whileHover={{ scale: 1.06, rotate: -1.5 }}
    >
      {children}
    </motion.span>
  );
}

/* ------- Heading (self-animating) ------- */
export function Heading({
  children,
  className = "",
}: {
  children: ReactNode;
  className?: string;
}) {
  return (
    <motion.h2
      className={`font-sora text-4xl font-extrabold leading-[0.95] tracking-tight sm:text-5xl lg:text-6xl ${className}`}
      initial={{ opacity: 0, y: 28 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={viewport}
      transition={{ duration: 0.75, ease: smoothEase }}
    >
      {children}
    </motion.h2>
  );
}

/* LytsUp wordmark */
export function Logo({ className = "" }: { className?: string }) {
  return (
    <Image
      src="/images/lytsup_app.png"
      alt="LytsUp"
      width={2048}
      height={554}
      priority
      className={`h-10 w-auto sm:h-12 ${className}`}
    />
  );
}
