// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { AppAgent, AppAgentManifest } from "@typeagent/agent-sdk";
import {
    createGenericChannel,
    createGenericChannelProvider,
} from "agent-rpc/channel";
import { createRpc } from "agent-rpc/rpc";
import { createAgentRpcServer } from "agent-rpc/server";
import { isWebAgentMessageFromDispatcher } from "../../dist/common/webAgentMessageTypes.mjs";
import {
    WebAgentRegisterMessage,
    WebAgentRpcMessage,
} from "../common/webAgentMessageTypes.mjs";

declare global {
    function registerTypeAgent(
        name: string,
        manifest: AppAgentManifest,
        agent: AppAgent,
    ): void;

    interface Window {
        webAgentApi: {
            onWebAgentMessage: (callback: (message: any) => void) => void;
            sendWebAgentMessage: (message: any) => void;
        };
    }
}

type DynamicTypeAgentManager = {
    addTypeAgent: (
        name: string,
        manifest: AppAgentManifest,
        agent: AppAgent,
    ) => Promise<void>;
};

type DynamicTypeAgentManagerInvokeFunctions = {
    addTypeAgent: (param: {
        name: string;
        manifest: AppAgentManifest;
    }) => Promise<void>;
};

let manager: DynamicTypeAgentManager | undefined;
function ensureDynamicTypeAgentManager(): DynamicTypeAgentManager {
    if (manager !== undefined) {
        return manager;
    }
    const messageChannelProvider = createGenericChannelProvider(
        (message: any) => {
            const rpcMessage = {
                source: "webAgent",
                method: "webAgent/message",
                params: message,
            } as WebAgentRpcMessage;

            if (window.webAgentApi) {
                window.webAgentApi.sendWebAgentMessage(rpcMessage);
            } else {
                window.postMessage(rpcMessage);
            }
        },
    );

    const registerChannel = createGenericChannel((message: any) => {
        const rpcMessage = {
            source: "webAgent",
            method: "webAgent/register",
            params: message,
        } as WebAgentRegisterMessage;

        if (window.webAgentApi) {
            window.webAgentApi.sendWebAgentMessage(rpcMessage);
        } else {
            window.postMessage(rpcMessage);
        }
    });

    const rpc = createRpc<DynamicTypeAgentManagerInvokeFunctions>(
        registerChannel.channel,
    );
    manager = {
        addTypeAgent: async (name, manifest, agent) => {
            const p = rpc.invoke("addTypeAgent", {
                name,
                manifest,
            });

            const closeFn = createAgentRpcServer(
                name,
                agent,
                messageChannelProvider,
            );
            try {
                await p;
            } catch (e) {
                closeFn();
                throw e;
            }
        },
    };

    const messageHandler = (event: MessageEvent) => {
        const data = event.data;
        if (isWebAgentMessageFromDispatcher(data)) {
            switch (data.method) {
                case "webAgent/register":
                    registerChannel.message(data.params);
                    break;
                case "webAgent/message":
                    messageChannelProvider.message(data.params);
                    break;
                case "webAgent/disconnect":
                    messageChannelProvider.disconnect();
                    registerChannel.disconnect();
                    window.removeEventListener("message", messageHandler);
                    manager = undefined;
                    break;
            }
        }
    };

    if (window.webAgentApi) {
        window.webAgentApi.onWebAgentMessage((event) => {
            messageHandler(event);
        });
    } else {
        window.addEventListener("message", messageHandler);
    }

    return manager;
}

global.registerTypeAgent = async (
    name: string,
    manifest: AppAgentManifest,
    agent: AppAgent,
): Promise<void> => {
    const manager = ensureDynamicTypeAgentManager();
    return manager.addTypeAgent(name, manifest, agent);
};
