/*
 * Copyright 2026 Element Creations Ltd.
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 * Please see LICENSE files in the repository root for full details.
 */

import { mocked } from "jest-mock";
import { JoinRule, type MatrixClient, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";

import { RoomListHeaderViewModel } from "../../../src/viewmodels/room-list/RoomListHeaderViewModel";
import { MetaSpace, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../../../src/stores/spaces";
import SpaceStore from "../../../src/stores/spaces/SpaceStore";
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
import SettingsStore from "../../../src/settings/SettingsStore";
import { SortingAlgorithm } from "../../../src/stores/room-list-v3/skip-list/sorters";
import RoomListStoreV3 from "../../../src/stores/room-list-v3/RoomListStoreV3";
import {
    shouldShowSpaceSettings,
    showCreateNewRoom,
    showSpaceInvite,
    showSpacePreferences,
    showSpaceSettings,
} from "../../../src/utils/space";
import { createTestClient, mkSpace } from "../../test-utils";
import { createRoom, hasCreateRoomRights } from "../../../src/viewmodels/room-list/utils";
import PosthogTrackers from "../../../src/PosthogTrackers";

jest.mock("../../../src/PosthogTrackers", () => ({
    trackInteraction: jest.fn(),
}));

jest.mock("../../../src/utils/space", () => ({
    shouldShowSpaceSettings: jest.fn(),
    showCreateNewRoom: jest.fn(),
    showSpaceInvite: jest.fn(),
    showSpacePreferences: jest.fn(),
    showSpaceSettings: jest.fn(),
}));

jest.mock("../../../src/viewmodels/room-list/utils", () => ({
    createRoom: jest.fn(),
    hasCreateRoomRights: jest.fn(),
}));

describe("RoomListHeaderViewModel", () => {
    let matrixClient: MatrixClient;
    let mockSpace: Room;
    let vm: RoomListHeaderViewModel;

    beforeEach(() => {
        matrixClient = createTestClient();

        mockSpace = mkSpace(matrixClient, "!space:server");

        mocked(hasCreateRoomRights).mockReturnValue(true);
        mocked(shouldShowSpaceSettings).mockReturnValue(true);

        jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
            if (settingName === "RoomList.preferredSorting") return SortingAlgorithm.Recency;
            if (settingName === "feature_video_rooms") return true;
            if (settingName === "feature_element_call_video_rooms") return true;
            if (settingName === "RoomList.OrderedCustomSections") return [];
            return false;
        });
    });

    afterEach(() => {
        jest.restoreAllMocks();
        vm.dispose();
    });

    describe("snapshot", () => {
        it("should compute snapshot for Home space", () => {
            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(MetaSpace.Home);
            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(null);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

            const snapshot = vm.getSnapshot();
            expect(snapshot.title).toBe("Home");
            expect(snapshot.displayComposeMenu).toBe(true);
            expect(snapshot.displaySpaceMenu).toBe(false);
            expect(snapshot.canCreateRoom).toBe(true);
            expect(snapshot.canCreateVideoRoom).toBe(true);
            expect(snapshot.activeSortOption).toBe("recent");
        });

        it("should compute snapshot for active space", () => {
            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(mockSpace.roomId);
            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(mockSpace);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

            const snapshot = vm.getSnapshot();
            expect(snapshot.title).toBe(mockSpace.roomId);
        });

        it("should hide video room option when feature is disabled", () => {
            jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
                if (settingName === "feature_video_rooms") return false;
                return false;
            });

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().canCreateVideoRoom).toBe(false);
        });

        it("should show alphabetical sort option when RoomList.preferredSorting is Alphabetic", () => {
            jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
                if (settingName === "RoomList.preferredSorting") return SortingAlgorithm.Alphabetic;
                return false;
            });

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().activeSortOption).toBe("alphabetical");
        });

        it("should hide compose menu when user cannot create rooms", () => {
            mocked(hasCreateRoomRights).mockReturnValue(false);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

            const snapshot = vm.getSnapshot();
            expect(snapshot.displayComposeMenu).toBe(false);
            expect(snapshot.canCreateRoom).toBe(false);
        });

        it("should show invite option when space is public", () => {
            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(mockSpace.roomId);
            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(mockSpace);
            jest.spyOn(mockSpace, "getJoinRule").mockReturnValue(JoinRule.Public);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().canInviteInSpace).toBe(true);
        });

        it("should hide invite option when user cannot invite", () => {
            mocked(mockSpace.canInvite).mockReturnValue(false);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().canInviteInSpace).toBe(false);
        });

        it("should hide space settings when user cannot access them", () => {
            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(mockSpace.roomId);
            mocked(shouldShowSpaceSettings).mockReturnValue(false);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().canAccessSpaceSettings).toBe(false);
        });

        it("should show message preview when RoomList.showMessagePreview is enabled", () => {
            jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
                if (settingName === "RoomList.showMessagePreview") return true;
                return false;
            });

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().isMessagePreviewEnabled).toBe(true);
        });

        it.each([
            [true, true, false],
            [false, false, true],
        ])(
            "when feature_room_list_sections is %s: canCreateSection=%s, useComposeIcon=%s",
            (featureEnabled, expectedCanCreateSection, expectedUseComposeIcon) => {
                jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
                    if (settingName === "feature_room_list_sections") return featureEnabled;
                    return false;
                });

                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
                expect(vm.getSnapshot().canCreateSection).toBe(expectedCanCreateSection);
                expect(vm.getSnapshot().useComposeIcon).toBe(expectedUseComposeIcon);
            },
        );
    });

    describe("event listeners", () => {
        it.each([UPDATE_SELECTED_SPACE, UPDATE_HOME_BEHAVIOUR])(
            "should update snapshot when %s event is emitted",
            (event) => {
                jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(MetaSpace.Home);
                jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(null);

                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

                jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(mockSpace.roomId);
                jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(mockSpace);
                SpaceStore.instance.emit(event);

                expect(vm.getSnapshot().title).toBe(mockSpace.roomId);
            },
        );

        it("should update snapshot when space name changes", () => {
            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(mockSpace.roomId);
            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(mockSpace);

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

            mockSpace.name = "new name";
            mockSpace.emit(RoomEvent.Name, mockSpace);

            expect(vm.getSnapshot().title).toBe("new name");
        });
    });

    describe("actions", () => {
        beforeEach(() => {
            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockReturnValue(mockSpace.roomId);
            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(mockSpace);
        });

        it("should fire CreateChat action when createChatRoom is called", () => {
            const fireSpy = jest.spyOn(defaultDispatcher, "fire");
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

            vm.createChatRoom(new Event("click"));
            expect(fireSpy).toHaveBeenCalledWith(Action.CreateChat);
        });

        it("should call createRoom with active space when in a space", () => {
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.createRoom(new Event("click"));

            expect(createRoom).toHaveBeenCalledWith(mockSpace);
        });

        it("should show create video room dialog for space when createVideoRoom is called", () => {
            jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
                if (settingName === "feature_element_call_video_rooms") return false;
                return false;
            });

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.createVideoRoom();
            expect(showCreateNewRoom).toHaveBeenCalledWith(mockSpace, RoomType.ElementVideo);
        });

        it("should use UnstableCall type when element_call_video_rooms is enabled", () => {
            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(null);

            const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.createVideoRoom();

            expect(dispatchSpy).toHaveBeenCalledWith({
                action: Action.CreateRoom,
                type: RoomType.UnstableCall,
            });
        });

        it("should dispatch ViewRoom action when openSpaceHome is called", () => {
            const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.openSpaceHome();

            expect(dispatchSpy).toHaveBeenCalledWith({
                action: Action.ViewRoom,
                room_id: "!space:server",
                metricsTrigger: undefined,
            });
        });

        it("should show space invite dialog when inviteInSpace is called", () => {
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.inviteInSpace();

            expect(showSpaceInvite).toHaveBeenCalledWith(mockSpace);
        });

        it("should show space preferences dialog when openSpacePreferences is called", () => {
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.openSpacePreferences();

            expect(showSpacePreferences).toHaveBeenCalledWith(mockSpace);
        });

        it("should show space settings dialog when openSpaceSettings is called", () => {
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.openSpaceSettings();

            expect(showSpaceSettings).toHaveBeenCalledWith(mockSpace);
        });

        it.each([
            ["recent" as const, SortingAlgorithm.Recency],
            ["alphabetical" as const, SortingAlgorithm.Alphabetic],
            ["unread-first" as const, SortingAlgorithm.Unread],
        ])("should resort when sort is called with '%s'", (option, expectedAlgorithm) => {
            const resortSpy = jest.spyOn(RoomListStoreV3.instance, "resort").mockImplementation(jest.fn());
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.sort(option);
            expect(resortSpy).toHaveBeenCalledWith(expectedAlgorithm);
        });

        it("should track analytics on resort", () => {
            jest.spyOn(RoomListStoreV3.instance, "activeSortAlgorithm", "get").mockReturnValue(
                SortingAlgorithm.Alphabetic,
            );
            PosthogTrackers.trackRoomListSortingAlgorithmChange = jest.fn();

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            jest.spyOn(RoomListStoreV3.instance, "resort").mockImplementation(jest.fn());
            vm.sort("unread-first");

            expect(PosthogTrackers.trackRoomListSortingAlgorithmChange).toHaveBeenCalledWith(
                SortingAlgorithm.Alphabetic,
                SortingAlgorithm.Unread,
            );
        });

        it("should call createSection on RoomListStoreV3 when createSection is called", () => {
            const createSectionSpy = jest
                .spyOn(RoomListStoreV3.instance, "createSection")
                .mockResolvedValue("element.io.section.work");
            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            vm.createSection();
            expect(createSectionSpy).toHaveBeenCalled();
        });

        describe("collapseOrExpandSections", () => {
            it("should dispatch RoomListCollapseAllSections when collapseSections is not 'expand'", () => {
                const fireSpy = jest.spyOn(defaultDispatcher, "fire");
                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

                vm.collapseOrExpandSections();

                expect(fireSpy).toHaveBeenCalledWith(Action.RoomListCollapseAllSections);
            });

            it("should dispatch RoomListExpandAllSections when collapseSections is 'expand'", () => {
                const fireSpy = jest.spyOn(defaultDispatcher, "fire");
                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

                // Drive the VM into the "expand" state by simulating all sections collapsed
                defaultDispatcher.dispatch(
                    {
                        action: Action.RoomListSectionsCollapseStateChanged,
                        collapseSections: "collapse",
                    },
                    true,
                );
                expect(vm.getSnapshot().collapseSections).toBe("expand");
                vm.collapseOrExpandSections();

                expect(fireSpy).toHaveBeenCalledWith(Action.RoomListExpandAllSections);
            });
        });

        describe("RoomListSectionsCollapseStateChanged handling", () => {
            it("should set collapseSections to 'expand' when collapseSections is collapse", () => {
                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

                defaultDispatcher.dispatch(
                    {
                        action: Action.RoomListSectionsCollapseStateChanged,
                        collapseSections: "collapse",
                    },
                    true,
                );

                expect(vm.getSnapshot().collapseSections).toBe("expand");
            });

            it("should set collapseSections to 'collapse' when collapseSections is expand", () => {
                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

                defaultDispatcher.dispatch(
                    {
                        action: Action.RoomListSectionsCollapseStateChanged,
                        collapseSections: "expand",
                    },
                    true,
                );

                expect(vm.getSnapshot().collapseSections).toBe("collapse");
            });

            it("should set collapseSections to undefined when collapseSections is undefined", () => {
                vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });

                // First drive it into a non-undefined state
                defaultDispatcher.dispatch(
                    {
                        action: Action.RoomListSectionsCollapseStateChanged,
                        collapseSections: "collapse",
                    },
                    true,
                );
                expect(vm.getSnapshot().collapseSections).toBe("expand");

                defaultDispatcher.dispatch(
                    {
                        action: Action.RoomListSectionsCollapseStateChanged,
                        collapseSections: undefined,
                    },
                    true,
                );

                expect(vm.getSnapshot().collapseSections).toBeUndefined();
            });
        });

        it("should toggle message preview from enabled to disabled", () => {
            jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
                if (settingName === "RoomList.showMessagePreview") return true;
                return false;
            });
            const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockImplementation(jest.fn());

            vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
            expect(vm.getSnapshot().isMessagePreviewEnabled).toBe(true);

            vm.toggleMessagePreview();

            expect(setValueSpy).toHaveBeenCalledWith("RoomList.showMessagePreview", null, expect.anything(), false);
            expect(vm.getSnapshot().isMessagePreviewEnabled).toBe(false);
        });
    });
});
