/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
import { screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Mocked } from "jest-mock";
import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi";
import { getMockClientWithEventEmitter, mkRoom, stubClient } from "../test-utils";
import { setLanguage } from "../../src/languageHandler";
import { ModuleRunner } from "../../src/modules/ModuleRunner";
import { registerMockModule } from "./MockModule";
import defaultDispatcher from "../../src/dispatcher/dispatcher";
import { Action } from "../../src/dispatcher/actions";
import WidgetStore, { IApp } from "../../src/stores/WidgetStore";
import { Container, WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
describe("ProxiedApiModule", () => {
    afterEach(() => {
        ModuleRunner.instance.reset();
    });
    // Note: Remainder is implicitly tested from end-to-end tests of modules.
    describe("translations", () => {
        it("should cache translations", () => {
            const api = new ProxiedModuleApi();
            expect(api.translations).toBeFalsy();
            const translations: TranslationStringsObject = {
                ["custom string"]: {
                    en: "custom string",
                    fr: "custom french string",
                },
            };
            api.registerTranslations(translations);
            expect(api.translations).toBe(translations);
        });
        it("should overwriteAccountAuth", async () => {
            const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
            const api = new ProxiedModuleApi();
            const accountInfo = {} as unknown as AccountAuthInfo;
            const promise = api.overwriteAccountAuth(accountInfo);
            expect(dispatchSpy).toHaveBeenCalledWith(
                expect.objectContaining({
                    action: Action.OverwriteLogin,
                    credentials: {
                        ...accountInfo,
                        guest: false,
                    },
                }),
                expect.anything(),
            );
            defaultDispatcher.fire(Action.OnLoggedIn);
            await expect(promise).resolves.toBeUndefined();
        });
        describe("integration", () => {
            it("should translate strings using translation system", async () => {
                // Test setup
                stubClient();
                // Set up a module to pull translations through
                const module = registerMockModule();
                const en = "custom string";
                const de = "custom german string";
                const enVars = "custom variable %(var)s";
                const varVal = "string";
                const deVars = "custom german variable %(var)s";
                const deFull = `custom german variable ${varVal}`;
                expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi);
                module.apiInstance.registerTranslations({
                    [en]: {
                        en: en,
                        de: de,
                    },
                    [enVars]: {
                        en: enVars,
                        de: deVars,
                    },
                });
                await setLanguage("de"); // calls `registerCustomTranslations()` for us
                // See if we can pull the German string out
                expect(module.apiInstance.translateString(en)).toEqual(de);
                expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull);
            });
            afterEach(async () => {
                await setLanguage("en"); // reset the language
            });
        });
    });
    describe("openDialog", () => {
        it("should open dialog with a custom title and default options", async () => {
            class MyDialogContent extends DialogContent {
                public constructor(props: DialogProps) {
                    super(props);
                }
                trySubmit = async () => ({ result: true });
                render = () => 
This is my example content.
;
            }
            const api = new ProxiedModuleApi();
            const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>(
                "My Dialog Title",
                (props, ref) => ,
            );
            const dialog = await screen.findByRole("dialog");
            expect(within(dialog).getByRole("heading", { name: "My Dialog Title" })).toBeInTheDocument();
            expect(within(dialog).getByText("This is my example content.")).toBeInTheDocument();
            expect(within(dialog).getByRole("button", { name: "Cancel" })).toBeInTheDocument();
            await userEvent.click(within(dialog).getByRole("button", { name: "OK" }));
            expect(await resultPromise).toEqual({
                didOkOrSubmit: true,
                model: { result: true },
            });
            expect(dialog).not.toBeInTheDocument();
        });
        it("should open dialog with custom options", async () => {
            class MyDialogContent extends DialogContent {
                public constructor(props: DialogProps) {
                    super(props);
                }
                trySubmit = async () => ({ result: true });
                render = () => This is my example content.
;
            }
            const api = new ProxiedModuleApi();
            const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>(
                {
                    title: "My Custom Dialog Title",
                    actionLabel: "Submit it",
                    cancelLabel: "Cancel it",
                    canSubmit: false,
                },
                (props, ref) => ,
            );
            const dialog = await screen.findByRole("dialog");
            expect(within(dialog).getByRole("heading", { name: "My Custom Dialog Title" })).toBeInTheDocument();
            expect(within(dialog).getByText("This is my example content.")).toBeInTheDocument();
            expect(within(dialog).getByRole("button", { name: "Submit it" })).toBeDisabled();
            await userEvent.click(within(dialog).getByRole("button", { name: "Cancel it" }));
            expect(await resultPromise).toEqual({ didOkOrSubmit: false });
            expect(dialog).not.toBeInTheDocument();
        });
        it("should update the options from the opened dialog", async () => {
            class MyDialogContent extends DialogContent {
                public constructor(props: DialogProps) {
                    super(props);
                }
                trySubmit = async () => ({ result: true });
                render = () => {
                    const onClick = () => {
                        this.props.setOptions({
                            title: "My New Title",
                            actionLabel: "New Action",
                            cancelLabel: "New Cancel",
                        });
                        // check if delta updates work
                        this.props.setOptions({
                            canSubmit: false,
                        });
                    };
                    return (
                        
                    );
                };
            }
            const api = new ProxiedModuleApi();
            const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>(
                "My Dialog Title",
                (props, ref) => ,
            );
            const dialog = await screen.findByRole("dialog");
            expect(within(dialog).getByRole("heading", { name: "My Dialog Title" })).toBeInTheDocument();
            expect(within(dialog).getByRole("button", { name: "Cancel" })).toBeInTheDocument();
            expect(within(dialog).getByRole("button", { name: "OK" })).toBeEnabled();
            await userEvent.click(within(dialog).getByRole("button", { name: "Change the settings!" }));
            expect(within(dialog).getByRole("heading", { name: "My New Title" })).toBeInTheDocument();
            expect(within(dialog).getByRole("button", { name: "New Action" })).toBeDisabled();
            await userEvent.click(within(dialog).getByRole("button", { name: "New Cancel" }));
            expect(await resultPromise).toEqual({
                didOkOrSubmit: false,
                model: undefined,
            });
            expect(dialog).not.toBeInTheDocument();
        });
        it("should cancel the dialog from within the dialog", async () => {
            class MyDialogContent extends DialogContent {
                public constructor(props: DialogProps) {
                    super(props);
                }
                trySubmit = async () => ({ result: true });
                render = () => (
                    
                );
            }
            const api = new ProxiedModuleApi();
            const resultPromise = api.openDialog<{ result: boolean }, DialogProps, MyDialogContent>(
                "My Dialog Title",
                (props, ref) => ,
            );
            const dialog = await screen.findByRole("dialog");
            await userEvent.click(within(dialog).getByRole("button", { name: "No need for action" }));
            expect(await resultPromise).toEqual({
                didOkOrSubmit: false,
                model: undefined,
            });
            expect(dialog).not.toBeInTheDocument();
        });
    });
    describe("getApps", () => {
        it("should return apps from the widget store", () => {
            const api = new ProxiedModuleApi();
            const app = {} as unknown as IApp;
            const apps: IApp[] = [app];
            jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue(apps);
            expect(api.getApps("!room:example.com")).toEqual(apps);
        });
    });
    describe("getAppAvatarUrl", () => {
        const app = {} as unknown as IApp;
        const avatarUrl = "https://example.com/avatar.png";
        let api: ProxiedModuleApi;
        let client: Mocked;
        beforeEach(() => {
            api = new ProxiedModuleApi();
            client = getMockClientWithEventEmitter({ mxcUrlToHttp: jest.fn().mockReturnValue(avatarUrl) });
        });
        it("should return null if the app has no avatar URL", () => {
            expect(api.getAppAvatarUrl(app)).toBeNull();
        });
        it("should return the app avatar URL", () => {
            expect(api.getAppAvatarUrl({ ...app, avatar_url: avatarUrl })).toBe(avatarUrl);
        });
        it("should support optional thumbnail params", () => {
            api.getAppAvatarUrl({ ...app, avatar_url: avatarUrl }, 1, 2, "3");
            // eslint-disable-next-line no-restricted-properties
            expect(client.mxcUrlToHttp).toHaveBeenCalledWith(avatarUrl, 1, 2, "3");
        });
    });
    describe("isAppInContainer", () => {
        const app = {} as unknown as IApp;
        const roomId = "!room:example.com";
        let api: ProxiedModuleApi;
        let client: MatrixClient;
        beforeEach(() => {
            api = new ProxiedModuleApi();
            client = stubClient();
            jest.spyOn(WidgetLayoutStore.instance, "isInContainer");
        });
        it("should return false if there is no room", () => {
            client.getRoom = jest.fn().mockReturnValue(null);
            expect(api.isAppInContainer(app, Container.Top, roomId)).toBe(false);
            expect(WidgetLayoutStore.instance.isInContainer).not.toHaveBeenCalled();
        });
        it("should return false if the app is not in the container", () => {
            jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false);
            expect(api.isAppInContainer(app, Container.Top, roomId)).toBe(false);
        });
        it("should return true if the app is in the container", () => {
            jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true);
            expect(api.isAppInContainer(app, Container.Top, roomId)).toBe(true);
        });
    });
    describe("moveAppToContainer", () => {
        const app = {} as unknown as IApp;
        const roomId = "!room:example.com";
        let api: ProxiedModuleApi;
        let client: MatrixClient;
        beforeEach(() => {
            api = new ProxiedModuleApi();
            client = stubClient();
            jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
        });
        it("should not move if there is no room", () => {
            client.getRoom = jest.fn().mockReturnValue(null);
            api.moveAppToContainer(app, Container.Top, roomId);
            expect(WidgetLayoutStore.instance.moveToContainer).not.toHaveBeenCalled();
        });
        it("should move if there is a room", () => {
            const room = mkRoom(client, roomId);
            client.getRoom = jest.fn().mockReturnValue(room);
            api.moveAppToContainer(app, Container.Top, roomId);
            expect(WidgetLayoutStore.instance.moveToContainer).toHaveBeenCalledWith(room, app, Container.Top);
        });
    });
});