(function () {
    const jsonRpcVersion = "2.0";
    const protocolVersion = "2026-01-26";
    const queuedHandlerNames = [
        "ontoolinput",
        "ontoolinputpartial",
        "ontoolresult",
        "ontoolcancelled",
        "onhostcontextchanged",
    ];

    const errorCodes = {
        parseError: -32700,
        invalidRequest: -32600,
        methodNotFound: -32601,
        invalidParams: -32602,
        internalError: -32603,
    };

    let nextRequestId = 0;

    const pendingRequests = new Map();
    const handlers = {};
    const queuedNotifications = queuedHandlerNames.reduce(function (
        queue,
        name,
    ) {
        queue[name] = [];

        return queue;
    }, {});
    const state = {
        hostContext: null,
        hostInfo: null,
        hostCapabilities: null,
    };

    let resizeObserver = null;

    function disconnectResizeObserver() {
        if (resizeObserver) {
            resizeObserver.disconnect();
            resizeObserver = null;
        }
    }

    const notificationHandlers = {
        "ui/notifications/host-context-changed": applyHostContext,
        "ui/notifications/tool-input": function (params) {
            emit("ontoolinput", params ?? {});
        },
        "ui/notifications/tool-input-partial": function (params) {
            emit("ontoolinputpartial", params ?? {});
        },
        "ui/notifications/tool-result": function (params) {
            emit("ontoolresult", params ?? {});
        },
        "ui/notifications/tool-cancelled": function (params) {
            emit("ontoolcancelled", params ?? {});
        },
    };

    function send(message) {
        message.jsonrpc = jsonRpcVersion;

        window.parent.postMessage(message, "*");
    }

    function request(method, params) {
        return new Promise(function (resolve, reject) {
            const id = ++nextRequestId;

            pendingRequests.set(id, { resolve, reject });

            send({ id, method, params });
        });
    }

    function notify(method, params) {
        const message = { method };

        if (params !== undefined) {
            message.params = params;
        }

        send(message);
    }

    function respond(id, result) {
        send({ id, result });
    }

    function respondWithError(id, code, message) {
        send({ id, error: { code, message } });
    }

    function parseMessage(data) {
        if (typeof data === "string") {
            try {
                return JSON.parse(data);
            } catch (error) {
                return null;
            }
        }

        if (data && typeof data === "object") {
            return data;
        }

        return null;
    }

    function isObject(value) {
        return (
            value !== null && typeof value === "object" && !Array.isArray(value)
        );
    }

    function normalizeParams(value, key) {
        return isObject(value) ? value : { [key]: value };
    }

    function mergeObjects(original, toMerge) {
        return Object.assign({}, original || {}, toMerge || {});
    }

    function mergeHostContext(update) {
        if (!update) {
            return state.hostContext;
        }

        const current = state.hostContext || {};
        const next = mergeObjects(current, update);

        if (current.styles || update.styles) {
            const currentStyles = current.styles || {};
            const nextStyles = update.styles || {};

            next.styles = mergeObjects(currentStyles, nextStyles);
            next.styles.variables = mergeObjects(
                currentStyles.variables,
                nextStyles.variables,
            );
            next.styles.css = mergeObjects(currentStyles.css, nextStyles.css);
        }

        state.hostContext = next;

        return next;
    }

    function flushQueuedNotifications(name) {
        const callback = handlers[name];
        const queue = queuedNotifications[name];

        if (!callback || !queue || queue.length === 0) {
            return;
        }

        while (queue.length > 0) {
            callback(queue.shift());
        }
    }

    function emit(name, payload) {
        const callback = handlers[name];

        if (callback) {
            callback(payload);
            return;
        }

        if (queuedNotifications[name]) {
            queuedNotifications[name].push(payload);
        }
    }

    function setHandler(name, callback) {
        handlers[name] = callback;
        flushQueuedNotifications(name);
    }

    function applyTheme(theme) {
        if (!theme) {
            return;
        }

        document.documentElement.setAttribute("data-theme", theme);
        document.documentElement.style.colorScheme = theme;
    }

    function applyStyleVariables(variables) {
        if (!variables) {
            return;
        }

        Object.keys(variables)
            .filter((key) => variables[key] !== undefined)
            .forEach(function (key) {
                document.documentElement.style.setProperty(key, variables[key]);
            });
    }

    function applyFonts(fontCss) {
        if (!fontCss) {
            return;
        }

        let style = document.getElementById("__mcp-host-fonts");

        if (!style) {
            style = document.createElement("style");
            style.id = "__mcp-host-fonts";
            document.head.appendChild(style);
        }

        style.textContent = fontCss;
    }

    function applyHostContext(update) {
        const hostContext = mergeHostContext(update);

        if (!hostContext) {
            return;
        }

        applyTheme(hostContext.theme);
        applyStyleVariables(hostContext.styles?.variables);
        applyFonts(hostContext.styles?.css?.fonts);
        emit("onhostcontextchanged", hostContext);
    }

    function currentSize() {
        return {
            width: document.documentElement.scrollWidth,
            height: document.documentElement.scrollHeight,
        };
    }

    function notifySizeChanged() {
        notify("ui/notifications/size-changed", currentSize());
    }

    function autoResize() {
        if (typeof ResizeObserver === "undefined" || !document.body) {
            return;
        }

        disconnectResizeObserver();

        resizeObserver = new ResizeObserver(notifySizeChanged);

        resizeObserver.observe(document.body);

        return disconnectResizeObserver;
    }

    async function handleTeardown(id) {
        try {
            disconnectResizeObserver();

            respond(id, await (handlers.onteardown?.() ?? {}));
        } catch (error) {
            respondWithError(
                id,
                errorCodes.internalError,
                error instanceof Error
                    ? error.message
                    : "Unknown teardown error",
            );
        }
    }

    async function handleCallTool(id, params) {
        if (!handlers.oncalltool) {
            respondWithError(
                id,
                errorCodes.methodNotFound,
                "No tool handler registered.",
            );
            return;
        }

        try {
            respond(id, await handlers.oncalltool(params));
        } catch (error) {
            respondWithError(
                id,
                errorCodes.internalError,
                error instanceof Error ? error.message : "Unknown tool error",
            );
        }
    }

    async function handleListTools(id, params) {
        try {
            respond(
                id,
                await (handlers.onlisttools?.(params) ?? { tools: [] }),
            );
        } catch (error) {
            respondWithError(
                id,
                errorCodes.internalError,
                error instanceof Error
                    ? error.message
                    : "Unknown list tools error",
            );
        }
    }

    function handlePendingResponse(message) {
        if (message.id === undefined || !pendingRequests.has(message.id)) {
            return false;
        }

        const pending = pendingRequests.get(message.id);

        pendingRequests.delete(message.id);

        if (message.error) {
            pending.reject(new Error(message.error.message));
        } else {
            pending.resolve(message.result);
        }

        return true;
    }

    function handleNotification(message) {
        const handler = notificationHandlers[message.method];

        if (handler) {
            handler(message.params);
        }
    }

    const requestHandlers = {
        "ui/resource-teardown": function (message) {
            handleTeardown(message.id);
        },
        "tools/call": function (message) {
            handleCallTool(message.id, message.params);
        },
        "tools/list": function (message) {
            handleListTools(message.id, message.params);
        },
    };

    function handleIncomingRequest(message) {
        const handler = requestHandlers[message.method];

        if (handler) {
            handler(message);
        } else {
            respondWithError(
                message.id,
                errorCodes.methodNotFound,
                "Method not found: " + message.method,
            );
        }
    }

    window.addEventListener("message", function (event) {
        if (event.source !== window.parent) {
            return;
        }

        const message = parseMessage(event.data);

        if (!message || message.jsonrpc !== jsonRpcVersion) {
            return;
        }

        if (handlePendingResponse(message)) {
            return;
        }

        if (message.id === undefined) {
            handleNotification(message);
            return;
        }

        handleIncomingRequest(message);
    });

    window.createMcpApp = async function createMcpApp(setup) {
        const initializeResult = await request("ui/initialize", {
            protocolVersion: protocolVersion,
            appInfo: {
                name: document.title || "MCP App",
                version: "1.0.0",
            },
            appCapabilities: {},
        });

        state.hostInfo = initializeResult?.hostInfo ?? null;
        state.hostCapabilities = initializeResult?.hostCapabilities ?? null;
        applyHostContext(initializeResult?.hostContext ?? null);

        notify("ui/notifications/initialized");

        function callServerTool(nameOrParams, args) {
            const params = isObject(nameOrParams)
                ? {
                      name: nameOrParams.name,
                      arguments: nameOrParams.arguments || {},
                  }
                : {
                      name: nameOrParams,
                      arguments: args || {},
                  };

            return request("tools/call", params);
        }

        function listResources(cursorOrParams) {
            return request(
                "resources/list",
                cursorOrParams
                    ? normalizeParams(cursorOrParams, "cursor")
                    : undefined,
            );
        }

        function readResource(uriOrParams) {
            return request("resources/read", normalizeParams(uriOrParams, "uri"));
        }

        function sendMessage(messageOrContent, role) {
            const params =
                isObject(messageOrContent) &&
                ("content" in messageOrContent || "role" in messageOrContent)
                    ? {
                          role: messageOrContent.role || "user",
                          content: messageOrContent.content,
                      }
                    : {
                          role: role || "user",
                          content: messageOrContent,
                      };

            return request("ui/message", params);
        }

        function openLink(urlOrParams) {
            return request("ui/open-link", normalizeParams(urlOrParams, "url"));
        }

        function downloadFile(contentsOrParams) {
            const params =
                isObject(contentsOrParams) && "contents" in contentsOrParams
                    ? contentsOrParams
                    : { contents: contentsOrParams };

            return request("ui/download-file", params);
        }

        function requestDisplayMode(modeOrParams) {
            return request(
                "ui/request-display-mode",
                normalizeParams(modeOrParams, "mode"),
            );
        }

        function updateModelContext(params) {
            return request("ui/update-model-context", params || {});
        }

        function requestTeardown() {
            notify("ui/notifications/request-teardown");
        }

        function sendLog(levelOrParams, data, logger) {
            const params = isObject(levelOrParams)
                ? levelOrParams
                : {
                      level: levelOrParams,
                      data: data,
                  };

            if (!isObject(levelOrParams) && logger !== undefined) {
                params.logger = logger;
            }

            notify("notifications/message", params);

            return Promise.resolve();
        }

        await setup({
            getHostContext: function () {
                return state.hostContext;
            },
            getHostInfo: function () {
                return state.hostInfo;
            },
            getHostCapabilities: function () {
                return state.hostCapabilities;
            },
            callServerTool: callServerTool,
            listResources: listResources,
            readResource: readResource,
            sendMessage: sendMessage,
            openLink: openLink,
            downloadFile: downloadFile,
            requestDisplayMode: requestDisplayMode,
            updateModelContext: updateModelContext,
            requestTeardown: requestTeardown,
            sendLog: sendLog,
            resize: notifySizeChanged,
            autoResize: autoResize,
            onTeardown: function (callback) {
                handlers.onteardown = callback;
            },
            onCallTool: function (callback) {
                handlers.oncalltool = callback;
            },
            onListTools: function (callback) {
                handlers.onlisttools = callback;
            },
            onToolInput: function (callback) {
                setHandler("ontoolinput", callback);
            },
            onToolInputPartial: function (callback) {
                setHandler("ontoolinputpartial", callback);
            },
            onToolResult: function (callback) {
                setHandler("ontoolresult", callback);
            },
            onToolCancelled: function (callback) {
                setHandler("ontoolcancelled", callback);
            },
            onHostContextChanged: function (callback) {
                setHandler("onhostcontextchanged", callback);
            },
        });
    };
})();
