// =========================================================
// auth.jsx — Build Lab auth state + magic-link login modal
// Loaded via <script type="text/babel"> before landing.jsx.
// Exposes:
//   window.BL_AUTH      — token/email store + openLogin/closeLogin/logout
//   window.useBLAuth()  — React hook (subscribes to BL_AUTH changes)
//   window.BLLoginModal — the modal component (self-mounts to body)
// =========================================================

const _bl_listeners = new Set();
function _bl_notify() { _bl_listeners.forEach((fn) => fn()); }

window.BL_AUTH = {
  _open: false,
  _token: localStorage.getItem('bl_token'),
  _email: localStorage.getItem('bl_email'),
  _user: null,  // /api/me response.user, populated by _fetchUser

  getToken() { return this._token; },
  getEmail() { return this._email; },
  getUser() { return this._user; },
  isLoggedIn() { return !!this._token; },
  hasBuildLabAccess() { return !!this._token; },

  async _fetchUser() {
    if (!this._token) { this._user = null; _bl_notify(); return; }
    try {
      const res = await fetch('/api/build/me', { headers: { Authorization: 'Bearer ' + this._token } });
      if (res.ok) {
        const data = await res.json();
        this._user = (data && data.user) || null;
        _bl_notify();
      } else if (res.status === 401) {
        // Stale token — clean up
        this.logout();
      }
    } catch (e) { /* network error — keep prior state */ }
  },

  openLogin() { this._open = true; _bl_notify(); },
  closeLogin() { this._open = false; _bl_notify(); },

  logout() {
    localStorage.removeItem('bl_token');
    localStorage.removeItem('bl_email');
    this._token = null;
    this._email = null;
    this._user = null;
    _bl_notify();
  },

  _refresh() {
    this._token = localStorage.getItem('bl_token');
    this._email = localStorage.getItem('bl_email');
    _bl_notify();
    this._fetchUser();
  },
};

window.useBLAuth = function () {
  const [, setTick] = React.useState(0);
  React.useEffect(() => {
    const fn = () => setTick((t) => t + 1);
    _bl_listeners.add(fn);
    return () => _bl_listeners.delete(fn);
  }, []);
  return {
    isLoggedIn: window.BL_AUTH.isLoggedIn(),
    token: window.BL_AUTH.getToken(),
    email: window.BL_AUTH.getEmail(),
    user: window.BL_AUTH.getUser(),
    hasBuildLabAccess: window.BL_AUTH.hasBuildLabAccess(),
    isOpen: window.BL_AUTH._open,
    openLogin: () => window.BL_AUTH.openLogin(),
    closeLogin: () => window.BL_AUTH.closeLogin(),
    logout: () => window.BL_AUTH.logout(),
  };
};

// Refresh auth state when storage changes in another tab (verify flow)
window.addEventListener('storage', (e) => {
  if (e.key === 'bl_token' || e.key === 'bl_email') {
    window.BL_AUTH._refresh();
  }
});

// Refresh when window regains focus (user came back from verify tab)
window.addEventListener('focus', () => {
  const t = localStorage.getItem('bl_token');
  if (t !== window.BL_AUTH._token) {
    window.BL_AUTH._refresh();
  }
});

function BLLoginModal() {
  const auth = window.useBLAuth();
  const [email, setEmail] = React.useState('');
  const [state, setState] = React.useState('idle'); // idle | loading | sent | error
  const [errorMsg, setErrorMsg] = React.useState('');
  const [pendingSid, setPendingSid] = React.useState(null);

  // Reset form whenever the modal closes
  React.useEffect(() => {
    if (!auth.isOpen) {
      setEmail('');
      setState('idle');
      setErrorMsg('');
      setPendingSid(null);
    }
  }, [auth.isOpen]);


  // Cross-device polling: when magic link is sent, poll for verification
  React.useEffect(() => {
    if (state !== 'sent' || !pendingSid) return;
    let stopped = false;
    let tries = 0;
    const MAX_TRIES = 600; // ~20 min at 2s/poll
    const tick = async () => {
      if (stopped) return;
      tries++;
      try {
        const r = await fetch('/api/pending-session/' + pendingSid);
        const d = await r.json().catch(() => ({}));
        if (d.status === 'verified' && d.token) {
          localStorage.setItem('bl_token', d.token);
          // The GET response only ships { status, token, return_url } -- decode
          // the email out of the fresh JWT so the auth state has it.
          try {
            const body = d.token.split('.')[1];
            const padded = body.padEnd(body.length + (4 - body.length % 4) % 4, '=');
            const claims = JSON.parse(atob(padded.replace(/-/g, '+').replace(/_/g, '/')));
            if (claims && claims.email) localStorage.setItem('bl_email', claims.email);
          } catch (_) {}
          stopped = true;
          window.BL_AUTH._refresh();
          // _refresh updates auth state but doesn't touch the modal's _open
          // flag, so close it explicitly. Without this, the modal stays up
          // forever even though the user is signed in.
          try { window.BL_AUTH.closeLogin(); } catch (_) {}
          // Only navigate if return_url is actually somewhere else --
          // setting href to the current URL is a no-op and the user just
          // sees the (now-closed) modal disappear, which is what we want.
          const here = window.location.pathname + window.location.search + window.location.hash;
          if (d.return_url && d.return_url !== here) {
            window.location.href = d.return_url;
          }
          return;
        }
        if (d.status === 'expired' || tries >= MAX_TRIES) { stopped = true; return; }
      } catch (_) { /* network blip, keep polling */ }
      if (!stopped) setTimeout(tick, 2000);
    };
    const t = setTimeout(tick, 2000);
    return () => { stopped = true; clearTimeout(t); };
  }, [state, pendingSid]);

  if (!auth.isOpen) return null;

  async function submit(e) {
    if (e) e.preventDefault();
    const trimmed = email.trim();
    if (!trimmed || !trimmed.includes('@')) {
      setErrorMsg('Please enter a valid email');
      setState('error');
      return;
    }
    setState('loading');
    setErrorMsg('');
    // Create pending session for cross-device handoff
    try {
      const here = window.location.pathname + window.location.search + window.location.hash;
      try { localStorage.setItem('bl_return_url', here); } catch (_) {}
      const psRes = await fetch('/api/pending-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: trimmed, kind: 'build-magic', return_url: here }),
      });
      const psData = await psRes.json().catch(() => ({}));
      if (psData && psData.sid) setPendingSid(psData.sid);
    } catch (_) { /* polling is a bonus; flow works without it */ }

    try {
      const res = await fetch('/api/build/auth/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: trimmed }),
      });
      if (!res.ok) {
        const data = await res.json().catch(() => ({}));
        throw new Error(data && data.error ? data.error : 'Could not send magic link');
      }
      setState('sent');
    } catch (err) {
      setErrorMsg(err.message || 'Something went wrong');
      setState('error');
    }
  }

  return (
    <div
      onClick={auth.closeLogin}
      style={{
        position: 'fixed', inset: 0, zIndex: 9999,
        background: 'rgba(26,22,18,.55)',
        backdropFilter: 'blur(3px)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: 24,
        fontFamily: 'var(--sans)',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="bl-card"
        style={{ width: '100%', maxWidth: 440, padding: '32px', position: 'relative' }}
      >
        <button
          aria-label="Close"
          onClick={auth.closeLogin}
          style={{
            position: 'absolute', top: 14, right: 14,
            width: 30, height: 30, borderRadius: 999,
            background: 'transparent', border: '1px solid var(--line-paper)',
            color: 'var(--ink-mute)', fontSize: 16, lineHeight: 1,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            cursor: 'pointer',
          }}
        >×</button>

        <span className="bl-cornermark tl"></span>
        <span className="bl-cornermark tr"></span>
        <span className="bl-cornermark bl"></span>
        <span className="bl-cornermark br"></span>

        {state === 'sent' ? (
          <div>
            <div className="bl-eyebrow"><span className="dot"></span>Check your email</div>
            <h3 className="bl-display" style={{ fontSize: 28, marginTop: 12, lineHeight: 1.05, color: 'var(--ink)' }}>
              Link sent to <span className="bl-italic" style={{ color: 'var(--accent)' }}>{email}</span>
            </h3>
            <p style={{ marginTop: 14, fontSize: 14.5, color: 'var(--ink-dim)', lineHeight: 1.55 }}>
              Click the link in the email to sign in. Then come back here — we'll pick up where you left off.
            </p>
            <button
              className="bl-btn bl-btn-ghost"
              onClick={auth.closeLogin}
              style={{ marginTop: 22, width: '100%', justifyContent: 'center', padding: '12px' }}
            >
              Close
            </button>
            <div className="bl-mono" style={{ marginTop: 14, fontSize: 10, color: 'var(--ink-mute)', textAlign: 'center', letterSpacing: '.04em' }}>
              {"didn\u2019t get it? check spam, or "}
              <button
                onClick={() => setState('idle')}
                style={{ color: 'var(--accent-deep)', padding: 0, background: 'transparent', border: 0, font: 'inherit', textDecoration: 'underline', cursor: 'pointer' }}
              >
                try again
              </button>
            </div>
          </div>
        ) : (
          <div>
            <div className="bl-eyebrow"><span className="dot"></span>Sign in</div>
            <h3 className="bl-display" style={{ fontSize: 28, marginTop: 12, lineHeight: 1.05, color: 'var(--ink)' }}>
              Sign in to <span className="bl-italic" style={{ color: 'var(--accent)' }}>the Build Lab</span>
            </h3>
            <p style={{ marginTop: 12, fontSize: 14.5, color: 'var(--ink-dim)', lineHeight: 1.55 }}>
              We'll email you a one-time link. No password to remember.
            </p>
            <form onSubmit={submit} style={{ marginTop: 22 }}>
              <input
                type="email"
                autoFocus
                value={email}
                onChange={(e) => { setEmail(e.target.value); if (state === 'error') setState('idle'); }}
                placeholder="you@example.com"
                disabled={state === 'loading'}
                style={{
                  width: '100%',
                  padding: '14px 16px',
                  fontSize: 15,
                  fontFamily: 'var(--sans)',
                  background: 'var(--paper)',
                  border: '1px solid ' + (state === 'error' ? 'var(--accent-deep)' : 'var(--line-paper-2)'),
                  borderRadius: 'var(--r-sm)',
                  color: 'var(--ink)',
                  outline: 'none',
                  boxSizing: 'border-box',
                }}
              />
              {state === 'error' && errorMsg ? (
                <div style={{ marginTop: 8, fontSize: 13, color: 'var(--accent-deep)' }}>
                  {errorMsg}
                </div>
              ) : null}
              <button
                type="submit"
                disabled={state === 'loading'}
                className="bl-btn bl-btn-accent"
                style={{
                  marginTop: 14,
                  width: '100%',
                  justifyContent: 'center',
                  padding: '14px',
                  fontSize: 14.5,
                  opacity: state === 'loading' ? 0.7 : 1,
                  cursor: state === 'loading' ? 'wait' : 'pointer',
                }}
              >
                {state === 'loading' ? 'Sending\u2026' : 'Send magic link \u2192'}
              </button>
            </form>
            <div className="bl-mono" style={{ marginTop: 18, fontSize: 10, color: 'var(--ink-mute)', textAlign: 'center', letterSpacing: '.04em' }}>
              passwordless \u00b7 we\u2019ll email you a one-time link
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

window.BLLoginModal = BLLoginModal;

// Fetch user data on initial load if we already have a token
if (window.BL_AUTH._token) { window.BL_AUTH._fetchUser(); }

// Self-mount the modal into its own root on document.body
(function mountBLLoginModal() {
  if (document.getElementById('bl-auth-root')) return;
  const host = document.createElement('div');
  host.id = 'bl-auth-root';
  document.body.appendChild(host);
  ReactDOM.createRoot(host).render(<BLLoginModal />);
})();

// Handle #auth/verify?token=... from magic-link emails
//
// SECURITY: We DO NOT auto-consume the token on page load. Many corporate
// mail filters (Microsoft Defender Safe Links, Mimecast, Proofpoint,
// Barracuda, Cisco ESA) pre-fetch every URL in inbound email. An auto-POST
// here would let those filters silently complete the login, granting access
// to anyone who can read the mailbox or sit in the mail path.
//
// Instead: render an explicit "Confirm sign-in" overlay. The POST only fires
// when a human clicks the button. Security appliances do GETs (and some JS),
// but they don't click random buttons on confirmation pages.
(function handleBLVerifyClickToConfirm() {
  const h = window.location.hash || '';
  if (!h.startsWith('#auth/verify')) return;
  const params = new URLSearchParams(h.replace('#auth/verify?', ''));
  const token = params.get('token');
  if (!token) return;

  // Clear hash so a refresh does not re-trigger (the token is held in closure)
  history.replaceState(null, '', window.location.pathname + window.location.search);

  // Decode the email out of the JWT body (for the "Sign in as ..." label).
  // This is purely display — the backend still validates the token cryptographically.
  let emailHint = '';
  try {
    const body = token.split('.')[1];
    const padded = body.padEnd(body.length + (4 - body.length % 4) % 4, '=');
    const json = JSON.parse(atob(padded.replace(/-/g, '+').replace(/_/g, '/')));
    emailHint = json && json.email ? json.email : '';
  } catch (_) {}

  function VerifyConfirmOverlay({ token, emailHint }) {
    const [state, setState] = React.useState('idle'); // idle | sending | error | done
    const [err, setErr] = React.useState('');

    async function confirm() {
      setState('sending'); setErr('');
      try {
        const res = await fetch('/api/build/auth/verify', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ token }),
        });
        if (!res.ok) {
          const d = await res.json().catch(() => ({}));
          throw new Error(d && d.error ? d.error : 'Verify failed');
        }
        const data = await res.json();
        if (data && data.token) {
          localStorage.setItem('bl_token', data.token);
          if (data.user && data.user.email) localStorage.setItem('bl_email', data.user.email);
          window.BL_AUTH && window.BL_AUTH._refresh && window.BL_AUTH._refresh();
          setState('done');
          // Tiny pause so the user sees the success state, then close overlay.
          setTimeout(() => {
            // Restore the URL the user was on before clicking the magic link.
            // Stored in localStorage at send time so we don't depend on the
            // backend echoing it through the verify response.
            try {
              const ret = localStorage.getItem('bl_return_url');
              localStorage.removeItem('bl_return_url');
              if (ret) {
                const hashIdx = ret.indexOf('#');
                if (hashIdx >= 0) {
                  const hash = ret.slice(hashIdx);
                  if (hash && hash !== window.location.hash) {
                    window.location.hash = hash;
                  }
                }
              }
            } catch (_) {}
            const host = document.getElementById('bl-verify-overlay');
            if (host && host.parentNode) host.parentNode.removeChild(host);
          }, 700);
        } else {
          throw new Error('No session returned');
        }
      } catch (e) {
        setErr(e.message || 'Could not sign in');
        setState('error');
      }
    }

    const T = {
      paper:"#f5f1ea", ink:"#1a1612", inkDim:"#5b5247", inkMute:"#8a8073",
      accent:"#d96846", accentDeep:"#b8451f",
      line:"#d9d1c5", paper2:"#ebe5da",
      mono:"'Menlo','Courier New',monospace",
      sans:"system-ui,-apple-system,sans-serif",
    };

    return (
      <div style={{
        position:'fixed', inset:0, zIndex:9999,
        background:'rgba(26,22,18,.55)',
        backdropFilter:'blur(3px)',
        display:'flex', alignItems:'center', justifyContent:'center',
        padding:24, fontFamily:T.sans,
      }}>
        <div style={{
          width:'100%', maxWidth:440, padding:'32px 28px 24px',
          background:T.paper, border:`1px solid ${T.line}`, borderRadius:12,
          boxShadow:'0 30px 80px -30px rgba(26,22,18,.4)',
        }}>
          <div style={{ fontFamily:T.mono, fontSize:11, color:T.inkMute, letterSpacing:'.1em', marginBottom:6 }}>
            THE BUILD LAB · SIGN-IN CONFIRMATION
          </div>
          <h2 style={{ margin:'4px 0 14px', fontSize:22, color:T.ink, fontWeight:700, lineHeight:1.25 }}>
            Confirm sign-in
          </h2>
          {emailHint && (
            <div style={{ marginBottom:18, padding:'10px 12px', background:T.paper2, borderRadius:6, fontFamily:T.mono, fontSize:13, color:T.ink }}>
              {emailHint}
            </div>
          )}
          <p style={{ margin:'0 0 22px', fontSize:13.5, color:T.inkDim, lineHeight:1.55 }}>
            Click the button below to finish signing in. We require an explicit
            click so email security tools that auto-scan links can't sign you in
            without your knowledge.
          </p>
          <button onClick={confirm} disabled={state === 'sending' || state === 'done'}
            style={{
              width:'100%', padding:'12px 18px',
              background: state === 'done' ? '#6b9a5f' : T.accent,
              color:'#fff', border:'none', borderRadius:8,
              fontFamily:T.mono, fontSize:14, fontWeight:600, letterSpacing:'.02em',
              cursor: (state === 'sending' || state === 'done') ? 'default' : 'pointer',
              opacity: state === 'sending' ? 0.7 : 1,
            }}>
            {state === 'sending' ? 'signing you in…' : state === 'done' ? '✓ signed in' : 'Confirm sign-in →'}
          </button>
          {state === 'error' && err && (
            <div style={{ marginTop:14, padding:'10px 12px', background:'#fae5d8', borderRadius:6, fontSize:13, color:T.accentDeep }}>
              {err}
            </div>
          )}
          <button onClick={() => {
            const host = document.getElementById('bl-verify-overlay');
            if (host && host.parentNode) host.parentNode.removeChild(host);
          }} style={{
            display:'block', margin:'14px auto 0', background:'transparent',
            border:'none', color:T.inkMute, fontSize:12, fontFamily:T.mono,
            cursor:'pointer', textDecoration:'underline',
          }}>
            cancel
          </button>
        </div>
      </div>
    );
  }

  // Mount the overlay AFTER React/ReactDOM are available and BL_AUTH is set up.
  function mount() {
    if (!window.React || !window.ReactDOM) { setTimeout(mount, 60); return; }
    let host = document.getElementById('bl-verify-overlay');
    if (!host) {
      host = document.createElement('div');
      host.id = 'bl-verify-overlay';
      document.body.appendChild(host);
    }
    ReactDOM.createRoot(host).render(<VerifyConfirmOverlay token={token} emailHint={emailHint} />);
  }
  mount();
})();
