// Keep semantic HTML visible until the local Three.js module has rendered.
function ThreeHeading({ lines, variant, settings, onStatus, as: Tag = "h2", style, className = "", specialWord }) {
  const ref = React.useRef(null);
  const textKey = JSON.stringify(lines);
  React.useEffect(() => {
    let cancelled = false;
    let dispose;
    const report = (state, message = "") => { if (!cancelled) onStatus?.(variant, { state, message }); };
    report("loading");
    // Babel injects this JSX as an inline script; resolve against the actual page.
    const moduleURL = new URL("scripts/three-text.mjs?v=18", document.baseURI).href;
    import(moduleURL)
      .then(({ mountHeading }) => cancelled ? null : mountHeading(ref.current, JSON.parse(textKey), variant, report))
      .then((cleanup) => {
        if (cancelled) cleanup?.();
        else dispose = cleanup;
      })
      .catch((error) => {
        report("error", error.message || "Unable to load 3D text.");
        console.warn("3D heading unavailable; using HTML text.", error);
      });
    return () => { cancelled = true; dispose?.(); };
  }, [textKey, variant, onStatus, specialWord]);
  return (
    <Tag ref={ref} className={`three-heading three-heading--${variant} ${className}`} style={style}
      data-special-word={specialWord}
      data-hover-effect={settings.effect} data-hover-intensity={settings.intensity} data-hover-preview={settings.preview}
      data-hover-allow-motion={settings.allowMotion}
      data-entrance-effect={variant === "roles" ? settings.scrollEntrance : settings.heroEntrance}
      data-entrance-replay={variant === "roles" ? settings.scrollReplay : settings.heroReplay}
      data-entrance-ready={settings.pageReady}>
      <span className="three-heading__fallback">
        {lines.map((line, index) => (
          <span className="three-heading__line" key={index}>{line}{" "}</span>
        ))}
      </span>
    </Tag>
  );
}

// Shared primary page title: real glyph meshes, boot-gated entrance, hover/tap scatter.
function PageTitle({ lines, text, style, as = "h1", className = "", specialWord }) {
  const [ready, setReady] = React.useState(false);
  React.useEffect(() => {
    const onReady = () => setReady(!!document.querySelector('.page.booted') &&
      (!window.ZTTransition || window.ZTTransition.getState() === 'idle'));
    window.addEventListener("zt-ready", onReady);
    window.addEventListener('zt-transition-state', onReady);
    onReady();
    return () => {
      window.removeEventListener('zt-ready', onReady);
      window.removeEventListener('zt-transition-state', onReady);
    };
  }, []);
  const titleLines = React.useMemo(() => {
    // Keep CMS project names readable on phones instead of compressing one long line.
    const source = lines?.length ? lines : [text || ""];
    return source.flatMap((line) => {
      const rows = [];
      let row = "";
      for (const word of String(line).split(/\s+/)) {
        if (row && (row + " " + word).length > 18) { rows.push(row); row = word; }
        else row += (row ? " " : "") + word;
      }
      if (row) rows.push(row);
      return rows;
    });
  }, [JSON.stringify(lines), text]);
  return <ThreeHeading as={as} variant="title" lines={titleLines} style={style} className={className} specialWord={specialWord}
    settings={{ effect: "scatter", intensity: 1, preview: false, allowMotion: true,
      heroEntrance: "random", heroReplay: 0, pageReady: ready }} />;
}

// Section headings share glyph picking, touch interaction and spring return.
// Their renderer sleeps off screen and entrances wait for the page reveal.
function SectionTitle({ lines, text, style, specialWord }) {
  return <PageTitle as="h2" className="three-heading--section" lines={lines} text={text} style={style} specialWord={specialWord} />;
}
