// Connecting to Stand with a sign-in code: start, open Stand's sign-in page in
// a Stand window, wait while the person signs in there, reopen or cancel, and
// continue once a new account is set up in Stand. Used by Welcome, Settings
// and the screens that offer Reconnect.
import { useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { RequestError, errorMessage, post } from '../shared/api.ts';
import type { PluginState } from '../shared/types.ts';
import { adminPage } from './boot.ts';
import { useApp } from './context.tsx';
import { SKIP_KEY } from './screens/Setup.tsx';

interface Started {
  verificationUrl: string;
  userCode: string;
  expiresAt: string;
}

interface Polled {
  status: 'connected' | 'waiting' | 'onboarding' | 'cancelled' | 'expired' | 'failed' | 'none';
  message?: string;
  live?: boolean;
}

const WINDOW = 'stand-chat-connect';
const FEATURES = 'popup,width=520,height=780';

// After connecting: setup, unless the Site is live already (WordPress picked
// up the AI assistant it had before disconnecting).
function afterConnect(live: boolean): void {
  window.sessionStorage.removeItem(SKIP_KEY);
  window.location.href = adminPage('stand-chat', live ? {} : { view: 'setup' });
}

function stoppedMessage(polled: Polled): string {
  switch (polled.status) {
    case 'cancelled':
      return __('Connecting was cancelled in the Stand window. You can start again any time.', 'stand-chat');
    case 'expired':
      return __('The sign-in code expired before you finished. Start again from here.', 'stand-chat');
    default:
      return polled.message || __("Stand Chat couldn't finish connecting. Start again from here.", 'stand-chat');
  }
}

export function useConnect() {
  const { refresh, setState } = useApp();
  const [waiting, setWaiting] = useState(false);
  const [code, setCode] = useState('');
  const [error, setError] = useState('');
  const [busy, setBusy] = useState(false);
  const popup = useRef<Window | null>(null);
  const lastUrl = useRef('');

  useEffect(() => {
    if (!waiting) return undefined;
    let stopped = false;
    // WordPress asks Stand no more often than Stand allows, however often this asks.
    const timer = window.setInterval(() => {
      void post<Polled>('/connect/poll').then(
        (polled) => {
          if (stopped || polled.status === 'waiting') return;
          stopped = true;
          popup.current?.close();
          if (polled.status === 'connected') {
            afterConnect(Boolean(polled.live));
            return;
          }
          setWaiting(false);
          if (polled.status === 'onboarding') void refresh().catch((caught) => setError(errorMessage(caught)));
          // None: the sign-in ended elsewhere, such as in another tab, without connecting.
          else setError(stoppedMessage(polled));
        },
        // Tried again on the next tick.
        () => undefined,
      );
    }, 3000);
    return () => {
      stopped = true;
      window.clearInterval(timer);
    };
  }, [waiting, refresh]);

  const start = async (): Promise<void> => {
    setError('');
    setBusy(true);
    // Open the window during the click so popup blockers allow it.
    const opened = window.open('', WINDOW, FEATURES);
    popup.current = opened;
    try {
      const started = await post<Started>('/connect/start');
      lastUrl.current = started.verificationUrl;
      setCode(started.userCode);
      // A blocked window opens from Open the Stand window instead.
      if (opened && !opened.closed) opened.location.href = started.verificationUrl;
      setWaiting(true);
    } catch (caught) {
      opened?.close();
      setError(errorMessage(caught));
    } finally {
      setBusy(false);
    }
  };

  const reopen = (): void => {
    if (popup.current && !popup.current.closed) popup.current.focus();
    else if (lastUrl.current) popup.current = window.open(lastUrl.current, WINDOW, FEATURES);
  };

  const cancel = async (): Promise<void> => {
    setError('');
    try {
      // Cancelled once WordPress agrees: while a connection is completing, it
      // says so, and this keeps waiting for it.
      setState(await post<PluginState>('/connect/cancel'));
      popup.current?.close();
      setWaiting(false);
    } catch (caught) {
      setError(errorMessage(caught));
    }
  };

  // A new account finished setting up in Stand. A quiet check, made while the
  // person finishes their profile, says nothing until there's news, and
  // waits while another change is made; it returns whether to keep checking.
  const finish = async (quiet = false): Promise<boolean> => {
    if (!quiet) {
      setError('');
      setBusy(true);
    }
    try {
      const finished = await post<Polled>('/connect/finish');
      afterConnect(Boolean(finished.live));
      return false;
    } catch (caught) {
      const unfinished = caught instanceof RequestError && (caught.code === 'stand_onboarding' || caught.code === 'stand_busy');
      if (!quiet || !unfinished) setError(errorMessage(caught));
      if (!quiet) setBusy(false);
      return unfinished;
    }
  };

  return { start, reopen, cancel, finish, waiting, code, error, busy };
}
