/* global React, window */
/**
 * One-time code gate for pos-fresh application links.
 *
 * A device that does not hold a session for this loan sees nothing but this
 * screen. On mount it asks the server to send one six-digit code to the
 * email and phone already on the application, shows only their masked
 * forms, and unlocks after the code is entered. A correct code sets the
 * device cookie server-side; the page then reloads and boots exactly like
 * the device that started the application. Sibling of InviteGate.jsx.
 */
(function () {
  const { useState, useEffect, useRef, useCallback } = React;

  const CODE_LENGTH = 6;

  function GateShell({ children, kicker, subtitle }) {
    return (
      <div className="app app-welcome" style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px 16px' }}>
        <div style={{ width: '100%', maxWidth: 440, background: 'var(--panel, #fff)', border: '1px solid var(--line, #e5e2da)', borderRadius: 16, padding: '28px 26px', boxShadow: '0 12px 40px rgba(0,0,0,0.08)' }}>
          <div style={{ fontSize: 13, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--sage, #7d8c6f)', fontWeight: 700, marginBottom: 6 }}>
            {kicker || 'Your loan application'}
          </div>
          {subtitle ? <div style={{ fontSize: 15, color: 'var(--text, #222)', lineHeight: 1.5, marginBottom: 18 }}>{subtitle}</div> : null}
          {children}
        </div>
      </div>
    );
  }

  const primaryButton = {
    width: '100%', padding: '13px 14px', fontSize: 15, fontWeight: 700, cursor: 'pointer',
    border: 'none', borderRadius: 10, background: 'var(--sage, #7d8c6f)', color: '#fff',
  };
  const secondaryButton = {
    width: '100%', padding: '11px 14px', fontSize: 14, fontWeight: 600, cursor: 'pointer',
    border: '1px solid var(--line, #e5e2da)', borderRadius: 10, background: 'transparent', color: 'var(--text, #222)',
  };

  function Destinations({ masked }) {
    const parts = [masked && masked.email, masked && masked.phone].filter(Boolean);
    if (!parts.length) return null;
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, margin: '4px 0 18px' }}>
        {parts.map(function (p) {
          return (
            <div key={p} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10, background: 'var(--panel-2, #f6f5f1)', border: '1px solid var(--line, #e5e2da)', fontSize: 14, color: 'var(--text, #222)' }}>
              <span aria-hidden="true" style={{ width: 8, height: 8, borderRadius: 4, background: 'var(--sage, #7d8c6f)', flex: '0 0 auto' }} />
              <span style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', letterSpacing: '0.02em' }}>{p}</span>
            </div>
          );
        })}
      </div>
    );
  }

  function CodeInput({ value, onChange, disabled, invalid, onComplete }) {
    const refs = useRef([]);
    const digits = Array.from({ length: CODE_LENGTH }, function (_, i) { return value[i] || ''; });

    function commit(next) {
      const cleaned = next.replace(/\D/g, '').slice(0, CODE_LENGTH);
      onChange(cleaned);
      if (cleaned.length === CODE_LENGTH && onComplete) onComplete(cleaned);
    }
    function focusAt(i) {
      const el = refs.current[Math.max(0, Math.min(CODE_LENGTH - 1, i))];
      if (el) { el.focus(); try { el.select(); } catch {} }
    }
    function handleInput(i, e) {
      const raw = (e.target.value || '').replace(/\D/g, '');
      if (!raw) { commit(value.slice(0, i) + value.slice(i + 1)); return; }
      if (raw.length > 1) {
        const merged = (value.slice(0, i) + raw).slice(0, CODE_LENGTH);
        commit(merged);
        focusAt(merged.length);
        return;
      }
      const merged = (value.slice(0, i) + raw + value.slice(i + 1)).slice(0, CODE_LENGTH);
      commit(merged);
      if (i < CODE_LENGTH - 1) focusAt(i + 1);
    }
    function handleKey(i, e) {
      if (e.key === 'Backspace' && !digits[i] && i > 0) { e.preventDefault(); commit(value.slice(0, i - 1)); focusAt(i - 1); }
      else if (e.key === 'ArrowLeft' && i > 0) { e.preventDefault(); focusAt(i - 1); }
      else if (e.key === 'ArrowRight' && i < CODE_LENGTH - 1) { e.preventDefault(); focusAt(i + 1); }
    }
    function handlePaste(e) {
      const text = (e.clipboardData && e.clipboardData.getData('text')) || '';
      const cleaned = text.replace(/\D/g, '').slice(0, CODE_LENGTH);
      if (!cleaned) return;
      e.preventDefault();
      commit(cleaned);
      focusAt(cleaned.length);
    }

    return (
      <div style={{ display: 'flex', gap: 8, justifyContent: 'space-between', margin: '6px 0 14px' }} onPaste={handlePaste}>
        {digits.map(function (d, i) {
          return (
            <input
              key={i}
              ref={function (el) { refs.current[i] = el; }}
              type="text"
              inputMode="numeric"
              pattern="[0-9]*"
              autoComplete={i === 0 ? 'one-time-code' : 'off'}
              aria-label={'Digit ' + (i + 1) + ' of ' + CODE_LENGTH}
              maxLength={i === 0 ? CODE_LENGTH : 1}
              value={d}
              disabled={disabled}
              onChange={function (e) { handleInput(i, e); }}
              onKeyDown={function (e) { handleKey(i, e); }}
              onFocus={function (e) { try { e.target.select(); } catch {} }}
              style={{
                width: '100%', minWidth: 0, height: 56, textAlign: 'center', fontSize: 24, fontWeight: 700,
                fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', borderRadius: 12,
                border: '2px solid ' + (invalid ? 'var(--danger, #b3452f)' : d ? 'var(--sage, #7d8c6f)' : 'var(--line, #e5e2da)'),
                background: 'var(--panel, #fff)', color: 'var(--text, #222)', outline: 'none',
                transition: 'border-color .15s ease',
              }}
            />
          );
        })}
      </div>
    );
  }

  function LoHelpCard() {
    const [lo, setLo] = useState(function () { return window.__bevriLoBranding || null; });
    const [photoBroken, setPhotoBroken] = useState(false);
    useEffect(function () {
      let alive = true;
      if (window.__bevriLoBranding !== undefined) { setLo(window.__bevriLoBranding); return undefined; }
      const fetcher = window.fetchPosLoBranding;
      if (typeof fetcher !== 'function') return undefined;
      fetcher().then(function (card) { if (alive) setLo(card || null); }).catch(function () {});
      return function () { alive = false; };
    }, []);
    if (!lo || !lo.displayName) {
      return (
        <p style={{ margin: '18px 0 0', fontSize: 13, color: 'var(--muted, #77746c)', lineHeight: 1.5 }}>
          Didn't get it? Check your spam folder, or ask your loan officer to resend your application link.
        </p>
      );
    }
    return (
      <div style={{ marginTop: 18, paddingTop: 16, borderTop: '1px solid var(--line, #e5e2da)' }}>
        <div style={{ fontSize: 12, color: 'var(--muted, #77746c)', marginBottom: 8 }}>Didn't get it? Check your spam folder, or reach out to your loan officer.</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          {lo.photoUrl && !photoBroken ? (
            <img src={lo.photoUrl} alt="" onError={function () { setPhotoBroken(true); }} style={{ width: 40, height: 40, borderRadius: 20, objectFit: 'cover', flex: '0 0 auto' }} />
          ) : (
            <div aria-hidden="true" style={{ width: 40, height: 40, borderRadius: 20, background: 'var(--sage, #7d8c6f)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, flex: '0 0 auto' }}>
              {String(lo.displayName).trim().split(/\s+/).map(function (w) { return w[0]; }).join('').slice(0, 2).toUpperCase()}
            </div>
          )}
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text, #222)' }}>{lo.displayName}{lo.nmls ? <span style={{ fontWeight: 400, color: 'var(--muted, #77746c)' }}> · NMLS {lo.nmls}</span> : null}</div>
            <div style={{ fontSize: 13, color: 'var(--muted, #77746c)', display: 'flex', gap: 10, flexWrap: 'wrap' }}>
              {lo.phone ? <a href={'tel:' + String(lo.phone).replace(/[^\d+]/g, '')} style={{ color: 'var(--sage, #7d8c6f)', textDecoration: 'none' }}>{lo.phone}</a> : null}
              {lo.email ? <a href={'mailto:' + lo.email} style={{ color: 'var(--sage, #7d8c6f)', textDecoration: 'none', overflowWrap: 'anywhere' }}>{lo.email}</a> : null}
            </div>
          </div>
        </div>
      </div>
    );
  }

  function useCountdown(untilMs) {
    const [left, setLeft] = useState(function () { return Math.max(0, untilMs - Date.now()); });
    useEffect(function () {
      setLeft(Math.max(0, untilMs - Date.now()));
      if (untilMs <= Date.now()) return undefined;
      const t = setInterval(function () { setLeft(Math.max(0, untilMs - Date.now())); }, 500);
      return function () { clearInterval(t); };
    }, [untilMs]);
    return left;
  }

  function formatClock(ms) {
    const total = Math.ceil(ms / 1000);
    const m = Math.floor(total / 60);
    const s = total % 60;
    return m + ':' + (s < 10 ? '0' : '') + s;
  }

  function ResumeGate({ loanId, onUnlocked }) {
    const [mode, setMode] = useState('sending'); // sending | enter | verifying | unlocked | closed | nocontact | failed
    const [masked, setMasked] = useState(null);
    const [loanNumber, setLoanNumber] = useState('');
    const [channels, setChannels] = useState([]);
    const [code, setCode] = useState('');
    const [error, setError] = useState('');
    const [note, setNote] = useState('');
    const [rejected, setRejected] = useState(false);
    const [attemptsLeft, setAttemptsLeft] = useState(null);
    const [locked, setLocked] = useState(false);
    const [resendAt, setResendAt] = useState(0);
    const [expiresAt, setExpiresAt] = useState(0);
    const [devCode, setDevCode] = useState('');
    const sessionId = (window.BevriPosStorage && window.BevriPosStorage.sessionId) || '';

    const resendIn = useCountdown(resendAt);
    const expiresIn = useCountdown(expiresAt);

    const onUnlockedRef = useRef(onUnlocked);
    onUnlockedRef.current = onUnlocked;

    const requestCode = useCallback(async function (reason) {
      setMode(function (m) { return m === 'enter' ? 'enter' : 'sending'; });
      setError('');
      setNote('');
      setRejected(false);
      setLocked(false);
      setAttemptsLeft(null);
      setCode('');
      try {
        const res = await fetch('/api/pos/intake/resume/challenge', {
          method: 'POST',
          credentials: 'same-origin',
          headers: { 'Content-Type': 'application/json', 'x-pos-session-id': sessionId },
          body: JSON.stringify({ loanId: loanId, reason: reason || 'open' }),
        });
        const body = await res.json().catch(function () { return {}; });
        if (!res.ok || !body.success) { setMode('failed'); setError(body.error || 'We could not send your code just now.'); return; }
        if (body.alreadyAuthorized) { setMode('unlocked'); onUnlockedRef.current(); return; }
        if (body.loanNumber) setLoanNumber(body.loanNumber);
        if (body.masked) setMasked(body.masked);
        if (body.closed) { setMode('closed'); return; }
        if (body.reason === 'no_destination') { setMode('nocontact'); return; }
        if (body.devCode) setDevCode(body.devCode);
        const cooldown = typeof body.resendAfterMs === 'number' ? body.resendAfterMs : 45000;
        setResendAt(Date.now() + cooldown);
        if (body.sent) {
          setChannels(Array.isArray(body.channels) ? body.channels : []);
          setExpiresAt(body.expiresAt ? Date.parse(body.expiresAt) : Date.now() + 10 * 60 * 1000);
          setMode('enter');
          return;
        }
        // Not sent: cooldown or rate limit. A code from the previous request
        // may still be valid, so keep the entry screen usable.
        setMode('enter');
        if (body.reason === 'cooldown' || body.reason === 'rate_limited') {
          setNote('A code was sent a moment ago. Check your email and texts, or request another in ' + formatClock(cooldown) + '.');
        } else {
          setError('We could not send the code just now. Please try again in a moment.');
        }
      } catch {
        setMode('failed');
        setError('We could not reach our servers. Please check your connection and try again.');
      }
    }, [loanId, sessionId]);

    // Exactly one request on mount, even under StrictMode's double effect.
    const requestedRef = useRef(false);
    useEffect(function () {
      if (requestedRef.current) return;
      requestedRef.current = true;
      requestCode('open');
    }, [requestCode]);

    const verify = useCallback(async function (value) {
      const cleaned = (value || code).replace(/\D/g, '');
      if (cleaned.length !== CODE_LENGTH || mode === 'verifying') return;
      setMode('verifying');
      setError('');
      try {
        const res = await fetch('/api/pos/intake/resume/verify', {
          method: 'POST',
          credentials: 'same-origin',
          headers: { 'Content-Type': 'application/json', 'x-pos-session-id': sessionId },
          body: JSON.stringify({ loanId: loanId, code: cleaned }),
        });
        const body = await res.json().catch(function () { return {}; });
        if (res.ok && body.success) { setMode('unlocked'); onUnlockedRef.current(); return; }
        setMode('enter');
        setCode('');
        setRejected(true);
        if (body.reason === 'locked' || body.reason === 'expired' || body.reason === 'no_challenge') {
          setLocked(true);
          setError(body.error || 'Please request a new code to continue.');
          return;
        }
        if (typeof body.attemptsLeft === 'number') setAttemptsLeft(body.attemptsLeft);
        setError(body.error || 'That code didn\'t match. Please try again.');
      } catch {
        setMode('enter');
        setError('We could not check that code just now. Please try again.');
      }
    }, [code, loanId, mode, sessionId]);

    const loanLine = loanNumber ? ' (loan ' + loanNumber + ')' : '';

    if (mode === 'sending' || mode === 'unlocked') {
      return (
        <GateShell kicker="Welcome back" subtitle={mode === 'unlocked' ? 'Opening your application...' : 'For your security, we\'re sending a one-time code to the contact details on your application.'}>
          <div style={{ height: 6, borderRadius: 3, background: 'var(--line, #eee)', overflow: 'hidden' }}>
            <div style={{ width: '40%', height: '100%', background: 'var(--sage, #7d8c6f)', animation: 'pos-gate-slide 1.1s ease-in-out infinite alternate' }} />
          </div>
          <style>{'@keyframes pos-gate-slide { from { margin-left: 0; } to { margin-left: 60%; } }'}</style>
        </GateShell>
      );
    }

    if (mode === 'closed') {
      return (
        <GateShell kicker="Your loan application" subtitle={'This application' + loanLine + ' is no longer open for editing online.'}>
          <p style={{ fontSize: 14, color: 'var(--muted, #77746c)', margin: 0, lineHeight: 1.55 }}>
            Your loan officer can answer any questions about where things stand and send you a fresh link if you need to start again.
          </p>
          <LoHelpCard />
        </GateShell>
      );
    }

    if (mode === 'nocontact') {
      return (
        <GateShell kicker="Your loan application" subtitle="We can't send a code for this application yet.">
          <p style={{ fontSize: 14, color: 'var(--muted, #77746c)', margin: 0, lineHeight: 1.55 }}>
            This application doesn't have an email address or mobile number on it yet, so there's nowhere to send a code. Continue on the device you started with, or ask your loan officer for help.
          </p>
          <LoHelpCard />
        </GateShell>
      );
    }

    if (mode === 'failed') {
      return (
        <GateShell kicker="Welcome back" subtitle="We hit a snag sending your code.">
          <p style={{ fontSize: 14, color: 'var(--danger, #b3452f)', margin: '0 0 14px', lineHeight: 1.5 }}>{error}</p>
          <button style={primaryButton} type="button" onClick={function () { requestCode('retry'); }}>Try again</button>
          <LoHelpCard />
        </GateShell>
      );
    }

    const canResend = resendIn === 0;
    const expired = expiresAt > 0 && expiresIn === 0;
    const busy = mode === 'verifying';
    const sentLine = channels.length === 2
      ? 'We sent the same code to your email and by text.'
      : channels[0] === 'sms'
        ? 'We sent a code by text.'
        : 'We sent a code to your email.';

    return (
      <GateShell kicker="Welcome back" subtitle={'Enter the 6-digit code to open your application' + loanLine + '.'}>
        <div style={{ fontSize: 13, color: 'var(--muted, #77746c)', marginBottom: 8 }}>{sentLine}</div>
        <Destinations masked={masked} />
        <form onSubmit={function (e) { e.preventDefault(); verify(); }}>
          <CodeInput value={code} onChange={setCode} disabled={busy || locked} invalid={rejected && !locked} onComplete={function (v) { if (!locked) verify(v); }} />
          {note && !error ? (
            <div style={{ color: 'var(--muted, #77746c)', fontSize: 13, marginBottom: 12, lineHeight: 1.45 }}>{note}</div>
          ) : null}
          {error ? (
            <div role="alert" style={{ color: 'var(--danger, #b3452f)', fontSize: 13, marginBottom: 12, lineHeight: 1.45 }}>
              {error}{attemptsLeft !== null && attemptsLeft > 0 && !locked ? ' You have ' + attemptsLeft + (attemptsLeft === 1 ? ' try' : ' tries') + ' left.' : ''}
            </div>
          ) : null}
          {devCode ? (
            <div style={{ fontSize: 12, color: 'var(--muted, #77746c)', marginBottom: 12, padding: '8px 10px', border: '1px dashed var(--line, #e5e2da)', borderRadius: 8 }}>
              Local dev: texting is not configured, your code is <strong style={{ fontFamily: 'ui-monospace, Menlo, monospace' }}>{devCode}</strong>
            </div>
          ) : null}
          {locked || expired ? (
            <button style={{ ...primaryButton, opacity: canResend ? 1 : 0.6 }} type="button" disabled={!canResend} onClick={function () { requestCode('resend'); }}>
              {canResend ? 'Send me a new code' : 'Send a new code in ' + formatClock(resendIn)}
            </button>
          ) : (
            <button style={{ ...primaryButton, opacity: busy || code.length < CODE_LENGTH ? 0.7 : 1 }} disabled={busy || code.length < CODE_LENGTH} type="submit">
              {busy ? 'Checking...' : 'Open my application'}
            </button>
          )}
        </form>
        {!locked && !expired ? (
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 12, gap: 10, flexWrap: 'wrap' }}>
            <button type="button" style={{ ...secondaryButton, width: 'auto', padding: '8px 12px', opacity: canResend ? 1 : 0.6 }} disabled={!canResend} onClick={function () { requestCode('resend'); }}>
              {canResend ? 'Resend code' : 'Resend in ' + formatClock(resendIn)}
            </button>
            {expiresAt > 0 ? <span style={{ fontSize: 12, color: 'var(--muted, #77746c)' }}>Code expires in {formatClock(expiresIn)}</span> : null}
          </div>
        ) : null}
        <LoHelpCard />
      </GateShell>
    );
  }

  window.BevriResumeGate = ResumeGate;
})();
