import { Box, Button, config, Dialog, IconButton, Menu, MenuItem, PopOut, RectCords, Spinner, Text } from 'folds';
import { Icon, Icons } from '../../components/icons';
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixClient, SyncState } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react';
import React, { MouseEventHandler, ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import {
  clearCacheAndReload,
  clearLoginData,
  initClient,
  logoutClient,
  startClient,
} from '../../../client/initMatrix';
import { SplashScreen } from '../../components/splash-screen';
import { ServerConfigsLoader } from '../../components/ServerConfigsLoader';
import { CapabilitiesProvider } from '../../hooks/useCapabilities';
import { MediaConfigProvider } from '../../hooks/useMediaConfig';
import { MatrixClientProvider } from '../../hooks/useMatrixClient';
import { PluginLoader } from '../../features/settings/plugins';
import { SpecVersions } from './SpecVersions';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useSyncState } from '../../hooks/useSyncState';
import { stopPropagation } from '../../utils/keyboard';
import { AuthMetadataProvider } from '../../hooks/useAuthMetadata';
import { getCurrentSession } from '../../state/sessions';
import { CallProviderWrapper } from '../../features/call/CallProviderWrapper';
import { isTauriMobile, startBackgroundSync, stopBackgroundSync } from '../../utils/tauri';
import { TitleBar } from '../../components/title-bar';
import { AccountSwitcher } from '../../components/account-switcher';
import { getLoginPath, withSearchParam } from '../pathUtils';
import { useViewTransitions } from '../../hooks/useViewTransitions';

function ClientRootLoading() {
  return (
    <SplashScreen>
      <Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
        <Spinner variant="Secondary" size="600" />
        <Text>Heating up</Text>
      </Box>
    </SplashScreen>
  );
}

const useLogoutListener = (mx?: MatrixClient) => {
  useEffect(() => {
    const handleLogout: HttpApiEventHandlerMap[HttpApiEvent.SessionLoggedOut] = async () => {
      mx?.stopClient();
      await mx?.clearStores();
      window.localStorage.clear();
      window.location.reload();
    };

    mx?.on(HttpApiEvent.SessionLoggedOut, handleLogout);
    return () => {
      mx?.removeListener(HttpApiEvent.SessionLoggedOut, handleLogout);
    };
  }, [mx]);
};

/**
 * Hook to handle visibility changes and restart sync on mobile
 * When the app goes to background and comes back, sync may be stale/stopped
 */
const useVisibilitySync = (mx?: MatrixClient) => {
  useEffect(() => {
    if (!mx || !isTauriMobile()) return;

    const handleVisibilityChange = () => {
      if (document.visibilityState === 'visible') {
        console.log('[useVisibilitySync] App became visible, checking sync state');
        const syncState = mx.getSyncState();
        console.log('[useVisibilitySync] Current sync state:', syncState);
        
        // If sync is not running or errored, restart it
        if (!mx.clientRunning || syncState === SyncState.Error || syncState === SyncState.Stopped || syncState === null) {
          console.log('[useVisibilitySync] Restarting sync...');
          startClient(mx).catch((err) => {
            console.error('[useVisibilitySync] Failed to restart sync:', err);
          });
        } else {
          // Force a sync even if client is running (to catch up quickly)
          console.log('[useVisibilitySync] Triggering immediate sync');
          mx.retryImmediately();
        }
      }
    };

    document.addEventListener('visibilitychange', handleVisibilityChange);
    
    // Also handle resume event for mobile (if available)
    window.addEventListener('resume', handleVisibilityChange);
    
    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
      window.removeEventListener('resume', handleVisibilityChange);
    };
  }, [mx]);
};

/**
 * Hook to start/stop native Rust background sync on mobile
 * This allows notifications to work even when the app is backgrounded
 */
const useBackgroundSync = (mx?: MatrixClient) => {
  useEffect(() => {
    if (!mx || !isTauriMobile()) return;

    // Start background sync with credentials from the Matrix client
    const startSync = async () => {
      try {
        const homeserverUrl = mx.getHomeserverUrl();
        const userId = mx.getUserId();
        const accessToken = mx.getAccessToken();
        const deviceId = mx.getDeviceId();

        if (!homeserverUrl || !userId || !accessToken || !deviceId) {
          console.warn('[useBackgroundSync] Missing credentials, cannot start background sync');
          return;
        }

        await startBackgroundSync({
          homeserverUrl,
          userId,
          accessToken,
          deviceId,
        });
        console.log('[useBackgroundSync] Background sync started');
      } catch (err) {
        console.error('[useBackgroundSync] Failed to start background sync:', err);
      }
    };

    startSync();

    // Stop background sync on cleanup
    return () => {
      stopBackgroundSync().catch((err) => {
        console.error('[useBackgroundSync] Failed to stop background sync:', err);
      });
    };
  }, [mx]);
};

type ClientRootProps = {
  children: ReactNode;
};

export function ClientRoot({ children }: ClientRootProps) {
  const [loading, setLoading] = useState(true);
  const mxRef = useRef<MatrixClient | undefined>(undefined);
  const recoverInFlight = useRef(false);
  const { baseUrl } = getCurrentSession() ?? {};

  // Enable view transitions for smooth navigation
  useViewTransitions();

  const [loadState, loadMatrix] = useAsyncCallback<MatrixClient, Error, []>(
    useCallback(() => {
      const session = getCurrentSession();
      if (!session) {
        throw new Error('No session Found!');
      }
      return initClient(session);
    }, [])
  );
  const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined;

  useEffect(() => {
    mxRef.current = mx;
  }, [mx]);

  const [startState, startMatrix] = useAsyncCallback<void, Error, [MatrixClient]>(
    useCallback((m) => startClient(m), [])
  );

  useLogoutListener(mx);
  useVisibilitySync(mx);
  useBackgroundSync(mx);

  useEffect(() => {
    if (loadState.status === AsyncStatus.Idle) {
      loadMatrix();
    }
  }, [loadState, loadMatrix]);

  useEffect(() => {
    if (mx && !mx.clientRunning) {
      startMatrix(mx);
    }
  }, [mx, startMatrix]);

  // Timeout if sync takes too long - nudge the client rather than immediately erroring
  useEffect(() => {
    if (!mx || !loading) return;

    const timeout = setTimeout(() => {
      console.warn('[ClientRoot] Sync taking too long, nudging client...');
      mx.retryImmediately();
    }, 30000);

    return () => clearTimeout(timeout);
  }, [mx, loading]);

  // Soft-recover from sync ERROR (e.g. sleep/wake with no network).
  // Never block the UI — SyncStatus / title bar already show "Connection Lost".
  useSyncState(
    mx,
    useCallback((state) => {
      const client = mxRef.current;
      if (state === SyncState.Prepared || state === SyncState.Syncing) {
        setLoading(false);
        recoverInFlight.current = false;
        return;
      }

      if (state !== SyncState.Error || !client || recoverInFlight.current) {
        return;
      }

      recoverInFlight.current = true;
      console.warn('[ClientRoot] Sync error after sleep/network blip, soft-recovering...');

      const recover = () => {
        if (!mxRef.current) {
          recoverInFlight.current = false;
          return;
        }
        const c = mxRef.current;
        if (!c.clientRunning) {
          startClient(c)
            .catch((err) => console.warn('[ClientRoot] Soft recover startClient failed:', err))
            .finally(() => {
              recoverInFlight.current = false;
            });
          return;
        }
        try {
          c.retryImmediately();
        } catch (err) {
          console.warn('[ClientRoot] Soft recover retryImmediately failed:', err);
        }
        recoverInFlight.current = false;
      };

      // Give Wi‑Fi a moment to come back after lid open / resume
      window.setTimeout(recover, 1500);
    }, [])
  );

  // Also nudge sync when the laptop wakes / network returns
  useEffect(() => {
    if (!mx) return;

    const nudge = () => {
      if (document.visibilityState !== 'visible') return;
      const syncState = mx.getSyncState();
      if (syncState === SyncState.Error || syncState === SyncState.Reconnecting || !mx.clientRunning) {
        console.log('[ClientRoot] Online/visible again, nudging sync', syncState);
        if (!mx.clientRunning) {
          startClient(mx).catch(() => {});
        } else {
          mx.retryImmediately();
        }
      }
    };

    window.addEventListener('online', nudge);
    document.addEventListener('visibilitychange', nudge);
    return () => {
      window.removeEventListener('online', nudge);
      document.removeEventListener('visibilitychange', nudge);
    };
  }, [mx]);

  return (
    <SpecVersions baseUrl={baseUrl!}>
      <TitleBar mx={mx} />
      {(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && (
        <SplashScreen>
          <Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
            <Dialog>
              <Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
                {loadState.status === AsyncStatus.Error && (
                  <Text>{`Failed to load. ${loadState.error.message}`}</Text>
                )}
                {startState.status === AsyncStatus.Error && (
                  <Text>{`Failed to start. ${startState.error.message}`}</Text>
                )}
                <Button variant="Critical" onClick={mx ? () => startMatrix(mx) : loadMatrix}>
                  <Text as="span" size="B400">
                    Retry
                  </Text>
                </Button>
              </Box>
            </Dialog>
          </Box>
        </SplashScreen>
      )}
      {loading || !mx ? (
        <ClientRootLoading />
      ) : (
        <MatrixClientProvider value={mx}>
          <PluginLoader matrixClient={mx}>
            <CallProviderWrapper>
              <ServerConfigsLoader>
                {(serverConfigs) => (
                  <CapabilitiesProvider value={serverConfigs.capabilities ?? {}}>
                    <MediaConfigProvider value={serverConfigs.mediaConfig ?? {}}>
                      <AuthMetadataProvider value={serverConfigs.authMetadata}>
                        {children}
                      </AuthMetadataProvider>
                    </MediaConfigProvider>
                  </CapabilitiesProvider>
                )}
              </ServerConfigsLoader>
            </CallProviderWrapper>
          </PluginLoader>
        </MatrixClientProvider>
      )}
    </SpecVersions>
  );
}
