// JoinGame.test.tsx

import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { vi } from 'vitest';
import JoinGame from './JoinGame';
import { httpsCallable } from 'firebase/functions';
import { ensureAnonymousAuth } from '../../firebase/auth/auth';
import usePlayerStore from '../../store/usePlayerStore';

// Mock the dependencies
vi.mock('firebase/functions');
vi.mock('../../firebase/auth/auth');
vi.mock('../../store/usePlayerStore');

const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
    const actual = await vi.importActual('react-router-dom');
    return {
        ...actual,
        useNavigate: () => mockNavigate,
    };
});

describe('JoinGame Component', () => {

    const mockJoinGame = vi.fn();

    beforeEach(() => {
        vi.clearAllMocks();
        (usePlayerStore as unknown as vi.Mock).mockReturnValue({ name: '', setName: vi.fn() });
        (httpsCallable as unknown as vi.Mock).mockReturnValue(mockJoinGame);
        (ensureAnonymousAuth as unknown as vi.Mock).mockResolvedValue(null);
    });

    it('renders game code input when player name is set', () => {
        (usePlayerStore as unknown as vi.Mock).mockReturnValue({ name: 'TestPlayer', setName: vi.fn() });

        render(
            <BrowserRouter>
                <JoinGame />
            </BrowserRouter>
        );

        expect(screen.getByText('Join a Game')).toBeInTheDocument();
        expect(screen.getByLabelText('Game Code')).toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Join Game' })).toBeInTheDocument();
    });

    it('validates game code format', async () => {
        render(
            <BrowserRouter>
                <JoinGame />
            </BrowserRouter>
        );

        const input = screen.getByLabelText('Game Code');
        const submitButton = screen.getByRole('button', { name: 'Join Game' });

        fireEvent.change(input, { target: { value: 'ABC12' } });
        fireEvent.click(submitButton);

        await waitFor(() => {
            expect(screen.getByText('Invalid game code. Please enter a 6-character alphanumeric code.')).toBeInTheDocument();
        });
    });

    it('successfully joins a game', async () => {
        (usePlayerStore as unknown as vi.Mock).mockReturnValue('TestPlayer');
        mockJoinGame.mockResolvedValue({ data: { gameId: 'testGameId' } });

        render(
            <BrowserRouter>
                <JoinGame />
            </BrowserRouter>
        );

        const input = screen.getByLabelText('Game Code');
        const submitButton = screen.getByRole('button', { name: 'Join Game' });

        fireEvent.change(input, { target: { value: 'ABC123' } });
        fireEvent.click(submitButton);

        await waitFor(() => {
            expect(ensureAnonymousAuth).toHaveBeenCalled();
            expect(mockJoinGame).toHaveBeenCalledWith({ gameCode: 'ABC123', playerName: 'TestPlayer' });
            expect(mockNavigate).toHaveBeenCalledWith('/game-lobby/testGameId');
        });
    });

    it('handles join game error', async () => {
        (httpsCallable as unknown as vi.Mock).mockReturnValue(() => Promise.reject(new Error('Game not found')));

        render(
            <BrowserRouter>
                <JoinGame />
            </BrowserRouter>
        );

        const input = screen.getByLabelText('Game Code');
        const submitButton = screen.getByRole('button', { name: 'Join Game' });

        fireEvent.change(input, { target: { value: 'ABC123' } });
        fireEvent.click(submitButton);

        await waitFor(() => {
            expect(screen.getByText('Game not found')).toBeInTheDocument();
        });
    });
});

// joinGame.ts

import * as functions from 'firebase-functions';
import { db } from './firebase/config';
import { Game, Player } from './types';

export const joinGame = functions.https.onCall(async (data, context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError(
            'unauthenticated',
            'The function must be called while authenticated.'
        );
    }

    const { gameCode, playerName } = data;

    if (!gameCode || typeof gameCode !== 'string' || gameCode.length !== 6) {
        throw new functions.https.HttpsError(
            'invalid-argument',
            'Game code must be a 6-character string.'
        );
    }

    if (!playerName || typeof playerName !== 'string' || playerName.trim().length === 0) {
        throw new functions.https.HttpsError(
            'invalid-argument',
            'Player name is required.'
        );
    }

    try {
        // Find the game with the given code
        const gamesSnapshot = await db.collection('games').where('gameCode', '==', gameCode).get();

        if (gamesSnapshot.empty) {
            throw new functions.https.HttpsError('not-found', 'Game not found.');
        }

        const gameDoc = gamesSnapshot.docs[0];
        const gameData = gameDoc.data() as Game;

        // Check if the game is full
        const totalPlayers = gameData.teams.reduce((sum, team) => sum + team.players.length, 0);
        if (totalPlayers >= gameData.numberOfTeams * gameData.playersPerTeam) {
            throw new functions.https.HttpsError('failed-precondition', 'The game is full.');
        }

        // Find a team with an open slot
        const teamIndex = gameData.teams.findIndex(team => team.players.length < gameData.playersPerTeam);
        if (teamIndex === -1) {
            throw new functions.https.HttpsError('failed-precondition', 'No open slots in any team.');
        }

        // Create a new player
        const newPlayer: Player = {
            name: playerName.trim()
        };

        // Add the player to the team
        await gameDoc.ref.update({
            [`teams.${teamIndex}.players`]: [...gameData.teams[teamIndex].players, newPlayer],
        });

        return { gameId: gameDoc.id };
    } catch (error) {
        console.error('Error joining game:', error);
        if (error instanceof functions.https.HttpsError) {
            // If it's already an HttpsError, rethrow it
            throw error;
        }
        // For any other type of error, throw a generic error
        throw new functions.https.HttpsError('internal', 'Unable to join the game at this time.');
    }
});

// createGame.ts

import * as functions from 'firebase-functions';
import { db as firestore } from './firebase/config';
import { FieldValue } from 'firebase-admin/firestore';
import { v4 as uuidv4 } from 'uuid';
import { Game, Team, Player } from './types';

/**
 * Firebase Cloud Function to create a new game session.
 * Initializes a game document in Firestore with specified parameters,
 * generates a unique game code, assigns the creator to a team,
 * and sets them as the initial Round Host.
 */
export const createGame = functions.https.onCall(async (data, context) => {
  // Ensure the user is authenticated (using Anonymous Authentication)
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'The function must be called while authenticated.'
    );
  }

  const { name, numberOfTeams, playersPerTeam, numberOfRounds, jokerUsage } = data;

  // Validate input parameters
  if (
    !name ||
    typeof name !== 'string' ||
    name.trim().length < 3 ||
    name.trim().length > 20
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Name is required and must be between 3 and 20 characters.'
    );
  }

  if (
    !numberOfTeams ||
    typeof numberOfTeams !== 'number' ||
    numberOfTeams <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of teams is required and must be a positive integer.'
    );
  }

  if (
    !playersPerTeam ||
    typeof playersPerTeam !== 'number' ||
    playersPerTeam <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of players per team is required and must be a positive integer.'
    );
  }

  if (
    !numberOfRounds ||
    typeof numberOfRounds !== 'number' ||
    numberOfRounds <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of rounds is required and must be a positive integer.'
    );
  }

  if (typeof jokerUsage !== 'boolean') {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Joker usage must be a boolean.'
    );
  }

  /**
   * Generates a unique game code.
   * Uses a 6-character UUID substring and ensures uniqueness by checking Firestore.
   */
  const generateUniqueGameCode = async (): Promise<string> => {
    const code = uuidv4().slice(0, 6).toUpperCase(); // e.g., 'A1B2C3'
    const existingGame = await firestore
      .collection('games')
      .where('gameCode', '==', code)
      .get();
    if (!existingGame.empty) {
      // Collision detected, retry generating a code
      return generateUniqueGameCode();
    }
    return code;
  };

  try {
    const gameCode = await generateUniqueGameCode();

    const teams: Team[] = Array.from({ length: numberOfTeams }, (_, i) => ({
      teamName: `Team ${i + 1}`,
      players: [],
    }));

    const creatorPlayer: Player = {
      name: name.trim(),
    };

    // Add creator to the first team
    teams[0].players.push(creatorPlayer);

    const newGame: Game = {
      gameCode,
      numberOfTeams,
      playersPerTeam,
      numberOfRounds,
      jokerUsage,
      createdBy: name.trim(),
      roundHost: name.trim(),
      createdAt: FieldValue.serverTimestamp(),
      teams,
      currentRound: 0,
    };

    const gameRef = await firestore.collection('games').add(newGame);

    return { gameId: gameRef.id, gameCode };
  } catch (error) {
    console.error('Error creating game:', error);
    if (error instanceof functions.https.HttpsError) {
      // If it's already an HttpsError, rethrow it
      throw error;
    }
    // For any other type of error, throw a generic error
    throw new functions.https.HttpsError('internal', 'Unable to create game at this time.');
  }
});

// JoinGame.tsx

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { httpsCallable } from 'firebase/functions';
import { functions } from '../../firebase/config';
import { ensureAnonymousAuth } from '../../firebase/auth/auth';
import usePlayerStore from '../../store/usePlayerStore';
import NameEntry from '../shared/NameEntry/NameEntry';

const JoinGame: React.FC = () => {
  const [gameCode, setGameCode] = useState('');
  const [error, setError] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const navigate = useNavigate();
  const playerName = usePlayerStore((state) => state.name);

  const validateGameCode = (code: string): boolean => {
    // Game code should be 6 characters long and alphanumeric
    const gameCodeRegex = /^[A-Za-z0-9]{6}$/;
    return gameCodeRegex.test(code);
  };

  const handleJoinGame = async (event: React.FormEvent) => {
    event.preventDefault();
    setError('');

    if (!validateGameCode(gameCode)) {
      setError('Invalid game code. Please enter a 6-character alphanumeric code.');
      return;
    }

    if (!playerName) {
      setError('Please set your name before joining a game.');
      return;
    }

    setIsLoading(true);

    try {
      await ensureAnonymousAuth();
      const joinGameFunction = httpsCallable(functions, 'joinGame');
      const result = await joinGameFunction({ gameCode, playerName });
      const data = result.data as { gameId: string };
      navigate(`/game-lobby/${data.gameId}`);
    } catch (err: any) {
      console.error('Error joining game:', err);
      setError(err.message || 'An unexpected error occurred. Please try again.');
    } finally {
      setIsLoading(false);
    }
  };

  const handleNameValid = () => {
    // This function will be called when a valid name is submitted
    // You can add any additional logic here if needed
  };

  return (
    <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded shadow">
      <h2 className="text-2xl font-bold mb-4">Join a Game</h2>
      {!playerName ? (
        <NameEntry onNameValid={handleNameValid} />
      ) : (
        <form onSubmit={handleJoinGame}>
          <div className="mb-4">
            <label htmlFor="gameCode" className="block text-gray-700 mb-2">
              Game Code
            </label>
            <input
              type="text"
              id="gameCode"
              value={gameCode}
              onChange={(e) => setGameCode(e.target.value.toUpperCase())}
              className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
              placeholder="Enter 6-character game code"
              required
            />
          </div>
          {error && <p className="text-red-500 mb-4">{error}</p>}
          <button
            type="submit"
            className="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
            disabled={isLoading}
          >
            {isLoading ? 'Joining Game...' : 'Join Game'}
          </button>
        </form>
      )}
    </div>
  );
};

export default JoinGame;

// joinGame.test.ts

import * as admin from 'firebase-admin';
import functionsTest from 'firebase-functions-test';
import { joinGame } from './joinGame';

const testEnv = functionsTest();

describe('joinGame Cloud Function', () => {
    let wrappedJoinGame: any;

    beforeAll(() => {
        wrappedJoinGame = testEnv.wrap(joinGame);
    });

    afterAll(() => {
        testEnv.cleanup();
    });

    it('should throw an error if not authenticated', async () => {
        await expect(wrappedJoinGame({ gameCode: 'ABC123', playerName: 'TestPlayer' }, { auth: null }))
            .rejects.toThrow('The function must be called while authenticated.');
    });

    it('should throw an error for invalid game code', async () => {
        await expect(wrappedJoinGame({ gameCode: 'ABC12', playerName: 'TestPlayer' }, { auth: { uid: 'testUid' } }))
            .rejects.toThrow('Game code must be a 6-character string.');
    });

    it('should throw an error for empty player name', async () => {
        await expect(wrappedJoinGame({ gameCode: 'ABC123', playerName: '' }, { auth: { uid: 'testUid' } }))
            .rejects.toThrow('Player name is required.');
    });

    it('should successfully join a game', async () => {
        // Mock Firestore query and document
        const mockGet = vi.fn().mockResolvedValue({
            empty: false,
            docs: [{
                id: 'testGameId',
                data: () => ({
                    gameCode: 'ABC123',
                    numberOfTeams: 2,
                    playersPerTeam: 2,
                    teams: [{ players: [] }, { players: [] }],
                }),
                ref: {
                    update: vi.fn().mockResolvedValue(null),
                },
            }],
        });

        admin.firestore().collection = vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
                get: mockGet,
            }),
        });

        const result = await wrappedJoinGame(
            { gameCode: 'ABC123', playerName: 'TestPlayer' },
            { auth: { uid: 'testUid' } }
        );

        expect(result).toEqual({ gameId: 'testGameId' });
        expect(mockGet).toHaveBeenCalled();
    });

    it('should throw an error if game is full', async () => {
        // Mock a full game
        const mockGet = vi.fn().mockResolvedValue({
            empty: false,
            docs: [{
                data: () => ({
                    gameCode: 'ABC123',
                    numberOfTeams: 2,
                    playersPerTeam: 2,
                    teams: [
                        { players: [{ name: 'Player1' }, { name: 'Player2' }] },
                        { players: [{ name: 'Player3' }, { name: 'Player4' }] },
                    ],
                }),
            }],
        });

        admin.firestore().collection = vi.fn().mockReturnValue({
            where: vi.fn().mockReturnValue({
                get: mockGet,
            }),
        });

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrappedJoinGame(
            { gameCode: 'ABC123', playerName: 'TestPlayer' },
            { auth: { uid: 'testUid' } }
        )).rejects.toThrow('The game is full.');
    });
});

// createGame.test.ts

import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
import functionsTest from 'firebase-functions-test';
import { createGame } from './createGame';
import { FieldValue } from 'firebase-admin/firestore';
import { db } from './firebase/config';

vi.mock('./firebase/config', () => ({
    db: {
        collection: vi.fn(),
    },
}));

vi.mock('firebase-admin/firestore', () => {
    const originalModule = vi.importActual('firebase-admin/firestore');
    return {
        ...originalModule,
        FieldValue: {
            ...originalModule.FieldValue,
            serverTimestamp: vi.fn(() => 'mocked-timestamp'),
        },
    };
});

// Initialize the Firebase Functions Test environment
const testEnv = functionsTest();

// Access the mocks from the mocked module
const mockCollection = db.collection as vi.Mock;

describe('createGame', () => {
    let wrapped: any;

    // Declare mockGet and mockAdd at the describe block level
    let mockGet: vi.Mock;
    let mockAdd: vi.Mock;

    beforeAll(() => {
        wrapped = testEnv.wrap(createGame);
    });

    beforeEach(() => {
        vi.resetAllMocks();

        // Initialize mocks for Firestore methods
        mockGet = vi.fn();
        mockAdd = vi.fn();

        // Mock db.collection() behavior
        mockCollection.mockImplementation((collectionName: string) => {
            if (collectionName === 'games') {
                return {
                    // Mocking the 'where' method to handle game code collisions
                    where: vi.fn().mockImplementation(() => ({
                        get: mockGet,
                    })),
                    // Mocking the 'add' method to simulate adding a new game
                    add: mockAdd,
                };
            }
            // Return an empty object for other collections if necessary
            return {};
        });

        // Default behavior for mockGet and mockAdd
        mockGet.mockResolvedValue({ empty: true }); // No collision by default
        mockAdd.mockResolvedValue({ id: 'game123' });

        // Ensure FieldValue.serverTimestamp() is mocked to return 'mocked-timestamp'
        (FieldValue.serverTimestamp as vi.Mock).mockReturnValue('mocked-timestamp');
    });

    afterAll(() => {
        vi.restoreAllMocks();
        testEnv.cleanup();
    });

    it('should successfully create a new game with valid inputs', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        const result = await wrapped(data, context);

        expect(result).toHaveProperty('gameId', 'game123');
        expect(result).toHaveProperty('gameCode');

        // Verify that collection was called with 'games'
        expect(mockCollection).toHaveBeenCalledWith('games');

        // Verify that add was called with expected data
        const expectedGameData = expect.objectContaining({
            gameCode: expect.any(String),
            createdBy: 'TestPlayer',
            roundHost: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
            teams: expect.arrayContaining([
                expect.objectContaining({
                    teamName: 'Team 1',
                    players: expect.arrayContaining([{ name: 'TestPlayer' }]),
                }),
                // Optionally, verify other teams if needed
            ]),
            createdAt: 'mocked-timestamp', // Updated expectation
            currentRound: 0,
        });

        expect(mockAdd).toHaveBeenCalledWith(expectedGameData);
    });

    it('should throw an unauthenticated error if user is not authenticated', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {}; // No auth

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'The function must be called while authenticated.'
        );
    });

    it('should throw an invalid-argument error for missing name', async () => {
        const data = {
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Name is required and must be between 3 and 20 characters.'
        );
    });

    it('should throw an invalid-argument error for missing numberOfTeams', async () => {
        const data = {
            name: 'TestPlayer',
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of teams is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid playersPerTeam', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: -1,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of players per team is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid numberOfRounds', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 0,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of rounds is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid jokerUsage', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: 'yes', // Invalid boolean
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Joker usage must be a boolean.'
        );
    });

    it('should throw an internal error if Firestore add fails', async () => {
        // Mock add to throw an error
        mockAdd.mockRejectedValueOnce(new Error('Firestore add error'));

        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        vi.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Unable to create game at this time.'
        );

        // Verify that add was called
        expect(mockAdd).toHaveBeenCalled();
    });

    it('should retry generating a unique game code on collision', async () => {
        // Mock get to return collision on first call, then no collision
        mockGet
            .mockResolvedValueOnce({ empty: false }) // Collision
            .mockResolvedValueOnce({ empty: true }); // No collision

        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        const result = await wrapped(data, context);

        expect(result).toHaveProperty('gameId', 'game123');
        expect(result).toHaveProperty('gameCode');

        // Ensure the get method was called twice due to collision retry
        expect(mockGet).toHaveBeenCalledTimes(2);
    });
});

// backlog.md

# **Dance Floor Ranking - Product Backlog**

As the Product Owner for **Dance Floor Ranking**, the following epics and user stories have been created to guide the development process. Each user story includes a **Description** and a **Definition of Done** to ensure clarity and facilitate Test-Driven Development (TDD).

---

## **Epic 1: User Onboarding**

### **User Story 1.1: Player Name Entry (Done)**

**Description:**  
*As a user, I want to enter my name to join a game session so that I can participate with other players without needing an account.*

**Definition of Done:**
- **Frontend:**
  - UI component (`NameEntry.tsx`) for players to enter their names before joining a game.
  - Validation for name input (e.g., required, character limits).
- **Backend:**
  - Firestore setup to store player names and associate them with game sessions.
- **Testing:**
  - Unit tests using Vitest for name input validation.
  - End-to-end tests using Cypress to ensure players can join games with valid names.
- **Documentation:**
  - Updated instructions for players to enter names without needing authentication.
- **Acceptance Criteria:**
  - Players can successfully enter a name to join a game session.
  - Names are stored in Firestore and associated with the correct game session.
  - Users receive feedback for invalid or duplicate names.

### **User Story 1.2: User Login (Deprecated)**

**Description:**  
*This user story has been deprecated in favor of a name-based entry system.*

### **User Story 1.3: Spotify OAuth Integration**

**Description:**  
As a user, I want to authorize my Spotify account so that I can select and play songs within the game.

**Definition of Done:**
- **Frontend:**
  - OAuth flow initiation button directing users to Spotify authorization.
  - Handling callback with Spotify access tokens.
- **Backend:**
  - Secure storage of Spotify access and refresh tokens using Firebase Functions or Firestore.
  - Refresh tokens when necessary.
- **Testing:**
  - Unit tests for OAuth flow.
  - Integration tests for successful authorization and token storage.
- **Documentation:**
  - Spotify OAuth integration steps documented.
- **Acceptance Criteria:**
  - Users can initiate and complete the Spotify OAuth flow.
  - Access and refresh tokens are securely stored.
  - Users receive confirmation upon successful Spotify authorization.

---

## **Epic 2: Game Setup and Management**

### **User Story 2.1: Create a New Game Session (Done)**

**Description:**  
As a user, I want to create a new game session so that I can gather players and start playing.

**Definition of Done:**
- **Frontend:**
  - UI component (`CreateGame.tsx`) with options to set game parameters (e.g., number of teams, players).
  - Form validation for required game settings.
  - Button to submit and create the game.
- **Backend:**
  - Firebase Cloud Function to initialize a new game document in Firestore.
  - Generate a unique game code or link for players to join.
  - Assign the creating user to a team and set initial Round Host.
- **Testing:**
  - Unit tests for form validation.
  - Integration tests for game creation and Firestore document initialization.
- **Documentation:**
  - Steps to create a new game session documented.
- **Acceptance Criteria:**
  - Users can specify game settings and create a game session.
  - A unique game code/link is generated and displayed to the creator.
  - The creator is assigned to a team and set as the initial Round Host.

### **User Story 2.2: Join an Existing Game Session (Done)**

**Description:**  
As a user, I want to join an existing game session using a game code or link so that I can participate with other players.

**Definition of Done:**
- **Frontend:**
  - UI component (`JoinGame.tsx`) with input fields for game code or link.
  - Form validation for correct code/link format.
  - Button to submit and join the game.
- **Backend:**
  - Firebase Cloud Function to validate game code/link.
  - Add the user to the game’s players list in Firestore.
  - Assign the user to a team if team assignments are automatic, or prompt assignment.
- **Testing:**
  - Unit tests for form validation.
  - Integration tests for successful and failed game joins.
- **Documentation:**
  - Steps to join an existing game session documented.
- **Acceptance Criteria:**
  - Users can enter a valid game code/link to join a game session.
  - Users receive appropriate error messages for invalid or non-existent game codes/links.
  - Upon joining, users are assigned to a team and added to the game’s player list.

### **User Story 2.3: Configure Game Parameters(Deprecated: fixed in feature/2.1)**

**Description:**  
*As a game creator, I want to set game parameters (e.g., number of teams, rounds) so that the game can be customized to my preferences.*

**Definition of Done:**
- **Frontend:**
  - UI component (`ConfigureGame.tsx`) allowing creators to set parameters before game start.
  - Validation for each game parameter input.
- **Backend:**
  - Cloud Function updates to handle configurable parameters.
  - Firestore documents reflect the customized settings.
- **Testing:**
  - Unit tests for parameter validation.
  - Integration tests to ensure parameters are correctly applied during game initialization.
- **Documentation:**
  - Instructions on configuring game parameters documented.
- **Acceptance Criteria:**
  - Creators can set and modify game parameters before starting the game.
  - Game sessions reflect the customized settings accurately.
  - Users are informed of any invalid parameter inputs with appropriate feedback.

---

### **User Story 2.4: Combine Name Entry and Game Creation (Done)**

**Description:**  
As a user, I want to enter my name and create a game in a single action so that the onboarding process is streamlined and efficient.

**Definition of Done:**
- **Frontend:**
  - Updated (`createGame.tsx`) that combines name entry and game creation.
  - Unified form with fields for player name and game parameters.
  - Validation for both name input and game parameters.
  - Seamless form submission handling, ensuring both actions occur together.
- **Backend:**
  - Updated Cloud Function (`createGame`) to accept both player name and game creation data in a single call.
  - Ensure player names are persistently stored and associated with game sessions.
  - Modify Firestore data structures to utilize player names instead of user IDs where appropriate.
- **Testing:**
  - Unit tests covering the combined action of name entry and game creation.
  - Integration tests ensuring that names are correctly stored and displayed within game sessions.
  - End-to-end tests using Cypress to validate the complete flow from name entry to game creation.
- **Documentation:**
  - Updated instructions and user guides to reflect the combined name entry and game creation process.
- **Acceptance Criteria:**
  - Users can enter their name and set game parameters in a single, unified form.
  - Upon submission, the game is created, and the player is associated with the game using their entered name.
  - Player names are displayed correctly in the game lobby and other relevant components.
  - The system prevents duplicate names within the same game session and provides appropriate feedback.

---

### **User Story 2.5: Implement Game Lobby Screen**

**Description:**  
As a player, I want a game lobby screen where I can see other players, manage teams, and prepare for the game to start, so that I can coordinate with other players before the game begins.

**Definition of Done:**
- **Frontend:**
  - Create `LobbyScreen.tsx` component.
  - Implement real-time updates for player list and status changes.
  - Create UI for game settings and start game button (for host only).
- **Backend:**
  - Implement Firebase Cloud Functions to handle lobby operations (join, leave, ready status).
  - Set up Firestore listeners for real-time lobby updates.
- **Testing:**
  - Unit tests for lobby component and related functions.
  - Integration tests for real-time updates and chat functionality.
- **Documentation:**
  - Update game flow documentation to include the lobby stage.
  - Add comments explaining lobby logic and component structure.
- **Acceptance Criteria:**
  - Players can view a list of all participants in the lobby.
  - The lobby displays the current number of players and the maximum allowed.
  - Players can see and set their ready status.
  - The game creator/host has options to start the game or modify game settings.
  - The lobby shows the current team assignments (if applicable).
  - Players can leave the lobby if they choose to.

  ---

### **User Story 2.6: Player Ready System**

**Description:**  
As a player, I want to indicate my readiness to start the game so that the host knows when all players are prepared.

**Definition of Done:**
- **Frontend:**
  - Add a "Ready" button in `LobbyScreen.tsx`.
  - Visual indicator showing a player's ready status.
- **Backend:**
  - Firebase Cloud Function to update a player's ready status in Firestore.
  - Trigger game start when all players are ready.
- **Testing:**
  - Unit tests for ready button functionality.
  - Integration tests to ensure ready status updates correctly and triggers game start.
- **Documentation:**
  - Update lobby documentation to include ready system.
- **Acceptance Criteria:**
  - Players can toggle their ready status.
  - The host is notified when all players are ready.
  - The game automatically starts or allows the host to start the game once readiness is confirmed.

---

### **User Story 2.7: Persist player locally**

**Description:**  
// TODO think abour the best way to implement this. maybe local storage?. now when refreshing current player is not set.
## **Epic 3: Team Management**

### **User Story 3.1: Assign Players to Teams**

**Description:**  
As an admin or Round Host, I want to assign players to teams either automatically or manually so that teams are balanced and ready for gameplay.

**Definition of Done:**
- **Frontend:**
  - UI component (`TeamSetup.tsx`) updated to display player names instead of authenticated user profiles.
  - Options for automatic or manual team assignments based on player names.
  - Button to confirm team assignments.
- **Backend:**
  - Firebase Cloud Function to handle team assignments based on player names.
  - Update Firestore documents with team assignments.
- **Testing:**
  - Unit tests using Vitest for team assignment logic.
  - End-to-end tests using Cypress for automatic and manual team assignments.
- **Documentation:**
  - Team assignment process updated to use player names.
- **Acceptance Criteria:**
  - Players can be assigned to teams automatically, ensuring balanced team sizes based on names.
  - Admins can manually assign players to specific teams using their names.
  - Team assignments are accurately reflected in Firestore and visible to all participants.

### **User Story 3.2: Set Team Names and Captains**

**Description:**  
As a team leader, I want to set my team’s name and appoint a captain so that our team is identifiable and has a designated communication point.

**Definition of Done:**
- **Frontend:**
  - UI component (`TeamSetup.tsx`) for setting team names.
  - Interface to appoint a team captain from team members.
- **Backend:**
  - Firebase Cloud Function to update team names and captain assignments in Firestore.
- **Testing:**
  - Unit tests for team name and captain selection.
  - Integration tests for updating team information.
- **Documentation:**
  - Process for setting team names and captains documented.
- **Acceptance Criteria:**
  - Team names can be set or updated by team leaders.
  - Team captains can be appointed from within the team's members.
  - Changes are reflected in Firestore and visible to all participants.

---

## **Epic 4: Gameplay Mechanics**

### **User Story 4.1: Challenge Announcement**

**Description:**  
As the Round Host, I want to announce a musical challenge each round so that all teams know the criteria for song selection.

**Definition of Done:**
- **Frontend:**
  - UI component (`ChallengeAnnouncement.tsx`) for displaying the current challenge.
  - Interface for Round Host to select or create a challenge.
- **Backend:**
  - Firebase Cloud Function to update the current round’s challenge in Firestore.
- **Testing:**
  - Unit tests for challenge selection interface.
  - Integration tests for updating and broadcasting challenges.
- **Documentation:**
  - Challenge announcement process documented.
- **Acceptance Criteria:**
  - Round Host can select from predefined challenges or create a custom one.
  - Selected challenge is broadcasted in real-time to all players.
  - All players can view the current challenge on their game interface.

### **User Story 4.2: Song Selection Interface**

**Description:**  
As a player, I want to select or contribute a song that fits the current challenge so that my team can compete effectively.

**Definition of Done:**
- **Frontend:**
  - UI component (`SongSelection.tsx`) for displaying game-provided songs and contributing team songs.
  - Integration with Spotify search functionality for contributing songs.
  - Form validation to ensure song availability and appropriateness.
- **Backend:**
  - Firebase Cloud Function to validate and store contributed songs in Firestore.
  - Integration with Spotify Web API to verify song details.
- **Testing:**
  - Unit tests for song selection and contribution logic.
  - Integration tests for Spotify API interactions and song validation.
- **Documentation:**
  - Song selection and contribution process documented.
- **Acceptance Criteria:**
  - Players can view game-provided songs relevant to the challenge.
  - Players can search and contribute songs via Spotify.
  - Contributed songs are validated for availability and appropriateness before being added to the game.
  - All selected songs are visible to all players in the game interface.

### **User Story 4.3: Music Playback Integration**

**Description:**  
As a player, I want to listen to 30-second snippets of each selected song so that I can make informed voting decisions.

**Definition of Done:**
- **Frontend:**
  - UI component (`MusicPlayer.tsx`) integrating Spotify Web Playback SDK or alternative playback methods.
  - Controls for playing, pausing, and navigating song snippets.
  - Shuffle playback order functionality.
- **Backend:**
  - Ensure necessary permissions and token management for Spotify playback.
- **Testing:**
  - Unit tests for playback controls.
  - Integration tests for Spotify SDK functionality.
- **Documentation:**
  - Music playback integration steps and limitations documented.
- **Acceptance Criteria:**
  - 30-second snippets of all selected songs are playable within the game interface.
  - Playback order can be shuffled to avoid bias.
  - Users can control playback (play, pause, skip) seamlessly.
  - Playback complies with Spotify’s API restrictions and licensing terms.

### **User Story 4.4: Voting and Ranking System**

**Description:**  
As a player, I want to rank the songs based on how well they fit the challenge so that my preferences contribute to the scoring.

**Definition of Done:**
- **Frontend:**
  - UI component (`VotingScreen.tsx`) for ranking songs.
  - Logic to exclude the player's own contributed song from ranking.
  - Secure submission of rankings to the backend.
- **Backend:**
  - Firebase Cloud Function to collect and store rankings in Firestore.
  - Ensure rankings are anonymous and secure.
- **Testing:**
  - Unit tests for ranking logic and exclusion rules.
  - Integration tests for ranking submission and storage.
- **Documentation:**
  - Voting and ranking process documented.
- **Acceptance Criteria:**
  - Players can rank all songs except their own contributed song.
  - Rankings are submitted securely and stored correctly in Firestore.
  - Players receive confirmation of successful ranking submission.

### **User Story 4.5: Scoring and Points Allocation**

**Description:**  
As the system, I want to process player rankings to allocate points and bonuses so that scores reflect team performance accurately.

**Definition of Done:**
- **Frontend:**
  - Display updated scores and rankings in the `Scoreboard.tsx` component.
- **Backend:**
  - Firebase Cloud Function to aggregate rankings and calculate points based on predefined rules.
  - Apply bonus points for high-ranking contributed songs.
  - Handle Joker token multipliers when used.
- **Testing:**
  - Unit tests for scoring logic, including point allocation and bonus calculations.
  - Integration tests for end-to-end score updates based on rankings.
- **Documentation:**
  - Scoring and points allocation rules documented.
- **Acceptance Criteria:**
  - Points are allocated correctly based on player rankings.
  - Bonus points are awarded for top-ranking contributed songs.
  - Joker multipliers are applied when Jokers are used.
  - Updated scores are reflected in real-time on all players’ scoreboards.

### **User Story 4.6: Round Management and Progression**

**Description:**  
As the system, I want to manage the progression of game rounds so that each round flows smoothly from challenge announcement to score updating.

**Definition of Done:**
- **Frontend:**
  - UI component (`GameRoom.tsx`) to manage and display round progression.
- **Backend:**
  - Firebase Cloud Function to handle round transitions, including announcing the next Round Host and updating game status.
- **Testing:**
  - Unit tests for round progression logic.
  - Integration tests for transitioning between rounds and updating game status.
- **Documentation:**
  - Round management and progression steps documented.
- **Acceptance Criteria:**
  - Game progresses from one round to the next seamlessly.
  - Round Host role rotates correctly among teams.
  - All necessary updates (challenge announcement, song selection, scoring) occur in the correct order.
  - Game concludes after the predetermined number of rounds with correct final scoring.

---

### **User Story 4.7: Implement End Game Screen (New)**

**Description:**  
As a player, I want to see a summary screen at the end of the game showing final scores, rankings, and options to play again or return to the lobby.

**Definition of Done:**
- **Frontend:**
  - Create `EndGameScreen.tsx` component.
  - Display final scores and rankings for all teams/players.
  - Show a "Play Again" button that starts a new game with the same players.
  - Provide an option to return to the lobby.
  - Display interesting statistics from the game (e.g., most popular songs, closest rankings).
- **Backend:**
  - Firebase Cloud Function to compile final scores and statistics.
  - Update Firestore with final game results.
- **Testing:**
  - Unit tests for End Game Screen functionality.
  - Integration tests for displaying accurate final scores and statistics.
- **Documentation:**
  - End game screen process documented.
- **Acceptance Criteria:**
  - Final scores and rankings are displayed accurately.
  - Players can choose to play again or return to the lobby.
  - Game statistics are presented clearly and informatively.

---

### **User Story 4.8: Player Ready System (New)**

**Description:**  
As a player, I want to indicate my readiness to start the game so that the host knows when all players are prepared.

**Definition of Done:**
- **Frontend:**
  - Add a "Ready" button in `LobbyScreen.tsx`.
  - Visual indicator showing a player's ready status.
- **Backend:**
  - Firebase Cloud Function to update a player's ready status in Firestore.
  - Trigger game start when all players are ready.
- **Testing:**
  - Unit tests for ready button functionality.
  - Integration tests to ensure ready status updates correctly and triggers game start.
- **Documentation:**
  - Update lobby documentation to include ready system.
- **Acceptance Criteria:**
  - Players can toggle their ready status.
  - The host is notified when all players are ready.
  - The game automatically starts or allows the host to start the game once readiness is confirmed.

---

## **Epic 5: Joker Feature**

### **User Story 5.1: Implement Joker Token Usage**

**Description:**  
As a team, I want to use my Joker token to double my points in a round so that I can strategically boost our score.

**Definition of Done:**
- **Frontend:**
  - UI component to display Joker token availability.
  - Button or interface element to activate Joker before a round starts.
- **Backend:**
  - Firebase Cloud Function to validate Joker usage and apply multipliers to the team's points.
  - Ensure Jokers can only be used once per game.
- **Testing:**
  - Unit tests for Joker activation and validation logic.
  - Integration tests for score doubling upon Joker usage.
- **Documentation:**
  - Joker usage rules and procedures documented.
- **Acceptance Criteria:**
  - Teams can activate their Joker before a round begins.
  - Upon activation, the team's points for that round are doubled.
  - Jokers cannot be reused once activated.
  - Joker usage is reflected accurately in the scoreboard.

### **User Story 5.2: Display Joker Token Status**

**Description:**  
As a player, I want to see the status of my Joker token (available or used) so that I can decide when to use it strategically.

**Definition of Done:**
- **Frontend:**
  - UI indicator showing whether the Joker token is available or has been used.
  - Updated in real-time based on backend status.
- **Backend:**
  - Track Joker usage status in Firestore for each team.
- **Testing:**
  - Unit tests for Joker status indicator.
  - Integration tests for real-time updates of Joker status.
- **Documentation:**
  - Display and status of Joker tokens documented.
- **Acceptance Criteria:**
  - Players can clearly see if their Joker is available or has been used.
  - Joker status updates in real-time across all clients upon usage.

---

## **Epic 6: Real-time Updates and Synchronization**

### **User Story 6.1: Implement Firestore Real-time Listeners**

**Description:**  
As a player, I want to see live updates of game state, including song submissions, rankings, and scores so that I can stay informed in real-time.

**Definition of Done:**
- **Frontend:**
  - Implement Firestore listeners in relevant components (`GameRoom.tsx`, `Scoreboard.tsx`, etc.) to receive live updates.
  - Ensure UI reflects updates instantaneously without manual refresh.
- **Backend:**
  - Ensure Firestore documents are structured to support efficient real-time listening.
- **Testing:**
  - Unit tests for Firestore listener hooks.
  - Integration tests for real-time data updates across multiple clients.
- **Documentation:**
  - Real-time data synchronization process documented.
- **Acceptance Criteria:**
  - Players receive live updates on song submissions, rankings, and scores.
  - UI components update correctly and efficiently in response to Firestore changes.
  - No significant lag or performance issues during real-time updates.

### **User Story 6.2: Handle Concurrent Data Updates**

**Description:**  
As the system, I want to manage concurrent updates to ensure data integrity and avoid conflicts so that the game state remains consistent.

**Definition of Done:**
- **Frontend:**
  - Implement optimistic UI updates where applicable.
  - Provide feedback for unsuccessful updates due to concurrency issues.
- **Backend:**
  - Use Firestore transactions and atomic operations to handle simultaneous writes.
- **Testing:**
  - Unit tests for transaction logic.
  - Integration tests simulating concurrent updates to verify data integrity.
- **Documentation:**
  - Concurrency handling mechanisms documented.
- **Acceptance Criteria:**
  - Data integrity is maintained during simultaneous updates from multiple clients.
  - Conflicts are resolved correctly without data loss or corruption.
  - Users receive appropriate feedback in case of update conflicts.

---

## **Epic 7: Integration with Spotify Web API**

### **User Story 7.1: Implement Spotify Search Functionality**

**Description:**  
As a player, I want to search for songs using the Spotify API so that I can contribute relevant songs to the game.

**Definition of Done:**
- **Frontend:**
  - UI component within `SongSelection.tsx` for searching songs via Spotify.
  - Display search results with song titles, artists, and album covers.
- **Backend:**
  - Firebase Cloud Function to handle Spotify Search API requests securely.
  - Manage and refresh Spotify access tokens as needed.
- **Testing:**
  - Unit tests for search input and result handling.
  - Integration tests for successful and failed Spotify API requests.
- **Documentation:**
  - Spotify search integration steps documented.
- **Acceptance Criteria:**
  - Players can search for songs using keywords.
  - Search results display accurate song information from Spotify.
  - Errors are handled gracefully with appropriate messages to the user.

### **User Story 7.2: Validate Song Availability on Spotify**

**Description:**  
As the system, I want to ensure that contributed songs are available on Spotify so that they can be played during the game.

**Definition of Done:**
- **Frontend:**
  - Indicate to players if a selected song is unavailable before submission.
- **Backend:**
  - Firebase Cloud Function to verify song availability using Spotify API before accepting contributions.
- **Testing:**
  - Unit tests for song validation logic.
  - Integration tests for Spotify API song availability checks.
- **Documentation:**
  - Song validation process documented.
- **Acceptance Criteria:**
  - Only songs available on Spotify can be contributed to the game.
  - Players receive immediate feedback if a selected song is unavailable.
  - Validated songs are successfully added to the game’s song list.

### **User Story 7.3: Integrate Spotify Playback SDK**

**Description:**  
As a player, I want to listen to 30-second snippets of songs using the Spotify Playback SDK so that I can evaluate songs effectively.

**Definition of Done:**
- **Frontend:**
  - Embed Spotify Web Playback SDK within `MusicPlayer.tsx`.
  - Controls for playback (play, pause, skip).
  - Compliance with Spotify’s playback requirements.
- **Backend:**
  - Manage Spotify playback tokens and permissions.
- **Testing:**
  - Unit tests for playback controls and SDK integration.
  - Integration tests for successful playback of song snippets.
- **Documentation:**
  - Spotify Playback SDK integration steps and compliance guidelines documented.
- **Acceptance Criteria:**
  - 30-second snippets of songs are playable within the game interface.
  - Playback controls function correctly.
  - Playback adheres to Spotify’s API restrictions and licensing terms.
  - Players with Spotify Premium can use the playback features without issues.

---

## **Epic 8: Admin Tools**

### **User Story 8.1: Challenge Selection Interface for Round Host**

**Description:**  
As the Round Host, I want to select or create a musical challenge each round so that the game objectives are clear to all players.

**Definition of Done:**
- **Frontend:**
  - UI component (`SelectChallenge.tsx`) for Round Host to choose from predefined challenges or create a custom one.
  - Validation for custom challenge input.
- **Backend:**
  - Firebase Cloud Function to update the current round’s challenge in Firestore.
  - Ensure only the Round Host can access this functionality.
- **Testing:**
  - Unit tests for challenge selection and creation.
  - Integration tests for updating and broadcasting selected challenges.
- **Documentation:**
  - Challenge selection interface usage documented.
- **Acceptance Criteria:**
  - Round Host can access the challenge selection interface.
  - Predefined challenges are available for selection.
  - Round Host can create and submit a custom challenge.
  - Selected or created challenges are broadcasted in real-time to all players.

### **User Story 8.2: Manage Song Submissions**

**Description:**  
As the Round Host, I want to oversee song submissions to ensure they meet game rules and appropriateness before playback.

**Definition of Done:**
- **Frontend:**
  - UI component (`ManageSongs.tsx`) for Round Host to view and approve/reject song submissions.
- **Backend:**
  - Firebase Cloud Function to update song approval status in Firestore.
  - Notifications to teams about approval or rejection outcomes.
- **Testing:**
  - Unit tests for song approval logic.
  - Integration tests for message notifications upon approval/rejection.
- **Documentation:**
  - Song management process documented.
- **Acceptance Criteria:**
  - Round Host can view all song submissions for the current round.
  - Round Host can approve or reject each song based on game rules.
  - Teams receive notifications when their song is approved or rejected.
  - Approved songs are available for playback, while rejected songs are excluded from the round.

### **User Story 8.3: Finalize and Confirm Scores**

**Description:**  
As the Round Host, I want to review and confirm the scores after each round so that the scoreboard reflects accurate standings.

**Definition of Done:**
- **Frontend:**
  - UI component (`FinalizeScores.tsx`) for Round Host to review aggregated scores.
  - Option to confirm or request re-evaluation of scores.
- **Backend:**
  - Firebase Cloud Function to process and finalize scoring based on submitted rankings.
  - Update team scores and handle any disputes or tie-breakers.
- **Testing:**
  - Unit tests for score finalization logic.
  - Integration tests for score updates and conflict resolution.
- **Documentation:**
  - Score finalization process documented.
- **Acceptance Criteria:**
  - Round Host can view aggregated scores for all teams after rankings are submitted.
  - Round Host can confirm the scores, which updates the scoreboard in real-time.
  - In case of ties, Round Host can initiate a tie-breaker process as per game rules.
  - All score updates are accurately reflected across all players’ interfaces.

---

## **Epic 9: UI/UX Development**

### **User Story 9.1: Develop Responsive Design**

**Description:**  
As a user, I want the game interface to be responsive and compatible across various devices (desktop, tablet, mobile) so that I can play the game seamlessly on any device.

**Definition of Done:**
- **Frontend:**
  - Implement responsive layouts using Tailwind CSS with shadcn components.
  - Ensure all UI components adapt to different screen sizes and orientations.
- **Testing:**
  - Manual testing across multiple devices and screen sizes.
  - Automated responsive testing using tools like Storybook or Cypress.
- **Documentation:**
  - Responsive design guidelines documented.
- **Acceptance Criteria:**
  - The game interface displays correctly and is fully functional on desktop, tablet, and mobile devices.
  - UI elements are accessible and usable regardless of screen size.
  - No layout issues or broken components across supported devices.

### **User Story 9.2: Implement shadcn UI Components**

**Description:**  
As a developer, I want to utilize shadcn UI components with Tailwind CSS to ensure a consistent and visually appealing user interface.

**Definition of Done:**
- **Frontend:**
  - Integrate shadcn UI component library into the project.
  - Replace existing UI elements with shadcn components where applicable.
- **Testing:**
  - Visual regression testing to ensure UI consistency.
  - Unit tests for custom styled components.
- **Documentation:**
  - Guidelines for using shadcn components documented.
- **Acceptance Criteria:**
  - UI components are consistent in style and behavior across the application.
  - Visual appearance matches design specifications.
  - Components are properly styled and functional within the game interface.

### **User Story 9.3: Create Intuitive Navigation**

**Description:**  
As a user, I want intuitive navigation within the game so that I can easily move between different sections (e.g., lobby, game room, scoreboards) without confusion.

**Definition of Done:**
- **Frontend:**
  - Implement routing using React Router to navigate between different pages and components.
  - Design clear and consistent navigation menus and links.
- **Testing:**
  - Unit tests for routing logic.
  - Integration tests to ensure correct navigation flow.
- **Documentation:**
  - Navigation structure and flow documented.
- **Acceptance Criteria:**
  - Users can navigate smoothly between the game lobby, game room, team management, and other sections.
  - Navigation elements are clearly labeled and accessible from all relevant pages.
  - No broken links or navigation errors occur during gameplay.

### **User Story 9.4: Implement Dark Mode**

**Description:**  
As a user, I want to switch to dark mode so that I can reduce eye strain during gameplay sessions and customize my visual experience.

**Definition of Done:**
- **Frontend:**
  - Add a toggle switch for dark mode in the settings or navigation area.
  - Implement dark mode styles using Tailwind CSS and shadcn UI components.
  - Ensure all UI components adapt correctly to dark mode.
- **Testing:**
  - Unit tests for dark mode toggle functionality.
  - Visual regression tests to ensure proper rendering in both light and dark modes.
  - End-to-end tests to verify dark mode persistence across different screens.
- **Documentation:**
  - Dark mode implementation details and usage guidelines documented.
- **Acceptance Criteria:**
  - Users can switch between light and dark modes seamlessly from any screen.
  - All UI components, including shadcn components, are styled appropriately for dark mode.
  - User preference for dark mode is persisted across sessions and devices.
  - Dark mode respects system preferences but allows manual override.
  - Transition between modes is smooth and doesn't disrupt the user experience.

---

## **Epic 10: Security Implementation**

### **User Story 10.1: Implement Firestore Security Rules**

**Description:**  
As a developer, I want to enforce strict Firestore security rules to protect user data and ensure only authorized actions are permitted.

**Definition of Done:**
- **Backend:**
  - Define Firestore security rules as per the architecture document.
  - Implement role-based access controls for different collections (users, games, teams, rounds, songs).
- **Testing:**
  - Use Firebase Emulator Suite to test security rules.
  - Write unit tests to verify access controls.
- **Documentation:**
  - Firestore security rules documented in detail.
- **Acceptance Criteria:**
  - Only authenticated users can read/write their own user profiles.
  - Only authorized roles can access and modify game, team, round, and song data.
  - Security rules prevent unauthorized access and data manipulation.
  - All security rules pass testing without vulnerabilities.

### **User Story 10.2: Secure Handling of Sensitive Data**

**Description:**  
As a developer, I want to securely handle sensitive data such as API keys and access tokens to prevent unauthorized access and data breaches.

**Definition of Done:**
- **Frontend:**
  - Ensure API keys and secrets are not exposed in the frontend code.
  - Use environment variables for sensitive configurations.
- **Backend:**
  - Store sensitive data using Firebase’s secret management or Google Cloud Secret Manager.
  - Access sensitive data securely within Cloud Functions.
- **Testing:**
  - Security audits to verify no sensitive data is exposed.
  - Penetration testing to identify potential vulnerabilities.
- **Documentation:**
  - Sensitive data handling practices documented.
- **Acceptance Criteria:**
  - No sensitive data is present in the frontend codebase.
  - All sensitive configurations are securely stored and accessed in the backend.
  - Security audits confirm that sensitive data is protected against unauthorized access.

### **User Story 10.3: Enforce HTTPS and CORS Policies**

**Description:**  
As a system, I want to enforce HTTPS for all communications and configure CORS policies to allow only trusted origins so that data transmission is secure.

**Definition of Done:**
- **Frontend:**
  - Ensure all frontend communications occur over HTTPS.
- **Backend:**
  - Configure Firebase Functions to enforce HTTPS.
  - Set up CORS policies in Firebase Functions to allow only trusted origins (e.g., the frontend domain).
- **Testing:**
  - Verify that all API endpoints are accessible only over HTTPS.
  - Test CORS policies to ensure only authorized origins can make requests.
- **Documentation:**
  - HTTPS enforcement and CORS policies documented.
- **Acceptance Criteria:**
  - All communications between frontend and backend occur over HTTPS.
  - API requests from unauthorized origins are blocked by CORS policies.
  - Security configurations pass compliance checks and tests.

---

## **Epic 11: Testing Strategy**

### **User Story 11.1: Implement Unit Tests for Frontend Components**

**Description:**  
As a developer, I want to write unit tests for all frontend components to ensure they function correctly in isolation.

**Definition of Done:**
- **Frontend:**
  - Use testing frameworks like Jest and React Testing Library.
  - Write unit tests covering all major functionalities and edge cases for each component.
- **Testing:**
  - Ensure 80%+ code coverage for frontend components.
- **Documentation:**
  - Testing procedures and guidelines documented.
- **Acceptance Criteria:**
  - All frontend components have comprehensive unit tests.
  - Tests pass successfully with no uncovered critical paths.
  - Code coverage reports reflect high coverage rates.

### **User Story 11.2: Implement Integration Tests for Backend Functions**

**Description:**  
As a developer, I want to write integration tests for all backend Cloud Functions to ensure they interact correctly with Firestore and external APIs.

**Definition of Done:**
- **Backend:**
  - Use testing frameworks like Mocha or Jest with Firebase Emulator Suite.
  - Write integration tests for each Cloud Function, covering successful and failure scenarios.
- **Testing:**
  - Ensure tests cover interactions with Firestore and Spotify API.
- **Documentation:**
  - Integration testing procedures documented.
- **Acceptance Criteria:**
  - All backend Cloud Functions have comprehensive integration tests.
  - Tests successfully simulate real-world interactions and handle edge cases.
  - Integration tests pass with no critical failures.

### **User Story 11.3: Set Up Continuous Testing Pipeline**

**Description:**  
As a developer, I want to set up a continuous integration pipeline that runs all tests on every code commit to maintain code quality.

**Definition of Done:**
- **Infrastructure:**
  - Configure GitHub Actions (or preferred CI/CD tool) to run tests on every push and pull request.
- **Testing:**
  - Ensure all existing tests pass in the CI environment.
  - Configure notifications for test failures.
- **Documentation:**
  - CI pipeline setup and maintenance documented.
- **Acceptance Criteria:**
  - All tests run automatically on every code commit.
  - Test results are reported in the CI tool.
  - Developers are notified of test failures promptly.
  - CI pipeline does not block deployments unless critical tests fail.

---

## **Epic 12: Deployment and CI/CD**

### **User Story 12.1: Deploy Frontend to Firebase Hosting**

**Description:**  
As a developer, I want to deploy the React frontend to Firebase Hosting so that users can access the game via a web URL.

**Definition of Done:**
- **Frontend:**
  - Configure `vite.config.ts` for production builds.
  - Set up Firebase Hosting configuration files.
- **CI/CD:**
  - Integrate deployment steps into GitHub Actions or Firebase Hosting’s CI/CD.
- **Testing:**
  - Perform end-to-end testing post-deployment to ensure functionality.
- **Documentation:**
  - Deployment process documented.
- **Acceptance Criteria:**
  - Frontend is successfully deployed to Firebase Hosting.
  - The deployed site is accessible via the designated web URL.
  - All frontend functionalities work correctly in the production environment.

### **User Story 12.2: Deploy Backend Cloud Functions**

**Description:**  
As a developer, I want to deploy Firebase Cloud Functions so that backend logic is available to handle game operations.

**Definition of Done:**
- **Backend:**
  - Write and configure all necessary Cloud Functions.
  - Ensure environment variables and secrets are securely managed.
- **CI/CD:**
  - Integrate Cloud Function deployment steps into GitHub Actions or Firebase Hosting’s CI/CD.
- **Testing:**
  - Verify all deployed functions operate correctly in the production environment.
- **Documentation:**
  - Cloud Function deployment process documented.
- **Acceptance Criteria:**
  - All Cloud Functions are successfully deployed and operational.
  - Functions respond correctly to frontend requests.
  - Environment variables and secrets are securely managed and not exposed.

### **User Story 12.3: Set Up Continuous Deployment Pipeline**

**Description:**  
As a developer, I want to set up a continuous deployment pipeline that automatically deploys code changes after successful tests to streamline the release process.

**Definition of Done:**
- **Infrastructure:**
  - Configure GitHub Actions (or preferred CI/CD tool) to handle automated deployments for both frontend and backend.
  - Set up build and deployment scripts for efficient and reliable releases.
- **Testing:**
  - Ensure that the pipeline includes all necessary steps: build, test, and deploy.
  - Validate that deployments occur only when all tests pass.
- **Documentation:**
  - CI/CD pipeline configuration and usage documented.
- **Acceptance Criteria:**
  - Code changes are automatically built, tested, and deployed upon merging to the main branch.
  - Deployments only occur if all tests pass successfully.
  - Deployment logs are accessible for review and debugging.
  - The deployment process is secure, reliable, and repeatable.

---

## **Epic 13: Security and Permissions**

### **User Story 13.1: Role-Based Access Control (RBAC)**

**Description:**  
As a system, I want to implement role-based access controls so that different users (e.g., Admin, Round Host, Player) have appropriate permissions within the game.

**Definition of Done:**
- **Backend:**
  - Define roles and permissions in Firestore and Cloud Functions.
  - Ensure only users with specific roles can perform certain actions (e.g., only Round Host can select challenges).
- **Frontend:**
  - Adjust UI components to show/hide functionalities based on user roles.
- **Testing:**
  - Unit tests for RBAC logic.
  - Integration tests to verify that users cannot perform unauthorized actions.
- **Documentation:**
  - RBAC policies and role definitions documented.
- **Acceptance Criteria:**
  - Roles are correctly assigned and enforced across the system.
  - Unauthorized users cannot access or perform restricted actions.
  - UI elements reflect the user’s role appropriately, showing only relevant features.

### **User Story 13.2: Implement Rate Limiting and Input Validation**

**Description:**  
As a system, I want to implement rate limiting and input validation to prevent abuse and ensure data integrity.

**Definition of Done:**
- **Backend:**
  - Use middleware or Firebase Cloud Functions' capabilities to rate limit API requests (e.g., song submissions).
  - Validate and sanitize all user inputs to prevent injections and other malicious activities.
- **Frontend:**
  - Implement client-side input validation to complement backend validation.
- **Testing:**
  - Unit tests for input validation logic.
  - Integration tests to ensure rate limiting is enforced under high request rates.
- **Documentation:**
  - Rate limiting and input validation mechanisms documented.
- **Acceptance Criteria:**
  - The system effectively limits the number of requests a user can make within a specified timeframe.
  - All user inputs are validated and sanitized to prevent security vulnerabilities.
  - Users receive appropriate feedback when rate limits are exceeded or invalid inputs are detected.

---

## **Epic 14: Notifications and Alerts (Optional)**

### **User Story 14.1: Implement Real-time Notifications**

**Description:**  
As a player, I want to receive real-time notifications for important game events (e.g., new challenges, score updates) so that I stay informed throughout the game.

**Definition of Done:**
- **Frontend:**
  - UI component for displaying notifications (e.g., toast messages, modal pop-ups).
  - Integration with Firestore listeners to trigger notifications based on specific data changes.
- **Backend:**
  - Set up triggers in Firestore to emit notifications for relevant events.
- **Testing:**
  - Unit tests for notification triggers and display logic.
  - Integration tests for real-time notification delivery.
- **Documentation:**
  - Notification system setup and usage documented.
- **Acceptance Criteria:**
  - Players receive timely notifications for predefined game events.
  - Notifications are displayed unobtrusively and clearly.
  - Users can acknowledge or dismiss notifications as needed.

---

## **Epic 15: Analytics and Insights (Future Enhancements)**

### **User Story 15.1: Track Gameplay Analytics**

**Description:**  
As a product owner, I want to track gameplay analytics such as user engagement, popular songs, and challenge effectiveness so that I can make data-driven decisions for future improvements.

**Definition of Done:**
- **Backend:**
  - Integrate analytics tools (e.g., Google Analytics, Firebase Analytics) into the application.
  - Define and track key metrics related to user behavior and game performance.
- **Frontend:**
  - Implement tracking events for user interactions (e.g., game creation, song submissions).
- **Testing:**
  - Verify that analytics events are correctly recorded and sent to the analytics platform.
- **Documentation:**
  - Analytics tracking setup and key metrics documented.
- **Acceptance Criteria:**
  - Key gameplay metrics are being tracked and reported accurately.
  - Data is accessible through the chosen analytics platform for analysis.
  - Insights derived from analytics can inform future game enhancements and feature prioritization.

---

## **Epic 16: Centralized Error Handling and Logging (New)**

### **User Story 16.1: Implement Global Error Boundary in React**

**Description:**  
As a developer, I want to implement a global error boundary in React to catch and handle errors gracefully, improving the user experience during unexpected failures.

**Definition of Done:**
- **Frontend:**
  - Create a `ErrorBoundary.tsx` component to catch JavaScript errors in the component tree.
  - Wrap the main application component with the error boundary.
  - Display a fallback UI when an error is caught.
- **Testing:**
  - Unit tests for error boundary functionality.
  - Integration tests to ensure errors are caught and fallback UI is displayed.
- **Documentation:**
  - Error boundary implementation details documented.
- **Acceptance Criteria:**
  - Errors within the React component tree are caught by the global error boundary.
  - Users see a user-friendly fallback UI when an error occurs.
  - Errors are logged appropriately for further analysis.

### **User Story 16.2: Set Up Error Logging Service**

**Description:**  
As a developer, I want to set up an error logging service (e.g., Sentry, LogRocket) to automatically capture and report errors, enhancing our ability to monitor and fix issues.

**Definition of Done:**
- **Frontend:**
  - Integrate the chosen error logging service into the frontend application.
  - Configure the service to capture and send error details.
- **Backend:**
  - Integrate the error logging service with Firebase Cloud Functions to capture backend errors.
- **Testing:**
  - Simulate errors to verify they are captured and reported by the logging service.
- **Documentation:**
  - Error logging setup and usage documented.
- **Acceptance Criteria:**
  - Errors from both frontend and backend are captured and reported to the logging service.
  - Error reports include relevant details for diagnosis.
  - The development team can access and review logged errors through the service's dashboard.

### **User Story 16.3: Create Custom Error Types for Different Scenarios**

**Description:**  
As a developer, I want to create custom error types for different scenarios to improve error handling and provide more meaningful feedback.

**Definition of Done:**
- **Frontend:**
  - Define custom error classes for various error scenarios (e.g., `ValidationError`, `AuthenticationError`).
  - Utilize these custom errors within the application logic.
- **Backend:**
  - Define custom error classes for backend operations.
  - Throw and handle custom errors appropriately within Cloud Functions.
- **Testing:**
  - Unit tests for custom error creation and handling.
  - Integration tests to ensure custom errors are correctly processed and reported.
- **Documentation:**
  - Custom error types and their usage documented.
- **Acceptance Criteria:**
  - Custom error types are defined and used consistently across frontend and backend.
  - Errors provide clear and specific messages based on their type.
  - The error logging service captures custom error details accurately.

### **User Story 16.4: Implement Error Reporting in Cloud Functions**

**Description:**  
As a developer, I want to implement error reporting in Cloud Functions to ensure backend issues are tracked and addressed promptly.

**Definition of Done:**
- **Backend:**
  - Integrate the error logging service with Firebase Cloud Functions.
  - Add try-catch blocks in all Cloud Functions to capture and report errors.
- **Testing:**
  - Simulate backend errors to verify they are reported correctly.
- **Documentation:**
  - Cloud Function error reporting process documented.
- **Acceptance Criteria:**
  - All unhandled errors in Cloud Functions are captured and sent to the error logging service.
  - Error reports include relevant context and stack traces for debugging.
  - The development team is notified of critical backend errors in a timely manner.

### **User Story 16.5: Document Error Handling Procedures for the Team**

**Description:**  
As a developer, I want to document error handling procedures so that the team understands how to manage and respond to errors effectively.

**Definition of Done:**
- **Documentation:**
  - Create a comprehensive guide outlining error handling best practices.
  - Include instructions on accessing and interpreting error logs from the logging service.
  - Detail steps for responding to different types of errors.
- **Training:**
  - Conduct a team meeting or workshop to review the error handling documentation.
- **Acceptance Criteria:**
  - The team has access to clear and detailed error handling documentation.
  - Team members understand how to utilize the error logging service.
  - Procedures for responding to and resolving errors are well-defined and followed.

---

## **Epic 17: Game Flow Management (New)**

### **User Story 17.1: Manage Overall Game Flow**

**Description:**  
As a developer, I want to manage the overall game flow, transitioning between different stages (lobby, rounds, scoring, etc.), to ensure a seamless gaming experience.

**Definition of Done:**
- **Frontend:**
  - Implement state management to handle transitions between game stages.
  - UI components update based on the current game stage.
- **Backend:**
  - Firebase Cloud Functions to control game state transitions.
  - Ensure consistency of game state across all clients.
- **Testing:**
  - Unit tests for game flow logic.
  - Integration tests to verify smooth transitions between stages.
- **Documentation:**
  - Game flow management process documented.
- **Acceptance Criteria:**
  - The game transitions smoothly from the lobby to rounds, scoring, and end screens.
  - All players are synchronized with the current game stage.
  - Errors during transitions are handled gracefully with appropriate user feedback.

### **User Story 17.2: Spectator Mode (New)**

**Description:**  
As a spectator, I want to watch ongoing games without participating so that I can enjoy the game without affecting the outcome.

**Definition of Done:**
- **Frontend:**
  - Create `SpectatorView.tsx` component for spectators to view the game.
  - Hide interactive elements that spectators should not access.
- **Backend:**
  - Modify authentication and game joining logic to allow spectators.
  - Ensure spectators have read-only access to game data.
- **Testing:**
  - Unit tests for spectator view functionalities.
  - Integration tests to ensure spectators cannot perform game actions.
- **Documentation:**
  - Spectator mode usage and limitations documented.
- **Acceptance Criteria:**
  - Spectators can join and view ongoing games.
  - Spectators cannot interact with game elements or influence the game state.
  - The game state is accurately displayed to spectators in real-time.

---

# **Definitions and Guidelines**

## **Definition of Done (DoD)**

A user story is considered **Done** when the following criteria are met:

1. **Code Implementation:**
   - All functionality described in the user story is fully implemented.
   - Code follows the project's coding standards and conventions.

2. **Testing:**
   - Unit tests cover all critical paths and edge cases.
   - Integration tests verify interactions between components and services.
   - All tests pass successfully with no critical failures.

3. **Documentation:**
   - Relevant documentation is updated, including setup instructions, usage guidelines, and technical details.
   - Comments within the code are clear and helpful.

4. **Code Review:**
   - Code has been reviewed and approved by at least one other team member or automated review process.

5. **Deployment:**
   - Changes have been deployed to the appropriate environment (e.g., staging, production) as per the CI/CD pipeline.
   - Post-deployment testing confirms that the feature works as intended in the live environment.

6. **Acceptance Criteria:**
   - All acceptance criteria defined in the user story are met.
   - The feature has been demonstrated and accepted by the Product Owner or relevant stakeholders.

## **General Guidelines for User Stories**

- **Clarity:**  
  User stories should be written clearly and concisely, focusing on the user's perspective and value.

- **Completeness:**  
  Each user story should provide enough detail for developers to understand the requirements without ambiguity.

- **Testability:**  
  User stories should be testable, with clear acceptance criteria that can be used to verify functionality.

- **Independence:**  
  User stories should be as independent as possible to allow for flexible prioritization and parallel development.

- **Small and Manageable:**  
  Break down large functionalities into smaller, manageable user stories to facilitate incremental development and frequent delivery.

---

# **Conclusion**

The outlined epics and user stories provide a structured roadmap for developing the **Dance Floor Ranking** game. By adhering to the **Definition of Done** and following the provided guidelines, the development team can ensure high-quality, reliable, and engaging features that align with the game’s design objectives. Test-Driven Development (TDD) practices will further enhance code quality and maintainability, ensuring the game's success and scalability.

---

// types.ts

import { FieldValue } from '@google-cloud/firestore';

export interface Player {
    name: string;
    isReady: boolean;
}

export interface Team {
    teamName: string;
    players: Player[];
}

export interface Game {
    gameCode: string;
    numberOfTeams: number;
    playersPerTeam: number;
    numberOfRounds: number;
    jokerUsage: boolean;
    createdBy: string;
    roundHost: string;
    currentRound: number;
    createdAt: FieldValue;
    teams: Team[];
}


// CreateGame.tsx

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { httpsCallable } from 'firebase/functions';
import { functions } from '../../firebase/config';
import { ensureAnonymousAuth } from '../../firebase/auth/auth';
import usePlayerStore from '../../store/usePlayerStore';
import NameEntry from '../shared/NameEntry/NameEntry';

interface CreateGameProps { }

const CreateGame: React.FC<CreateGameProps> = () => {
    const [numberOfTeams, setNumberOfTeams] = useState(2);
    const [playersPerTeam, setPlayersPerTeam] = useState(2);
    const [numberOfRounds, setNumberOfRounds] = useState(3);
    const [jokerUsage, setJokerUsage] = useState(true);
    const [error, setError] = useState('');
    const [isLoading, setIsLoading] = useState(false);
    const navigate = useNavigate();
    const name = usePlayerStore((state) => state.name);

    const sanitizeInput = (input: string): string => {
        const div = document.createElement('div');
        div.textContent = input;
        return div.innerHTML;
    };

    const handleGameCreate = async (event: React.FormEvent) => {
        event.preventDefault();
        setError('');

        if (!name.trim()) {
            setError('Please enter your name.');
            return;
        }

        setIsLoading(true);

        try {
            await ensureAnonymousAuth();
            const createGameFunction = httpsCallable(functions, 'createGame');
            const result = await createGameFunction({
                name: sanitizeInput(name),
                numberOfTeams,
                playersPerTeam,
                numberOfRounds,
                jokerUsage,
            });
            const data = result.data as { gameId: string; gameCode: string };
            navigate(`/game-lobby/${data.gameId}`, { state: { gameCode: data.gameCode } });
        } catch (err: any) {
            console.error('Error creating game:', err);
            setError(err.message || 'An unexpected error occurred. Please try again later.');
        } finally {
            setIsLoading(false);
        }
    };

    const handleNameValid = () => {
        // Optionally handle actions after the name is validated and set
        // For example, you can automatically submit the form or enable the create game form
    };

    return (
        <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded shadow">
            {!name ? (
                <NameEntry onNameValid={handleNameValid} />
            ) : (
                <form noValidate onSubmit={handleGameCreate}>
                    <h2 className="text-2xl font-bold mb-4">Create a New Game</h2>
                    <div className="mb-4">
                        <label className="block text-gray-700 mb-2" htmlFor="numberOfTeams">
                            Number of Teams
                        </label>
                        <input
                            type="number"
                            id="numberOfTeams"
                            value={numberOfTeams}
                            onChange={(e) => setNumberOfTeams(Number(e.target.value))}
                            min="2"
                            max="10"
                            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
                            required
                        />
                    </div>
                    <div className="mb-4">
                        <label className="block text-gray-700 mb-2" htmlFor="playersPerTeam">
                            Players per Team
                        </label>
                        <input
                            type="number"
                            id="playersPerTeam"
                            value={playersPerTeam}
                            onChange={(e) => setPlayersPerTeam(Number(e.target.value))}
                            min="1"
                            max="10"
                            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
                            required
                        />
                    </div>
                    <div className="mb-4">
                        <label className="block text-gray-700 mb-2" htmlFor="numberOfRounds">
                            Number of Rounds
                        </label>
                        <input
                            type="number"
                            id="numberOfRounds"
                            value={numberOfRounds}
                            onChange={(e) => setNumberOfRounds(Number(e.target.value))}
                            min="1"
                            max="20"
                            className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
                            required
                        />
                    </div>
                    <div className="mb-4">
                        <label className="flex items-center">
                            <input
                                type="checkbox"
                                checked={jokerUsage}
                                onChange={(e) => setJokerUsage(e.target.checked)}
                                className="mr-2"
                            />
                            <span className="text-gray-700">Allow Joker Usage</span>
                        </label>
                    </div>
                    {error && <p className="text-red-500 mb-4">{error}</p>}
                    <button
                        type="submit"
                        className="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
                        disabled={isLoading}
                    >
                        {isLoading ? 'Creating Game...' : 'Create Game'}
                    </button>
                </form>
            )}
        </div>
    );
};

export default CreateGame;

// CreateGame.test.tsx

import React from 'react';
import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import CreateGame from './CreateGame';
import { BrowserRouter } from 'react-router-dom';
import { httpsCallable } from 'firebase/functions';
import { ensureAnonymousAuth } from '../../firebase/auth/auth';
import usePlayerStore from '../../store/usePlayerStore';

// Mock the usePlayerStore hook
vi.mock('../../store/usePlayerStore', () => ({
  default: vi.fn(),
}));


// Mock ensureAnonymousAuth
vi.mock('../../firebase/auth/auth', () => ({
  ensureAnonymousAuth: vi.fn(),
}));

// Mock useNavigate from react-router-dom
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
  return {
    ...actual,
    useNavigate: () => mockNavigate,
  };
});

describe('CreateGame Component', () => {
  const mockCreateGame = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    // Mock implementation of usePlayerStore
    (usePlayerStore as unknown as vi.Mock).mockImplementation((selector) =>
      selector({ name: 'TestUser' })
    );
    // Mock implementation of httpsCallable
    (httpsCallable as unknown as vi.Mock).mockReturnValue(mockCreateGame);
    // Mock ensureAnonymousAuth to resolve successfully
    (ensureAnonymousAuth as unknown as vi.Mock).mockResolvedValue(null);
  });

  it('renders correctly', () => {
    render(
      <BrowserRouter>
        <CreateGame />
      </BrowserRouter>
    );

    expect(screen.getByText('Create a New Game')).toBeInTheDocument();
    expect(screen.getByLabelText('Number of Teams')).toBeInTheDocument();
    expect(screen.getByLabelText('Players per Team')).toBeInTheDocument();
    expect(screen.getByLabelText('Number of Rounds')).toBeInTheDocument();
    expect(screen.getByLabelText('Allow Joker Usage')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Create Game/i })).toBeInTheDocument();
  });

  it('submits form with correct data and navigates to game lobby', async () => {
    const user = userEvent.setup();
    mockCreateGame.mockResolvedValue({
      data: { gameId: 'game123', gameCode: 'ABC123' },
    });

    render(
      <BrowserRouter>
        <CreateGame />
      </BrowserRouter>
    );

    const numberOfTeamsInput = screen.getByLabelText('Number of Teams');
    const playersPerTeamInput = screen.getByLabelText('Players per Team');
    const numberOfRoundsInput = screen.getByLabelText('Number of Rounds');
    const jokerUsageCheckbox = screen.getByLabelText('Allow Joker Usage');
    const submitButton = screen.getByRole('button', { name: /Create Game/i });

    // Change form inputs
    await user.clear(numberOfTeamsInput);
    await user.type(numberOfTeamsInput, '3');
    await user.clear(playersPerTeamInput);
    await user.type(playersPerTeamInput, '4');
    await user.clear(numberOfRoundsInput);
    await user.type(numberOfRoundsInput, '5');
    await user.click(jokerUsageCheckbox); // Toggle to false

    await act(async () => {
      await user.click(submitButton);
    });

    await waitFor(() => {
      expect(ensureAnonymousAuth).toHaveBeenCalled();
      expect(mockCreateGame).toHaveBeenCalledWith({
        name: 'TestUser',
        numberOfTeams: 3,
        playersPerTeam: 4,
        numberOfRounds: 5,
        jokerUsage: false,
      });
      expect(mockNavigate).toHaveBeenCalledWith('/game-lobby/game123', {
        state: { gameCode: 'ABC123' },
      });
    });
  });

  it('displays error message on backend failure', async () => {
    const user = userEvent.setup();
    mockCreateGame.mockRejectedValue(new Error('Failed to create game'));

    render(
      <BrowserRouter>
        <CreateGame />
      </BrowserRouter>
    );

    const submitButton = screen.getByRole('button', { name: /Create Game/i });

    await act(async () => {
      await user.click(submitButton);
    });

    await waitFor(() => {
      expect(screen.getByText('Failed to create game')).toBeInTheDocument();
      expect(mockNavigate).not.toHaveBeenCalled();
    });
  });

  it('disables submit button while loading', async () => {
    const user = userEvent.setup();
    // Make the createGame function return a promise that never resolves
    mockCreateGame.mockReturnValue(new Promise(() => {}));

    render(
      <BrowserRouter>
        <CreateGame />
      </BrowserRouter>
    );

    const submitButton = screen.getByRole('button', { name: /Create Game/i });

    await act(async () => {
      await user.click(submitButton);
    });

    expect(submitButton).toBeDisabled();
    expect(submitButton).toHaveTextContent('Creating Game...');
  });

  // Option 2: Test the error message when name is only whitespace
  it('shows error when name is only whitespace', async () => {
    // Mock usePlayerStore to return name with only spaces
    (usePlayerStore as unknown as vi.Mock).mockImplementation((selector) =>
      selector({ name: '   ' })
    );

    const user = userEvent.setup();

    render(
      <BrowserRouter>
        <CreateGame />
      </BrowserRouter>
    );

    const submitButton = screen.getByRole('button', { name: /Create Game/i });

    await act(async () => {
      await user.click(submitButton);
    });

    await waitFor(() => {
      expect(screen.getByText('Please enter your name.')).toBeInTheDocument();
      expect(mockCreateGame).not.toHaveBeenCalled();
      expect(mockNavigate).not.toHaveBeenCalled();
    });
  });
});

// usePlayerStore.ts

import { create } from 'zustand';

interface PlayerState {
  name: string;
  setName: (name: string) => void;
}

const usePlayerStore = create<PlayerState>((set) => ({
  name: '',
  setName: (name) => set({ name }),
}));

export default usePlayerStore;

// App.tsx

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import CreateGame from './components/createGame/CreateGame';
import JoinGame from './components/joinGame/JoinGame';
import LobbyScreen from './pages/lobbyScreen/LobbyScreen';
const App: React.FC = () => {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<CreateGame />} />
        <Route path="/join-game" element={<JoinGame />} />
        <Route path="/game-lobby/:gameId" element={<LobbyScreen />} />
        {/* <Route path="/create-game" element={<CreateGame />} /> */}
        {/* Add other routes as needed */}
      </Routes>
    </Router>
  );
};

export default App;


// firestore.rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Players can read their own profiles and create new ones via callable functions
    match /players/{playerId} {
      allow read: if true;
      allow write: if false; // Writes handled via Cloud Functions
    }

    // Games collection
    match /games/{gameId} {
      allow read: if true;
      allow write: if request.auth != null;
    }

    // Teams collection
    match /teams/{teamId} {
      allow read: if true;
      allow write: if false; // Writes handled via Cloud Functions
    }

    // Rounds collection
    match /rounds/{roundId} {
      allow read: if true;
      allow write: if false; // Writes handled via Cloud Functions
    }

    // Songs collection
    match /songs/{songId} {
      allow read: if true;
      allow write: if false; // Writes handled via Cloud Functions
    }
  }
}

// index.ts

import { createGame } from './createGame';
import { joinGame } from './joinGame';
import { leaveGame } from './leaveGame';
import { startGame } from './startGame';

export { createGame, joinGame, leaveGame, startGame };


// LobbyScreen.tsx

import { useParams } from 'react-router-dom';
import PlayerList from './PLayerList';
import TeamManagement from './TeamManagement';
import GameSettings from './GameSettings';
import useLobbyData from './useLobbyData';

const LobbyScreen: React.FC = () => {
    const { gameId } = useParams<{ gameId: string }>();

    const { game, players, isHost, updatePlayerStatus, leaveGame, startGame } = useLobbyData(gameId);

    if (!game) {
        return <div>Loading...</div>;
    }

    return (
        <div className="container mx-auto p-4">
            <h1 className="text-2xl font-bold mb-4">Game Lobby</h1>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                <PlayerList players={players} updatePlayerStatus={updatePlayerStatus} />
                <TeamManagement teams={game.teams} />
            </div>
            {isHost && <GameSettings game={game} handleStartGame={startGame} />}
            <button
                onClick={leaveGame}
                className="mt-4 bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
            >
                Leave Game
            </button>
        </div>
    );
};

export default LobbyScreen;

// GameSettings.tsx

import React from 'react';
import { Game } from '../../types';

interface GameSettingsProps {
    game: Game;
    handleStartGame: () => void;
}

const GameSettings: React.FC<GameSettingsProps> = ({ game, handleStartGame }) => {

    return (
        <div className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
            <h2 className="text-xl font-semibold mb-4">Game Settings</h2>
            <div className="mb-4">
                <p>Number of Teams: {game.numberOfTeams}</p>
                <p>Players per Team: {game.playersPerTeam}</p>
                <p>Number of Rounds: {game.numberOfRounds}</p>
                <p>Joker Usage: {game.jokerUsage ? 'Enabled' : 'Disabled'}</p>
            </div>
            <button
                onClick={handleStartGame}
                className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
            >
                Start Game
            </button>
        </div>
    );
};

export default GameSettings;

// useLobbyData.ts

import { useState, useEffect } from 'react';
import { doc, onSnapshot, updateDoc } from 'firebase/firestore';
import { db } from '../../firebase/config';
import { Game, Player } from '../../types';
import usePlayerStore from '../../store/usePlayerStore';
import { httpsCallable } from 'firebase/functions';
import { functions } from '../../firebase/config';
import { useNavigate } from 'react-router-dom';

const useLobbyData = (gameId: string) => {
    const [game, setGame] = useState<Game | null>(null);
    const [players, setPlayers] = useState<Player[]>([]);
    const [isHost, setIsHost] = useState(false);
    const currentPlayer = usePlayerStore((state) => state.name);
    const navigate = useNavigate();

    useEffect(() => {
        if (!gameId) {
            return;
        }

        const gameRef = doc(db, 'games', gameId);
        const unsubscribe = onSnapshot(gameRef, (doc) => {
            if (doc.exists()) {
                const gameData = doc.data() as Game;
                setGame(gameData);
                setPlayers(gameData.teams.flatMap((team) => team.players));
                setIsHost(gameData.createdBy === currentPlayer);

                // Check if the game has started and redirect if necessary
                if (gameData.status === 'in-progress') {
                    navigate(`/game/${gameId}`);
                }
            }
        });

        return () => unsubscribe();
    }, [gameId, currentPlayer, navigate]);

    const updatePlayerStatus = async (playerName: string, isReady: boolean) => {
        if (!game) return;
        const updatedTeams = game.teams.map((team) => ({
            ...team,
            players: team.players.map((player) =>
                player.name === playerName ? { ...player, isReady } : player
            ),
        }));
        await updateDoc(doc(db, 'games', gameId), { teams: updatedTeams });
    };

    const leaveGame = async () => {
        if (!game) return;
        try {
            const leaveGameFunction = httpsCallable(functions, 'leaveGame');
            await leaveGameFunction({ gameId, playerName: currentPlayer });
            navigate('/');
        } catch (error) {
            console.error('Error leaving game:', error);
            // Handle error (e.g., show error message to user)
        }
    };

    const startGame = async () => {
        if (!game) return;
        try {
            const startGameFunction = httpsCallable(functions, 'startGame');
            await startGameFunction({ gameId });
            // The game state change will be handled by the onSnapshot listener
        } catch (error) {
            console.error('Error starting game:', error);
            // Handle error (e.g., show error message to user)
        }
    };

    return { game, players, isHost, updatePlayerStatus, leaveGame, startGame };
};

export default useLobbyData;

// leaveGame.test.ts

import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
import functionsTest from 'firebase-functions-test';
import { leaveGame } from './leaveGame';
import { db } from './firebase/config';

vi.mock('./firebase/config', () => ({
    db: {
        collection: vi.fn(),
        runTransaction: vi.fn(),
    },
}));

const testEnv = functionsTest();

describe('leaveGame', () => {
    let wrapped: any;
    let mockCollection: vi.Mock;
    let mockRunTransaction: vi.Mock;
    let mockDoc: vi.Mock;

    beforeAll(() => {
        wrapped = testEnv.wrap(leaveGame);
    });

    beforeEach(() => {
        vi.resetAllMocks();
        mockCollection = db.collection as vi.Mock;
        mockRunTransaction = db.runTransaction as vi.Mock;
        mockDoc = vi.fn();

        // Mock the collection().doc() chain
        mockCollection.mockReturnValue({
            doc: mockDoc,
        });
        mockDoc.mockReturnValue({
            // Add any necessary mock methods here if needed
        });
    });

    afterAll(() => {
        vi.restoreAllMocks();
        testEnv.cleanup();
    });

    it('should throw an unauthenticated error if user is not authenticated', async () => {
        const data = { gameId: 'game123', playerName: 'TestPlayer' };
        const context = {}; // No auth

        await expect(wrapped(data, context)).rejects.toThrow(
            'The function must be called while authenticated.'
        );
    });

    it('should throw an invalid-argument error for missing gameId or playerName', async () => {
        const data = { gameId: 'game123' }; // Missing playerName
        const context = { auth: { uid: 'user123' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Game ID and player name are required.'
        );
    });

    it('should successfully remove a player from the game', async () => {
        const gameData = {
            teams: [
                { players: [{ name: 'TestPlayer' }, { name: 'OtherPlayer' }] },
                { players: [{ name: 'Player3' }] }
            ],
            createdBy: 'OtherPlayer'
        };

        const mockTransaction = {
            get: vi.fn().mockResolvedValue({
                exists: true,
                data: () => gameData,
            }),
            update: vi.fn(),
        };

        mockRunTransaction.mockImplementation(async (callback) => {
            return callback(mockTransaction);
        });

        const data = { gameId: 'game123', playerName: 'TestPlayer' };
        const context = { auth: { uid: 'user123' } };

        const result = await wrapped(data, context);

        expect(result).toEqual({ message: 'Successfully left the game.' });
        expect(mockTransaction.update).toHaveBeenCalledWith(expect.anything(), {
            teams: [
                { players: [{ name: 'OtherPlayer' }] },
                { players: [{ name: 'Player3' }] }
            ]
        });
    });

    it('should delete the game if all players have left', async () => {
        const gameData = {
            teams: [
                { players: [{ name: 'TestPlayer' }] }
            ],
            createdBy: 'TestPlayer'
        };

        const mockTransaction = {
            get: vi.fn().mockResolvedValue({
                exists: true,
                data: () => gameData,
            }),
            delete: vi.fn(),
            update: vi.fn(), // Add this line
        };

        mockRunTransaction.mockImplementation(async (callback) => {
            return callback(mockTransaction);
        });

        const data = { gameId: 'game123', playerName: 'TestPlayer' };
        const context = { auth: { uid: 'user123' } };

        const result = await wrapped(data, context);

        expect(result).toEqual({ message: 'Game deleted as all players have left.' });
        expect(mockTransaction.delete).toHaveBeenCalled();
    });

    it('should assign a new host if the leaving player was the host', async () => {
        const gameData = {
            teams: [
                { players: [{ name: 'TestPlayer' }, { name: 'NewHost' }] },
                { players: [{ name: 'Player3' }] }
            ],
            createdBy: 'TestPlayer'
        };

        const mockTransaction = {
            get: vi.fn().mockResolvedValue({
                exists: true,
                data: () => gameData,
            }),
            update: vi.fn(),
        };

        mockRunTransaction.mockImplementation(async (callback) => {
            return callback(mockTransaction);
        });

        const data = { gameId: 'game123', playerName: 'TestPlayer' };
        const context = { auth: { uid: 'user123' } };

        await wrapped(data, context);

        expect(mockTransaction.update).toHaveBeenCalledTimes(2);

        // Check the first update call
        expect(mockTransaction.update).toHaveBeenCalledWith(
            expect.anything(),
            expect.objectContaining({
                teams: [
                    { players: [{ name: 'NewHost' }] },
                    { players: [{ name: 'Player3' }] }
                ]
            })
        );

        // Check the second update call
        expect(mockTransaction.update).toHaveBeenCalledWith(
            expect.anything(),
            expect.objectContaining({
                createdBy: 'NewHost'
            })
        );
    });
});

// startGame.test.ts

import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
import functionsTest from 'firebase-functions-test';
import { startGame } from './startGame';
import { db } from './firebase/config';

vi.mock('./firebase/config', () => ({
    db: {
        collection: vi.fn(),
    },
}));

const testEnv = functionsTest();

describe('startGame', () => {
    let wrapped: any;
    let mockCollection: vi.Mock;

    beforeAll(() => {
        wrapped = testEnv.wrap(startGame);
    });

    beforeEach(() => {
        vi.resetAllMocks();
        mockCollection = db.collection as vi.Mock;
    });

    afterAll(() => {
        vi.restoreAllMocks();
        testEnv.cleanup();
    });

    it('should throw an unauthenticated error if user is not authenticated', async () => {
        const data = { gameId: 'game123' };
        const context = {}; // No auth

        await expect(wrapped(data, context)).rejects.toThrow(
            'The function must be called while authenticated.'
        );
    });

    it('should throw an invalid-argument error for missing or invalid gameId', async () => {
        const data = {}; // Missing gameId
        const context = { auth: { uid: 'user123' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Game ID is required and must be a string.'
        );
    });

    it('should throw a not-found error if the game does not exist', async () => {
        const mockGet = vi.fn().mockResolvedValue({ exists: false });
        const mockDoc = vi.fn().mockReturnValue({ get: mockGet });
        mockCollection.mockReturnValue({ doc: mockDoc });

        const data = { gameId: 'nonexistent-game' };
        const context = { auth: { uid: 'user123' } };

        await expect(wrapped(data, context)).rejects.toThrow('Game not found.');
    });

    it('should successfully start the game', async () => {
        const mockUpdate = vi.fn().mockResolvedValue(null);
        const mockGet = vi.fn().mockResolvedValue({ exists: true });
        const mockDoc = vi.fn().mockReturnValue({
            get: mockGet,
            update: mockUpdate
        });
        mockCollection.mockReturnValue({ doc: mockDoc });

        const data = { gameId: 'game123' };
        const context = { auth: { uid: 'user123' } };

        const result = await wrapped(data, context);

        expect(result).toEqual({ message: 'Game started successfully' });
        expect(mockUpdate).toHaveBeenCalledWith({
            status: 'in-progress',
            currentRound: 1,
        });
    });

    it('should handle errors and throw an internal error', async () => {
        const mockGet = vi.fn().mockRejectedValue(new Error('Database error'));
        const mockDoc = vi.fn().mockReturnValue({ get: mockGet });
        mockCollection.mockReturnValue({ doc: mockDoc });

        const data = { gameId: 'game123' };
        const context = { auth: { uid: 'user123' } };

        await expect(wrapped(data, context)).rejects.toThrow('Unable to start the game at this time.');
    });
});

// LobbyScreen.test.tsx

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { vi } from 'vitest';
import LobbyScreen from './LobbyScreen';
import useLobbyData from './useLobbyData';

// Mock the useLobbyData hook
vi.mock('./useLobbyData');

// Mock useParams
vi.mock('react-router-dom', async () => {
    const actual = await vi.importActual('react-router-dom');
    return {
        ...actual,
        useParams: () => ({ gameId: 'testGameId' }),
        useNavigate: () => vi.fn(),
    };
});

describe('LobbyScreen Component', () => {
    const mockLeaveGame = vi.fn();
    const mockUpdatePlayerStatus = vi.fn();
    const mockStartGame = vi.fn();

    beforeEach(() => {
        vi.clearAllMocks();
        (useLobbyData as vi.Mock).mockReturnValue({
            game: {
                id: 'testGameId',
                teams: [{ teamName: 'Team 1', players: [{ name: 'Player 1', isReady: false }] }],
                numberOfTeams: 2,
                playersPerTeam: 2,
                numberOfRounds: 3,
                jokerUsage: true,
            },
            players: [{ name: 'Player 1', isReady: false }],
            isHost: true,
            updatePlayerStatus: mockUpdatePlayerStatus,
            leaveGame: mockLeaveGame,
            startGame: mockStartGame,
        });
    });

    it('renders lobby screen correctly', () => {
        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        expect(screen.getByText('Game Lobby')).toBeInTheDocument();
        expect(screen.getByText('Players (1)')).toBeInTheDocument();
        expect(screen.getByText('Teams')).toBeInTheDocument();
        expect(screen.getByText('Game Settings')).toBeInTheDocument();
        expect(screen.getByText('Leave Game')).toBeInTheDocument();
    });

    it('calls leaveGame function when Leave Game button is clicked', async () => {
        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        const leaveButton = screen.getByText('Leave Game');
        fireEvent.click(leaveButton);

        await waitFor(() => {
            expect(mockLeaveGame).toHaveBeenCalledTimes(1);
        });
    });

    it('does not render Game Settings for non-host players', () => {
        (useLobbyData as vi.Mock).mockReturnValue({
            game: {
                id: 'testGameId',
                teams: [{ teamName: 'Team 1', players: [{ name: 'Player 1', isReady: false }] }],
            },
            players: [{ name: 'Player 1', isReady: false }],
            isHost: false,
            updatePlayerStatus: mockUpdatePlayerStatus,
            leaveGame: mockLeaveGame,
            startGame: mockStartGame,
        });

        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        expect(screen.queryByText('Game Settings')).not.toBeInTheDocument();
    });

    it('displays loading state when game data is not available', () => {
        (useLobbyData as vi.Mock).mockReturnValue({
            game: null,
            players: [],
            isHost: false,
            updatePlayerStatus: mockUpdatePlayerStatus,
            leaveGame: mockLeaveGame,
            startGame: mockStartGame,
        });

        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        expect(screen.getByText('Loading...')).toBeInTheDocument();
    });

    it('calls startGame function when Start Game button is clicked by host', async () => {
        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        const startButton = screen.getByText('Start Game');
        fireEvent.click(startButton);

        await waitFor(() => {
            expect(mockStartGame).toHaveBeenCalledTimes(1);
        });
    });

    it('updates player status when ready button is clicked', async () => {
        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        const readyButton = screen.getByText('Are you Ready?');
        fireEvent.click(readyButton);

        await waitFor(() => {
            expect(mockUpdatePlayerStatus).toHaveBeenCalledWith('Player 1', true);
        });
    });

    it('renders correct number of players and teams', () => {
        (useLobbyData as vi.Mock).mockReturnValue({
            game: {
                id: 'testGameId',
                teams: [
                    { teamName: 'Team 1', players: [{ name: 'Player 1', isReady: false }] },
                    { teamName: 'Team 2', players: [{ name: 'Player 2', isReady: true }] },
                ],
                numberOfTeams: 2,
                playersPerTeam: 2,
                numberOfRounds: 3,
                jokerUsage: true,
            },
            players: [
                { name: 'Player 1', isReady: false },
                { name: 'Player 2', isReady: true },
            ],
            isHost: true,
            updatePlayerStatus: mockUpdatePlayerStatus,
            leaveGame: mockLeaveGame,
            startGame: mockStartGame,
        });

        render(
            <BrowserRouter>
                <LobbyScreen />
            </BrowserRouter>
        );

        expect(screen.getByText('Players (2)')).toBeInTheDocument();
        expect(screen.getAllByRole('listitem')).toHaveLength(4); // 2 in PlayerList + 2 in TeamManagement
        expect(screen.getByText('Team 1')).toBeInTheDocument();
        expect(screen.getByText('Team 2')).toBeInTheDocument();
    });
});

// useLobbyData.test.tsx

import { renderHook, act, waitFor } from '@testing-library/react';
import { vi } from 'vitest';
import useLobbyData from './useLobbyData';
import { doc, onSnapshot, updateDoc } from 'firebase/firestore';
import { httpsCallable } from 'firebase/functions';
import usePlayerStore from '../../store/usePlayerStore';

// Mock the dependencies
vi.mock('firebase/firestore', () => ({
    doc: vi.fn(() => undefined),
    onSnapshot: vi.fn(),
    updateDoc: vi.fn(),
}));
vi.mock('firebase/functions');
vi.mock('../../store/usePlayerStore');
const mockNavigate = vi.fn();
vi.mock('react-router-dom', () => ({
    useNavigate: () => mockNavigate,
}));

describe('useLobbyData', () => {
    const mockGameId = 'testGameId';
    const mockCurrentPlayer = 'TestPlayer';
    const mockGame = {
        id: mockGameId,
        teams: [{ teamName: 'Team 1', players: [{ name: 'Player 1', isReady: false }] }],
        createdBy: mockCurrentPlayer,
        status: 'waiting',
    };

    beforeEach(() => {
        vi.clearAllMocks();
        (usePlayerStore as jest.Mock).mockReturnValue(mockCurrentPlayer);
        (onSnapshot as jest.Mock).mockImplementation((_, callback) => {
            callback({
                exists: () => true,
                data: () => mockGame,
            });
            return vi.fn();
        });
    });

    it('initializes with correct data', async () => {
        const { result } = renderHook(() => useLobbyData(mockGameId));

        await waitFor(() => {
            expect(result.current.game).toEqual(mockGame);
            expect(result.current.players).toEqual([{ name: 'Player 1', isReady: false }]);
            expect(result.current.isHost).toBe(true);
        });
    });

    it('updates player status correctly', async () => {
        const { result } = renderHook(() => useLobbyData(mockGameId));

        await waitFor(() => expect(result.current.game).toBeTruthy());

        await act(async () => {
            await result.current.updatePlayerStatus('Player 1', true);
        });

        expect(updateDoc).toHaveBeenCalledWith(
            undefined,
            expect.objectContaining({
                teams: [{ teamName: 'Team 1', players: [{ name: 'Player 1', isReady: true }] }],
            })
        );
    });

    it('leaves game correctly', async () => {
        const mockLeaveGame = vi.fn().mockResolvedValue({});
        (httpsCallable as jest.Mock).mockReturnValue(mockLeaveGame);

        const { result } = renderHook(() => useLobbyData(mockGameId));

        await waitFor(() => expect(result.current.game).toBeTruthy());

        await act(async () => {
            await result.current.leaveGame();
        });

        expect(mockLeaveGame).toHaveBeenCalledWith({ gameId: mockGameId, playerName: mockCurrentPlayer });
    });

    it('starts game correctly', async () => {
        const mockStartGame = vi.fn().mockResolvedValue({});
        (httpsCallable as jest.Mock).mockReturnValue(mockStartGame);

        const { result } = renderHook(() => useLobbyData(mockGameId));

        await waitFor(() => expect(result.current.game).toBeTruthy());

        await act(async () => {
            await result.current.startGame();
        });

        expect(mockStartGame).toHaveBeenCalledWith({ gameId: mockGameId });
    });

    it('navigates when game status changes to in-progress', async () => {
        let snapshotCallback: (doc: any) => void;
        (onSnapshot as jest.Mock).mockImplementation((_, callback) => {
            snapshotCallback = callback;
            return vi.fn();
        });

        renderHook(() => useLobbyData(mockGameId));

        act(() => {
            snapshotCallback({
                exists: () => true,
                data: () => ({ ...mockGame, status: 'in-progress' }),
            });
        });

        expect(mockNavigate).toHaveBeenCalledWith(`/game/${mockGameId}`);
    });
});

// setupTests.ts

import '@testing-library/jest-dom';
import { vi } from 'vitest';

/**
 * Mock firebase/functions
 */
vi.mock('firebase/functions', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        getFunctions: vi.fn(() => {
            return {};
        }),
        httpsCallable: vi.fn(() => {
            return vi.fn();
        }),
        connectFunctionsEmulator: vi.fn(),
    };
});

/**
 * Mock firebase/auth
 */
vi.mock('firebase/auth', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        getAuth: vi.fn(() => {
            return {
                currentUser: null,
                onAuthStateChanged: vi.fn(),
            };
        }),
        signInAnonymously: vi.fn(),
        connectAuthEmulator: vi.fn(),
    };
});

/**
 * Mock firebase/firestore
 */
vi.mock('firebase/firestore', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        getFirestore: vi.fn(() => {
            return {};
        }),
        connectFirestoreEmulator: vi.fn(),
    };
});

// Mock useNavigate from react-router-dom
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        useNavigate: () => mockNavigate,
    };
});