/* global React, ReactDOM */
const { useRef, useEffect, useState, useCallback } = React;

const Bodies = window.KestrelBodies;
const Footer = window.KestrelFooter;

/* ----- stage geometry (native photo px) ----- */
const STAGE_W = 1376;
const STAGE_H = 768;

/* Camera focus tuning. Two constraints, S = min(sByWidth, sByHeight).
   FOCUS_FILL_W: how much of the viewport WIDTH the monitor box fills.
   FOCUS_FILL_H: how much of the viewport HEIGHT the TERM content fills
                 (term is taller than the photo monitor box — see ratio).
   TERM_TALL_RATIO: empirical ratio of term content height to monitor box
                    height (varies per monitor, but ~3.5 covers the worst
                    case Monitor 04 daily-sweep with 10 checklist rows). */
const FOCUS_FILL_W    = 0.62;
const FOCUS_FILL_H    = 0.78;
const TERM_TALL_RATIO = 1.6;

/* journey scroll budget: monitors occupy p in [J_START, J_END] */
const J_START = 0.04;
const J_END   = 0.93;

/* ----- the 10 monitors -----
   cx,cy,w,h = center + box size as fractions of the stage.
   tilt = CSS 3D transform matching the photo monitor's perspective.
   tw   = terminal authoring width (px); scaled onto the monitor box.
   file = title-bar filename. Positions traced from bunker-annotated.png. */
const MONITORS = [
  { id: "01", file: "dispatch.no.i",  cx: 0.338, cy: 0.628, w: 0.150, h: 0.150, tw: 480, tilt: "rotateY(0deg)" },
  { id: "02", file: "about.txt",      cx: 0.150, cy: 0.624, w: 0.130, h: 0.140, tw: 480, tilt: "rotateY(0deg)" },
  { id: "03", file: "origin.log",     cx: 0.118, cy: 0.182, w: 0.118, h: 0.120, tw: 560, tilt: "rotateY(0deg)" },
  { id: "04", file: "daily-sweep.sh", cx: 0.108, cy: 0.360, w: 0.120, h: 0.120, tw: 720, tilt: "rotateY(0deg)" },
  { id: "05", file: "inbox/03:47.eml",cx: 0.492, cy: 0.182, w: 0.124, h: 0.122, tw: 560, tilt: "rotateY(0deg)" },
  { id: "06", file: "why.txt",        cx: 0.492, cy: 0.360, w: 0.124, h: 0.122, tw: 600, tilt: "rotateY(0deg)" },
  { id: "07", file: "boundaries.txt", cx: 0.845, cy: 0.360, w: 0.120, h: 0.120, tw: 520, tilt: "rotateY(0deg)" },
  { id: "08", file: "pricing.tsv",    cx: 0.858, cy: 0.182, w: 0.120, h: 0.120, tw: 640, tilt: "rotateY(0deg)" },
  { id: "09", file: "roadmap.md",     cx: 0.854, cy: 0.512, w: 0.122, h: 0.122, tw: 520, tilt: "rotateY(0deg)" },
  { id: "10", file: "sign-up.sh",     cx: 0.662, cy: 0.628, w: 0.140, h: 0.140, tw: 680, tilt: "rotateY(0deg)" },
];

const SLOT = (J_END - J_START) / MONITORS.length;

/* ----- camera ----- */
function overviewCam(vw, vh) {
  return { S: Math.min(vw / STAGE_W, vh / STAGE_H),
           fx: STAGE_W / 2, fy: STAGE_H / 2, monitor: null };
}
function monitorCam(m, vw, vh) {
  const boxWpx = m.w * STAGE_W;
  const boxHpx = m.h * STAGE_H;
  // Width-fit: monitor box fills FOCUS_FILL_W of viewport width.
  const sByW = (FOCUS_FILL_W * vw) / boxWpx;
  // Height-fit: term content (≈ box × TERM_TALL_RATIO tall) fits in
  // FOCUS_FILL_H of viewport height. Prevents top/bottom clipping on
  // shorter viewports.
  const sByH = (FOCUS_FILL_H * vh) / (boxHpx * TERM_TALL_RATIO);
  return { S: Math.min(sByW, sByH),
           fx: m.cx * STAGE_W, fy: m.cy * STAGE_H, monitor: m.id };
}
/* stops: room -> hold -> [arrive, read] x10 -> pull back to room.
   Two stops sharing a cam = a flat hold (reading time). */
function buildStops(vw, vh) {
  const over = overviewCam(vw, vh);
  const stops = [{ p: 0, cam: over }, { p: J_START, cam: over }];
  MONITORS.forEach((m, i) => {
    const cam = monitorCam(m, vw, vh);
    const base = J_START + i * SLOT;
    stops.push({ p: base + SLOT * 0.5, cam }); // arrive
    stops.push({ p: base + SLOT,       cam }); // hold / read
  });
  stops.push({ p: 1.0, cam: over });           // pull back
  return stops;
}
/* p that lands mid-read on monitor index i (for nav jumps) */
function monitorReadP(i) { return J_START + i * SLOT + SLOT * 0.75; }

const easeInOutCubic = (t) =>
  t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const lerp = (a, b, t) => a + (b - a) * t;

function Terminal({ title, children }) {
  return (
    <div className="term">
      <div className="term__bar">
        <div className="term__dots">
          <span className="term__dot term__dot--c"></span>
          <span className="term__dot term__dot--a"></span>
          <span className="term__dot term__dot--g"></span>
        </div>
        <span className="term__title">{title}</span>
      </div>
      <div className="term__body">{children}</div>
      <div className="term__scan"></div>
      <div className="term__sweep"></div>
    </div>
  );
}

/* ----- IntroSequence -----
   Three-phase intro:
     phase 1 ("typing", 0 -> 2.4s):  full-screen terminal types the alert
     phase 2 ("video", 2.4 -> 8.3s): terminal fades, bunker-intro.mp4 plays
                                     (5.9s cinematic dolly into the room)
     phase 3 ("done",  8.3s+):       overlay fades, scroll journey is live
   Reduced-motion: skipped entirely, overlay dismissed on mount.
   The video is muted + playsInline + autoplayed from JS, which Chrome
   permits because muted videos satisfy the autoplay policy. */
function IntroSequence({ onDone }) {
  const [stage, setStage] = useState("typing");
  const videoRef = useRef(null);

  useEffect(() => {
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) { onDone(); return; }
    const t1 = setTimeout(() => {
      setStage("video");
      const v = videoRef.current;
      if (v) { const p = v.play(); if (p && p.catch) p.catch(() => {}); }
    }, 2400);
    const t2 = setTimeout(() => { setStage("done"); onDone(); }, 8300);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [onDone]);

  const skip = () => { setStage("done"); onDone(); };

  return (
    <div className={`intro intro--${stage} intro--variant-B`}>
      <div className="intro__bg"></div>
      <video
        ref={videoRef}
        className="intro__video"
        src="assets/bunker-intro.mp4"
        muted
        playsInline
        preload="auto"
      />
      <div className="intro__wm-mask" aria-hidden="true"></div>
      <div className="intro__term-wrap">
        <div className="intro__term">
          <div className="intro__term-bar">
            <div className="intro__term-dots">
              <span className="intro__term-dot intro__term-dot--c"></span>
              <span className="intro__term-dot intro__term-dot--a"></span>
              <span className="intro__term-dot intro__term-dot--g"></span>
            </div>
            <span className="intro__term-title">kestrel@watchtower:~ — incoming.eml</span>
          </div>
          <div className="intro__term-body">
            <div className="intro__line i1"><span className="label">From:</span>    <span className="val">kestrel &lt;alerts@kestrel-sec.com&gt;</span></div>
            <div className="intro__line i2"><span className="label">To:</span>      <span className="val">you &lt;hello@acme.example&gt;</span></div>
            <div className="intro__line i3"><span className="label">Subject:</span> <span className="subj">A new database server appeared on the internet.</span></div>
            <div className="intro__line i4"><span className="label">At:</span>      <span className="val">03:47:12 UTC</span></div>
            <div className="intro__line i5">&nbsp;</div>
            <div className="intro__line i6"><span className="label">host: </span><span className="lead">db-staging.acme.example</span><span className="label">   port 3306</span></div>
            <div className="intro__line i7">&nbsp;</div>
            <div className="intro__line i8"><span className="lead">That's a MariaDB 10.0.36 sitting open.</span></div>
            <div className="intro__line i9"><span className="lead">Attackers will see it within the hour.</span></div>
            <div className="intro__line i10">&nbsp;</div>
            <div className="intro__line i11"><span className="findbtn">[ open finding → ]</span></div>
          </div>
          <div className="intro__term-scan"></div>
        </div>
      </div>
      <button className="intro__skip" onClick={skip}>skip →</button>
    </div>
  );
}

/* ----- SignupModal -----
   Opens on "begin a watch" click. Captures email + domain, POSTs JSON to
   the Formspree endpoint (xjgdrnnb). On success shows a "queued" state;
   the form auto-clears on close so the user can sign up again.
   No third-party JS — pure fetch + the existing Formspree project. */
const FORMSPREE_URL = "https://formspree.io/f/xjgdrnnb";

function SignupModal({ onClose }) {
  const [email, setEmail]   = useState("");
  const [domain, setDomain] = useState("");
  const [status, setStatus] = useState("idle"); // idle | sending | queued | error
  const [errorMsg, setErrorMsg] = useState("");

  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [onClose]);

  const submit = async (e) => {
    e.preventDefault();
    if (!email || !domain) {
      setStatus("error");
      setErrorMsg("both fields are required.");
      return;
    }
    setStatus("sending");
    setErrorMsg("");
    try {
      const resp = await fetch(FORMSPREE_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
        body: JSON.stringify({
          email,
          domain,
          _subject: "kestrel · begin a watch — new signup",
        }),
      });
      if (!resp.ok) throw new Error(`status ${resp.status}`);
      setStatus("queued");
    } catch (err) {
      setStatus("error");
      setErrorMsg(`couldn't queue your watch: ${err.message}. retry or email hello@kestrel-sec.com.`);
    }
  };

  return (
    <div className="sm-overlay" onClick={onClose}>
      <div className="sm-modal" onClick={(e) => e.stopPropagation()}>
        <div className="sm-bar">
          <div className="sm-dots">
            <span className="sm-dot sm-dot--c"></span>
            <span className="sm-dot sm-dot--a"></span>
            <span className="sm-dot sm-dot--g"></span>
          </div>
          <span className="sm-title">kestrel@watchtower:~ — begin-watch.sh</span>
          <button className="sm-close" onClick={onClose} aria-label="close">×</button>
        </div>
        {status === "queued" ? (
          <div className="sm-body sm-body--done">
            <div className="sm-line"><span className="prompt">$ </span><span className="cmd">queued.</span></div>
            <div className="sm-line sm-line--note">your watch is on the roster.</div>
            <div className="sm-line sm-line--note">we'll reach out to <span className="sm-em">{email}</span> when your sweep is scheduled.</div>
            <button className="sm-cta" onClick={onClose}>close →</button>
          </div>
        ) : (
          <form className="sm-body" onSubmit={submit}>
            <div className="sm-line"><span className="prompt">$ </span><span className="cmd">./begin-watch.sh</span></div>
            <label className="sm-field">
              <span className="sm-label">domain ──</span>
              <input
                type="text"
                placeholder="acme.example"
                value={domain}
                onChange={(e) => setDomain(e.target.value)}
                disabled={status === "sending"}
                autoFocus
              />
            </label>
            <label className="sm-field">
              <span className="sm-label">email  ──</span>
              <input
                type="email"
                placeholder="you@acme.example"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                disabled={status === "sending"}
              />
            </label>
            <div className="sm-microcopy">
              from €20/mo · 7-day trial · cancel anytime · we'll only ever email about your watch.
            </div>
            {status === "error" && (
              <div className="sm-err">{errorMsg}</div>
            )}
            <button type="submit" className="sm-cta" disabled={status === "sending"}>
              {status === "sending" ? "queueing . . ." : "begin a watch →"}
            </button>
          </form>
        )}
      </div>
    </div>
  );
}

function App() {
  const stageRef = useRef(null);
  const ctaRef = useRef(null);
  const hintRef = useRef(null);
  const monRefs = useRef({});
  const [introGone, setIntroGone] = useState(false);
  const [signupOpen, setSignupOpen] = useState(false);

  const dismissIntro = useCallback(() => setIntroGone(true), []);
  const openSignup   = useCallback((e) => { if (e) e.preventDefault(); setSignupOpen(true); }, []);
  const closeSignup  = useCallback(() => setSignupOpen(false), []);

  // Monitor 10's ASCII box dispatches kestrel:opensignup so the body
  // components don't need to receive openSignup as a prop.
  useEffect(() => {
    const handler = () => setSignupOpen(true);
    document.addEventListener("kestrel:opensignup", handler);
    return () => document.removeEventListener("kestrel:opensignup", handler);
  }, []);

  // camera loop driven by scroll
  useEffect(() => {
    let raf = 0;
    let vw = window.innerWidth, vh = window.innerHeight;
    let stops = buildStops(vw, vh);

    const update = () => {
      raf = 0;
      const journey = document.querySelector(".journey");
      if (!journey || !stageRef.current) return;
      const maxScroll = journey.offsetHeight - vh;
      const p = maxScroll > 0 ? Math.min(1, Math.max(0, window.scrollY / maxScroll)) : 0;

      let i = 0;
      while (i < stops.length - 2 && p > stops[i + 1].p) i++;
      const s0 = stops[i], s1 = stops[i + 1];
      const span = s1.p - s0.p;
      const local = span > 0 ? (p - s0.p) / span : 1;
      const t = easeInOutCubic(Math.min(1, Math.max(0, local)));
      const a = s0.cam, b = s1.cam;

      const S  = lerp(a.S, b.S, t);
      const fx = lerp(a.fx, b.fx, t);
      const fy = lerp(a.fy, b.fy, t);
      const tx = vw / 2 - S * fx;
      const ty = vh / 2 - S * fy;
      stageRef.current.style.transform =
        `translate(${tx.toFixed(2)}px, ${ty.toFixed(2)}px) scale(${S.toFixed(4)})`;

      const focused = t > 0.5 ? b.monitor : a.monitor;
      Object.entries(monRefs.current).forEach(([id, el]) => {
        if (el) el.classList.toggle("is-active", id === focused);
      });

      if (ctaRef.current)
        ctaRef.current.classList.toggle("is-shown", focused === "01" || focused === "10");
      if (hintRef.current) hintRef.current.style.opacity = p > 0.03 ? "0" : "1";
    };

    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    const onResize = () => {
      vw = window.innerWidth; vh = window.innerHeight;
      stops = buildStops(vw, vh);
      update();
    };

    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onResize);
    update();
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  const jumpToP = (frac) => {
    const journey = document.querySelector(".journey");
    if (!journey) return;
    const maxScroll = journey.offsetHeight - window.innerHeight;
    window.scrollTo({ top: maxScroll * frac, behavior: "smooth" });
  };

  return (
    <React.Fragment>
      <nav className="nav">
        <div className="nav__brand">
          <img className="nav__shield" src="assets/kestrel-logo.png" alt="Kestrel shield" />
          <span className="nav__wordmark">Kestrel-Sec</span>
          <span className="nav__sub">sec · the watchtower</span>
        </div>
        <div className="nav__links">
          <button className="nav__link" onClick={() => jumpToP(monitorReadP(7))}>pricing →</button>
          <button className="nav__link" onClick={() => jumpToP(monitorReadP(9))}>sign in →</button>
        </div>
      </nav>

      <div className="journey">
        <div className="bunker-viewport">
          <div className="bunker-stage" ref={stageRef}>
            <div className="bunker-stage__veil"></div>

            {MONITORS.map((m) => {
              const boxWpx = m.w * STAGE_W;
              const k = boxWpx / m.tw;
              const Body = Bodies[m.id];
              return (
                <div
                  key={m.id}
                  className="monitor"
                  ref={(el) => (monRefs.current[m.id] = el)}
                  style={{
                    left: `${m.cx * 100}%`,
                    top: `${m.cy * 100}%`,
                    width: `${m.w * 100}%`,
                    height: `${m.h * 100}%`,
                  }}
                >
                  <div
                    className="monitor__tilt"
                    style={{ transform: `translate(-50%, -50%) ${m.tilt} scale(${k})`,
                             width: `${m.tw}px`,
                             position: "absolute",
                             left: "50%", top: "50%",
                             transformOrigin: "center center" }}
                  >
                    <Terminal title={`kestrel@watchtower:~ — ${m.file}`}>
                      <Body />
                    </Terminal>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>

      <Footer />

      {/* sticky CTA, floats lower-left at the hero + final monitor */}
      <div className="cta" ref={ctaRef}>
        <a className="cta__btn" href="#" onClick={openSignup}>
          begin a watch <span className="arr">→</span>
        </a>
        <span className="cta__micro">from €20/mo · 7-day trial · cancel anytime</span>
      </div>

      <div className="hint" ref={hintRef}>
        <span>scroll to enter the watchtower</span>
        <span className="bar"></span>
      </div>

      <div className="led"><span className="led__dot"></span><span>rack online</span></div>

      {!introGone && <IntroSequence onDone={dismissIntro} />}
      {signupOpen && <SignupModal onClose={closeSignup} />}
    </React.Fragment>
  );
}

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