/* app.jsx — Digital Flowers (finished gift)
   hold-to-bloom · single rAF loop scrubs transforms only · 3D parallax scene
   · wrapped bouquet · love note in clear sky · keepsake */

const { useState, useRef, useEffect, useCallback, useMemo } = React;

const GIFT = {
  recipient: "Little One",
  message: "💙 Little One, you are my World, my Everything. I love you to the moon and back. I want you to always remember that you ARE enough. 💙",
  signPre: "Love,",
  signName: "Donovan",
  count: 7,
};

const PAL = {
  "--bg-0": "#3a2438", "--bg-1": "#160f1c", "--bg-2": "#0c0810",
  "--glow": "#ecc05a", "--text": "#fdeef2",
  "--muted": "rgba(253,238,242,0.64)", "--hint": "rgba(253,238,242,0.32)",
  "--vig": "0.46",
  petals: ["#e8517f", "#f084ad", "#edb350", "#d83f6e"],
};

const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
const clamp = (x, a, b) => (x < a ? a : x > b ? b : x);
function smooth(e0, e1, x) {
  const t = clamp01((x - e0) / (e1 - e0));
  return t * t * (3 - 2 * t);
}
function splitLines(msg) {
  const parts = (msg || "").split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);
  const out = [];
  for (const p of parts) {
    if (out.length && !/[a-z0-9]/i.test(p)) out[out.length - 1] += " " + p;
    else out.push(p);
  }
  return out.length ? out : [msg];
}

function Sparkles() {
  const items = useMemo(() => {
    const r = [];
    for (let i = 0; i < 16; i++) r.push({
      left: Math.random() * 100, size: 2 + Math.random() * 4,
      dur: 8 + Math.random() * 7, delay: -Math.random() * 12,
      drift: (Math.random() * 2 - 1) * 60, maxO: 0.32 + Math.random() * 0.42,
    });
    return r;
  }, []);
  return items.map((s, i) => (
    <span key={i} className="spark" style={{
      left: s.left + "%", bottom: 0, width: s.size, height: s.size,
      "--dur": s.dur + "s", "--delay": s.delay + "s", "--drift": s.drift + "px", "--max-o": s.maxO,
    }} />
  ));
}

function FallingPetals() {
  const cols = PAL.petals;
  const items = useMemo(() => {
    const r = [];
    for (let i = 0; i < 15; i++) r.push({
      left: Math.random() * 100, w: 12 + Math.random() * 12,
      dur: 9 + Math.random() * 8, delay: -Math.random() * 16,
      drift: (Math.random() * 2 - 1) * 120, spin: (Math.random() * 2 - 1) * 540,
      color: cols[i % cols.length],
    });
    return r;
  }, []);
  return items.map((p, i) => (
    <span key={i} className="petal-fall" style={{
      left: p.left + "%", width: p.w, height: p.w * 1.35, background: p.color,
      "--dur": p.dur + "s", "--delay": p.delay + "s", "--drift": p.drift + "px", "--spin": p.spin + "deg",
    }} />
  ));
}

function App() {
  const [started, setStarted] = useState(false);
  const [bloomed, setBloomed] = useState(false);
  const [showMsg, setShowMsg] = useState(false);

  const stageRef = useRef(null);
  const sceneRef = useRef(null);
  const arcRef = useRef(null);
  const tulipsRef = useRef([]);
  const wrapElsRef = useRef(null);
  const pRef = useRef(0);
  const pressedRef = useRef(false);
  const startedRef = useRef(false);
  const bloomedRef = useRef(false);

  // parallax tilt
  const tTiltX = useRef(0), tTiltY = useRef(0);
  const cTiltX = useRef(0), cTiltY = useRef(0);
  const gyroOn = useRef(false);

  const registerRef = useCallback((entry) => {
    if (entry && entry.growEl) tulipsRef.current.push(entry);
  }, []);

  useEffect(() => {
    try {
      if (localStorage.getItem("df-opened-v3") === "1") {
        pRef.current = 1;
        setStarted(true); startedRef.current = true;
        setBloomed(true); bloomedRef.current = true;
        setShowMsg(true);
      }
    } catch (e) {}
  }, []);

  // pointer parallax (desktop + during touch hold)
  useEffect(() => {
    const onMove = (e) => {
      if (gyroOn.current) return;
      const nx = (e.clientX / window.innerWidth - 0.5) * 2;
      const ny = (e.clientY / window.innerHeight - 0.5) * 2;
      tTiltY.current = clamp(nx * 7, -8, 8);
      tTiltX.current = clamp(-ny * 5, -6, 6);
    };
    const onOrient = (e) => {
      if (e.gamma == null || e.beta == null) return;
      gyroOn.current = true;
      tTiltY.current = clamp(e.gamma / 4, -10, 10);
      tTiltX.current = clamp((e.beta - 42) / 4, -8, 8);
    };
    window.addEventListener("pointermove", onMove, { passive: true });
    window.addEventListener("deviceorientation", onOrient, true);
    return () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("deviceorientation", onOrient, true);
    };
  }, []);

  useEffect(() => {
    let raf, last = performance.now();
    const ARC_LEN = 2 * Math.PI * 30;
    const frame = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000);
      last = now;
      let p = pRef.current;
      if (!bloomedRef.current) {
        if (pressedRef.current) p += dt / 3.0;
        else p -= dt / 11.0;
        p = clamp01(p);
        pRef.current = p;
        if (p > 0.012 && !startedRef.current) { startedRef.current = true; setStarted(true); }
        if (p >= 0.999 && !bloomedRef.current) {
          bloomedRef.current = true; setBloomed(true);
          try { localStorage.setItem("df-opened-v3", "1"); } catch (e) {}
          setTimeout(() => setShowMsg(true), 650);
        }
      } else { p = 1; pRef.current = 1; }

      // tulips
      const tl = tulipsRef.current;
      for (let i = 0; i < tl.length; i++) {
        const { growEl, petalsEl, cfg } = tl[i];
        const g = smooth(cfg.gStart, cfg.gStart + cfg.gSpan, p);
        const o = smooth(cfg.oStart, cfg.oStart + cfg.oSpan, p);
        const vis = smooth(cfg.gStart, cfg.gStart + 0.12, p);
        if (growEl) {
          growEl.style.setProperty("--grow", g.toFixed(4));
          growEl.style.setProperty("--vis", vis.toFixed(4));
        }
        if (petalsEl) petalsEl.style.setProperty("--open", o.toFixed(4));
      }
      // wrap
      if (!wrapElsRef.current && stageRef.current)
        wrapElsRef.current = stageRef.current.querySelectorAll(".wrap-grow");
      if (wrapElsRef.current) {
        const w = smooth(0, 0.32, p).toFixed(4);
        wrapElsRef.current.forEach((el) => el.style.setProperty("--wrap", w));
      }
      // glow + arc
      if (stageRef.current) {
        stageRef.current.style.setProperty("--glow-s", (0.55 + p * 0.7).toFixed(3));
        stageRef.current.style.setProperty("--glow-o", p.toFixed(3));
      }
      if (arcRef.current) {
        arcRef.current.style.strokeDasharray = ARC_LEN;
        arcRef.current.style.strokeDashoffset = (ARC_LEN * (1 - p)).toFixed(2);
      }
      // parallax: ease toward target + gentle idle float
      cTiltX.current += (tTiltX.current - cTiltX.current) * 0.07;
      cTiltY.current += (tTiltY.current - cTiltY.current) * 0.07;
      const idleX = Math.sin(now * 0.00045) * 1.3;
      const idleY = Math.cos(now * 0.00035) * 2.0;
      if (sceneRef.current) {
        sceneRef.current.style.setProperty("--tiltX", (cTiltX.current + idleX).toFixed(3) + "deg");
        sceneRef.current.style.setProperty("--tiltY", (cTiltY.current + idleY).toFixed(3) + "deg");
      }
      raf = requestAnimationFrame(frame);
    };
    raf = requestAnimationFrame(frame);
    return () => cancelAnimationFrame(raf);
  }, []);

  const requestGyro = useCallback(() => {
    try {
      const D = window.DeviceOrientationEvent;
      if (D && typeof D.requestPermission === "function") {
        D.requestPermission().then((s) => {
          if (s === "granted")
            window.addEventListener("deviceorientation", (e) => {
              if (e.gamma == null) return;
              gyroOn.current = true;
              tTiltY.current = clamp(e.gamma / 4, -10, 10);
              tTiltX.current = clamp((e.beta - 42) / 4, -8, 8);
            }, true);
        }).catch(() => {});
      }
    } catch (e) {}
  }, []);

  const onDown = useCallback(() => {
    requestGyro();
    if (!bloomedRef.current) pressedRef.current = true;
  }, [requestGyro]);
  const onUp = useCallback(() => { pressedRef.current = false; }, []);

  const replay = useCallback(() => {
    pRef.current = 0; pressedRef.current = false;
    startedRef.current = false; bloomedRef.current = false;
    setShowMsg(false); setBloomed(false); setStarted(false);
    tulipsRef.current.forEach(({ growEl, petalsEl }) => {
      if (growEl) { growEl.style.setProperty("--grow", 0); growEl.style.setProperty("--vis", 0); }
      if (petalsEl) petalsEl.style.setProperty("--open", 0);
    });
    if (wrapElsRef.current) wrapElsRef.current.forEach((el) => el.style.setProperty("--wrap", 0));
  }, []);

  const stageVars = {
    ...Object.fromEntries(Object.entries(PAL).filter(([k]) => k.startsWith("--"))),
    "--scatter": 0,
  };
  const shiftVars = { "--settle-s": showMsg ? 0.8 : 1 };
  const lines = useMemo(() => splitLines(GIFT.message), []);

  return (
    <div className={"stage" + (bloomed ? " bloomed" : "")} ref={stageRef} style={stageVars}
         onPointerDown={onDown} onPointerUp={onUp} onPointerLeave={onUp} onPointerCancel={onUp}>

      <div className="scene" ref={sceneRef}>
        <div className="layer layer-glow"><div className="ground-glow" /></div>
        <div className="layer layer-bouquet">
          <div className="scene-shift" style={shiftVars}>
            <Bouquet count={GIFT.count} seed={7} registerRef={registerRef} />
          </div>
        </div>
        <div className="layer layer-front">
          {bloomed && <FallingPetals />}
        </div>
      </div>

      <div className="particles"><Sparkles /></div>
      <div className="vignette" />

      <div className={"intro" + (started ? " gone" : "")}>
        <div>
          <div className="intro-eyebrow">A gift for</div>
          <div className="intro-to">{GIFT.recipient}</div>
        </div>
        <div className="prompt">
          <div className="hold-ring">
            <span className="hold-dot" />
            <svg className="hold-progress" viewBox="0 0 64 64">
              <circle cx="32" cy="32" r="30" ref={arcRef} />
            </svg>
          </div>
          <div className="prompt-text">Press &amp; hold to make them bloom</div>
        </div>
      </div>

      <div className={"message" + (showMsg ? " show" : "")}>
        <div className="message-body">
          {lines.map((ln, i) => (
            <span className="message-line" key={i} style={{ transitionDelay: 0.25 + i * 0.5 + "s" }}>
              {ln}{" "}
            </span>
          ))}
        </div>
        <div className="note-rule" style={{ transitionDelay: 0.2 + lines.length * 0.5 + "s" }} />
        <div className="signoff" style={{ transitionDelay: 0.4 + lines.length * 0.5 + "s" }}>
          <span className="sign-pre">{GIFT.signPre}</span>
          {GIFT.signName}
        </div>
      </div>

      <div className={"footer" + (showMsg ? " show" : "")}>
        <button onClick={replay}>↺ &nbsp;Bloom again</button>
        <span className="dot" />
        <span style={{ fontFamily: "var(--sans)", fontSize: 12, letterSpacing: "0.2em",
                       textTransform: "uppercase", color: "var(--hint)" }}>
          Sent with love
        </span>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
