import { useEffect, useState } from 'react';
import { getOrCreateEncryptionKey } from './crypto';
import { HEALTH_URL, HEALTH_POLL_INTERVAL_MS, HEALTH_POLL_TIMEOUT_MS } from './config';

interface Props {
  /** Called once the embedded server responds to a health check. */
  onReady: () => void;
}

/**
 * Shown while the embedded Node.js server (nodejs-mobile) boots in local mode.
 * Starts the engine on mount, hands over the encryption key, then polls
 * /api/health until it responds. Surfaces a real error — instead of spinning
 * forever — if the key can't be produced, the server reports a startup error
 * over the channel, or the health check never succeeds within the timeout.
 */
export default function LoadingScreen({ onReady }: Props) {
  const [error, setError] = useState('');

  useEffect(() => {
    let cancelled = false;
    let pollTimer: ReturnType<typeof setTimeout>;
    const deadline = Date.now() + HEALTH_POLL_TIMEOUT_MS;

    function fail(msg: string) {
      if (!cancelled) setError(msg);
    }

    function pollHealth() {
      if (cancelled) return;
      if (Date.now() > deadline) {
        fail('The local server did not start in time. Please reopen the app to try again.');
        return;
      }
      fetch(HEALTH_URL)
        .then(res => {
          if (cancelled) return;
          if (res.ok) onReady();
          else pollTimer = setTimeout(pollHealth, HEALTH_POLL_INTERVAL_MS);
        })
        .catch(() => {
          if (!cancelled) pollTimer = setTimeout(pollHealth, HEALTH_POLL_INTERVAL_MS);
        });
    }

    const nodejs = window.nodejs;
    if (!nodejs) {
      setError('Local server engine is unavailable on this platform.');
      return;
    }

    nodejs.start('main.js', err => {
      if (cancelled) return;
      if (err) {
        fail('Failed to start local server: ' + String(err));
        return;
      }

      // The embedded process relays fatal startup errors as { type: 'serverError' }
      // (see main.js). Register the listener before replying to 'ready' so we
      // never miss it, and hand over the device-bound encryption key once main.js
      // reports ready — main.js waits for it before requiring the server.
      nodejs.channel.on('message', msg => {
        if (cancelled) return;
        const m = msg as { type?: string; message?: string };
        if (m?.type === 'serverError') {
          fail('The local server failed to start: ' + (m.message || 'unknown error'));
          return;
        }
        if (m?.type !== 'ready') return;
        getOrCreateEncryptionKey()
          .then(key => {
            if (!cancelled) nodejs.channel.post('message', { type: 'encryptionKey', key });
          })
          .catch(() => fail('Could not unlock secure storage on this device.'));
      });

      pollHealth();
    });

    return () => {
      cancelled = true;
      clearTimeout(pollTimer);
    };
  }, [onReady]);

  return (
    <div className="setup-root">
      <div className="setup-card center">
        {error ? (
          <>
            <h1 className="setup-title">Couldn't start local server</h1>
            <p className="setup-subtitle">{error}</p>
          </>
        ) : (
          <>
            <div className="spinner" />
            <p className="setup-subtitle">Starting Bill Tracker…</p>
          </>
        )}
      </div>
    </div>
  );
}
