import { expect, test } from '@playwright/test';
import { MEET_TESTAPP_URL } from '../config';
import {
	configureLayoutMode,
	createTestRoom,
	deleteAllRecordings,
	deleteAllRooms,
	joinRoomAs,
	muteAudio,
	prepareForJoiningRoom,
	waitForElementInIframe
} from '../helpers/function-helpers';
import {
	disconnectAllBrowserFakeParticipants,
	disconnectAllFakeParticipants,
	disconnectBrowserFakeParticipant,
	forceMuteParticipantAudio,
	getVisibleParticipantNames,
	getVisibleParticipantsCount,
	joinBrowserFakeParticipant,
	joinFakeParticipant,
	stopScreenShareBrowserFakeParticipant,
	waitForParticipantCount,
	waitForParticipantSwap,
	waitForParticipantVisible
} from '../helpers/participant.helper';

test.describe('Custom Layout Tests', () => {
	let subscribedToAppErrors = false;
	let roomId: string;
	let participantName: string;

	test.beforeEach(async ({ page }) => {
		// Create a new room for each test to avoid state pollution
		roomId = await createTestRoom('smart-mosaic-test-room');

		if (!subscribedToAppErrors) {
			page.on('console', (msg) => {
				const type = msg.type();
				const tag = type === 'error' ? 'ERROR' : type === 'warning' ? 'WARNING' : 'LOG';
				console.log('[' + tag + ']', msg.text());
			});
			subscribedToAppErrors = true;
		}

		participantName = `Local-${Math.random().toString(36).substring(2, 7)}`;
	});

	test.afterEach(async () => {
		// Clean up fake participants after each test
		await Promise.all([disconnectAllBrowserFakeParticipants(), disconnectAllFakeParticipants()]);
	});

	test.afterAll(async ({ browser }) => {
		const tempContext = await browser.newContext();
		const tempPage = await tempContext.newPage();
		await deleteAllRooms(tempPage);
		await deleteAllRecordings(tempPage);
		await tempContext.close();
		await tempPage.close();
	});

	// =========================================================================
	// SMART MOSAIC LAYOUT TESTS
	// These tests verify that the Smart Mosaic layout correctly displays
	// participants based on their speaking activity, showing only the most
	// recent active speakers up to the configured limit.
	// =========================================================================

	test.describe('Smart Mosaic Layout - Speaker Priority', () => {
		test('displays only active speaker when limit is 1 and other participant is muted', async ({ page }) => {
			// Scenario: 3 participants (local + remote A speaking + remote B muted), limit = 1
			// Expected: Grid shows local + remote A only (2 participants total)
			// Audio: Remote A uses continuous_speech.ogg, Remote B has no audio

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join fake participant A (speaking with continuous audio)
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				// Join fake participant B (muted/silent - no audio)
				joinFakeParticipant(roomId, 'RemoteB-Silent')
			]);

			// Wait for participants to appear and speaker detection to process
			await waitForParticipantVisible(page, 'RemoteA-Speaker');

			// Verify the grid shows exactly 2 participants (local + 1 remote speaker)
			const participantCount = await getVisibleParticipantsCount(page);
			expect(participantCount).toBe(2);

			// Step 8: Verify the visible participants are local and RemoteA (the speaker)
			const visibleIdentities = await getVisibleParticipantNames(page);
			// expect(visibleIdentities).toContain(participantName); // Local participant
			expect(visibleIdentities).toContain('RemoteA-Speaker'); // Active speaker
			expect(visibleIdentities).not.toContain('RemoteB-Silent'); // Silent participant should NOT be visible
		});

		test('reorders participants based on speech activity changes', async ({ page }) => {
			// Scenario: 3 participants, A speaks first (0-5s), then B speaks (5s onwards)
			// Expected: Initially A is prioritized, after B speaks, B becomes prioritized
			// Audio: A uses speech_5s_then_silence.ogg, B uses silence_5s_then_speech.ogg

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			// Only 1 remote participant should be visible at a time (plus local)
			await configureLayoutMode(page, 'smart-mosaic', 1);

			//  Join fake participant A (speaks first 5s, then silent)
			//  Join fake participant B (silent first 5s, then speaks)
			// Use browser-based fake participant to ensure VAD triggers correctly (lk CLI always send active speakers events when using audio files)
			await joinBrowserFakeParticipant(roomId, 'RemoteA-SpeaksFirst', {
				audioFile: 'speech_5s_then_silence.wav'
			});

			await waitForParticipantVisible(page, 'RemoteA-SpeaksFirst');

			await joinBrowserFakeParticipant(roomId, 'RemoteB-SpeaksLater', {
				audioFile: 'silence_5s_then_speech.wav'
			});

			// Verify that RemoteA is visible (he's speaking in first 5s)
			let [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).toContain('RemoteA-SpeaksFirst');
			expect(visibleIdentities).not.toContain('RemoteB-SpeaksLater');

			// Verify we have exactly 2 participants visible (local + 1 remote)
			expect(participantCount).toBe(2);
			expect(participantCount).toBe(2);

			// Wait for the speech transition (A stops at 5s, B starts at 5s)
			// Wait additional time for B to start speaking and be detected
			await waitForParticipantVisible(page, 'RemoteB-SpeaksLater');

			// Verify that RemoteB is now visible (he started speaking)
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).toContain('RemoteB-SpeaksLater');
			expect(visibleIdentities).not.toContain('RemoteA-SpeaksFirst');

			// Verify still exactly 2 participants visible (local + 1 remote)
			expect(participantCount).toBe(2);

			// Verify local participant remained visible throughout
			// The local participant should always be visible regardless of speaking state
			expect(visibleIdentities.length).toBe(2); // Local + current active speaker
		});

		test('rotates participants showing most recent speakers when limit exceeded', async ({ page }) => {
			// Scenario: 4 participants with limit = 2, speaking order A → B → C
			// Expected: After rotation, grid shows local + B + C (last 2 speakers)
			// Audio: A speaks 0-3s, B speaks 5-8s, C speaks 10-13s

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 2
			// 2 remote participants should be visible at a time (plus local = 3 total)
			await configureLayoutMode(page, 'smart-mosaic', 2);

			// Join three browser-based fake participants with sequential audio
			// speaker_seq_A.wav: speaks at 0-3s
			// speaker_seq_B.wav: speaks at 5-8s
			// speaker_seq_C.wav: speaks at 10-13s
			await joinBrowserFakeParticipant(roomId, 'RemoteA-First', {
				audioFile: 'speaker_seq_A.wav'
			});

			await joinBrowserFakeParticipant(roomId, 'RemoteB-Second', {
				audioFile: 'speaker_seq_B.wav'
			});

			await joinBrowserFakeParticipant(roomId, 'RemoteC-Third', {
				audioFile: 'speaker_seq_C.wav'
			});

			// Wait for A to become visible (speaks first at 0-3s)
			await waitForParticipantVisible(page, 'RemoteA-First');

			// Initially A and B should be visible (limit = 2, A is speaking, B fills the slot)
			let [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After A starts speaking:', visibleIdentities);
			expect(visibleIdentities).toContain('RemoteA-First');
			expect(participantCount).toBe(3); // Local + 2 remotes

			// Wait for C to become visible (speaks at 10-13s)
			// This is the key assertion - when C starts speaking, it should replace A (oldest speaker)
			await waitForParticipantSwap(page, 'RemoteC-Third', 'RemoteA-First', 30000);

			// Verify final state - B and C should be visible (most recent 2 speakers)
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After C speaks (rotation complete):', visibleIdentities);
			expect(participantCount).toBe(3); // Local + 2 remotes
			expect(visibleIdentities).toContain('RemoteB-Second');
			expect(visibleIdentities).toContain('RemoteC-Third');
			expect(visibleIdentities).not.toContain('RemoteA-First'); // A was rotated out
		});

		test('displays local and three most active speakers when limit is 3', async ({ page }) => {
			// Scenario: 5 participants (local + A, B, C speaking + D always silent), limit = 3
			// Expected: Grid shows local + A + B + C, D is never shown
			// Audio: A, B, C use continuous_speech.wav, D has no audio

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 3
			// 3 remote participants should be visible at a time (plus local = 4 total)
			await configureLayoutMode(page, 'smart-mosaic', 3);

			// Join silent participant first using lk CLI (no audio triggers VAD)
			await joinFakeParticipant(roomId, 'RemoteD-Silent');

			// Join three browser-based fake participants with continuous speech audio
			// These will all be detected as active speakers
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteB-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteC-Speaker', {
					audioFile: 'continuous_speech.wav'
				})
			]);

			// Wait for speaker detection to process all participants
			await Promise.all([
				waitForParticipantVisible(page, 'RemoteA-Speaker'),
				waitForParticipantVisible(page, 'RemoteB-Speaker'),
				waitForParticipantVisible(page, 'RemoteC-Speaker'),
				waitForParticipantCount(page, 4) // Local + 3 remotes
			]);
		});

		test('handles simultaneous speech and reorders when only one continues', async ({ page }) => {
			// Scenario: 3 remote participants + local, limit = 2
			// All 3 speak simultaneously for first 5s, then only A continues speaking
			// Expected: Initially any 2 of the 3 are visible (all speaking)
			// After 5s, only A continues → A should remain visible as active speaker
			// Audio: A uses simultaneous_then_solo.wav (15s speech)
			//        B, C use simultaneous_then_stop.wav (5s speech then silence)

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 2
			// Only 2 remote participants visible at a time
			await configureLayoutMode(page, 'smart-mosaic', 2);

			// Join three browser-based fake participants
			// A continues speaking (15s), B and C stop after 5s
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-ContinuesSpeaking', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteB-StopsSpeaking', {
					audioFile: 'simultaneous_then_stop.wav' //5s speech + 25s silence
				})
			]);
			// Wait for simultaneous speech period (first 5s - all speaking)
			await Promise.all([
				waitForParticipantVisible(page, 'RemoteA-ContinuesSpeaking'),
				waitForParticipantVisible(page, 'RemoteB-StopsSpeaking')
			]);

			await joinBrowserFakeParticipant(roomId, 'RemoteC-StopsSpeaking', {
				audioFile: 'simultaneous_then_stop.wav' //5s speech + 25s silence
			});

			let [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			// During simultaneous speech, we should see exactly 3 participants (local + 2 remotes)
			expect(participantCount).toBe(3);

			// At least A should be visible (continues speaking)
			// The other visible one could be B or C (both are speaking)
			expect(visibleIdentities).toContain('RemoteA-ContinuesSpeaking');
			expect(visibleIdentities).toContain('RemoteB-StopsSpeaking');
			expect(visibleIdentities).not.toContain('RemoteC-StopsSpeaking');

			// Wait for B and C to stop speaking (after 5s mark)
			// Only A continues speaking, so A should remain as priority speaker
			await page.waitForTimeout(6000); // Wait until ~11s mark (well past the 5s cutoff)

			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After B and C stop speaking (~11s):', visibleIdentities);

			// A should definitely be visible (still speaking)
			expect(visibleIdentities).toContain('RemoteA-ContinuesSpeaking');

			// Verify participant count is still 3 (local + 2 remotes)
			// Even though only A is speaking, the layout maintains 2 remotes
			expect(participantCount).toBe(3);
		});

		test('stabilizes layout when limit reached with continuous speakers', async ({ page }) => {
			// Scenario: 2 remote participants + local, limit = 1
			// Participants A and B speak continuously
			// Expected: Layout stabilizes showing local + 1 most recent active speakers
			// Audio: A and B use continuous_speech.wav

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			// Only 1 remote participant visible at a time
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join two browser-based fake participants with continuous speech audio
			await joinBrowserFakeParticipant(roomId, 'RemoteA-Continuous', {
				audioFile: 'continuous_speech.wav'
			});

			await waitForParticipantVisible(page, 'RemoteA-Continuous');

			// Verify the grid shows exactly 2 participants (local + 1 remote speaker)
			let [participantCount, visibleNames] = await Promise.all([
				getVisibleParticipantsCount(page),
				getVisibleParticipantNames(page)
			]);
			expect(participantCount).toBe(2);
			expect(visibleNames).toContain('RemoteA-Continuous');

			await joinBrowserFakeParticipant(roomId, 'RemoteB-Continuous', {
				audioFile: 'continuous_speech.wav'
			});

			// Verify participant count remains stable 20 times
			for (let i = 0; i < 20; i++) {
				[participantCount, visibleNames] = await Promise.all([
					getVisibleParticipantsCount(page),
					getVisibleParticipantNames(page)
				]);
				expect(visibleNames).toContain('RemoteA-Continuous');
				expect(visibleNames).not.toContain('RemoteB-Continuous');
				expect(participantCount).toBe(2);
				await page.waitForTimeout(50);
			}
		});

		test('prioritizes newly joined speaking participant over silent ones', async ({ page }) => {
			// Scenario: Local + 2 silent participants (A, B) in room, limit = 1
			// New participant C joins and starts speaking immediately
			// Expected: C immediately appears in the grid, replacing one of the silent participants
			// Audio: A, B are silent (lk CLI), C uses continuous_speech.wav

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			// Only 1 remote participant visible at a time
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join two silent participants using lk CLI (no VAD triggers)
			await Promise.all([
				joinFakeParticipant(roomId, 'RemoteA-Silent'),
				joinFakeParticipant(roomId, 'RemoteB-Silent')
			]);

			// Wait for silent participants to appear
			await page.waitForTimeout(2000);

			// New participant C joins and starts speaking immediately
			await joinBrowserFakeParticipant(roomId, 'RemoteC-NewSpeaker', {
				audioFile: 'silence_5s_then_speech.wav'
			});

			await page.waitForTimeout(2000);

			let [visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			expect(visibleNames).not.toContain('RemoteC-NewSpeaker');

			// Wait for speaker detection to process
			await waitForParticipantVisible(page, 'RemoteC-NewSpeaker');

			[visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			// Verify C is now visible (speaking has priority)
			expect(visibleNames).toContain('RemoteC-NewSpeaker');
			expect(participantCount).toBe(2); // Local + 1 remote
		});
	});

	test.describe('Smart Mosaic Layout - Participant Join/Leave Handling', () => {
		test('updates visible participants when speaker leaves', async ({ page }) => {
			// Scenario: Local + 3 remote participants (A, B, C) with limit = 2
			// A and B are visible speakers, C is silent
			// A leaves the room → B should remain visible, C should NOT appear
			// Audio: A and B use continuous_speech.wav, C is silent

			// Step 1: Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Step 2: Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });

			// Step 3: Configure Smart Mosaic layout with limit = 2
			await configureLayoutMode(page, 'smart-mosaic', 2);

			// Step 4: Join three browser-based fake participants
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteB-Speaker', {
					audioFile: 'continuous_speech.wav'
				})
			]);

			await joinFakeParticipant(roomId, 'RemoteC-Silent');

			// Step 5: Wait for speaker detection to process
			await Promise.all([
				waitForParticipantVisible(page, 'RemoteA-Speaker'),
				waitForParticipantVisible(page, 'RemoteB-Speaker')
			]);

			// Verify A and B are visible
			let [visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('Before A leaves, visible participants:', visibleNames);

			expect(visibleNames).toContain('RemoteA-Speaker');
			expect(visibleNames).toContain('RemoteB-Speaker');
			expect(participantCount).toBe(3); // Local + 2 remotes

			// Step 6: Disconnect participant A (visible speaker)
			await disconnectBrowserFakeParticipant(roomId, 'RemoteA-Speaker');

			// Step 7: Wait for layout to update
			await page.waitForTimeout(1000);

			[visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After A leaves, visible participants:', visibleNames);

			// Step 8: Verify B remains visible, C does NOT appear
			expect(visibleNames).toContain('RemoteB-Speaker');
			// expect(visibleNames).toContain('RemoteC-Silent');
			expect(visibleNames).not.toContain('RemoteA-Speaker');
			expect(participantCount).toBe(3); // Local + 2 remotes
		});

		test('does not show silent participants when they join', async ({ page }) => {
			// Scenario: Local + 2 remote participants (A speaking, B silent) with limit = 1
			// A is visible speaker, B is silent
			// C joins as silent participant → should NOT appear in the grid
			// Audio: A uses continuous_speech.wav, B and C are silent

			// Step 1: Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Step 2: Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });

			// Step 3: Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Step 4: Join two remote participants
			await joinBrowserFakeParticipant(roomId, 'RemoteA-Speaker', {
				audioFile: 'continuous_speech.wav'
			});

			await joinFakeParticipant(roomId, 'RemoteB-Silent');

			// Step 5: Wait for speaker detection to process
			await page.waitForTimeout(2000);

			// Verify A is visible
			let [visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('Before C joins, visible participants:', visibleNames);

			expect(visibleNames).toContain('RemoteA-Speaker');
			expect(participantCount).toBe(2); // Local + 1 remote

			// Step 6: Join new silent participant C
			await joinFakeParticipant(roomId, 'RemoteC-Silent');

			// Step 7: Wait for layout to update
			await page.waitForTimeout(3000);

			[visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After C joins, visible participants:', visibleNames);

			// Step 8: Verify C does NOT appear in the grid
			expect(visibleNames).toContain('RemoteA-Speaker');
			expect(participantCount).toBe(2); // Local + 1 remote
		});
	});

	test.describe('Mosaic Layout and Smart Mosaic Layout Switching', () => {
		test('switches from Smart Mosaic to Mosaic showing all participants', async ({ page }) => {
			// Scenario: Start in Smart Mosaic layout with limit = 2, switch to Mosaic
			// Expected: After switching, all participants become visible in the grid
			// Audio: Participants A, B, C, D use continuous_speech.wav

			// Step 1: Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Step 2: Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });

			// Step 3: Join four browser-based fake participants with continuous speech audio
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteB-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteC-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteD-Speaker', {
					audioFile: 'continuous_speech.wav'
				})
			]);

			// Step 4: Wait for all participants to appear
			await waitForParticipantCount(page, 5); // Local + 4 remotes

			// Step 5: Configure Smart Mosaic layout with limit = 2
			await configureLayoutMode(page, 'smart-mosaic', 2);

			// Verify only 2 remote participants are visible
			let [visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('In Smart Mosaic layout, visible participants:', visibleNames);

			expect(participantCount).toBe(3); // Local + 2 remotes

			// Step 6: Switch to Mosaic layout (all participants visible)
			await configureLayoutMode(page, 'mosaic');

			// Step 7: Wait for layout to update
			await page.waitForTimeout(3000);

			[visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After switching to Mosaic, visible participants:', visibleNames);

			// Step 8: Verify all 4 remote participants are now visible
			expect(participantCount).toBe(5); // Local + 4 remotes
		});

		test('switches from Mosaic to Smart Mosaic limiting by speakers', async ({ page }) => {
			// Scenario: Start in Mosaic layout with 4 participants visible, switch to Smart Mosaic with limit = 2
			// Expected: After switching, only the 2 most recent active speakers remain visible
			// Audio: Participants A, B, C, D use continuous_speech.wav

			// Wait for local participant to join the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });

			// Wait for four browser-based fake participants with continuous speech audio to join
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteB-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteC-Speaker', {
					audioFile: 'continuous_speech.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteD-Speaker', {
					audioFile: 'continuous_speech.wav'
				})
			]);

			// Wait for all participants to appear
			await waitForParticipantCount(page, 5); // Local + 4 remotes

			// Switch to Smart Mosaic layout with limit = 2
			await configureLayoutMode(page, 'smart-mosaic', 2);

			// Wait for layout to update speaker visibility
			await page.waitForTimeout(3000);

			const [visibleNames, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			console.log('After switching to Smart Mosaic, visible participants:', visibleNames);

			// Verify only 2 remote participants are visible (most recent speakers)
			expect(participantCount).toBe(3); // Local + 2 remotes
		});
	});

	// =========================================================================
	// SMART MOSAIC LAYOUT - AUDIO FILTERING TESTS
	// These tests verify the hysteresis mechanisms that filter out:
	// 1. Low volume audio (below audioLevel threshold of 0.15)
	// 2. Brief sounds (below minimum speaking duration of 1.5s)
	// =========================================================================

	test.describe('Smart Mosaic Layout - Audio Level and Duration Filtering', () => {
		test('filters out low volume audio below threshold', async ({ page }) => {
			// Scenario: 3 participants - Local + Remote A (normal volume) + Remote B (low volume ~10%)
			// Expected: Only Remote A appears in the grid, Remote B is filtered due to low audioLevel
			// Audio: A uses continuous_speech.wav, B uses low_volume_speech.wav (10% volume)

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join two participants: one with normal volume, one with very low volume
			await joinFakeParticipant(roomId, 'RemoteA-Silence');

			// Wait for speaker detection to process
			await waitForParticipantCount(page, 2);

			await joinBrowserFakeParticipant(roomId, 'RemoteB-LowVolume', {
				audioFile: 'low_volume_speech.wav' // 10% volume - below 0.15 threshold
			});

			// Wait additional time for B's low volume to be evaluated
			await page.waitForTimeout(4000);
			for (let i = 0; i < 5; i++) {
				const [visibleNames, participantCount] = await Promise.all([
					getVisibleParticipantNames(page),
					getVisibleParticipantsCount(page)
				]);

				console.log('Visible participants with volume filtering:', visibleNames);

				// Remote B (low volume) should NOT be prioritized as active speaker
				expect(visibleNames).not.toContain('RemoteB-LowVolume');
				expect(participantCount).toBe(2); // Local + 1 remote
			}
		});

		test('maintains stability with multiple low-volume speakers', async ({ page }) => {
			// Scenario: 3 participants all with low volume audio, limit = 2
			// Expected: Layout remains stable without constant swapping (all filtered by audioLevel threshold)
			// Audio: All use low_volume_speech.wav

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);
			await joinFakeParticipant(roomId, 'Remote-Initial');

			await waitForParticipantCount(page, 2);

			// Join three participants all with low volume
			await Promise.all([
				joinBrowserFakeParticipant(roomId, 'RemoteA-LowVol', {
					audioFile: 'ambient_pink_noise.wav'
				}),
				joinBrowserFakeParticipant(roomId, 'RemoteB-LowVol', {
					audioFile: 'ambient_pink_noise.wav'
				})
			]);

			await page.waitForTimeout(3000);

			// Record initial visible participants
			const initialVisibleNames = await getVisibleParticipantNames(page);
			console.log('Initial visible participants:', initialVisibleNames);

			// Check layout stability over time - should not swap since all are below threshold
			let swapCount = 0;
			let previousNames = [...initialVisibleNames];

			for (let i = 0; i < 10; i++) {
				await page.waitForTimeout(500);
				const currentNames = await getVisibleParticipantNames(page);

				// Check if any swap occurred
				const hasSwap = !previousNames.every((name) => currentNames.includes(name));
				if (hasSwap) {
					swapCount++;
					console.log(`Swap detected at check ${i + 1}:`, previousNames, '->', currentNames);
				}

				previousNames = [...currentNames];
			}

			console.log(`Total swaps detected: ${swapCount}`);

			// Layout should be stable - no swaps should occur since all are filtered
			expect(swapCount).toBe(0);
		});

		test('filters out brief sounds under minimum duration', async ({ page }) => {
			// Scenario: 3 participants - Local + Remote A (continuous speech) + Remote B (0.5s cough only)
			// Expected: Remote A appears as active speaker, Remote B's brief cough is filtered out
			// Audio: A uses continuous_speech.wav, B uses brief_cough.wav (0.5s sound)

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join silence participant first
			await joinFakeParticipant(roomId, 'RemoteA-Speaker');

			// Wait for A to be detected as speaker
			await waitForParticipantCount(page, 2);

			// Now join participant B with brief cough sound
			await joinBrowserFakeParticipant(roomId, 'RemoteB-Cough', {
				audioFile: 'brief_cough_at_5s.wav' // 0.5s sound - below minimum duration
			});

			// Wait for the brief sound to be processed
			await page.waitForTimeout(5000);

			for (let i = 0; i < 5; i++) {
				const [visibleNames, participantCount] = await Promise.all([
					getVisibleParticipantNames(page),
					getVisibleParticipantsCount(page)
				]);

				// Remote A should remain visible throughout
				expect(visibleNames).not.toContain('RemoteB-Cough');
				expect(participantCount).toBe(2); // Local + 1 remote
				await page.waitForTimeout(500);
			}
		});

		test('does not swap speaker for 1 second sound bursts', async ({ page }) => {
			// Scenario: 3 participants - Local + Remote A (speaking) + Remote B (1s sound burst)
			// Expected: A remains visible, B's 1 second sound is filtered (< 1.5s threshold)
			// Audio: A uses continuous_speech.wav, B uses brief_sound_1s.wav

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join participant A with continuous speech
			await joinFakeParticipant(roomId, 'RemoteA-Continuous');

			await waitForParticipantCount(page, 2);

			// Join participant B with 1 second sound burst
			await joinBrowserFakeParticipant(roomId, 'RemoteB-BriefSound', {
				audioFile: 'brief_sound_1s_at_5s.wav' // 1s sound
			});

			// Wait 5s to allow B's speaks
			await page.waitForTimeout(5000);

			// Track visible participants over time to ensure no swap occurs
			for (let i = 0; i < 5; i++) {
				const visibleNames = await getVisibleParticipantNames(page);

				// A should remain visible throughout
				expect(visibleNames).not.toContain('RemoteB-BriefSound');
				console.log(`Check ${i + 1}: Visible participants:`, visibleNames);

				await page.waitForTimeout(500);
			}
		});
	});

	test.describe('Smart Mosaic Layout - Mute participants', () => {
		test('should be able to mute/unmute participants and keep muted ones in view', async ({ page }) => {
			// Scenario: 2 remote participants + local, limit = 1
			// Remote A speaks first and is visible
			// Remote A is muted → Remote B speaks and becomes visible
			// Remote A is unmuted and speaks again → Remote A becomes visible again
			// Audio: A and B use silence_5s_then_speech.wav

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join Remote A who speaks first
			await joinBrowserFakeParticipant(roomId, 'RemoteA-Speaking', {
				audioFile: 'continuous_speech.wav'
			});

			// Wait for A to become visible
			await waitForParticipantVisible(page, 'RemoteA-Speaking');

			// Verify Remote A is visible
			let [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).toContain('RemoteA-Speaking');
			expect(participantCount).toBe(2); // Local + 1 remote
			// Mute Remote A
			await forceMuteParticipantAudio(page, 'RemoteA-Speaking');

			// Join Remote B who speaks next
			await joinBrowserFakeParticipant(roomId, 'RemoteB-SpeakingNext', {
				audioFile: 'continuous_speech.wav'
			});

			// Verify Remote A is visible although muted,
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).not.toContain('RemoteB-SpeakingNext');
			expect(visibleIdentities).toContain('RemoteA-Speaking');
			expect(participantCount).toBe(2); // Local + 1 remote
			// Unmute Remote A
			await forceMuteParticipantAudio(page, 'RemoteA-Speaking');
			// Verify Remote A is visible again
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).toContain('RemoteA-Speaking');
			expect(participantCount).toBe(2); // Local + 1 remote
		});
		test('should hidden participants who are audio muted', async ({ page }) => {
			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);
			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference
			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);
			// Join Remote A who speaks first
			const pageA = await joinBrowserFakeParticipant(roomId, 'RemoteA-Speaking', {
				audioFile: 'continuous_speech.wav'
			});
			// Wait for A to become visible
			await waitForParticipantVisible(page, 'RemoteA-Speaking');
			// Verify Remote A is visible
			let [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			await joinBrowserFakeParticipant(roomId, 'RemoteB-Speaking', {
				audioFile: 'continuous_speech.wav'
			});

			await page.waitForTimeout(2000);

			expect(visibleIdentities).toContain('RemoteA-Speaking');
			expect(participantCount).toBe(2); // Local + 1 remote
			// Audio mute Remote A
			await muteAudio(pageA);
			// Wait for layout to update
			await waitForParticipantVisible(page, 'RemoteB-Speaking');
			// Verify Remote A is hidden
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			expect(visibleIdentities).not.toContain('RemoteA-Speaking');
			expect(visibleIdentities).toContain('RemoteB-Speaking');
			expect(participantCount).toBe(2); // Local + 1 remote
		});
	});

	test.describe('Smart Mosaic Layout - Screen Sharing visibility', () => {
		test('retain screen sharing participant regardless limit', async ({ page }) => {
			// Scenario: 2 remote participants + local, limit = 1
			// Remote A shares screen (no audio) → should be visible
			// Remote B speaks → should become visible, replacing A
			// Remote A stops sharing → should not be visible unless speaking
			// Audio: B uses continuous_speech.wav

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 1
			await configureLayoutMode(page, 'smart-mosaic', 1);

			// Join Remote A who shares screen
			await joinBrowserFakeParticipant(roomId, 'RemoteA-ScreenShare', {
				screenShare: true,
				audioFile: 'complete_silence.wav' // No audio
			});

			await waitForParticipantVisible(page, 'RemoteA-ScreenShare');

			// Verify Remote A is visible due to screen sharing
			let [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).toContain('RemoteA-ScreenShare');
			expect(visibleIdentities).toContain('RemoteA-ScreenShare_SCREEN');
			expect(participantCount).toBe(3); // Local + screen share + 1 remote

			// Join Remote B who speaks
			await joinBrowserFakeParticipant(roomId, 'RemoteB-Speaking', {
				audioFile: 'continuous_speech.wav'
			});

			// Wait for B to become visible
			await waitForParticipantVisible(page, 'RemoteB-Speaking');

			// Verify Remote B is visible, replacing A
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			expect(visibleIdentities).toContain('RemoteB-Speaking');
			expect(visibleIdentities).not.toContain('RemoteA-ScreenShare');
			expect(visibleIdentities).toContain('RemoteA-ScreenShare_SCREEN');
			expect(participantCount).toBe(3); // Local + screen + 1 remote

			// Stop Remote A's screen sharing
			await stopScreenShareBrowserFakeParticipant(roomId, 'RemoteA-ScreenShare');

			// Wait for layout to update
			await page.waitForTimeout(2000);

			// Verify Remote A is no longer visible
			[visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);
			expect(visibleIdentities).toContain('RemoteB-Speaking');
			expect(visibleIdentities).not.toContain('RemoteA-ScreenShare');
			expect(participantCount).toBe(2); // Local + 1 remote
		});

		test('show screen sharing participant over silent ones', async ({ page }) => {
			// Scenario: 3 remote participants + local, limit = 2
			// Remote A shares screen (no audio) → should be visible
			// Remote B and C are silent → should NOT be visible
			// Audio: B and C are silent

			// Local participant joins the room
			await prepareForJoiningRoom(page, MEET_TESTAPP_URL, roomId);
			await joinRoomAs('moderator', participantName, page);

			// Wait for session to be ready
			await waitForElementInIframe(page, 'ov-session', { state: 'visible' });
			await muteAudio(page); // Mute local to avoid interference

			// Configure Smart Mosaic layout with limit = 2
			await configureLayoutMode(page, 'smart-mosaic', 2);

			// Join two silent participants
			await Promise.all([
				joinFakeParticipant(roomId, 'RemoteB-Silent'),
				joinFakeParticipant(roomId, 'RemoteC-Silent')
			]);

			await waitForParticipantCount(page, 3);

			// Join Remote A who shares screen
			await joinBrowserFakeParticipant(roomId, 'RemoteA-ScreenShare', {
				screenShare: true,
				audioFile: 'complete_silence.wav' // No audio
			});

			await waitForParticipantVisible(page, 'RemoteA-ScreenShare_SCREEN');

			// Wait for layout to update
			await page.waitForTimeout(3000);

			// Verify Remote A is visible due to screen sharing
			const [visibleIdentities, participantCount] = await Promise.all([
				getVisibleParticipantNames(page),
				getVisibleParticipantsCount(page)
			]);

			expect(visibleIdentities).not.toContain('RemoteA-ScreenShare');
			expect(visibleIdentities).toContain('RemoteA-ScreenShare_SCREEN');
			expect(participantCount).toBe(4); // Local + screen share + 1 remote
		});
	});
});
