import React, { useCallback, useEffect, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import {
  Dialog,
  Overlay,
  OverlayCenter,
  OverlayBackdrop,
  Header,
  config,
  Box,
  Text,
  IconButton,
  Icon,
  Icons,
  color,
  Button,
  Spinner,
} from 'folds';
import { useTranslation } from 'react-i18next';
import { isNativePlatform } from '../../utils/capacitor';
import {
  usePushNotificationStatus,
  useRegisterPushNotifications,
  usePushEnabled,
} from '../../hooks/usePushNotifications';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { stopPropagation } from '../../utils/keyboard';

// 7-day cooldown between dismissals. Short enough that users who change their mind
// don't have to dig into settings to find the toggle; long enough that saying "not now"
// doesn't feel ignored.
const DISMISS_KEY = 'vojo_push_prompt_dismissed_at';
const DISMISS_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;

// Slight delay so the prompt doesn't collide with login/first-render UI.
const INITIAL_DELAY_MS = 1500;

const wasRecentlyDismissed = (): boolean => {
  const raw = localStorage.getItem(DISMISS_KEY);
  if (!raw) return false;
  const ts = Number(raw);
  if (!Number.isFinite(ts)) return false;
  return Date.now() - ts < DISMISS_COOLDOWN_MS;
};

const markDismissed = (): void => {
  localStorage.setItem(DISMISS_KEY, String(Date.now()));
};

const noop = (): void => undefined;

export function PushPermissionPrompt() {
  const { t } = useTranslation();
  const status = usePushNotificationStatus();
  const register = useRegisterPushNotifications();
  const enabled = usePushEnabled();

  const [visible, setVisible] = useState(false);

  const [registerState, doRegister] = useAsyncCallback(
    useCallback(async () => {
      await register();
    }, [register])
  );
  const busy = registerState.status === AsyncStatus.Loading;

  useEffect(() => {
    // Only nag on native — browsers have their own permission UX and stacking
    // our soft prompt on top just spams the user.
    if (!isNativePlatform()) return undefined;
    if (status !== 'prompt') return undefined;
    // `enabled` is reactive via usePushEnabled — if push gets turned on from
    // anywhere else (settings, another tab) during the delay, the effect reruns,
    // cleanup cancels the timer, and the prompt stays hidden.
    if (enabled) {
      setVisible(false);
      return undefined;
    }
    if (wasRecentlyDismissed()) return undefined;

    const timer = setTimeout(() => setVisible(true), INITIAL_DELAY_MS);
    return () => clearTimeout(timer);
  }, [status, enabled]);

  // Close on a successful enable — the status change would hide it too, but
  // closing eagerly avoids a visible flicker while Capacitor round-trips permissions.
  useEffect(() => {
    if (registerState.status === AsyncStatus.Success) setVisible(false);
  }, [registerState.status]);

  // Dismiss paths are guarded against firing mid-register: closing during the
  // native permission round-trip would stamp the 7-day cooldown AND hide any
  // transient error, leaving the user stuck with push off and no nag for a week.
  const handleLater = () => {
    if (busy) return;
    markDismissed();
    setVisible(false);
  };

  const handleEnable = () => {
    if (busy) return;
    // useAsyncCallback re-throws so the state can be read via registerState;
    // we don't need the rejection at the call site and without .catch it
    // surfaces as an unhandled rejection for expected permission_denied flows.
    doRegister().catch(noop);
  };

  if (!visible) return null;

  const denied =
    registerState.status === AsyncStatus.Error &&
    (registerState.error as Error | undefined)?.message === 'permission_denied';

  return (
    <Overlay open backdrop={<OverlayBackdrop />}>
      <OverlayCenter>
        <FocusTrap
          focusTrapOptions={{
            initialFocus: false,
            onDeactivate: handleLater,
            // Lock out click-outside / Esc while register() is in-flight — same
            // reason as handleLater's busy guard: we don't want to cooldown the
            // prompt on an accidental dismiss during the native permission dialog.
            clickOutsideDeactivates: () => !busy,
            escapeDeactivates: (ev) => {
              if (busy) return false;
              return stopPropagation(ev);
            },
          }}
        >
          <Dialog variant="Surface">
            <Header
              style={{
                padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
                borderBottomWidth: config.borderWidth.B300,
              }}
              variant="Surface"
              size="500"
            >
              <Box grow="Yes">
                <Text size="H4">{t('Settings.push_prompt_title')}</Text>
              </Box>
              <IconButton size="300" onClick={handleLater} radii="300" disabled={busy}>
                <Icon src={Icons.Cross} />
              </IconButton>
            </Header>
            <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
              <Text priority="400">{t('Settings.push_prompt_body')}</Text>
              {denied && (
                <Text size="T200" style={{ color: color.Critical.Main }}>
                  {t('Settings.push_permission_blocked')}
                </Text>
              )}
              <Box direction="Row" gap="200" justifyContent="End">
                <Button variant="Secondary" fill="Soft" onClick={handleLater} disabled={busy}>
                  <Text size="B400">{t('Settings.push_prompt_later')}</Text>
                </Button>
                <Button
                  variant="Primary"
                  onClick={handleEnable}
                  before={busy ? <Spinner fill="Solid" variant="Primary" size="200" /> : undefined}
                  disabled={busy}
                >
                  <Text size="B400">{t('Settings.enable')}</Text>
                </Button>
              </Box>
            </Box>
          </Dialog>
        </FocusTrap>
      </OverlayCenter>
    </Overlay>
  );
}
