/**
 * rSpace Header Component
 *
 * Shared header bar with EncryptID sign-in/sign-up for all rSpace pages.
 * Reads session from localStorage and provides auth state to the page.
 */

const SESSION_KEY = 'encryptid_session';
const ENCRYPTID_URL = ''; // same-origin — avoids cross-origin issues on Safari

interface SessionState {
  accessToken: string;
  claims: {
    sub: string;       // user ID
    exp: number;
    username?: string;
    did?: string;
    eid: {
      authLevel: number;
      capabilities: {
        encrypt: boolean;
        sign: boolean;
        wallet: boolean;
      };
    };
  };
}

// ============================================================================
// SESSION HELPERS
// ============================================================================

function getSession(): SessionState | null {
  try {
    const stored = localStorage.getItem(SESSION_KEY);
    if (!stored) return null;
    const session = JSON.parse(stored) as SessionState;
    const now = Math.floor(Date.now() / 1000);
    if (now >= session.claims.exp) {
      localStorage.removeItem(SESSION_KEY);
      localStorage.removeItem('rspace-username');
      return null;
    }
    return session;
  } catch {
    return null;
  }
}


function clearSession(): void {
  // Notify server so all other browser sessions get revoked
  try {
    const stored = localStorage.getItem(SESSION_KEY);
    if (stored) {
      const session = JSON.parse(stored) as SessionState;
      if (session.accessToken) {
        fetch(`${ENCRYPTID_URL}/api/session/logout`, {
          method: 'POST',
          headers: { 'Authorization': `Bearer ${session.accessToken}` },
        }).catch(() => { /* best-effort */ });
      }
    }
  } catch { /* ignore */ }

  localStorage.removeItem(SESSION_KEY);
  localStorage.removeItem('rspace-username');
}

export function isAuthenticated(): boolean {
  return getSession() !== null;
}

export function getAccessToken(): string | null {
  return getSession()?.accessToken ?? null;
}

export function getUserDID(): string | null {
  return getSession()?.claims.did ?? getSession()?.claims.sub ?? null;
}

export function getUsername(): string | null {
  return getSession()?.claims.username ?? null;
}

// ============================================================================
// BASE64URL / JWT HELPERS
// ============================================================================

function base64urlToBuffer(base64url: string): ArrayBuffer {
  const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
  const padding = '='.repeat((4 - (base64.length % 4)) % 4);
  const binary = atob(base64 + padding);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes.buffer;
}

function bufferToBase64url(buffer: ArrayBuffer): string {
  const bytes = new Uint8Array(buffer);
  let binary = '';
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

function parseJWT(token: string): Record<string, any> {
  const parts = token.split('.');
  if (parts.length < 2) return {};
  try {
    const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
    const padding = '='.repeat((4 - (b64.length % 4)) % 4);
    return JSON.parse(atob(b64 + padding));
  } catch {
    return {};
  }
}

function storeSession(token: string, username: string, did: string): void {
  const payload = parseJWT(token);
  const session: SessionState = {
    accessToken: token,
    claims: {
      sub: payload.sub || '',
      exp: payload.exp || 0,
      username,
      did,
      eid: payload.eid || {
        authLevel: 3,
        capabilities: { encrypt: true, sign: true, wallet: false },
      },
    },
  };
  localStorage.setItem(SESSION_KEY, JSON.stringify(session));
  if (username) {
    localStorage.setItem('rspace-username', username);
  }
}

// ============================================================================
// HEADER STYLES
// ============================================================================

const HEADER_STYLES = `
  .rspace-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 24px;
    z-index: 9999;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }

  .rspace-header--dark {
    background: rgba(15, 23, 42, 0.85);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  }

  .rspace-header--light {
    background: rgba(255, 255, 255, 0.9);
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  }

  .rspace-header__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    font-size: 1.25rem;
    font-weight: 700;
  }

  .rspace-header--dark .rspace-header__brand {
    color: white;
  }

  .rspace-header--light .rspace-header__brand {
    color: #0f172a;
  }

  .rspace-header__brand-gradient {
    background: linear-gradient(135deg, #14b8a6, #22d3ee);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }

  .rspace-header__right {
    display: flex;
    align-items: center;
    gap: 12px;
  }

  .rspace-header__signin-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 20px;
    border-radius: 8px;
    border: none;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s;
    text-decoration: none;
  }

  .rspace-header--dark .rspace-header__signin-btn {
    background: linear-gradient(135deg, #06b6d4, #7c3aed);
    color: white;
  }

  .rspace-header--dark .rspace-header__signin-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
  }

  .rspace-header--light .rspace-header__signin-btn {
    background: linear-gradient(135deg, #06b6d4, #0891b2);
    color: white;
  }

  .rspace-header--light .rspace-header__signin-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
  }

  .rspace-header__user {
    display: flex;
    align-items: center;
    gap: 10px;
    position: relative;
    cursor: pointer;
  }

  .rspace-header__avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: linear-gradient(135deg, #06b6d4, #7c3aed);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 0.8rem;
    color: white;
  }

  .rspace-header__user-did {
    font-size: 0.8rem;
    max-width: 140px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .rspace-header--dark .rspace-header__user-did {
    color: #94a3b8;
  }

  .rspace-header--light .rspace-header__user-did {
    color: #64748b;
  }

  .rspace-header__dropdown {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 8px;
    min-width: 200px;
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
    display: none;
  }

  .rspace-header__dropdown.open {
    display: block;
  }

  .rspace-header--dark .rspace-header__dropdown {
    background: #1e293b;
    border: 1px solid rgba(255, 255, 255, 0.1);
  }

  .rspace-header--light .rspace-header__dropdown {
    background: white;
    border: 1px solid rgba(0, 0, 0, 0.1);
  }

  .rspace-header__dropdown-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    font-size: 0.875rem;
    cursor: pointer;
    transition: background 0.15s;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
  }

  .rspace-header--dark .rspace-header__dropdown-item {
    color: #e2e8f0;
  }

  .rspace-header--dark .rspace-header__dropdown-item:hover {
    background: rgba(255, 255, 255, 0.05);
  }

  .rspace-header--light .rspace-header__dropdown-item {
    color: #374151;
  }

  .rspace-header--light .rspace-header__dropdown-item:hover {
    background: #f1f5f9;
  }

  .rspace-header__dropdown-item--danger {
    color: #ef4444 !important;
  }

  .rspace-header__dropdown-divider {
    height: 1px;
    margin: 4px 0;
  }

  .rspace-header--dark .rspace-header__dropdown-divider {
    background: rgba(255, 255, 255, 0.08);
  }

  .rspace-header--light .rspace-header__dropdown-divider {
    background: rgba(0, 0, 0, 0.08);
  }

  /* Auth modal overlay */
  .rspace-auth-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
    animation: fadeIn 0.2s;
  }

  .rspace-auth-modal {
    background: #1e293b;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    padding: 2rem;
    max-width: 420px;
    width: 90%;
    text-align: center;
    color: white;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
    animation: slideUp 0.3s;
  }

  .rspace-auth-modal h2 {
    font-size: 1.5rem;
    margin-bottom: 0.5rem;
    background: linear-gradient(135deg, #06b6d4, #7c3aed);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
  }

  .rspace-auth-modal p {
    color: #94a3b8;
    font-size: 0.95rem;
    line-height: 1.6;
    margin-bottom: 1.5rem;
  }

  .rspace-auth-modal__input {
    width: 100%;
    padding: 12px 16px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.15);
    background: rgba(255, 255, 255, 0.05);
    color: white;
    font-size: 1rem;
    margin-bottom: 1rem;
    outline: none;
    transition: border-color 0.2s;
  }

  .rspace-auth-modal__input:focus {
    border-color: #06b6d4;
  }

  .rspace-auth-modal__input::placeholder {
    color: #64748b;
  }

  .rspace-auth-modal__actions {
    display: flex;
    gap: 12px;
    margin-top: 0.5rem;
  }

  .rspace-auth-modal__btn {
    flex: 1;
    padding: 12px 20px;
    border-radius: 8px;
    border: none;
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s;
  }

  .rspace-auth-modal__btn--primary {
    background: linear-gradient(135deg, #06b6d4, #7c3aed);
    color: white;
  }

  .rspace-auth-modal__btn--primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
  }

  .rspace-auth-modal__btn--primary:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
  }

  .rspace-auth-modal__btn--secondary {
    background: rgba(255, 255, 255, 0.08);
    color: #94a3b8;
    border: 1px solid rgba(255, 255, 255, 0.1);
  }

  .rspace-auth-modal__btn--secondary:hover {
    background: rgba(255, 255, 255, 0.12);
    color: white;
  }

  .rspace-auth-modal__error {
    color: #ef4444;
    font-size: 0.85rem;
    margin-top: 0.5rem;
    min-height: 1.2em;
  }

  .rspace-auth-modal__divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 1rem 0;
    font-size: 0.8rem;
    color: #64748b;
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }
  .rspace-auth-modal__divider::before,
  .rspace-auth-modal__divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: rgba(255,255,255,0.1);
  }

  .rspace-auth-modal__toggle {
    margin-top: 1rem;
    font-size: 0.85rem;
    color: #64748b;
  }

  .rspace-auth-modal__toggle a {
    color: #06b6d4;
    cursor: pointer;
    text-decoration: none;
  }

  .rspace-auth-modal__toggle a:hover {
    text-decoration: underline;
  }

  .rspace-auth-modal__spinner {
    display: inline-block;
    width: 18px;
    height: 18px;
    border: 2px solid transparent;
    border-top-color: currentColor;
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
    vertical-align: middle;
    margin-right: 6px;
  }

  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }

  @keyframes slideUp {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
  }

  @keyframes spin {
    to { transform: rotate(360deg); }
  }
`;

// ============================================================================
// AUTH MODAL
// ============================================================================

type AuthMode = 'signin' | 'register';

interface AuthModalCallbacks {
  onSuccess: () => void;
  onCancel: () => void;
}

let activeModal: HTMLElement | null = null;
let headerRenderFn: (() => void) | null = null;
let conditionalAbort: AbortController | null = null;

/**
 * Show the EncryptID auth modal for sign-in or registration.
 * Uses WebAuthn passkeys via the EncryptID server.
 */
export function showAuthModal(callbacks?: Partial<AuthModalCallbacks>): void {
  if (activeModal) return;

  const overlay = document.createElement('div');
  overlay.className = 'rspace-auth-overlay';
  let mode: AuthMode = 'signin';

  function render() {
    abortConditional();
    overlay.innerHTML = mode === 'signin' ? renderSignIn() : renderRegister();
    attachModalListeners();
    if (mode === 'signin') {
      startConditional();
      // Auto-focus the passkey input so mobile shows autofill suggestions
      setTimeout(() => (overlay.querySelector('#auth-signin-username') as HTMLInputElement)?.focus(), 100);
    }
  }

  function abortConditional() {
    if (conditionalAbort) {
      conditionalAbort.abort();
      conditionalAbort = null;
    }
  }

  /** Start conditional mediation (passkey autofill) for mobile/desktop parity */
  async function startConditional() {
    // Check if conditional mediation is supported
    // @ts-ignore
    if (!window.PublicKeyCredential?.isConditionalMediationAvailable) return;
    // @ts-ignore
    const available = await PublicKeyCredential.isConditionalMediationAvailable();
    if (!available) return;

    try {
      const startRes = await fetch(`${ENCRYPTID_URL}/api/auth/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),
      });
      if (!startRes.ok) return;
      const { options: serverOptions } = await startRes.json();

      conditionalAbort = new AbortController();
      const credential = await navigator.credentials.get({
        // @ts-ignore - conditional mediation
        mediation: 'conditional',
        signal: conditionalAbort.signal,
        publicKey: {
          challenge: new Uint8Array(base64urlToBuffer(serverOptions.challenge)),
          rpId: serverOptions.rpId || 'rspace.online',
          userVerification: 'required',
          timeout: 120000,
        },
      }) as PublicKeyCredential;
      if (!credential) return;

      // User selected a passkey via autofill — complete auth
      const errorEl = overlay.querySelector('#auth-error') as HTMLElement;
      const btn = overlay.querySelector('[data-action="signin"]') as HTMLButtonElement;
      if (btn) { btn.disabled = true; btn.innerHTML = '<span class="rspace-auth-modal__spinner"></span> Authenticating...'; }

      const completeRes = await fetch(`${ENCRYPTID_URL}/api/auth/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          challenge: serverOptions.challenge,
          credential: { credentialId: bufferToBase64url(credential.rawId) },
        }),
      });
      const data = await completeRes.json();
      if (!completeRes.ok || !data.success) throw new Error(data.error || 'Authentication failed');

      storeSession(data.token, data.username || '', data.did || '');
      closeModal();
      callbacks?.onSuccess?.();
    } catch (err: any) {
      if (err.name === 'AbortError') return; // user switched to register or closed modal
    }
  }

  function renderSignIn(): string {
    return `
      <div class="rspace-auth-modal">
        <h2>Sign in with EncryptID</h2>
        <p>
          Use your passkey to sign in instantly. No passwords needed —
          just your fingerprint, face, or device PIN.
        </p>
        <input
          class="rspace-auth-modal__input"
          id="auth-signin-username"
          type="text"
          placeholder="Tap to see saved passkeys..."
          autocomplete="username webauthn"
        />
        <div class="rspace-auth-modal__divider">or</div>
        <div class="rspace-auth-modal__actions">
          <button class="rspace-auth-modal__btn rspace-auth-modal__btn--secondary" data-action="cancel">Cancel</button>
          <button class="rspace-auth-modal__btn rspace-auth-modal__btn--primary" data-action="signin">
            🔑 Sign In with Passkey
          </button>
        </div>
        <div class="rspace-auth-modal__error" id="auth-error"></div>
        <div class="rspace-auth-modal__toggle">
          Don't have an account? <a data-action="switch-register">Create one</a>
        </div>
      </div>
    `;
  }

  function renderRegister(): string {
    return `
      <div class="rspace-auth-modal">
        <h2>Create your EncryptID</h2>
        <p>
          Set up a secure, passwordless identity. Your passkey is stored
          on your device — we never see your private keys.
        </p>
        <input
          class="rspace-auth-modal__input"
          id="auth-username"
          type="text"
          placeholder="Choose a username"
          autocomplete="username webauthn"
          maxlength="32"
        />
        <div class="rspace-auth-modal__actions">
          <button class="rspace-auth-modal__btn rspace-auth-modal__btn--secondary" data-action="cancel">Cancel</button>
          <button class="rspace-auth-modal__btn rspace-auth-modal__btn--primary" data-action="register">
            🔐 Create Passkey
          </button>
        </div>
        <div class="rspace-auth-modal__error" id="auth-error"></div>
        <div class="rspace-auth-modal__toggle">
          Already have an account? <a data-action="switch-signin">Sign in</a>
        </div>
      </div>
    `;
  }

  async function handleSignIn() {
    // Abort conditional mediation before starting modal flow
    abortConditional();

    const errorEl = overlay.querySelector('#auth-error') as HTMLElement;
    const btn = overlay.querySelector('[data-action="signin"]') as HTMLButtonElement;
    errorEl.textContent = '';
    btn.disabled = true;
    btn.innerHTML = '<span class="rspace-auth-modal__spinner"></span> Authenticating...';

    try {
      // 1. Get server challenge
      const startRes = await fetch(`${ENCRYPTID_URL}/api/auth/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),
      });
      if (!startRes.ok) throw new Error('Failed to start authentication');
      const { options: serverOptions } = await startRes.json();

      // 2. WebAuthn ceremony with server challenge
      const credential = await navigator.credentials.get({
        publicKey: {
          challenge: new Uint8Array(base64urlToBuffer(serverOptions.challenge)),
          rpId: serverOptions.rpId || 'rspace.online',
          userVerification: 'required',
          timeout: 60000,
        },
      }) as PublicKeyCredential;
      if (!credential) throw new Error('Authentication failed');

      // 3. Complete auth on EncryptID server → get signed JWT + username
      const completeRes = await fetch(`${ENCRYPTID_URL}/api/auth/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          challenge: serverOptions.challenge,
          credential: {
            credentialId: bufferToBase64url(credential.rawId),
          },
        }),
      });
      const data = await completeRes.json();
      if (!completeRes.ok || !data.success) {
        throw new Error(data.error || 'Authentication failed');
      }

      // 4. Store server-signed token with username
      storeSession(data.token, data.username || '', data.did || '');

      closeModal();
      callbacks?.onSuccess?.();
    } catch (err: any) {
      btn.disabled = false;
      btn.innerHTML = '🔑 Sign In with Passkey';
      if (err.name === 'NotAllowedError') {
        errorEl.textContent = 'Authentication was cancelled or no passkey found.';
      } else {
        errorEl.textContent = err.message || 'Authentication failed.';
      }
    }
  }

  async function handleRegister() {
    const usernameInput = overlay.querySelector('#auth-username') as HTMLInputElement;
    const errorEl = overlay.querySelector('#auth-error') as HTMLElement;
    const btn = overlay.querySelector('[data-action="register"]') as HTMLButtonElement;
    const username = usernameInput.value.trim();

    if (!username) {
      errorEl.textContent = 'Please enter a username.';
      usernameInput.focus();
      return;
    }

    errorEl.textContent = '';
    btn.disabled = true;
    btn.innerHTML = '<span class="rspace-auth-modal__spinner"></span> Creating passkey...';

    try {
      // 1. Get registration options from server
      const startRes = await fetch(`${ENCRYPTID_URL}/api/register/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, displayName: username }),
      });
      if (!startRes.ok) throw new Error('Failed to start registration');
      const { options: serverOptions, userId } = await startRes.json();

      // 2. WebAuthn ceremony with server challenge
      const credential = await navigator.credentials.create({
        publicKey: {
          challenge: new Uint8Array(base64urlToBuffer(serverOptions.challenge)),
          rp: {
            id: serverOptions.rp?.id || 'rspace.online',
            name: serverOptions.rp?.name || 'EncryptID',
          },
          user: {
            id: new Uint8Array(base64urlToBuffer(serverOptions.user.id)),
            name: username,
            displayName: username,
          },
          pubKeyCredParams: serverOptions.pubKeyCredParams || [
            { alg: -7, type: 'public-key' as const },
            { alg: -257, type: 'public-key' as const },
          ],
          authenticatorSelection: {
            residentKey: 'required',
            requireResidentKey: true,
            userVerification: 'required',
          },
          attestation: 'none',
          timeout: 60000,
        },
      }) as PublicKeyCredential;
      if (!credential) throw new Error('Failed to create credential');

      const response = credential.response as AuthenticatorAttestationResponse;
      const publicKey = response.getPublicKey?.();

      // 3. Complete registration on EncryptID server
      const completeRes = await fetch(`${ENCRYPTID_URL}/api/register/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          challenge: serverOptions.challenge,
          credential: {
            credentialId: bufferToBase64url(credential.rawId),
            publicKey: publicKey ? bufferToBase64url(publicKey) : '',
            transports: response.getTransports?.() || [],
          },
          userId,
          username,
        }),
      });
      const data = await completeRes.json().catch(() => null);
      if (!data || !completeRes.ok || !data.success) {
        throw new Error(data?.error || 'Registration failed');
      }

      // 4. Store server-signed token with username
      storeSession(data.token, username, data.did || '');

      closeModal();
      callbacks?.onSuccess?.();
    } catch (err: any) {
      btn.disabled = false;
      btn.innerHTML = '🔐 Create Passkey';
      if (err.name === 'NotAllowedError') {
        errorEl.textContent = 'Passkey creation was cancelled.';
      } else {
        errorEl.textContent = err.message || 'Registration failed.';
      }
    }
  }

  function attachModalListeners() {
    overlay.querySelector('[data-action="cancel"]')?.addEventListener('click', () => {
      closeModal();
      callbacks?.onCancel?.();
    });
    overlay.querySelector('[data-action="signin"]')?.addEventListener('click', handleSignIn);
    overlay.querySelector('[data-action="register"]')?.addEventListener('click', handleRegister);
    overlay.querySelector('[data-action="switch-register"]')?.addEventListener('click', () => {
      mode = 'register';
      render();
      // Focus the username input
      setTimeout(() => (overlay.querySelector('#auth-username') as HTMLInputElement)?.focus(), 50);
    });
    overlay.querySelector('[data-action="switch-signin"]')?.addEventListener('click', () => {
      mode = 'signin';
      render();
    });
    // Handle Enter key in username input
    overlay.querySelector('#auth-username')?.addEventListener('keydown', (e) => {
      if ((e as KeyboardEvent).key === 'Enter') handleRegister();
    });
    // Close on overlay background click
    overlay.addEventListener('click', (e) => {
      if (e.target === overlay) {
        closeModal();
        callbacks?.onCancel?.();
      }
    });
  }

  function closeModal() {
    abortConditional();
    activeModal?.remove();
    activeModal = null;
  }

  activeModal = overlay;
  document.body.appendChild(overlay);
  render();
}

// ============================================================================
// HEADER COMPONENT
// ============================================================================

export interface HeaderOptions {
  /** 'dark' for dark pages (landing), 'light' for canvas */
  theme: 'dark' | 'light';
  /** Show the rSpace brand link on the left */
  showBrand?: boolean;
  /** Called after successful sign-in/out to refresh page state */
  onAuthChange?: () => void;
}

/**
 * Mount the rSpace header bar at the top of the page.
 * Handles sign-in/sign-up with EncryptID and shows user state.
 */
export function mountHeader(options: HeaderOptions): void {
  const { theme, showBrand = true, onAuthChange } = options;

  // Inject styles
  if (!document.getElementById('rspace-header-styles')) {
    const style = document.createElement('style');
    style.id = 'rspace-header-styles';
    style.textContent = HEADER_STYLES;
    document.head.appendChild(style);
  }

  // Create header element
  const header = document.createElement('header');
  header.className = `rspace-header rspace-header--${theme}`;
  header.id = 'rspace-header';

  function renderHeader() {
    const session = getSession();
    const isLoggedIn = session !== null;

    const brandHTML = showBrand
      ? `<a href="/" class="rspace-header__brand">
           <span class="rspace-header__brand-gradient">rSpace</span>
         </a>`
      : '<div></div>';

    if (isLoggedIn) {
      const username = session!.claims.username || '';
      const did = session!.claims.did || session!.claims.sub;
      const displayName = username || (did.length > 24 ? did.slice(0, 16) + '...' + did.slice(-6) : did);
      const initial = username ? username[0].toUpperCase() : did.slice(8, 10).toUpperCase();

      header.innerHTML = `
        ${brandHTML}
        <div class="rspace-header__right">
          <div class="rspace-header__user" id="header-user-toggle">
            <div class="rspace-header__avatar">${initial}</div>
            <span class="rspace-header__user-did">${displayName}</span>
            <div class="rspace-header__dropdown" id="header-dropdown">
              <button class="rspace-header__dropdown-item" data-action="profile">
                👤 Profile
              </button>
              <button class="rspace-header__dropdown-item" data-action="recovery">
                🛡️ Recovery Settings
              </button>
              <div class="rspace-header__dropdown-divider"></div>
              <button class="rspace-header__dropdown-item rspace-header__dropdown-item--danger" data-action="signout">
                🚪 Sign Out
              </button>
            </div>
          </div>
        </div>
      `;
    } else {
      header.innerHTML = `
        ${brandHTML}
        <div class="rspace-header__right">
          <button class="rspace-header__signin-btn" id="header-signin-btn">
            🔑 Sign In
          </button>
        </div>
      `;
    }

    attachHeaderListeners();
  }

  function attachHeaderListeners() {
    // Sign in button
    document.getElementById('header-signin-btn')?.addEventListener('click', () => {
      showAuthModal({
        onSuccess: () => {
          renderHeader();
          onAuthChange?.();
        },
      });
    });

    // User toggle dropdown
    const userToggle = document.getElementById('header-user-toggle');
    const dropdown = document.getElementById('header-dropdown');
    if (userToggle && dropdown) {
      userToggle.addEventListener('click', (e) => {
        e.stopPropagation();
        dropdown.classList.toggle('open');
      });
      document.addEventListener('click', () => {
        dropdown.classList.remove('open');
      });
    }

    // Dropdown actions
    header.querySelectorAll('[data-action]').forEach((el) => {
      el.addEventListener('click', (e) => {
        e.stopPropagation();
        const action = (el as HTMLElement).dataset.action;
        switch (action) {
          case 'signout':
            clearSession();
            renderHeader();
            onAuthChange?.();
            break;
          case 'profile':
            window.open('https://auth.rspace.online', '_blank');
            break;
          case 'recovery':
            window.open('https://auth.rspace.online/recover', '_blank');
            break;
        }
        document.getElementById('header-dropdown')?.classList.remove('open');
      });
    });
  }

  // Store render function so requireAuth can update the header after auth
  headerRenderFn = renderHeader;

  // Mount to DOM
  document.body.prepend(header);
  renderHeader();

  // Auto-refresh token if nearing expiry (< 7 days remaining)
  (async () => {
    const session = getSession();
    if (!session) return;
    const now = Math.floor(Date.now() / 1000);
    const remaining = session.claims.exp - now;
    if (remaining >= 7 * 24 * 60 * 60) return;
    try {
      const res = await fetch(`${ENCRYPTID_URL}/api/session/refresh`, {
        method: 'POST',
        headers: { Authorization: `Bearer ${session.accessToken}` },
      });
      if (!res.ok) return;
      const { token } = await res.json();
      if (token) {
        storeSession(token, session.claims.username || '', session.claims.did || '');
        renderHeader();
      }
    } catch { /* silently ignore */ }
  })();

  // Validate session with server — detects logout from another browser session
  (async () => {
    const session = getSession();
    if (!session) return;
    try {
      const res = await fetch(`${ENCRYPTID_URL}/api/session/verify`, {
        headers: { 'Authorization': `Bearer ${session.accessToken}` },
      });
      if (!res.ok) {
        // Session revoked or invalid — clear locally and re-render
        localStorage.removeItem(SESSION_KEY);
        localStorage.removeItem('rspace-username');
        renderHeader();
        onAuthChange?.();
      }
    } catch { /* network error — let token expire naturally */ }
  })();
}

/**
 * Require authentication before proceeding.
 * Returns true if user is authenticated, or shows auth modal and returns false.
 * Use the callback to continue after successful auth.
 */
export function requireAuth(onAuthenticated: () => void): boolean {
  if (isAuthenticated()) {
    return true;
  }
  showAuthModal({
    onSuccess: () => {
      headerRenderFn?.();
      onAuthenticated();
    },
  });
  return false;
}
