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

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

const { mockVoiceStoreGetState, mockGetLocalCameraStream, mockGetLocalScreenshareStream } =
  vi.hoisted(() => ({
    mockVoiceStoreGetState: vi.fn(),
    mockGetLocalCameraStream: vi.fn((): MediaStream | null => null),
    mockGetLocalScreenshareStream: vi.fn((): MediaStream | null => null),
  }));

vi.mock("@stores/voice.store", () => ({
  voiceStore: { getState: mockVoiceStoreGetState },
}));

vi.mock("@lib/livekitSession", () => ({
  getLocalCameraStream: mockGetLocalCameraStream,
  getLocalScreenshareStream: mockGetLocalScreenshareStream,
  setScreenshareAudioVolume: vi.fn(),
}));

// ---------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------

import { createVideoModeController } from "../../src/pages/main-page/VideoModeController";
import type { VideoModeControllerOptions } from "../../src/pages/main-page/VideoModeController";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeSlots() {
  return {
    messagesSlot: document.createElement("div"),
    typingSlot: document.createElement("div"),
    inputSlot: document.createElement("div"),
    videoGridSlot: document.createElement("div"),
  };
}

function makeVideoGrid(): VideoModeControllerOptions["videoGrid"] {
  return {
    mount: vi.fn(),
    destroy: vi.fn(),
    addStream: vi.fn(),
    removeStream: vi.fn(),
    clearStreams: vi.fn(),
    hasStreams: vi.fn(() => false),
    setFocusedTile: vi.fn(),
    getFocusedTileId: vi.fn(() => null),
  } as unknown as VideoModeControllerOptions["videoGrid"];
}

interface VoiceStateStub {
  currentChannelId: number | null;
  localCamera: boolean;
  localScreenshare: boolean;
  voiceUsers: Map<
    number,
    Map<number, { userId: number; camera: boolean; screenshare: boolean; username: string }>
  >;
}

function makeVoiceState(overrides: Partial<VoiceStateStub> = {}): VoiceStateStub {
  return {
    currentChannelId: null,
    localCamera: false,
    localScreenshare: false,
    voiceUsers: new Map(),
    ...overrides,
  };
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("createVideoModeController", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockVoiceStoreGetState.mockReturnValue(makeVoiceState());
    mockGetLocalCameraStream.mockReturnValue(null);
    mockGetLocalScreenshareStream.mockReturnValue(null);
  });

  it("starts in chat mode", () => {
    const slots = makeSlots();
    const ctrl = createVideoModeController({
      slots,
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    expect(ctrl.isVideoMode()).toBe(false);
  });

  it("stays in chat mode when no voice channel", () => {
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();
    expect(ctrl.isVideoMode()).toBe(false);
  });

  it("checkVideoMode does NOT auto-open for remote camera (BUG-105)", () => {
    const users = new Map([[2, { userId: 2, camera: true, screenshare: false, username: "bob" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({ currentChannelId: 10, voiceUsers: new Map([[10, users]]) }),
    );

    const slots = makeSlots();
    const ctrl = createVideoModeController({
      slots,
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    // Remote streams require manual click — no auto-open
    expect(ctrl.isVideoMode()).toBe(false);
  });

  it("checkVideoMode auto-closes video grid when no streams remain", () => {
    const users = new Map([[2, { userId: 2, camera: true, screenshare: false, username: "bob" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({ currentChannelId: 10, voiceUsers: new Map([[10, users]]) }),
    );

    const slots = makeSlots();
    const ctrl = createVideoModeController({
      slots,
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });

    // Manually open video grid first
    ctrl.showVideoGrid();
    expect(ctrl.isVideoMode()).toBe(true);

    // All cameras off — should auto-close
    users.set(2, { userId: 2, camera: false, screenshare: false, username: "bob" });
    ctrl.checkVideoMode();
    expect(ctrl.isVideoMode()).toBe(false);
    expect(slots.messagesSlot.style.display).toBe("");
  });

  it("checkVideoMode auto-opens when local camera is on (BUG-105)", () => {
    const users = new Map([[1, { userId: 1, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localCamera: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();
    expect(ctrl.isVideoMode()).toBe(true);
  });

  it("adds local self-view tile when local camera is on", () => {
    const fakeStream = {} as MediaStream;
    mockGetLocalCameraStream.mockReturnValue(fakeStream);
    const users = new Map([[1, { userId: 1, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localCamera: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    expect(vg.addStream).toHaveBeenCalledWith(1, "me (You)", fakeStream, {
      isSelf: true,
      audioUserId: 1,
      isScreenshare: false,
    });
  });

  it("removes local tile when local camera is off", () => {
    const users = new Map([[1, { userId: 1, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localCamera: false,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    expect(vg.removeStream).toHaveBeenCalledWith(1);
  });

  it("does NOT remove remote tiles in checkVideoMode (delegated to onRemoteVideoRemoved)", () => {
    const users = new Map([
      [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      [2, { userId: 2, camera: false, screenshare: false, username: "bob" }],
    ]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    // Remote tile removal is handled by onRemoteVideoRemoved (LiveKit TrackUnsubscribed),
    // not by checkVideoMode, to avoid race conditions with voice store updates.
    expect(vg.removeStream).not.toHaveBeenCalledWith(2);
  });

  it("showChat switches back to chat mode", () => {
    const slots = makeSlots();
    const ctrl = createVideoModeController({
      slots,
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.showVideoGrid();
    expect(ctrl.isVideoMode()).toBe(true);

    ctrl.showChat();
    expect(ctrl.isVideoMode()).toBe(false);
    expect(slots.videoGridSlot.style.display).toBe("none");
  });

  it("destroy resets video mode state and restores DOM", () => {
    const slots = makeSlots();
    const ctrl = createVideoModeController({
      slots,
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.showVideoGrid();
    expect(ctrl.isVideoMode()).toBe(true);
    expect(slots.messagesSlot.style.display).toBe("none");

    ctrl.destroy();
    expect(ctrl.isVideoMode()).toBe(false);
    expect(slots.messagesSlot.style.display).toBe("");
    expect(slots.videoGridSlot.style.display).toBe("none");
  });

  it("checkVideoMode auto-opens when local screenshare is on (BUG-105)", () => {
    const users = new Map([[1, { userId: 1, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localCamera: false,
        localScreenshare: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const slots = makeSlots();
    const ctrl = createVideoModeController({
      slots,
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    expect(ctrl.isVideoMode()).toBe(true);
  });

  it("adds local screenshare self-view tile when local screenshare is on", () => {
    const fakeStream = {} as MediaStream;
    mockGetLocalScreenshareStream.mockReturnValue(fakeStream);
    const users = new Map([[1, { userId: 1, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localCamera: false,
        localScreenshare: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    // screenshareUserId = currentUserId + 1_000_000 = 1 + 1_000_000 = 1_000_001
    expect(vg.addStream).toHaveBeenCalledWith(1_000_001, "me (Screen)", fakeStream, {
      isSelf: true,
      audioUserId: 1,
      isScreenshare: true,
    });
  });

  it("removes local screenshare tile when screenshare is turned off", () => {
    const users = new Map([[1, { userId: 1, camera: false, screenshare: false, username: "me" }]]);

    // First call: screenshare on — tile added
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localScreenshare: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );
    const fakeStream = { getTracks: () => [] } as unknown as MediaStream;
    mockGetLocalScreenshareStream.mockReturnValue(fakeStream);

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();
    expect(vg.addStream).toHaveBeenCalledWith(1_000_001, "me (Screen)", fakeStream, {
      isSelf: true,
      audioUserId: 1,
      isScreenshare: true,
    });

    // Second call: screenshare off — tile removed
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localScreenshare: false,
        voiceUsers: new Map([[10, users]]),
      }),
    );
    ctrl.checkVideoMode();
    expect(vg.removeStream).toHaveBeenCalledWith(1_000_001);
  });

  it("checkVideoMode does NOT auto-open for remote screenshare (BUG-105)", () => {
    const users = new Map([
      [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      [2, { userId: 2, camera: false, screenshare: true, username: "bob" }],
    ]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({ currentChannelId: 10, voiceUsers: new Map([[10, users]]) }),
    );

    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.checkVideoMode();

    // Remote streams require manual click — no auto-open
    expect(ctrl.isVideoMode()).toBe(false);
  });

  // -----------------------------------------------------------------------
  // TileConfig verification tests (Spec 1)
  // -----------------------------------------------------------------------

  it("checkVideoMode passes isSelf:true for local camera tile", () => {
    const fakeStream = {} as MediaStream;
    mockGetLocalCameraStream.mockReturnValue(fakeStream);
    const users = new Map([[5, { userId: 5, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localCamera: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 5,
    });
    ctrl.checkVideoMode();

    expect(vg.addStream).toHaveBeenCalledWith(
      5,
      "me (You)",
      fakeStream,
      expect.objectContaining({ isSelf: true, audioUserId: 5, isScreenshare: false }),
    );
  });

  it("checkVideoMode passes isSelf:true and isScreenshare:true for local screenshare tile", () => {
    const fakeStream = {} as MediaStream;
    mockGetLocalScreenshareStream.mockReturnValue(fakeStream);
    const users = new Map([[5, { userId: 5, camera: false, screenshare: false, username: "me" }]]);
    mockVoiceStoreGetState.mockReturnValue(
      makeVoiceState({
        currentChannelId: 10,
        localScreenshare: true,
        voiceUsers: new Map([[10, users]]),
      }),
    );

    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 5,
    });
    ctrl.checkVideoMode();

    // screenshareUserId = 5 + 1_000_000 = 1_000_005
    expect(vg.addStream).toHaveBeenCalledWith(
      1_000_005,
      "me (Screen)",
      fakeStream,
      expect.objectContaining({ isSelf: true, audioUserId: 5, isScreenshare: true }),
    );
  });

  // -----------------------------------------------------------------------
  // Focus mode tests (Spec 2)
  // -----------------------------------------------------------------------

  it("setFocus sets focused tile and calls videoGrid.setFocusedTile", () => {
    const vg = makeVideoGrid();
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: vg,
      getCurrentUserId: () => 1,
    });
    ctrl.setFocus(42);
    expect(vg.setFocusedTile).toHaveBeenCalledWith(42);
  });

  it("getFocusedTileId returns null initially", () => {
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    expect(ctrl.getFocusedTileId()).toBeNull();
  });

  it("getFocusedTileId returns set value", () => {
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.setFocus(42);
    expect(ctrl.getFocusedTileId()).toBe(42);
  });

  it("showChat resets focusedTileId", () => {
    const ctrl = createVideoModeController({
      slots: makeSlots(),
      videoGrid: makeVideoGrid(),
      getCurrentUserId: () => 1,
    });
    ctrl.showVideoGrid();
    ctrl.setFocus(42);
    expect(ctrl.getFocusedTileId()).toBe(42);

    ctrl.showChat();
    expect(ctrl.getFocusedTileId()).toBeNull();
  });

  describe("sticky video-grid dismissal (v048)", () => {
    it("showChat while local video is on stays dismissed through a later checkVideoMode", () => {
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      mockVoiceStoreGetState.mockReturnValue(
        makeVoiceState({
          currentChannelId: 10,
          localCamera: true,
          voiceUsers: new Map([[10, users]]),
        }),
      );

      const slots = makeSlots();
      const ctrl = createVideoModeController({
        slots,
        videoGrid: makeVideoGrid(),
        getCurrentUserId: () => 1,
      });

      // Auto-opens because local camera is on.
      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(true);

      // User switches to a text channel — explicit dismissal.
      ctrl.showChat();
      expect(ctrl.isVideoMode()).toBe(false);

      // A remote peer's camera toggling re-invokes checkVideoMode(); local
      // video is still on, but the dismissal must stick.
      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(false);
      expect(slots.messagesSlot.style.display).toBe("");
    });

    it("re-arms auto-open once local video turns off after a dismissal", () => {
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      const state = makeVoiceState({
        currentChannelId: 10,
        localCamera: true,
        voiceUsers: new Map([[10, users]]),
      });
      mockVoiceStoreGetState.mockReturnValue(state);

      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: makeVideoGrid(),
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();
      ctrl.showChat();
      expect(ctrl.isVideoMode()).toBe(false);

      // Local camera turns off entirely — dismissal is cleared.
      mockVoiceStoreGetState.mockReturnValue({ ...state, localCamera: false });
      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(false);

      // Local camera turns back on — auto-open fires again since there is
      // nothing left to have dismissed.
      mockVoiceStoreGetState.mockReturnValue({ ...state, localCamera: true });
      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(true);
    });

    it("explicit showVideoGrid clears a prior dismissal", () => {
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      mockVoiceStoreGetState.mockReturnValue(
        makeVoiceState({
          currentChannelId: 10,
          localCamera: true,
          voiceUsers: new Map([[10, users]]),
        }),
      );

      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: makeVideoGrid(),
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();
      ctrl.showChat();
      expect(ctrl.isVideoMode()).toBe(false);

      // User manually re-opens the grid.
      ctrl.showVideoGrid();
      expect(ctrl.isVideoMode()).toBe(true);

      ctrl.showChat();
      ctrl.checkVideoMode();
      // Dismissal was cleared by showVideoGrid, but showChat() re-set it —
      // so this exercises that the flag responds to the most recent call.
      expect(ctrl.isVideoMode()).toBe(false);
    });

    it("does not treat leaving the channel as a dismissal (v048)", () => {
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      const inChannel = makeVoiceState({
        currentChannelId: 10,
        localCamera: true,
        voiceUsers: new Map([[10, users]]),
      });
      mockVoiceStoreGetState.mockReturnValue(inChannel);

      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: makeVideoGrid(),
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(true);

      // leaveVoice() clears currentChannelId before localCamera goes false,
      // and this checkVideoMode() returns early — closing the grid here must
      // not record a dismissal that outlives the session.
      mockVoiceStoreGetState.mockReturnValue({ ...inChannel, currentChannelId: null });
      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(false);

      // Next session, camera on again: auto-open must still work.
      mockVoiceStoreGetState.mockReturnValue(inChannel);
      ctrl.checkVideoMode();
      expect(ctrl.isVideoMode()).toBe(true);
    });
  });

  // -----------------------------------------------------------------------
  // Stale remote tile / focus cleanup on close (B1-8, B5-15)
  // -----------------------------------------------------------------------

  describe("grid cleanup on close", () => {
    it("clears videoGrid streams on a real leave (currentChannelId becomes null)", () => {
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      const inChannel = makeVoiceState({
        currentChannelId: 10,
        localCamera: true,
        voiceUsers: new Map([[10, users]]),
      });
      mockVoiceStoreGetState.mockReturnValue(inChannel);

      const vg = makeVideoGrid();
      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: vg,
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();
      expect(vg.clearStreams).not.toHaveBeenCalled();

      // Leaving voice clears currentChannelId — remote tiles from the ended
      // session must not survive into the next join.
      mockVoiceStoreGetState.mockReturnValue({ ...inChannel, currentChannelId: null });
      ctrl.checkVideoMode();

      expect(vg.clearStreams).toHaveBeenCalledTimes(1);
    });

    it("does not clear videoGrid streams while merely stopping local video mid-session", () => {
      // Auto-reconnect keeps currentChannelId set, so a transient no-video
      // state (all cameras off, still in the channel) must not wipe
      // in-flight remote tiles.
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      mockVoiceStoreGetState.mockReturnValue(
        makeVoiceState({ currentChannelId: 10, voiceUsers: new Map([[10, users]]) }),
      );

      const vg = makeVideoGrid();
      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: vg,
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();

      expect(vg.clearStreams).not.toHaveBeenCalled();
    });

    it("clears videoGrid streams on a voice channel switch (A -> B) even though currentChannelId never passes through null (OC-0012)", () => {
      // VoiceCallbacks.onVoiceJoin moves currentChannelId straight from the
      // old channel to the new one (joinVoiceChannel is optimistic), so the
      // channelId === null branch never runs on a switch. Remote tiles left
      // over from channel A must still be cleared when we land in channel B.
      const usersA = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      mockVoiceStoreGetState.mockReturnValue(
        makeVoiceState({ currentChannelId: 10, voiceUsers: new Map([[10, usersA]]) }),
      );

      const vg = makeVideoGrid();
      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: vg,
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();
      expect(vg.clearStreams).not.toHaveBeenCalled();

      // Switch straight to channel B — currentChannelId goes 10 -> 20, never
      // through null.
      const usersB = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      mockVoiceStoreGetState.mockReturnValue(
        makeVoiceState({ currentChannelId: 20, voiceUsers: new Map([[20, usersB]]) }),
      );
      ctrl.checkVideoMode();

      expect(vg.clearStreams).toHaveBeenCalledTimes(1);
    });

    it("does not clear videoGrid streams across repeated checkVideoMode calls for the same channel (auto-reconnect)", () => {
      const users = new Map([
        [1, { userId: 1, camera: false, screenshare: false, username: "me" }],
      ]);
      mockVoiceStoreGetState.mockReturnValue(
        makeVoiceState({ currentChannelId: 10, voiceUsers: new Map([[10, users]]) }),
      );

      const vg = makeVideoGrid();
      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: vg,
        getCurrentUserId: () => 1,
      });

      ctrl.checkVideoMode();
      ctrl.checkVideoMode();
      ctrl.checkVideoMode();

      expect(vg.clearStreams).not.toHaveBeenCalled();
    });

    it("closeVideoGrid clears the videoGrid's own focus state, not just the controller's", () => {
      const vg = makeVideoGrid();
      const ctrl = createVideoModeController({
        slots: makeSlots(),
        videoGrid: vg,
        getCurrentUserId: () => 1,
      });
      ctrl.showVideoGrid();
      ctrl.setFocus(42);
      expect(vg.setFocusedTile).toHaveBeenCalledWith(42);

      ctrl.showChat();

      // Without this, the grid reopens later still pinned in focus mode on
      // tile 42 even though the controller's own focusedTileId was reset.
      expect(vg.setFocusedTile).toHaveBeenCalledWith(null);
    });
  });
});
