/**
 * PushPermissionPrompt — post-install permission bottom sheet (D-08, UI-SPEC Surface 1).
 *
 * Shows after the PWA is installed (isInstalled() === true) and when:
 *   - Notification.permission === 'default' (not yet asked)
 *   - The user hasn't dismissed via "Not now" (localStorage pushPermissionDismissed !== '1')
 *
 * Accessibility:
 *   - role="dialog", aria-modal="true", aria-labelledby pointing to the heading
 *   - 48px primary CTA, 44px secondary ghost button
 *   - Backdrop does NOT dismiss (permission UX must be explicit — per UI-SPEC Surface 1)
 *
 * Security: T-02d-01 — all text is plain-text JSX children; no dangerouslySetInnerHTML.
 *
 * iOS constraint (D-08 / CLAUDE.md iOS table):
 *   subscribe() is called synchronously inside the onClick handler — no await before
 *   the pushManager.subscribe() call satisfies the iOS user-gesture requirement.
 */

import { useState, useEffect, useId } from 'react';
import { Bell, Loader2, X } from 'lucide-react';
import { usePushSubscription } from '../hooks/usePushSubscription.js';

// fetchVapidKey is the internal helper; we import it directly via the module
// rather than re-exporting it through usePushSubscription, since we need to
// store the resolved key in state (not just warm the cache).
async function fetchVapidKeyForPrompt(): Promise<string | null> {
  try {
    const cached = sessionStorage.getItem('vapidPublicKey');
    if (cached) return cached;
    const res = await fetch('/api/push/vapid-public-key', { credentials: 'include' });
    if (!res.ok) return null;
    const data = (await res.json()) as { publicKey: string };
    if (data.publicKey) {
      sessionStorage.setItem('vapidPublicKey', data.publicKey);
    }
    return data.publicKey ?? null;
  } catch {
    return null;
  }
}

// ── installed state check ──────────────────────────────────────────────────

function isInstalled(): boolean {
  return (
    window.matchMedia('(display-mode: standalone)').matches ||
    (navigator as unknown as { standalone?: boolean }).standalone === true
  );
}

// ── localStorage guards ────────────────────────────────────────────────────

function readDismissed(): boolean {
  try {
    return localStorage.getItem('pushPermissionDismissed') === '1';
  } catch {
    return false;
  }
}

function persistDismissed(): void {
  try {
    localStorage.setItem('pushPermissionDismissed', '1');
  } catch {
    // Private mode / storage disabled — ignore
  }
}

// ── PushPermissionPrompt ───────────────────────────────────────────────────

interface PushPermissionPromptProps {
  /** Optional callback after the prompt is closed (granted or dismissed) */
  onClose?: () => void;
}

export function PushPermissionPrompt({ onClose }: PushPermissionPromptProps) {
  const [dismissed, setDismissed] = useState<boolean>(readDismissed);
  const [installed, setInstalled] = useState(false);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  // CR-04: Pre-fetch the VAPID key into state so the tap handler can call
  // subscribe(registration, vapidKey) without any await before pushManager.subscribe().
  // Button is disabled until the key is ready (null = not yet loaded).
  const [vapidKey, setVapidKey] = useState<string | null>(null);
  // NEW-CR-01: Pre-resolve the ServiceWorkerRegistration into state so the tap
  // handler has ZERO awaits between the user gesture and pushManager.subscribe().
  // Any await (including navigator.serviceWorker.ready) between the tap and
  // pushManager.subscribe() breaks the iOS user-gesture requirement.
  const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null);
  const headingId = useId();

  const { subscribe, permission } = usePushSubscription();

  useEffect(() => {
    setInstalled(isInstalled());
  }, []);

  // Pre-fetch the VAPID key into state while the prompt is visible.
  // CR-04: We store the resolved key in state (not just sessionStorage) so the
  // tap handler has synchronous access — no network await inside the tap path.
  useEffect(() => {
    if (!installed) return;
    if (permission !== 'default') return;
    if (dismissed) return;
    void fetchVapidKeyForPrompt().then((key) => {
      if (key) setVapidKey(key);
    });
  }, [installed, permission, dismissed]);

  // NEW-CR-01: Pre-resolve the ServiceWorkerRegistration in a useEffect so the
  // tap handler never has to await navigator.serviceWorker.ready.
  // navigator.serviceWorker.ready resolves once the SW is active; doing this
  // eagerly means the result is in state before the user can tap the button.
  useEffect(() => {
    if (!installed) return;
    if (permission !== 'default') return;
    if (dismissed) return;
    if (!navigator.serviceWorker) return;
    void navigator.serviceWorker.ready.then((reg) => {
      setSwRegistration(reg);
    });
  }, [installed, permission, dismissed]);

  // Don't render when: not installed, already granted/denied, or dismissed
  if (!installed) return null;
  if (permission !== 'default') return null;
  if (dismissed) return null;

  function handleDismiss() {
    persistDismissed();
    setDismissed(true);
    onClose?.();
  }

  // onClick handler — subscribe() called synchronously (iOS user-gesture requirement).
  // NEW-CR-01: Both vapidKey AND swRegistration are pre-resolved in state (useEffects above).
  // There is ZERO await between the tap gesture and registration.pushManager.subscribe()
  // inside subscribe() — the iOS gesture gate is fully satisfied.
  function handleEnableClick() {
    if (loading || !vapidKey || !swRegistration) return;
    setLoading(true);
    setError(null);

    // Capture both synchronously — no await in this scope before subscribe().
    const resolvedVapidKey = vapidKey;
    const resolvedRegistration = swRegistration;

    void (async () => {
      try {
        await subscribe(resolvedRegistration, resolvedVapidKey);
        // On success: close the prompt (permission is now 'granted')
        setLoading(false);
        onClose?.();
      } catch (err) {
        setLoading(false);
        if (err instanceof Error && err.name === 'NotAllowedError') {
          // User denied in the native browser dialog — close the sheet
          setDismissed(true);
          onClose?.();
        } else {
          setError('Something went wrong. Please try again.');
        }
      }
    })();
  }

  return (
    // Full-screen backdrop — NO backdrop click-dismiss (permission UX must be explicit)
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={headingId}
      style={{
        position: 'fixed',
        inset: 0,
        background: 'var(--color-overlay, rgba(0,0,0,0.32))',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'flex-end',
        zIndex: 1000,
      }}
    >
      {/* Sheet */}
      <div
        style={{
          background: 'var(--color-surface, #ffffff)',
          borderRadius: '12px 12px 0 0',
          padding: 'var(--space-6, 24px)',
          maxHeight: '90dvh',
          overflowY: 'auto',
          display: 'flex',
          flexDirection: 'column',
          gap: 'var(--space-4, 16px)',
        }}
      >
        {/* Header */}
        <div
          style={{
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'space-between',
          }}
        >
          <h2
            id={headingId}
            style={{
              margin: 0,
              fontSize: 'var(--text-heading-size, 18px)',
              fontWeight: 'var(--text-heading-weight, 600)',
              lineHeight: 'var(--text-heading-line-height, 1.25)',
              color: 'var(--color-text-primary, #111318)',
              fontFamily: 'var(--font-family-base, system-ui, sans-serif)',
            }}
          >
            Stay in the loop
          </h2>
          {/* Dismiss X — "Not now" via header close */}
          <button
            onClick={handleDismiss}
            aria-label="Dismiss notification prompt"
            style={{
              background: 'none',
              border: 'none',
              cursor: 'pointer',
              minWidth: '44px',
              minHeight: '44px',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: 'var(--color-text-secondary, #5c6472)',
              borderRadius: 'var(--space-1, 4px)',
              flexShrink: 0,
            }}
          >
            <X size={20} aria-hidden="true" />
          </button>
        </div>

        {/* Bell icon + body */}
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            gap: 'var(--space-3, 12px)',
            textAlign: 'center',
          }}
        >
          <Bell size={40} aria-hidden="true" style={{ color: 'var(--color-member-0, #4A90D9)' }} />
          <p
            style={{
              margin: 0,
              fontSize: 'var(--text-body-size, 15px)',
              lineHeight: 'var(--text-body-line-height, 1.5)',
              color: 'var(--color-text-secondary, #5c6472)',
              fontFamily: 'var(--font-family-base, system-ui, sans-serif)',
            }}
          >
            Get notified when events are coming up or your family makes changes.
          </p>
        </div>

        {/* Error message */}
        {error && (
          <p
            role="alert"
            style={{
              margin: 0,
              fontSize: 'var(--text-label-size, 13px)',
              color: 'var(--color-destructive, #DC2626)',
              fontFamily: 'var(--font-family-base, system-ui, sans-serif)',
              textAlign: 'center',
            }}
          >
            {error}
          </p>
        )}

        {/* Actions */}
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: 'var(--space-2, 8px)',
          }}
        >
          {/* Primary CTA — 48px, accent color */}
          {/* NEW-CR-01: disabled until BOTH vapidKey and swRegistration are ready */}
          <button
            onClick={handleEnableClick}
            disabled={loading || !vapidKey || !swRegistration}
            aria-busy={loading}
            style={{
              background: 'var(--color-member-0, #4A90D9)',
              color: '#ffffff',
              border: 'none',
              borderRadius: 'var(--space-1, 4px)',
              minHeight: '48px',
              padding: '0 var(--space-4, 16px)',
              fontSize: 'var(--text-label-size, 13px)',
              fontWeight: 600,
              cursor: loading ? 'not-allowed' : 'pointer',
              fontFamily: 'inherit',
              alignSelf: 'stretch',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              gap: 'var(--space-2, 8px)',
              opacity: loading ? 0.7 : 1,
            }}
          >
            {loading ? (
              <>
                <Loader2
                  size={16}
                  aria-hidden="true"
                  style={{ animation: 'spin 1s linear infinite' }}
                />
                Enabling...
              </>
            ) : (
              'Enable Notifications'
            )}
          </button>

          {/* Secondary — "Not now", 44px ghost */}
          <button
            onClick={handleDismiss}
            disabled={loading}
            style={{
              background: 'none',
              color: 'var(--color-text-secondary, #5c6472)',
              border: 'none',
              borderRadius: 'var(--space-1, 4px)',
              minHeight: '44px',
              padding: '0 var(--space-4, 16px)',
              fontSize: 'var(--text-label-size, 13px)',
              fontWeight: 400,
              cursor: loading ? 'not-allowed' : 'pointer',
              fontFamily: 'inherit',
              alignSelf: 'stretch',
            }}
          >
            Not now
          </button>
        </div>
      </div>
    </div>
  );
}
