// NodeQr.jsx — field-support page for fielded ASD Edge Nodes.
//
// Route:  #/node-qr   The label on each node carries a QR encoding the clean
//                     path https://www.luli.us/node-qr (nginx 302s it here)
//                     and the node's access code printed beside it.
//
// Everything gated (diagrams, help text, support details) is fetched from the
// api AFTER the code is verified; nothing in this file is sensitive on its own.
//   1. GET  /api/node-qr/me       already verified on this device? → content
//   2. POST /api/node-qr/verify   { code } → node_qr cookie (24 h, api-scoped)
//   3. GET  /api/node-qr/content  + GET /api/node-qr/diagram/:name → render
// A regenerated or disabled code invalidates open sessions server-side; the
// page then drops back to the code step with a notice.

const { useState: useSNq, useEffect: useENq, useRef: useRNq } = React;

const NQ_API = "/api/node-qr";
const NQ_FALLBACK_SUPPORT = { email: "support@luli.us", phone: "775-223-4416", tel: "+17752234416" };

function nqFetch(path, opts) {
  return fetch(NQ_API + path, { credentials: "same-origin", ...(opts || {}) });
}

// Keep only the code alphabet, upper-case, dash after the fourth character.
function nqFormatCode(raw) {
  const clean = String(raw || "").toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8);
  return clean.length > 4 ? clean.slice(0, 4) + "-" + clean.slice(4) : clean;
}

function NodeQrPage({ onNavigate }) {
  const [phase, setPhase] = useSNq("checking"); // checking | code | content
  const [code, setCode] = useSNq("");
  const [busy, setBusy] = useSNq(false);
  const [errorMsg, setErrorMsg] = useSNq("");
  const [notice, setNotice] = useSNq("");
  const [data, setData] = useSNq(null);   // /content response
  const [svgs, setSvgs] = useSNq({});     // diagram name → svg markup
  const aliveRef = useRNq(true);

  // Private page: keep it out of search indexes and give the tab a useful
  // title while mounted. Both reverted when the visitor navigates away.
  useENq(() => {
    aliveRef.current = true;
    const prevTitle = document.title;
    document.title = "Node support · Lulius Innovation";
    const meta = document.createElement("meta");
    meta.setAttribute("name", "robots");
    meta.setAttribute("content", "noindex, nofollow");
    document.head.appendChild(meta);
    // Closed <details> print empty; open them all before the print dialog.
    const openFaq = () => document.querySelectorAll(".nq-faq details").forEach((d) => { d.open = true; });
    window.addEventListener("beforeprint", openFaq);
    return () => {
      aliveRef.current = false;
      document.title = prevTitle;
      meta.remove();
      window.removeEventListener("beforeprint", openFaq);
    };
  }, []);

  const loadContent = async () => {
    try {
      const r = await nqFetch("/content");
      if (r.status === 401) {
        if (aliveRef.current) { setData(null); setPhase("code"); }
        return false;
      }
      const j = await r.json().catch(() => ({ ok: false }));
      if (!r.ok || !j.ok) {
        if (aliveRef.current) { setErrorMsg(j.error || "Could not load the node page. Call support."); setPhase("code"); }
        return false;
      }
      const names = (j.help?.diagrams || []).map((d) => d.name);
      const entries = await Promise.all(names.map(async (name) => {
        const rr = await nqFetch(`/diagram/${encodeURIComponent(name)}`);
        return [name, rr.ok ? await rr.text() : ""];
      }));
      if (!aliveRef.current) return false;
      setSvgs(Object.fromEntries(entries));
      setData(j);
      setPhase("content");
      return true;
    } catch (err) {
      if (aliveRef.current) { setErrorMsg("Network error. Check your connection and try again."); setPhase("code"); }
      return false;
    }
  };

  // On arrival: an existing session on this device skips the code step.
  useENq(() => {
    nqFetch("/me")
      .then((r) => { if (!aliveRef.current) return; if (r.ok) return loadContent(); setPhase("code"); })
      .catch(() => { if (aliveRef.current) setPhase("code"); });
  }, []);

  const verify = async () => {
    const clean = code.replace(/[^A-Z0-9]/g, "");
    if (busy || clean.length !== 8) return;
    setBusy(true); setErrorMsg(""); setNotice("");
    try {
      const r = await nqFetch("/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ code: clean }),
      });
      const j = await r.json().catch(() => ({ ok: false, error: "Bad server response." }));
      if (r.ok && j.ok) {
        setCode("");
        await loadContent();
      } else {
        setErrorMsg(j.error || "Verification failed.");
      }
    } catch (err) {
      setErrorMsg("Network error. Check your connection and try again.");
    }
    if (aliveRef.current) setBusy(false);
  };

  const clearDevice = async () => {
    try { await nqFetch("/logout", { method: "POST" }); } catch (err) { /* cookie may already be gone */ }
    setData(null); setSvgs({}); setCode(""); setErrorMsg("");
    setNotice("Access cleared on this device. Enter the code from the label to open the page again.");
    setPhase("code");
    window.scrollTo(0, 0);
  };

  if (phase === "checking") {
    return (
      <main className="nq-page" style={{ padding: "220px 0" }}>
        <div className="wrap"><div className="eyebrow mute"><span className="dot"></span>CHECKING ACCESS</div></div>
      </main>
    );
  }

  if (phase === "code") {
    const sup = NQ_FALLBACK_SUPPORT;
    const ready = code.replace(/[^A-Z0-9]/g, "").length === 8;
    return (
      <main className="nq-page">
        <PageHeader
          idx="FIELD SUPPORT"
          title={<>Node <span className="accent-text">access.</span></>}
          lede="Enter the access code printed on the node's label, beside the QR you scanned. The page that opens is specific to that node."
        />
        <section className="section">
          <div className="wrap" style={{ maxWidth: 560 }}>
            {errorMsg && <div className="nq-err" role="alert">ERR · {errorMsg}</div>}
            {notice && !errorMsg && <div className="nq-notice">{notice}</div>}
            <form className="nq-form" onSubmit={(e) => { e.preventDefault(); verify(); }}>
              <div>
                <label className="nq-label" htmlFor="nq-code">Access code</label>
                <input
                  id="nq-code"
                  className="nq-input"
                  value={code}
                  onChange={(e) => setCode(nqFormatCode(e.target.value))}
                  placeholder="XXXX-XXXX"
                  autoCapitalize="characters"
                  autoComplete="off"
                  autoCorrect="off"
                  spellCheck={false}
                  inputMode="text"
                  maxLength={9}
                  autoFocus
                  aria-describedby="nq-code-hint"
                />
                <div id="nq-code-hint" className="nq-hint">8 characters. Case does not matter; the dash is optional.</div>
              </div>
              <button type="submit" className="btn" disabled={busy || !ready}>
                {busy ? "Checking…" : "Open node page"} <span className="arrow">→</span>
              </button>
            </form>
            <p className="mute nq-help-line">
              Label unreadable, or no code? Call Lulius support at <a href={`tel:${sup.tel}`}>{sup.phone}</a>.
            </p>
          </div>
        </section>
      </main>
    );
  }

  const node = data.node;
  const help = data.help || { diagrams: [], sections: [] };
  const sup = { ...NQ_FALLBACK_SUPPORT, ...(data.support || {}) };
  const mailSubject = `[${node.id}] ${node.site} — field support`;
  const mailBody = `Node: ${node.id}\nSite: ${node.site}\nIssue: \n\nUPS display: \nDODIN link light: \nACN link light: \nWhat changed: \n`;
  const mailto = `mailto:${sup.email}?subject=${encodeURIComponent(mailSubject)}&body=${encodeURIComponent(mailBody)}`;
  const tel = `tel:${sup.tel}`;
  let n = 0;

  return (
    <main className="nq-page">
      <section className="nq-head">
        <div className="wrap">
          <div className="eyebrow accent-text"><span className="dot"></span>ASD EDGE NODE · {node.id}</div>
          <h1 className="display nq-title">{node.site}</h1>
          <div className="nq-meta">
            {data.spec}{data.updated ? ` · UPDATED ${data.updated}` : ""} · ACCESS VALID {data.sessionHours} H ON THIS DEVICE
          </div>
          <div className="nq-actions nq-noprint">
            <a className="btn" href={tel}>Call support {sup.phone} <span className="arrow">→</span></a>
            <a className="btn ghost" href={mailto}>Email support <span className="arrow">→</span></a>
            <button type="button" className="nq-textbtn" onClick={() => window.print()}>Print this page</button>
            <button type="button" className="nq-textbtn" onClick={clearDevice}>Clear access on this device</button>
          </div>
        </div>
      </section>

      {help.diagrams.map((d) => (
        <NqSection key={d.name} n={++n} kicker="Diagram" title={d.title}>
          {d.caption && <p className="nq-caption mute">{d.caption}</p>}
          <div className={`nq-diagram nq-diagram-${d.name}`} dangerouslySetInnerHTML={{ __html: svgs[d.name] || "" }} />
          <a className="nq-textbtn nq-noprint" href={`${NQ_API}/diagram/${encodeURIComponent(d.name)}`} target="_blank" rel="noopener">
            Open full-screen ↗
          </a>
        </NqSection>
      ))}

      {help.sections.map((s) => (
        <NqSection key={s.id} n={++n} kicker={s.kicker} title={s.title}>
          <NqBody s={s} />
        </NqSection>
      ))}

      <section className="section nq-support">
        <div className="wrap">
          <div className="eyebrow"><span className="dot"></span>LULIUS SUPPORT</div>
          <div className="nq-support-grid">
            <a href={tel} className="nq-big">{sup.phone}</a>
            <a href={mailto} className="nq-big">{sup.email}</a>
          </div>
          {sup.hours && <p className="mute" style={{ maxWidth: "60ch", lineHeight: 1.6 }}>{sup.hours}</p>}
          <p className="nq-meta">Quote the node ID {node.id} in every call or email.</p>
        </div>
      </section>

      <div className="nq-bar nq-noprint">
        <a className="btn" href={tel}>Call</a>
        <a className="btn ghost" href={mailto}>Email</a>
      </div>
    </main>
  );
}

function NqSection({ n, kicker, title, children }) {
  return (
    <section className="section tight nq-sec">
      <div className="wrap">
        <div className="nq-grid">
          <div className="nq-idx"><span className="nq-num">{String(n).padStart(2, "0")} /</span> {String(kicker || "").toUpperCase()}</div>
          <div>
            {title && <h2 className="h2 nq-h2">{title}</h2>}
            {children}
          </div>
        </div>
      </div>
    </section>
  );
}

// Renders one help.json section by its declared type, so copy edits never
// need a JSX change.
function NqBody({ s }) {
  const items = s.items || [];
  switch (s.type) {
    case "steps":
      return <ol className="nq-steps">{items.map((t, i) => <li key={i}>{t}</li>)}</ol>;
    case "connections":
      return (
        <div className="nq-conn">
          <div className="nq-conn-head"><span>From</span><span>To</span><span>Cable</span></div>
          {items.map((c, i) => (
            <div className="nq-conn-row" key={i}>
              <span><i className={`nq-kind nq-kind-${c.kind || "data"}`}></i>{c.from}</span>
              <span>{c.to}</span>
              <span className="mute">{c.cable}</span>
            </div>
          ))}
          <div className="nq-legend mute">
            <i className="nq-kind nq-kind-data"></i>data
            <i className="nq-kind nq-kind-power"></i>power
            <i className="nq-kind nq-kind-usb"></i>usb
          </div>
        </div>
      );
    case "faq":
      return (
        <div className="nq-faq">
          {items.map((f, i) => (
            <details key={i}>
              <summary>{f.q}</summary>
              <p>{f.a}</p>
            </details>
          ))}
        </div>
      );
    case "checklist":
      return (
        <div className="nq-check">
          {s.note && <p className="mute nq-note">{s.note}</p>}
          {items.map((it, i) => (
            <div className="nq-check-row" key={i}>
              <span className="nq-box" aria-hidden="true"></span>
              <span className="nq-ref">{it.ref}</span>
              <span>{it.item}{it.where ? <span className="mute"> · {it.where}</span> : null}</span>
              <span className="nq-qty">{it.qty}</span>
            </div>
          ))}
        </div>
      );
    case "text":
      return <div>{items.map((t, i) => <p key={i} className="nq-p">{t}</p>)}</div>;
    default:
      return <ul className="nq-list">{items.map((t, i) => <li key={i}>{t}</li>)}</ul>;
  }
}

window.NodeQrPage = NodeQrPage;
