// Reactive headline. Bio words react when their bound filter(s) toggle:
// scale pop + horizontal shine sweep + particle burst. Layout never reflows
// (transform-only animation, period kept inside its segment).

function ReactiveHeadline({ active, lastToggled, style }) {
  const colorById = React.useMemo(() => {
    const m = {};
    (window.RARE_FILTERS || []).forEach((f) => {
      m[f.id] = f.color;
    });
    return m;
  }, []);
  const seg = (text, filterIds, italic = false) => {
    const ids = Array.isArray(filterIds) ? filterIds : [filterIds];
    // Trigger = count of THIS segment's filters that are ON. The segment
    // fires shine only when this number goes UP (an ON event for one of
    // our tracked filters). Going down (OFF) is silent.
    const trigger = ids.reduce((n, id) => n + (active[id] ? 1 : 0), 0);
    // Color preference: the filter that just toggled (if it's one of ours),
    // else any active filter, else the first id.
    const colorSourceId =
      lastToggled && ids.includes(lastToggled)
        ? lastToggled
        : ids.find((id) => active[id]) || ids[0];
    const flourishColor = colorById[colorSourceId] || "var(--accent)";
    return (
      <ReactiveHeadlineSegment
        key={ids.join(",")}
        text={text}
        italic={italic}
        trigger={trigger}
        flourishColor={flourishColor}
      />
    );
  };
  return (
    <h1
      style={{
        fontFamily: "var(--font-serif)",
        fontSize: 44,
        lineHeight: 1.18,
        margin: 0,
        fontWeight: 400,
        letterSpacing: "-0.02em",
        textWrap: "balance",
        ...style,
      }}
    >
      I ship the{" "}
      <span style={{ whiteSpace: "nowrap" }}>
        {seg("frontend", "fullstack")},
      </span>{" "}
      the{" "}
      <span style={{ whiteSpace: "nowrap" }}>
        {seg("backend", "fullstack")},
      </span>{" "}
      the{" "}
      <span style={{ whiteSpace: "nowrap" }}>
        {seg("smart contracts", "web3")},
      </span>{" "}
      <em style={{ fontStyle: "italic", opacity: 0.72, color: "var(--fg)" }}>
        and
      </em>{" "}
      {seg("run the investor update.", "clientfacing")}
    </h1>
  );
}

// Fires shine in two cases:
//   1. On mount — once, in silver, no particles. Acts as a "hello" pass that
//      tells the visitor these words are interactive.
//   2. On a subsequent trigger UP — `trigger` is the count of the segment's
//      tracked filters that are ON, so a rise = an ON event for one of our
//      filters. Going down is silent.
//
// `baseColor` defaults to var(--accent) for headline use. Pass "inherit" to
// reuse the surrounding text color (e.g. when dropped inside the gray bio).
// `particles` defaults to true — set false for inline bio use where the
// burst would feel too loud against body text.
function ReactiveHeadlineSegment({
  text,
  italic,
  trigger,
  flourishColor,
  baseColor,
  particles = true,
}) {
  // shineConfig: null | { color, particles } — when non-null, a shine sweep
  // is currently running. Tracks the per-fire visual config so the mount
  // shine can override the prop-derived defaults without mutating them.
  const [shineConfig, setShineConfig] = React.useState(null);
  const [burst, setBurst] = React.useState(0);
  const prev = React.useRef(null); // null = pre-mount; otherwise last trigger

  React.useEffect(() => {
    let next = null;
    if (prev.current === null) {
      // First-load "hello" sweep — silver shine only, no scale pop, no
      // particles. The pop reads as a reaction to user input; firing it
      // on mount would feel like a glitch.
      next = { color: "silver", particles: false, pop: false };
    } else if (trigger > prev.current) {
      next = { color: flourishColor, particles, pop: true };
    }
    prev.current = trigger;
    if (!next) return;
    setShineConfig(next);
    setBurst((n) => n + 1);
    // Must outlive the CSS animation (1.5s) so the shine doesn't cut mid-sweep.
    const id = setTimeout(() => setShineConfig(null), 1600);
    return () => clearTimeout(id);
  }, [trigger]);

  const shining = shineConfig !== null;
  const shineColor = shineConfig ? shineConfig.color : flourishColor;
  const showParticles = shineConfig ? shineConfig.particles : false;
  const popping = shineConfig ? shineConfig.pop : false;

  const containerColor = baseColor || "var(--accent)";

  // Outer hosts the in-flow text + an absolute shine overlay + particles.
  // The shine overlay uses background-clip:text so the gradient paints on
  // the glyph shapes only — the rectangular bounding box is never visible.
  // ParticleBurst is conditional on `shining` so it unmounts cleanly when
  // the shine ends (keeps DOM lean across rapid toggles).
  return (
    <span
      style={{
        display: "inline-block",
        position: "relative",
        verticalAlign: "baseline",
        // overflowWrap lets multi-word phrases (e.g. "smart contracts",
        // "run the investor update.") break across lines on narrow viewports
        // instead of forcing the inline-block wider than the page.
        maxWidth: "100%",
        overflowWrap: "break-word",
        wordBreak: "break-word",
        color: containerColor,
        fontStyle: italic ? "italic" : "normal",
        animation: popping
          ? "rareWordPop 0.7s cubic-bezier(0.22,0.9,0.3,1) 1"
          : "none",
      }}
    >
      {text}
      {shining && (
        <span
          key={burst}
          aria-hidden="true"
          style={{
            position: "absolute",
            inset: 0,
            pointerEvents: "none",
            // Inherit every glyph metric so the overlay's text traces the
            // underlying text exactly. Any drift here = visible double-text.
            font: "inherit",
            fontStyle: "inherit",
            fontWeight: "inherit",
            letterSpacing: "inherit",
            lineHeight: "inherit",
            // ─── Shine gradient — tweak here ────────────────────────────
            // Solid filter-color band from 40% to 60% with short fade-in/out
            // tails. The flat plateau means the glyph reads as the full filter
            // color the whole time the band is over it (no alpha-blend tinting
            // toward white). Diagonal angle reads as a specular highlight.
            //
            // To narrow the band:    push 40%→45% and 60%→55%.
            // To widen / make slower: extend to 35% and 65%.
            // To brighten the peak:  swap both ${flourishColor} stops to
            //                        `color-mix(in srgb, ${flourishColor}, white 25%)`.
            // To make the shine more saturated: lower the white-mix to ~10%
            //                        or drop it (current).
            backgroundImage: `linear-gradient(105deg, transparent 25%, ${shineColor} 40%, ${shineColor} 60%, transparent 75%)`,
            backgroundSize: "200% 100%",
            backgroundRepeat: "no-repeat",
            // ─────────────────────────────────────────────────────────────
            WebkitBackgroundClip: "text",
            backgroundClip: "text",
            WebkitTextFillColor: "transparent",
            color: "transparent",
            animation:
              "rareShineSweep 1.5s cubic-bezier(0.22,0.9,0.3,1) 1 forwards",
          }}
        >
          {text}
        </span>
      )}
      {shining && showParticles && (
        <window.ParticleBurst key={burst} color={shineColor} />
      )}
    </span>
  );
}

window.ReactiveHeadline = ReactiveHeadline;
window.ReactiveHeadlineSegment = ReactiveHeadlineSegment;
