import { describe, it, expect, vi, beforeEach } from 'vitest';

describe('URL Routing and Navigation', () => {
  describe('URL Path Generation', () => {
    const getTargetPath = (
      gameState: string,
      gamePin: string | null,
      role: 'HOST' | 'CLIENT',
      currentPath: string
    ): string => {
      switch (gameState) {
        case 'LANDING':
          if (gamePin && currentPath.startsWith('/play/')) {
            return `/play/${gamePin}`;
          }
          return '/';
        case 'CREATING':
        case 'GENERATING':
          return '/create';
        case 'EDITING':
          return '/edit';
        case 'LOBBY':
        case 'COUNTDOWN':
        case 'QUESTION':
        case 'REVEAL':
        case 'SCOREBOARD':
        case 'PODIUM':
        case 'HOST_RECONNECTED':
          if (gamePin) {
            return role === 'HOST' ? `/host/${gamePin}` : `/play/${gamePin}`;
          }
          return '/';
        case 'DISCONNECTED':
        case 'WAITING_TO_REJOIN':
          if (gamePin) {
            return `/play/${gamePin}`;
          }
          return '/';
        default:
          return '/';
      }
    };

    describe('LANDING state', () => {
      it('should return "/" for LANDING state without gamePin', () => {
        expect(getTargetPath('LANDING', null, 'HOST', '/')).toBe('/');
      });

      it('should preserve /play/:pin URL when LANDING with gamePin (join flow)', () => {
        expect(getTargetPath('LANDING', '123456', 'CLIENT', '/play/123456')).toBe('/play/123456');
      });

      it('should return "/" when LANDING with gamePin but not on /play/ path', () => {
        expect(getTargetPath('LANDING', '123456', 'HOST', '/')).toBe('/');
      });
    });

    describe('CREATING/GENERATING states', () => {
      it('should return "/create" for CREATING state', () => {
        expect(getTargetPath('CREATING', null, 'HOST', '/')).toBe('/create');
      });

      it('should return "/create" for GENERATING state', () => {
        expect(getTargetPath('GENERATING', null, 'HOST', '/')).toBe('/create');
      });
    });

    describe('EDITING state', () => {
      it('should return "/edit" for EDITING state', () => {
        expect(getTargetPath('EDITING', null, 'HOST', '/')).toBe('/edit');
      });
    });

    describe('In-game HOST states', () => {
      const hostStates = ['LOBBY', 'COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD', 'PODIUM', 'HOST_RECONNECTED'];
      
      hostStates.forEach(state => {
        it(`should return "/host/:pin" for ${state} state as HOST`, () => {
          expect(getTargetPath(state, '123456', 'HOST', '/')).toBe('/host/123456');
        });
      });

      it('should return "/" for HOST states without gamePin', () => {
        expect(getTargetPath('LOBBY', null, 'HOST', '/')).toBe('/');
      });
    });

    describe('In-game CLIENT states', () => {
      const clientStates = ['LOBBY', 'COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD', 'PODIUM'];
      
      clientStates.forEach(state => {
        it(`should return "/play/:pin" for ${state} state as CLIENT`, () => {
          expect(getTargetPath(state, '123456', 'CLIENT', '/')).toBe('/play/123456');
        });
      });
    });

    describe('Disconnected states', () => {
      it('should return "/play/:pin" for DISCONNECTED state with gamePin', () => {
        expect(getTargetPath('DISCONNECTED', '123456', 'CLIENT', '/')).toBe('/play/123456');
      });

      it('should return "/play/:pin" for WAITING_TO_REJOIN state with gamePin', () => {
        expect(getTargetPath('WAITING_TO_REJOIN', '123456', 'CLIENT', '/')).toBe('/play/123456');
      });

      it('should return "/" for DISCONNECTED state without gamePin', () => {
        expect(getTargetPath('DISCONNECTED', null, 'CLIENT', '/')).toBe('/');
      });
    });

    describe('Unknown states', () => {
      it('should return "/" for unknown game state', () => {
        expect(getTargetPath('UNKNOWN_STATE', '123456', 'HOST', '/')).toBe('/');
      });
    });
  });

  describe('URL Initialization Logic', () => {
    interface StoredSession {
      pin: string;
      role: 'HOST' | 'CLIENT';
      hostSecret?: string;
      playerName?: string;
      playerId?: string;
    }

    const simulateUrlInit = (
      path: string,
      storedSession: StoredSession | null
    ): { action: string; gamePin?: string; shouldReconnect?: boolean } => {
      const hostMatch = path.match(/^\/host\/(\d+)$/);
      const playMatch = path.match(/^\/play\/(\d+)$/);

      if (hostMatch) {
        const pin = hostMatch[1];
        if (storedSession && storedSession.pin === pin && storedSession.role === 'HOST') {
          return { action: 'reconnectAsHost', shouldReconnect: true };
        }
        return { action: 'navigateHome' };
      }

      if (playMatch) {
        const pin = playMatch[1];
        if (storedSession && storedSession.pin === pin && storedSession.role === 'CLIENT') {
          return { action: 'reconnectAsClient', shouldReconnect: true };
        }
        return { action: 'setGamePin', gamePin: pin };
      }

      if (path === '/create') {
        return { action: 'startCreating' };
      }

      if (path === '/edit') {
        if (!storedSession) {
          return { action: 'navigateHome' };
        }
        return { action: 'continueEditing' };
      }

      if (storedSession) {
        if (storedSession.role === 'HOST') {
          return { action: 'reconnectAsHost', shouldReconnect: true };
        }
        return { action: 'reconnectAsClient', shouldReconnect: true };
      }

      return { action: 'showLanding' };
    };

    describe('/host/:pin URL', () => {
      it('should reconnect as host when session matches', () => {
        const session: StoredSession = { pin: '123456', role: 'HOST', hostSecret: 'secret' };
        const result = simulateUrlInit('/host/123456', session);
        expect(result.action).toBe('reconnectAsHost');
        expect(result.shouldReconnect).toBe(true);
      });

      it('should navigate home when no session for host URL', () => {
        const result = simulateUrlInit('/host/123456', null);
        expect(result.action).toBe('navigateHome');
      });

      it('should navigate home when session pin does not match', () => {
        const session: StoredSession = { pin: '654321', role: 'HOST', hostSecret: 'secret' };
        const result = simulateUrlInit('/host/123456', session);
        expect(result.action).toBe('navigateHome');
      });

      it('should navigate home when session role is CLIENT not HOST', () => {
        const session: StoredSession = { pin: '123456', role: 'CLIENT', playerName: 'Test' };
        const result = simulateUrlInit('/host/123456', session);
        expect(result.action).toBe('navigateHome');
      });
    });

    describe('/play/:pin URL', () => {
      it('should reconnect as client when session matches', () => {
        const session: StoredSession = { pin: '123456', role: 'CLIENT', playerName: 'Test' };
        const result = simulateUrlInit('/play/123456', session);
        expect(result.action).toBe('reconnectAsClient');
        expect(result.shouldReconnect).toBe(true);
      });

      it('should set gamePin when no session (for join flow)', () => {
        const result = simulateUrlInit('/play/123456', null);
        expect(result.action).toBe('setGamePin');
        expect(result.gamePin).toBe('123456');
      });

      it('should set gamePin when session pin does not match', () => {
        const session: StoredSession = { pin: '654321', role: 'CLIENT', playerName: 'Test' };
        const result = simulateUrlInit('/play/123456', session);
        expect(result.action).toBe('setGamePin');
        expect(result.gamePin).toBe('123456');
      });
    });

    describe('/create URL', () => {
      it('should start creating mode', () => {
        const result = simulateUrlInit('/create', null);
        expect(result.action).toBe('startCreating');
      });

      it('should start creating even with existing session', () => {
        const session: StoredSession = { pin: '123456', role: 'HOST', hostSecret: 'secret' };
        const result = simulateUrlInit('/create', session);
        expect(result.action).toBe('startCreating');
      });
    });

    describe('/edit URL', () => {
      it('should navigate home when no session', () => {
        const result = simulateUrlInit('/edit', null);
        expect(result.action).toBe('navigateHome');
      });

      it('should continue editing when session exists', () => {
        const session: StoredSession = { pin: '123456', role: 'HOST', hostSecret: 'secret' };
        const result = simulateUrlInit('/edit', session);
        expect(result.action).toBe('continueEditing');
      });
    });

    describe('Root URL "/"', () => {
      it('should show landing when no session', () => {
        const result = simulateUrlInit('/', null);
        expect(result.action).toBe('showLanding');
      });

      it('should reconnect as host when HOST session exists', () => {
        const session: StoredSession = { pin: '123456', role: 'HOST', hostSecret: 'secret' };
        const result = simulateUrlInit('/', session);
        expect(result.action).toBe('reconnectAsHost');
      });

      it('should reconnect as client when CLIENT session exists', () => {
        const session: StoredSession = { pin: '123456', role: 'CLIENT', playerName: 'Test' };
        const result = simulateUrlInit('/', session);
        expect(result.action).toBe('reconnectAsClient');
      });
    });
  });

  describe('Navigation Functions', () => {
    describe('cancelCreation', () => {
      it('should set gameState to LANDING', () => {
        let gameState = 'CREATING';
        
        const cancelCreation = () => {
          gameState = 'LANDING';
        };
        
        cancelCreation();
        expect(gameState).toBe('LANDING');
      });

      it('should trigger URL change to "/"', () => {
        let gameState = 'CREATING';
        let currentPath = '/create';
        
        const cancelCreation = () => {
          gameState = 'LANDING';
        };
        
        cancelCreation();
        
        if (gameState === 'LANDING') {
          currentPath = '/';
        }
        
        expect(currentPath).toBe('/');
      });
    });

    describe('endGame', () => {
      it('should clear session storage', () => {
        const storage = new Map<string, string>();
        storage.set('kaboot_session', JSON.stringify({ pin: '123456', role: 'HOST' }));
        
        const clearStoredSession = () => storage.delete('kaboot_session');
        
        clearStoredSession();
        expect(storage.has('kaboot_session')).toBe(false);
      });

      it('should reset gamePin to null', () => {
        let gamePin: string | null = '123456';
        
        const endGame = () => {
          gamePin = null;
        };
        
        endGame();
        expect(gamePin).toBeNull();
      });

      it('should reset quiz to null', () => {
        let quiz: any = { title: 'Test Quiz', questions: [] };
        
        const endGame = () => {
          quiz = null;
        };
        
        endGame();
        expect(quiz).toBeNull();
      });

      it('should reset players to empty array', () => {
        let players = [{ id: 'player1', name: 'Test', score: 100 }];
        
        const endGame = () => {
          players = [];
        };
        
        endGame();
        expect(players).toEqual([]);
      });

      it('should set gameState to LANDING', () => {
        let gameState = 'PODIUM';
        
        const endGame = () => {
          gameState = 'LANDING';
        };
        
        endGame();
        expect(gameState).toBe('LANDING');
      });

      it('should navigate to "/" with replace', () => {
        let navigatedTo: string | null = null;
        let replaceUsed = false;
        
        const navigate = (path: string, options?: { replace?: boolean }) => {
          navigatedTo = path;
          replaceUsed = options?.replace ?? false;
        };
        
        navigate('/', { replace: true });
        
        expect(navigatedTo).toBe('/');
        expect(replaceUsed).toBe(true);
      });
    });

    describe('goHomeFromDisconnected', () => {
      it('should clear session storage', () => {
        const storage = new Map<string, string>();
        storage.set('kaboot_session', JSON.stringify({ pin: '123456', role: 'CLIENT' }));
        
        const clearStoredSession = () => storage.delete('kaboot_session');
        
        clearStoredSession();
        expect(storage.has('kaboot_session')).toBe(false);
      });

      it('should reset gamePin to null', () => {
        let gamePin: string | null = '123456';
        
        const goHomeFromDisconnected = () => {
          gamePin = null;
        };
        
        goHomeFromDisconnected();
        expect(gamePin).toBeNull();
      });

      it('should set gameState to LANDING', () => {
        let gameState = 'DISCONNECTED';
        
        const goHomeFromDisconnected = () => {
          gameState = 'LANDING';
        };
        
        goHomeFromDisconnected();
        expect(gameState).toBe('LANDING');
      });

      it('should clear error state', () => {
        let error: string | null = 'Connection lost';
        
        const goHomeFromDisconnected = () => {
          error = null;
        };
        
        goHomeFromDisconnected();
        expect(error).toBeNull();
      });

      it('should result in URL change to "/" (not stay on /play/:pin)', () => {
        let gameState = 'DISCONNECTED';
        let gamePin: string | null = '123456';
        
        const goHomeFromDisconnected = () => {
          gamePin = null;
          gameState = 'LANDING';
        };
        
        goHomeFromDisconnected();
        
        const getTargetPath = () => {
          if (gameState === 'LANDING' && !gamePin) {
            return '/';
          }
          return `/play/${gamePin}`;
        };
        
        expect(getTargetPath()).toBe('/');
      });
    });

    describe('backFromEditor', () => {
      it('should reset quiz to null', () => {
        let quiz: any = { title: 'Test', questions: [] };
        
        const backFromEditor = () => {
          quiz = null;
        };
        
        backFromEditor();
        expect(quiz).toBeNull();
      });

      it('should reset pendingQuizToSave to null', () => {
        let pendingQuizToSave: any = { quiz: {}, topic: 'Test' };
        
        const backFromEditor = () => {
          pendingQuizToSave = null;
        };
        
        backFromEditor();
        expect(pendingQuizToSave).toBeNull();
      });

      it('should reset sourceQuizId to null', () => {
        let sourceQuizId: string | null = 'quiz-123';
        
        const backFromEditor = () => {
          sourceQuizId = null;
        };
        
        backFromEditor();
        expect(sourceQuizId).toBeNull();
      });

      it('should set gameState to LANDING', () => {
        let gameState = 'EDITING';
        
        const backFromEditor = () => {
          gameState = 'LANDING';
        };
        
        backFromEditor();
        expect(gameState).toBe('LANDING');
      });
    });
  });

  describe('Non-Happy Path Scenarios', () => {
    describe('Page reload on different URLs', () => {
      it('should NOT get stuck on /create after cancel (regression test)', () => {
        let gameState = 'CREATING';
        let navigatedTo: string | null = null;
        
        const cancelCreation = () => {
          gameState = 'LANDING';
        };
        
        cancelCreation();
        
        if (gameState === 'LANDING') {
          navigatedTo = '/';
        }
        
        expect(gameState).toBe('LANDING');
        expect(navigatedTo).toBe('/');
      });

      it('should NOT get stuck on /host/:pin after endGame (regression test)', () => {
        let gameState = 'PODIUM';
        let gamePin: string | null = '123456';
        let sessionCleared = false;
        let navigatedTo: string | null = null;
        
        const endGame = () => {
          sessionCleared = true;
          gamePin = null;
          gameState = 'LANDING';
          navigatedTo = '/';
        };
        
        endGame();
        
        expect(sessionCleared).toBe(true);
        expect(gamePin).toBeNull();
        expect(gameState).toBe('LANDING');
        expect(navigatedTo).toBe('/');
      });

      it('should NOT get stuck on /play/:pin after goHomeFromDisconnected (regression test)', () => {
        let gameState = 'DISCONNECTED';
        let gamePin: string | null = '123456';
        let sessionCleared = false;
        
        const goHomeFromDisconnected = () => {
          sessionCleared = true;
          gamePin = null;
          gameState = 'LANDING';
        };
        
        goHomeFromDisconnected();
        
        expect(gamePin).toBeNull();
        expect(gameState).toBe('LANDING');
      });
    });

    describe('Missing or invalid sessions', () => {
      it('should redirect to home when accessing /host/:pin without host session', () => {
        const path = '/host/123456';
        const session = null;
        
        const hostMatch = path.match(/^\/host\/(\d+)$/);
        let action = 'none';
        
        if (hostMatch) {
          if (!session) {
            action = 'navigateHome';
          }
        }
        
        expect(action).toBe('navigateHome');
      });

      it('should redirect to home when accessing /host/:pin with CLIENT session', () => {
        const path = '/host/123456';
        const session: { pin: string; role: 'HOST' | 'CLIENT'; playerName?: string } = { 
          pin: '123456', 
          role: 'CLIENT', 
          playerName: 'Test' 
        };
        
        const hostMatch = path.match(/^\/host\/(\d+)$/);
        let action = 'none';
        
        if (hostMatch) {
          const pin = hostMatch[1];
          if (!session || session.role !== 'HOST' || session.pin !== pin) {
            action = 'navigateHome';
          }
        }
        
        expect(action).toBe('navigateHome');
      });

      it('should show join form when accessing /play/:pin without session (shareable link)', () => {
        const path = '/play/123456';
        const session = null;
        
        const playMatch = path.match(/^\/play\/(\d+)$/);
        let gamePin: string | null = null;
        
        if (playMatch) {
          const pin = playMatch[1];
          if (!session) {
            gamePin = pin;
          }
        }
        
        expect(gamePin).toBe('123456');
      });

      it('should redirect to home when accessing /edit without any session', () => {
        const path = '/edit';
        const session = null;
        
        let action = 'none';
        
        if (path === '/edit') {
          if (!session) {
            action = 'navigateHome';
          }
        }
        
        expect(action).toBe('navigateHome');
      });
    });

    describe('Stale session handling', () => {
      it('should handle mismatched PIN in session vs URL', () => {
        const urlPin = '123456';
        const session = { pin: '654321', role: 'HOST' as const, hostSecret: 'secret' };
        
        const shouldReconnect = session && session.pin === urlPin && session.role === 'HOST';
        
        expect(shouldReconnect).toBe(false);
      });

      it('should handle expired/deleted game on reconnection attempt', () => {
        const session = { pin: '123456', role: 'HOST' as const, hostSecret: 'secret' };
        const gameExistsOnServer = false;
        
        let action = 'none';
        
        if (session && !gameExistsOnServer) {
          action = 'clearSessionAndGoHome';
        }
        
        expect(action).toBe('clearSessionAndGoHome');
      });
    });

    describe('URL with search params preservation', () => {
      it('should preserve search params when navigating', () => {
        const currentPath = '/';
        const currentSearch = '?modal=library';
        const targetPath = '/';
        
        const fullPath = targetPath + currentSearch;
        
        expect(fullPath).toBe('/?modal=library');
      });

      it('should preserve modal param when gameState changes', () => {
        const searchParams = new URLSearchParams('?modal=settings');
        const modalParam = searchParams.get('modal');
        
        expect(modalParam).toBe('settings');
      });
    });

    describe('Race conditions', () => {
      it('should handle URL init completing before auth loads', () => {
        let authLoaded = false;
        let urlInitComplete = false;
        let gameState = 'LANDING';
        
        urlInitComplete = true;
        
        setTimeout(() => {
          authLoaded = true;
        }, 100);
        
        expect(gameState).toBe('LANDING');
        expect(urlInitComplete).toBe(true);
      });

      it('should not navigate when isInitializingFromUrl is true', () => {
        let isInitializingFromUrl = true;
        let navigateCalled = false;
        
        const urlSyncEffect = () => {
          if (isInitializingFromUrl) return;
          navigateCalled = true;
        };
        
        urlSyncEffect();
        
        expect(navigateCalled).toBe(false);
      });
    });
  });

  describe('Replace vs Push Navigation', () => {
    it('should use replace for transient states (COUNTDOWN, QUESTION, REVEAL, SCOREBOARD)', () => {
      const transientStates = ['COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD'];
      
      transientStates.forEach(state => {
        const useReplace = ['COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD'].includes(state);
        expect(useReplace).toBe(true);
      });
    });

    it('should use push for non-transient states', () => {
      const nonTransientStates = ['LANDING', 'CREATING', 'EDITING', 'LOBBY', 'PODIUM'];
      
      nonTransientStates.forEach(state => {
        const useReplace = ['COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD'].includes(state);
        expect(useReplace).toBe(false);
      });
    });
  });
});

describe('Host/Join Mode URL Parameter Tests', () => {
  describe('Mode state from URL', () => {
    const getModeFromUrl = (searchParams: URLSearchParams, initialPin?: string): 'HOST' | 'JOIN' => {
      if (initialPin) return 'JOIN';
      const modeParam = searchParams.get('mode');
      return modeParam === 'host' ? 'HOST' : 'JOIN';
    };

    it('should return JOIN by default when no mode param', () => {
      const searchParams = new URLSearchParams('');
      expect(getModeFromUrl(searchParams)).toBe('JOIN');
    });

    it('should return HOST when ?mode=host', () => {
      const searchParams = new URLSearchParams('?mode=host');
      expect(getModeFromUrl(searchParams)).toBe('HOST');
    });

    it('should return JOIN when ?mode=join', () => {
      const searchParams = new URLSearchParams('?mode=join');
      expect(getModeFromUrl(searchParams)).toBe('JOIN');
    });

    it('should return JOIN for invalid mode values', () => {
      const searchParams = new URLSearchParams('?mode=invalid');
      expect(getModeFromUrl(searchParams)).toBe('JOIN');
    });

    it('should force JOIN mode when initialPin is provided', () => {
      const searchParams = new URLSearchParams('?mode=host');
      expect(getModeFromUrl(searchParams, '123456')).toBe('JOIN');
    });

    it('should force JOIN mode with initialPin even without mode param', () => {
      const searchParams = new URLSearchParams('');
      expect(getModeFromUrl(searchParams, '123456')).toBe('JOIN');
    });
  });

  describe('Setting mode URL param', () => {
    it('should set ?mode=host when switching to HOST', () => {
      const searchParams = new URLSearchParams();
      
      const setMode = (newMode: 'HOST' | 'JOIN') => {
        if (newMode === 'HOST') {
          searchParams.set('mode', 'host');
        } else {
          searchParams.delete('mode');
        }
      };
      
      setMode('HOST');
      expect(searchParams.get('mode')).toBe('host');
    });

    it('should remove mode param when switching to JOIN', () => {
      const searchParams = new URLSearchParams('?mode=host');
      
      const setMode = (newMode: 'HOST' | 'JOIN') => {
        if (newMode === 'HOST') {
          searchParams.set('mode', 'host');
        } else {
          searchParams.delete('mode');
        }
      };
      
      setMode('JOIN');
      expect(searchParams.get('mode')).toBeNull();
    });

    it('should preserve other params when setting mode', () => {
      const searchParams = new URLSearchParams('?modal=library');
      
      const setMode = (newMode: 'HOST' | 'JOIN') => {
        if (newMode === 'HOST') {
          searchParams.set('mode', 'host');
        } else {
          searchParams.delete('mode');
        }
      };
      
      setMode('HOST');
      expect(searchParams.get('mode')).toBe('host');
      expect(searchParams.get('modal')).toBe('library');
    });
  });

  describe('Mode with modal params', () => {
    it('should support both mode and modal params together', () => {
      const searchParams = new URLSearchParams('?mode=host&modal=settings');
      
      expect(searchParams.get('mode')).toBe('host');
      expect(searchParams.get('modal')).toBe('settings');
    });

    it('should preserve mode when opening modal', () => {
      const searchParams = new URLSearchParams('?mode=host');
      
      searchParams.set('modal', 'library');
      
      expect(searchParams.get('mode')).toBe('host');
      expect(searchParams.get('modal')).toBe('library');
    });

    it('should preserve mode when closing modal', () => {
      const searchParams = new URLSearchParams('?mode=host&modal=library');
      
      searchParams.delete('modal');
      
      expect(searchParams.get('mode')).toBe('host');
      expect(searchParams.get('modal')).toBeNull();
    });
  });

  describe('URL representation', () => {
    it('should generate clean URL for JOIN mode (no mode param)', () => {
      const searchParams = new URLSearchParams();
      const url = '/' + (searchParams.toString() ? '?' + searchParams.toString() : '');
      expect(url).toBe('/');
    });

    it('should generate correct URL for HOST mode', () => {
      const searchParams = new URLSearchParams();
      searchParams.set('mode', 'host');
      const url = '/?' + searchParams.toString();
      expect(url).toBe('/?mode=host');
    });

    it('should generate correct URL for HOST mode with modal', () => {
      const searchParams = new URLSearchParams();
      searchParams.set('mode', 'host');
      searchParams.set('modal', 'settings');
      const url = '/?' + searchParams.toString();
      expect(url).toContain('mode=host');
      expect(url).toContain('modal=settings');
    });
  });
});

describe('Modal URL Parameter Tests', () => {
  describe('Modal state from URL', () => {
    it('should detect library modal from ?modal=library', () => {
      const searchParams = new URLSearchParams('?modal=library');
      const modalParam = searchParams.get('modal');
      
      const libraryOpen = modalParam === 'library';
      expect(libraryOpen).toBe(true);
    });

    it('should detect preferences modal from ?modal=preferences', () => {
      const searchParams = new URLSearchParams('?modal=preferences');
      const modalParam = searchParams.get('modal');
      
      const preferencesOpen = modalParam === 'preferences';
      expect(preferencesOpen).toBe(true);
    });

    it('should detect settings modal from ?modal=settings', () => {
      const searchParams = new URLSearchParams('?modal=settings');
      const modalParam = searchParams.get('modal');
      
      const settingsOpen = modalParam === 'settings';
      expect(settingsOpen).toBe(true);
    });

    it('should detect account modal from ?modal=account', () => {
      const searchParams = new URLSearchParams('?modal=account');
      const modalParam = searchParams.get('modal');
      
      const accountOpen = modalParam === 'account';
      expect(accountOpen).toBe(true);
    });

    it('should return false for all modals when no param', () => {
      const searchParams = new URLSearchParams('');
      const modalParam = searchParams.get('modal');
      
      expect(modalParam).toBeNull();
      expect(modalParam === 'library').toBe(false);
      expect(modalParam === 'preferences').toBe(false);
      expect(modalParam === 'settings').toBe(false);
      expect(modalParam === 'account').toBe(false);
    });

    it('should return false for all modals with unknown param value', () => {
      const searchParams = new URLSearchParams('?modal=unknown');
      const modalParam = searchParams.get('modal');
      
      expect(modalParam === 'library').toBe(false);
      expect(modalParam === 'preferences').toBe(false);
      expect(modalParam === 'settings').toBe(false);
      expect(modalParam === 'account').toBe(false);
    });
  });

  describe('Setting modal URL params', () => {
    it('should set ?modal=library when opening library', () => {
      const searchParams = new URLSearchParams();
      
      const setLibraryOpen = (open: boolean) => {
        if (open) {
          searchParams.set('modal', 'library');
        } else {
          searchParams.delete('modal');
        }
      };
      
      setLibraryOpen(true);
      expect(searchParams.get('modal')).toBe('library');
    });

    it('should clear modal param when closing modal', () => {
      const searchParams = new URLSearchParams('?modal=library');
      
      const setLibraryOpen = (open: boolean) => {
        if (open) {
          searchParams.set('modal', 'library');
        } else {
          searchParams.delete('modal');
        }
      };
      
      setLibraryOpen(false);
      expect(searchParams.get('modal')).toBeNull();
    });

    it('should replace existing modal param when opening different modal', () => {
      const searchParams = new URLSearchParams('?modal=library');
      
      searchParams.set('modal', 'settings');
      
      expect(searchParams.get('modal')).toBe('settings');
    });
  });

  describe('Modal with /play/:pin URL', () => {
    it('should work with modal param on /play/:pin URL', () => {
      const pathname = '/play/123456';
      const search = '?modal=preferences';
      const fullUrl = pathname + search;
      
      expect(fullUrl).toBe('/play/123456?modal=preferences');
      
      const searchParams = new URLSearchParams(search);
      expect(searchParams.get('modal')).toBe('preferences');
    });
  });
});

describe('OAuth Callback Handling', () => {
  describe('onSigninCallback redirect', () => {
    it('should redirect to "/" after successful OAuth callback', () => {
      let replacedPath: string | null = null;
      
      const mockReplaceState = (_data: unknown, _title: string, url: string) => {
        replacedPath = url;
      };
      
      const onSigninCallback = () => {
        mockReplaceState({}, document.title, '/');
      };
      
      onSigninCallback();
      
      expect(replacedPath).toBe('/');
    });

    it('should NOT stay on /callback path after signin', () => {
      let currentPath = '/callback';
      
      const onSigninCallback = () => {
        currentPath = '/';
      };
      
      onSigninCallback();
      
      expect(currentPath).not.toBe('/callback');
      expect(currentPath).toBe('/');
    });

    it('should clear OAuth query params (code, state) from URL', () => {
      const urlWithOAuthParams = '/callback?code=abc123&state=xyz789';
      let finalUrl: string | null = null;
      
      const onSigninCallback = () => {
        finalUrl = '/';
      };
      
      onSigninCallback();
      
      expect(finalUrl).toBe('/');
      expect(finalUrl).not.toContain('code=');
      expect(finalUrl).not.toContain('state=');
    });
  });

  describe('URL initialization skips /callback', () => {
    const shouldSkipUrlInit = (pathname: string): boolean => {
      return pathname === '/callback';
    };

    it('should skip URL initialization on /callback path', () => {
      expect(shouldSkipUrlInit('/callback')).toBe(true);
    });

    it('should NOT skip URL initialization on other paths', () => {
      expect(shouldSkipUrlInit('/')).toBe(false);
      expect(shouldSkipUrlInit('/create')).toBe(false);
      expect(shouldSkipUrlInit('/edit')).toBe(false);
      expect(shouldSkipUrlInit('/host/123456')).toBe(false);
      expect(shouldSkipUrlInit('/play/123456')).toBe(false);
    });

    it('should not interfere with normal navigation when not on /callback', () => {
      let urlInitRan = false;
      const pathname: string = '/';
      
      const initFromUrl = () => {
        if (pathname === '/callback') return;
        urlInitRan = true;
      };
      
      initFromUrl();
      
      expect(urlInitRan).toBe(true);
    });

    it('should prevent URL sync from running during OAuth flow', () => {
      let urlSyncRan = false;
      const pathname = '/callback';
      
      const syncUrl = () => {
        if (pathname === '/callback') return;
        urlSyncRan = true;
      };
      
      syncUrl();
      
      expect(urlSyncRan).toBe(false);
    });
  });

  describe('localStorage preservation during OAuth', () => {
    it('should NOT clear OIDC state from localStorage on signout', () => {
      const storage = new Map<string, string>();
      storage.set('kaboot_session', JSON.stringify({ pin: '123456' }));
      storage.set('oidc.user:https://auth.example.com:client-id', 'oidc-state');
      storage.set('oidc.some-other-key', 'other-state');
      
      const onRemoveUser = () => {
        storage.delete('kaboot_session');
      };
      
      onRemoveUser();
      
      expect(storage.has('kaboot_session')).toBe(false);
      expect(storage.has('oidc.user:https://auth.example.com:client-id')).toBe(true);
      expect(storage.has('oidc.some-other-key')).toBe(true);
    });

    it('should only clear kaboot_session on signout', () => {
      const storage = new Map<string, string>();
      storage.set('kaboot_session', 'session-data');
      storage.set('other_app_data', 'preserved');
      storage.set('user_preferences', 'also-preserved');
      
      const onRemoveUser = () => {
        storage.delete('kaboot_session');
      };
      
      onRemoveUser();
      
      expect(storage.has('kaboot_session')).toBe(false);
      expect(storage.get('other_app_data')).toBe('preserved');
      expect(storage.get('user_preferences')).toBe('also-preserved');
    });
  });

  describe('OAuth callback regression tests', () => {
    it('should NOT get stuck on /callback after login (regression test)', () => {
      let currentPath: string = '/callback?code=auth_code&state=random_state';
      
      const onSigninCallback = () => {
        currentPath = '/';
      };
      
      onSigninCallback();
      
      expect(currentPath).toBe('/');
      expect(currentPath.includes('callback')).toBe(false);
    });

    it('should NOT show "No matching state found" error by preserving OIDC storage', () => {
      const storage = new Map<string, string>();
      const oidcStateKey = 'oidc.state.abc123';
      storage.set(oidcStateKey, JSON.stringify({ nonce: 'xyz', redirect_uri: '/callback' }));
      storage.set('kaboot_session', 'old-session');
      
      const onRemoveUser = () => {
        storage.delete('kaboot_session');
      };
      
      onRemoveUser();
      
      expect(storage.has(oidcStateKey)).toBe(true);
    });

    it('should handle multiple rapid signin/signout cycles', () => {
      const storage = new Map<string, string>();
      let signinCount = 0;
      let signoutCount = 0;
      
      const onSigninCallback = () => {
        signinCount++;
        storage.set('kaboot_session', `session-${signinCount}`);
      };
      
      const onRemoveUser = () => {
        signoutCount++;
        storage.delete('kaboot_session');
      };
      
      onSigninCallback();
      onRemoveUser();
      onSigninCallback();
      onRemoveUser();
      onSigninCallback();
      
      expect(signinCount).toBe(3);
      expect(signoutCount).toBe(2);
      expect(storage.has('kaboot_session')).toBe(true);
    });
  });
});

describe('State Sync with Async Data Loading', () => {
  describe('Settings modal data sync', () => {
    it('should update editingDefaultConfig when defaultConfig loads', () => {
      const DEFAULT_CONFIG = { shuffleQuestions: false };
      const LOADED_CONFIG = { shuffleQuestions: true, streakBonusEnabled: true };
      
      let defaultConfig = DEFAULT_CONFIG;
      let editingDefaultConfig = DEFAULT_CONFIG;
      let defaultConfigOpen = true;
      
      defaultConfig = LOADED_CONFIG;
      
      if (defaultConfigOpen) {
        editingDefaultConfig = defaultConfig;
      }
      
      expect(editingDefaultConfig).toEqual(LOADED_CONFIG);
    });

    it('should not update editingDefaultConfig when modal is closed', () => {
      const DEFAULT_CONFIG = { shuffleQuestions: false };
      const LOADED_CONFIG = { shuffleQuestions: true };
      
      let defaultConfig = DEFAULT_CONFIG;
      let editingDefaultConfig: any = null;
      let defaultConfigOpen = false;
      
      defaultConfig = LOADED_CONFIG;
      
      if (defaultConfigOpen) {
        editingDefaultConfig = defaultConfig;
      } else {
        editingDefaultConfig = null;
      }
      
      expect(editingDefaultConfig).toBeNull();
    });
  });

  describe('Preferences modal data sync', () => {
    it('should update localPrefs when preferences loads', () => {
      const DEFAULT_PREFS = { colorScheme: 'blue' };
      const LOADED_PREFS = { colorScheme: 'purple', geminiApiKey: 'key123' };
      
      let preferences = DEFAULT_PREFS;
      let localPrefs = DEFAULT_PREFS;
      let isOpen = true;
      
      preferences = LOADED_PREFS;
      
      if (isOpen) {
        localPrefs = preferences;
      }
      
      expect(localPrefs).toEqual(LOADED_PREFS);
    });
  });
});
