/* ══════════════════════════════════════════════════════════════════
   Auth screens — LoginScreen, SsoScreen, InviteScreen
   ══════════════════════════════════════════════════════════════════
   2026-05-19 redesign: editorial split layout with a looping jellyfish
   swarm on the brand canvas (left) and the form on the right. See
   docs/login-overhaul-plan.md for the rationale and public/cms/auth.css
   for the visual rules. Brand asset is generated through the existing
   video-background pipeline:
     JELLY_ASPECT=9:16 node scripts/generate-jellyfish-login-bg.js
   then FILM-bridged + re-encoded → /brand/login-hero.mp4.

   Auth backend wiring is unchanged: POST /api/auth/login,
   /api/auth/invitations/verify/:token, /api/auth/invitations/accept/:token.
   ══════════════════════════════════════════════════════════════════ */

/* AuthBrandMark — kept as a no-op for backward compat with any
   downstream that references it. The brand identity is now the
   wordmark + a small terminator square inline; there is no separate
   mark element to render before the word. See .aq-auth-wordmark in
   auth.css for the square styling. */
function AuthBrandMark() { return null; }

/* SSO shield — used as a fallback IDP icon and inside the "Sign in
   with SSO" ghost button. */
function AuthShieldIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M12 3l8 3v6c0 5-3.5 8.5-8 9-4.5-.5-8-4-8-9V6l8-3z" />
    </svg>
  );
}

/* AuthShell — the 2-column grid that every auth screen sits inside.
   Left half is the brand canvas (video + wordmark chip + headline +
   region pill). Right half is the form panel where `children` renders.
   `topRight` is rendered in the top-right of the form panel. */
function AuthShell({ children, topRight }) {
  return (
    <div className="aq-auth">

      {/* Brand canvas — left. A single hero photograph: an aquarium
          kiosk in use, with a family interacting with a Copperband
          Butterflyfish species page. Sells the product in one image.
          The previous looping jellyfish video is retained at
          /brand/login-hero.mp4 in case we ever want to flip back. */}
      <aside className="aq-auth-brand-panel" aria-hidden="true">
        <img
          className="aq-auth-brand-image"
          src="/brand/login-hero.jpg?v=1"
          alt=""
          loading="eager"
        />
        <div className="aq-auth-brand-overlay" />
      </aside>

      {/* Form panel — right. Wordmark + form stacked in a single column. */}
      <section className="aq-auth-form-panel">
        <div className="aq-auth-form-top">{topRight}</div>
        <div className="aq-auth-form-stage">
          <div className="aq-auth-form-col">
            <div className="aq-auth-wordmark">
              <span className="aq-auth-wordmark-name">slate</span>
              <span className="aq-auth-wordmark-square" aria-hidden="true" />
              {/* Optional: uncomment to add a neutral descriptor.
                  Strict palette, --slate-dim, factual not promotional.
                  <span className="aq-auth-wordmark-tag">Software for aquariums &amp; museums</span>
              */}
            </div>
            {children}
          </div>
        </div>
      </section>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────────
   LoginScreen — email + password sign-in.
   ──────────────────────────────────────────────────────────────── */
function LoginScreen({ onAuthed }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  async function submit(e) {
    e && e.preventDefault();
    setBusy(true);
    setError(null);
    try {
      const res = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });
      const body = await res.json();
      if (!res.ok) throw new Error(body.error || 'Sign in failed');
      Auth.setSession(body.token, body.user);
      onAuthed && onAuthed(body.user);
    } catch (err) {
      setError(err.message);
    } finally {
      setBusy(false);
    }
  }

  /* SSO surface: the dev environment has no providers wired, so
     the ghost button hands off to the SSO redirect screen which is
     itself a visual stub. When a real provider is configured, the
     button can read from a getEnabledSsoProviders() helper and either
     auto-redirect (single provider) or show a small chooser modal. */
  return (
    <AuthShell>
      <form className="aq-auth-card" onSubmit={submit}>
        <h1>Welcome back</h1>
        <p className="aq-auth-card-sub">Sign in to your workspace.</p>

        <div className="aq-auth-field">
          <label className="aq-auth-label" htmlFor="auth-email">
            <span>Work email</span>
          </label>
          <div className="aq-auth-input">
            <input
              id="auth-email"
              type="email" required autoComplete="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@workspace.com"
            />
          </div>
        </div>

        <div className="aq-auth-field">
          <label className="aq-auth-label" htmlFor="auth-password">
            <span>Password</span>
            <a
              href="#"
              className="aq-auth-label-link"
              onClick={(e) => { e.preventDefault(); window.location.hash = '#reset-password'; window.location.reload(); }}
            >
              Forgot?
            </a>
          </label>
          <div className="aq-auth-input">
            <input
              id="auth-password"
              type="password" required autoComplete="current-password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="••••••••••••"
            />
          </div>
        </div>

        {error && <div className="aq-auth-err">{error}</div>}

        <button className="aq-auth-btn" type="submit" disabled={busy}>
          <span>{busy ? 'Signing in…' : 'Continue'}</span>
          <span className="aq-auth-kbd">↵</span>
        </button>

        <button
          type="button"
          className="aq-auth-btn-ghost"
          onClick={() => { window.location.hash = '#sso'; window.location.reload(); }}
        >
          <AuthShieldIcon />
          Sign in with SSO
        </button>
      </form>
    </AuthShell>
  );
}

/* ────────────────────────────────────────────────────────────────
   SsoScreen — visual handoff to the configured identity provider.
   In production this is a brief render before the IDP redirect
   completes; the "use email instead" escape hatch is always present.
   ──────────────────────────────────────────────────────────────── */
function SsoScreen() {
  return (
    <AuthShell topRight={
      <a href="#" onClick={(e) => { e.preventDefault(); window.location.hash = '#login'; window.location.reload(); }}>
        Use email instead
      </a>
    }>
      <div className="aq-auth-card">
        <h1>Redirecting…</h1>
        <p className="aq-auth-card-sub">
          Handing you off to your identity provider. Hardware-key MFA is required for admin accounts.
        </p>

        <div className="aq-auth-sso-handoff">
          <div className="aq-auth-sso-icon" />
          <div style={{ flex: 1 }}>
            <div className="aq-auth-sso-name">aquaos.demo.idp</div>
            <div className="aq-auth-sso-meta">SAML 2.0 · 30-day session · MFA required</div>
          </div>
          <div className="aq-auth-sso-spin" />
        </div>

        <button
          className="aq-auth-btn-ghost"
          onClick={() => { window.location.hash = '#login'; window.location.reload(); }}
        >
          Use email &amp; password instead
        </button>

        <div className="aq-auth-card-foot">
          Trouble signing in? <a href="#">Contact your admin</a>
        </div>
      </div>
    </AuthShell>
  );
}

/* ────────────────────────────────────────────────────────────────
   InviteScreen — accept an invitation token and set initial password.
   Verifies POST /api/auth/invitations/verify/:token on mount, then
   accepts via /api/auth/invitations/accept/:token on submit.
   ──────────────────────────────────────────────────────────────── */
function InviteScreen({ param }) {
  const [invite, setInvite] = useState(null);
  const [loading, setLoading] = useState(true);
  const [verifyErr, setVerifyErr] = useState(null);
  const [name, setName] = useState('');
  const [password, setPassword] = useState('');
  const [confirm, setConfirm] = useState('');
  const [busy, setBusy] = useState(false);
  const [acceptErr, setAcceptErr] = useState(null);

  useEffect(() => {
    if (!param) {
      setVerifyErr('No invitation token in the URL — open the link from your invitation email.');
      setLoading(false);
      return;
    }
    let cancelled = false;
    fetch(`/api/auth/invitations/verify/${encodeURIComponent(param)}`)
      .then(async (r) => {
        const data = await r.json().catch(() => ({}));
        if (cancelled) return;
        if (!r.ok) throw new Error(data.error || 'Invitation not found, expired, or already used.');
        setInvite(data);
        setName(data.name || '');
        setLoading(false);
      })
      .catch((err) => {
        if (cancelled) return;
        setVerifyErr(err.message);
        setLoading(false);
      });
    return () => { cancelled = true; };
  }, [param]);

  async function accept() {
    if (!password || password.length < 8) {
      setAcceptErr('Password must be at least 8 characters.');
      return;
    }
    if (password !== confirm) {
      setAcceptErr('The two passwords don’t match.');
      return;
    }
    setBusy(true);
    setAcceptErr(null);
    try {
      const res = await fetch(`/api/auth/invitations/accept/${encodeURIComponent(param)}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ password, name: name.trim() || undefined }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || 'Acceptance failed.');
      if (data.token && data.user) {
        Auth.setSession(data.token, data.user);
        window.toast && window.toast.success(`Welcome, ${data.user.name || data.user.email}!`);
        window.location.hash = '#dashboard';
        window.location.reload();
      }
    } catch (err) {
      setAcceptErr(err.message);
    } finally {
      setBusy(false);
    }
  }

  function expiresLabel(iso) {
    if (!iso) return '';
    const days = Math.max(0, Math.round((new Date(iso).getTime() - Date.now()) / (1000 * 60 * 60 * 24)));
    if (days === 0) return 'expires today';
    if (days === 1) return 'expires in 1 day';
    return `expires in ${days} days`;
  }
  const roleLabel = ({
    aquaos_admin: 'Platform admin',
    org_admin: 'Org admin',
    site_manager: 'Site manager',
    marketing_admin: 'Marketing admin',
    designer: 'Designer',
    operator: 'Operator',
  })[invite && invite.role] || (invite && invite.role) || 'Member';

  /* Org initials for the borderless mark — first letters of the first
     two words, uppercased. Avoids needing a per-org logo for the visual. */
  const orgInitials = ((invite && invite.org_name) || '')
    .split(/\s+/).slice(0, 2).map(w => w[0] || '').join('').toUpperCase() || '·';

  return (
    <AuthShell topRight={
      <a href="#" onClick={(e) => { e.preventDefault(); window.location.hash = '#login'; window.location.reload(); }}>
        Sign in instead
      </a>
    }>
      <div className="aq-auth-card">
        <h1>You're invited to Slate</h1>

        {loading && (
          <p className="aq-auth-card-sub">Verifying your invitation…</p>
        )}

        {verifyErr && (
          <>
            <p className="aq-auth-card-sub" style={{ color: 'var(--aqos-danger, oklch(0.70 0.16 25))' }}>
              {verifyErr}
            </p>
            <button
              className="aq-auth-btn"
              onClick={() => { window.location.hash = '#login'; window.location.reload(); }}
            >
              <span>Go to sign-in</span>
            </button>
          </>
        )}

        {!loading && invite && (
          <>
            <p className="aq-auth-card-sub">
              Accept the invite to join <b style={{ color: 'var(--slate-bone)' }}>{invite.org_name}</b>{' '}
              as a <b style={{ color: 'var(--slate-bone)' }}>{roleLabel}</b>. Set a password to finish setting up your account.
            </p>

            <div className="aq-auth-invite-org">
              <div className="aq-auth-invite-org-mark">{orgInitials}</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="aq-auth-invite-org-name">{invite.org_name}</div>
                <div className="aq-auth-invite-org-email">{invite.email}</div>
              </div>
              <span className="aq-auth-invite-role">{roleLabel}</span>
            </div>

            <div className="aq-auth-field">
              <label className="aq-auth-label" htmlFor="auth-invite-email"><span>Email</span></label>
              <div className="aq-auth-input">
                <input id="auth-invite-email" value={invite.email} disabled />
              </div>
            </div>

            <div className="aq-auth-field">
              <label className="aq-auth-label" htmlFor="auth-invite-name"><span>Your full name</span></label>
              <div className="aq-auth-input">
                <input
                  id="auth-invite-name"
                  value={name}
                  onChange={(e) => setName(e.target.value)}
                  placeholder="Olivia Underwood"
                />
              </div>
            </div>

            <div className="aq-auth-field">
              <label className="aq-auth-label" htmlFor="auth-invite-password"><span>Choose a password</span></label>
              <div className="aq-auth-input">
                <input
                  id="auth-invite-password"
                  type="password"
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                  placeholder="At least 8 characters"
                  autoComplete="new-password"
                  autoFocus
                />
              </div>
            </div>

            <div className="aq-auth-field">
              <label className="aq-auth-label" htmlFor="auth-invite-confirm"><span>Confirm password</span></label>
              <div className="aq-auth-input">
                <input
                  id="auth-invite-confirm"
                  type="password"
                  value={confirm}
                  onChange={(e) => setConfirm(e.target.value)}
                  placeholder="Re-enter your password"
                  autoComplete="new-password"
                  onKeyDown={(e) => { if (e.key === 'Enter') accept(); }}
                />
              </div>
            </div>

            {acceptErr && <div className="aq-auth-err">{acceptErr}</div>}

            <button
              className="aq-auth-btn"
              disabled={busy || !password || !confirm}
              onClick={accept}
            >
              <span>{busy ? 'Accepting…' : 'Accept invitation'}</span>
              <span className="aq-auth-kbd">↵</span>
            </button>

            {invite.expires_at && (
              <div className="aq-auth-invite-expires">
                Invitation {expiresLabel(invite.expires_at)}
              </div>
            )}
          </>
        )}
      </div>
    </AuthShell>
  );
}

/* ────────────────────────────────────────────────────────────────
   ResetScreen — password reset, both halves of the flow:
     • No token in the URL (#reset-password)        → self-service
       "Forgot password?" request form. POSTs an email to
       /api/auth/password-reset/request, which always 200s so the
       page can't be used to probe which emails are registered.
     • Token in the URL (#/reset-password/:token)   → set-new-password
       form. Verifies the token on mount, then POSTs the new password
       to /api/auth/password-reset/:token and auto-logs-in (same as
       invitation acceptance).
   ──────────────────────────────────────────────────────────────── */
function ResetScreen({ param }) {
  const hasToken = !!param;

  // ── Request-form state (no token) ──
  const [email, setEmail] = useState('');
  const [sent, setSent] = useState(false);

  // ── Token-form state ──
  const [loading, setLoading] = useState(hasToken);
  const [verifyErr, setVerifyErr] = useState(null);
  const [resetEmail, setResetEmail] = useState('');
  const [password, setPassword] = useState('');
  const [confirm, setConfirm] = useState('');

  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!hasToken) return;
    let cancelled = false;
    fetch(`/api/auth/password-reset/verify/${encodeURIComponent(param)}`)
      .then(async (r) => {
        const data = await r.json().catch(() => ({}));
        if (cancelled) return;
        if (!r.ok) throw new Error(data.error || 'This reset link is invalid, expired, or already used.');
        setResetEmail(data.email || '');
        setLoading(false);
      })
      .catch((err) => { if (!cancelled) { setVerifyErr(err.message); setLoading(false); } });
    return () => { cancelled = true; };
  }, [param]);

  async function requestReset(e) {
    e && e.preventDefault();
    if (!email.trim()) { setError('Enter your work email.'); return; }
    setBusy(true); setError(null);
    try {
      // Always resolves ok — uniform response prevents account enumeration.
      await fetch('/api/auth/password-reset/request', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: email.trim() }),
      });
      setSent(true);
    } catch (_) {
      // Network error only — still show the neutral confirmation.
      setSent(true);
    } finally { setBusy(false); }
  }

  async function submitNewPassword(e) {
    e && e.preventDefault();
    if (!password || password.length < 8) { setError('Password must be at least 8 characters.'); return; }
    if (password !== confirm) { setError('The two passwords don’t match.'); return; }
    setBusy(true); setError(null);
    try {
      const res = await fetch(`/api/auth/password-reset/${encodeURIComponent(param)}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ password }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || 'Could not reset your password.');
      if (data.token && data.user) {
        Auth.setSession(data.token, data.user);
        window.toast && window.toast.success('Password updated — you’re signed in.');
        window.location.hash = '#dashboard';
        window.location.reload();
      }
    } catch (err) {
      setError(err.message);
    } finally { setBusy(false); }
  }

  const backToSignIn = (
    <a href="#" onClick={(e) => { e.preventDefault(); window.location.hash = '#login'; window.location.reload(); }}>
      Back to sign-in
    </a>
  );

  // ── Token form ──
  if (hasToken) {
    return (
      <AuthShell topRight={backToSignIn}>
        <div className="aq-auth-card">
          <h1>Set a new password</h1>

          {loading && <p className="aq-auth-card-sub">Checking your reset link…</p>}

          {verifyErr && (
            <>
              <p className="aq-auth-card-sub" style={{ color: 'var(--aqos-danger, oklch(0.70 0.16 25))' }}>{verifyErr}</p>
              <button className="aq-auth-btn" onClick={() => { window.location.hash = '#reset-password'; window.location.reload(); }}>
                <span>Request a new link</span>
              </button>
            </>
          )}

          {!loading && !verifyErr && (
            <form onSubmit={submitNewPassword}>
              <p className="aq-auth-card-sub">
                Choose a new password for <b style={{ color: 'var(--slate-bone)' }}>{resetEmail}</b>.
              </p>

              <div className="aq-auth-field">
                <label className="aq-auth-label" htmlFor="auth-reset-password"><span>New password</span></label>
                <div className="aq-auth-input">
                  <input
                    id="auth-reset-password"
                    type="password"
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                    placeholder="At least 8 characters"
                    autoComplete="new-password"
                    autoFocus
                  />
                </div>
              </div>

              <div className="aq-auth-field">
                <label className="aq-auth-label" htmlFor="auth-reset-confirm"><span>Confirm new password</span></label>
                <div className="aq-auth-input">
                  <input
                    id="auth-reset-confirm"
                    type="password"
                    value={confirm}
                    onChange={(e) => setConfirm(e.target.value)}
                    placeholder="Re-enter your new password"
                    autoComplete="new-password"
                  />
                </div>
              </div>

              {error && <div className="aq-auth-err">{error}</div>}

              <button className="aq-auth-btn" type="submit" disabled={busy || !password || !confirm}>
                <span>{busy ? 'Updating…' : 'Update password'}</span>
                <span className="aq-auth-kbd">↵</span>
              </button>
            </form>
          )}
        </div>
      </AuthShell>
    );
  }

  // ── Request form (no token) ──
  return (
    <AuthShell topRight={backToSignIn}>
      <div className="aq-auth-card">
        <h1>Reset your password</h1>

        {sent ? (
          <>
            <p className="aq-auth-card-sub">
              If an account exists for <b style={{ color: 'var(--slate-bone)' }}>{email.trim()}</b>, we’ve sent a link to reset your password. The link expires in 1 hour.
            </p>
            <button className="aq-auth-btn" onClick={() => { window.location.hash = '#login'; window.location.reload(); }}>
              <span>Back to sign-in</span>
            </button>
          </>
        ) : (
          <form onSubmit={requestReset}>
            <p className="aq-auth-card-sub">Enter your work email and we’ll send you a link to set a new password.</p>

            <div className="aq-auth-field">
              <label className="aq-auth-label" htmlFor="auth-reset-email"><span>Work email</span></label>
              <div className="aq-auth-input">
                <input
                  id="auth-reset-email"
                  type="email" required autoComplete="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  placeholder="you@workspace.com"
                  autoFocus
                />
              </div>
            </div>

            {error && <div className="aq-auth-err">{error}</div>}

            <button className="aq-auth-btn" type="submit" disabled={busy}>
              <span>{busy ? 'Sending…' : 'Send reset link'}</span>
              <span className="aq-auth-kbd">↵</span>
            </button>
          </form>
        )}
      </div>
    </AuthShell>
  );
}

window.LoginScreen = LoginScreen;
window.SsoScreen = SsoScreen;
window.InviteScreen = InviteScreen;
window.ResetScreen = ResetScreen;
