"use client";

import { Fragment } from "react";
import { motion, useReducedMotion } from "motion/react";
import type { ComponentType, ElementType, ReactNode } from "react";

type Renderable = ComponentType<{
  className?: string;
  children?: ReactNode;
  "aria-label"?: string;
}>;

/**
 * Word by word heading reveal. Words rise out of a clipped line, the way a
 * heading would appear if it were being knitted row by row.
 *
 * The full string stays in the DOM as one accessible label, so screen readers
 * and search engines never see it as loose fragments. Reduced motion changes
 * only the timing, so the markup is identical on the server and the client.
 */
export function SplitText({
  text,
  as: Tag = "h2",
  className,
  delay = 0,
  stagger = 0.055,
  amount = 0.5,
}: {
  text: string;
  as?: ElementType;
  className?: string;
  delay?: number;
  stagger?: number;
  amount?: number;
}) {
  const reduced = useReducedMotion();
  const words = text.split(" ");
  const Heading = Tag as unknown as Renderable;

  return (
    <Heading className={className} aria-label={text}>
      <motion.span
        aria-hidden="true"
        initial="hidden"
        whileInView="shown"
        viewport={{ once: true, amount }}
        variants={{
          hidden: {},
          shown: {
            transition: {
              staggerChildren: reduced ? 0 : stagger,
              delayChildren: reduced ? 0 : delay,
            },
          },
        }}
        style={{ display: "inline" }}
      >
        {words.map((word, i) => (
          <Fragment key={`${word}-${i}`}>
            <span
              style={{
                display: "inline-block",
                overflow: "hidden",
                verticalAlign: "bottom",
                paddingBottom: "0.08em",
                marginBottom: "-0.08em",
              }}
            >
              <motion.span
                style={{ display: "inline-block", willChange: "transform" }}
                variants={{
                  hidden: { y: "110%", opacity: 0 },
                  shown: {
                    y: "0%",
                    opacity: 1,
                    transition: {
                      duration: reduced ? 0 : 0.85,
                      ease: [0.16, 1, 0.3, 1],
                    },
                  },
                }}
              >
                {word}
              </motion.span>
            </span>
            {/* The separator is a sibling of the word box, not a child of it.
                Inside the inline-block a trailing space is collapsed away by
                normal white space processing, which ran every heading on the
                site together into one word. Out here it sits in the parent's
                inline context, so it renders and lets the line wrap. */}
            {i < words.length - 1 ? " " : null}
          </Fragment>
        ))}
      </motion.span>
    </Heading>
  );
}
