import { ReactNode, useEffect, useRef, useState, CSSProperties } from "react";

type Direction = "up" | "down" | "left" | "right" | "none";

interface AnimateInProps {
  children: ReactNode;
  /** Delay in ms before the animation starts after becoming visible. */
  delay?: number;
  /** Duration of the animation in ms. Defaults to 700. */
  duration?: number;
  /** Direction of the slide. Defaults to "up" (slide-up). */
  direction?: Direction;
  /** Distance in px the element slides. Defaults to 32. */
  distance?: number;
  /** IntersectionObserver threshold. Defaults to 0.15. */
  threshold?: number;
  /** If true, animation replays every time the element re-enters the viewport. */
  replay?: boolean;
  /** Optional extra className. */
  className?: string;
  /** Optional inline style to merge. */
  style?: CSSProperties;
  /** Render as a specific tag. Defaults to "div". */
  as?: keyof JSX.IntrinsicElements;
}

/**
 * AnimateIn
 * ----------
 * Wraps its children and animates them (fade + slide) into view the first
 * time they intersect the viewport, using IntersectionObserver. Plays nicely
 * with the one-page snap scroller on the homepage because each section's
 * content animates in when the section becomes active.
 *
 * Usage:
 *   <AnimateIn>...content...</AnimateIn>
 *   <AnimateIn delay={150} direction="left">...</AnimateIn>
 */
export default function AnimateIn({
  children,
  delay = 0,
  duration = 700,
  direction = "up",
  distance = 32,
  threshold = 0.15,
  replay = true,
  className = "",
  style,
  as = "div",
}: AnimateInProps) {
  const ref = useRef<HTMLElement | null>(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    // Respect reduced-motion preference — show immediately.
    if (typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      setVisible(true);
      return;
    }

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setVisible(true);
            if (!replay) observer.unobserve(entry.target);
          } else if (replay) {
            setVisible(false);
          }
        });
      },
      { threshold, rootMargin: "0px 0px -50px 0px" }
    );

    observer.observe(el);
    return () => observer.disconnect();
  }, [threshold, replay]);

  const initialTransform = (() => {
    switch (direction) {
      case "up":    return `translate3d(0, ${distance}px, 0)`;
      case "down":  return `translate3d(0, -${distance}px, 0)`;
      case "left":  return `translate3d(${distance}px, 0, 0)`;
      case "right": return `translate3d(-${distance}px, 0, 0)`;
      case "none":  return "translate3d(0, 0, 0)";
    }
  })();

  const mergedStyle: CSSProperties = {
    opacity: visible ? 1 : 0,
    transform: visible ? "translate3d(0, 0, 0)" : initialTransform,
    transition: `opacity ${duration}ms cubic-bezier(0.22, 1, 0.36, 1) ${delay}ms, transform ${duration}ms cubic-bezier(0.22, 1, 0.36, 1) ${delay}ms`,
    willChange: "opacity, transform",
    ...style,
  };

  const Tag = as as keyof JSX.IntrinsicElements;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return (
    <Tag
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      ref={ref as any}
      className={className}
      style={mergedStyle}
    >
      {children}
    </Tag>
  );
}