// Ref: https://github.com/jupyterlite/jupyterlite/blob/f2ecc9cf7189cb19722bec2f0fc7ff5dfd233d47/packages/pyolite-kernel/src/kernel.ts
import { PromiseDelegate } from "@stlite/common";
import { makeAbsoluteWheelURL } from "./url";
import { CrossOriginWorkerMaker as Worker } from "./cross-origin-worker";
import { assertStreamlitConfig } from "./types";
// Since v0.19.0, Pyodide raises an exception when importing not pure Python 3 wheels, whose path does not end with "py3-none-any.whl",
// so configuration on file-loader here is necessary so that the hash is not included in the bundled URL.
// About this change on Pyodide, see the links below:
// https://github.com/pyodide/pyodide/pull/1859
// https://pyodide.org/en/stable/project/changelog.html#micropip
import STLITE_SERVER_WHEEL from "!!file-loader?name=pypi/[name].[ext]&context=.!../py/stlite-server/dist/stlite_server-0.1.0-py3-none-any.whl"; // TODO: Extract the import statement to an auto-generated file like `_pypi.ts` in JupyterLite: https://github.com/jupyterlite/jupyterlite/blob/f2ecc9cf7189cb19722bec2f0fc7ff5dfd233d47/packages/pyolite-kernel/src/_pypi.ts
import STREAMLIT_WHEEL from "!!file-loader?name=pypi/[name].[ext]&context=.!../py/streamlit/lib/dist/streamlit-1.30.0-cp311-none-any.whl";
// Ref: https://github.com/streamlit/streamlit/blob/1.12.2/frontend/src/lib/UriUtil.ts#L32-L33
const FINAL_SLASH_RE = /\/+$/;
const INITIAL_SLASH_RE = /^\/+/;
export class StliteKernel {
    constructor(options) {
        var _a, _b;
        this._isDisposed = false;
        this._loaded = new PromiseDelegate();
        this.handleWebSocketMessage = null;
        this.basePath = ((_a = options.basePath) !== null && _a !== void 0 ? _a : window.location.pathname)
            .replace(FINAL_SLASH_RE, "")
            .replace(INITIAL_SLASH_RE, "");
        this.hostConfigResponse = (_b = options.hostConfigResponse) !== null && _b !== void 0 ? _b : {};
        this.onProgress = options.onProgress;
        this.onLoad = options.onLoad;
        this.onError = options.onError;
        // HACK: Use `CrossOriginWorkerMaker` imported as `Worker` here.
        // Read the comment in `cross-origin-worker.ts` for the detail.
        const workerMaker = new Worker(new URL("./worker.js", import.meta.url));
        this._worker = workerMaker.worker;
        this._worker.onmessage = (e) => {
            this._processWorkerMessage(e.data);
        };
        let wheels = undefined;
        if (options.mountedSitePackagesSnapshotFilePath == null) {
            console.debug("Custom wheel URLs:", {
                STLITE_SERVER_WHEEL,
                STREAMLIT_WHEEL,
            });
            const stliteServerWheelUrl = makeAbsoluteWheelURL(STLITE_SERVER_WHEEL, options.wheelBaseUrl);
            const streamlitWheelUrl = makeAbsoluteWheelURL(STREAMLIT_WHEEL, options.wheelBaseUrl);
            wheels = {
                stliteServer: stliteServerWheelUrl,
                streamlit: streamlitWheelUrl,
            };
            console.debug("Custom wheel resolved URLs:", wheels);
        }
        // TODO: Assert other options as well.
        if (options.streamlitConfig != null) {
            assertStreamlitConfig(options.streamlitConfig);
        }
        this._workerInitData = {
            entrypoint: options.entrypoint,
            files: options.files,
            archives: options.archives,
            requirements: options.requirements,
            pyodideUrl: options.pyodideUrl,
            wheels,
            mountedSitePackagesSnapshotFilePath: options.mountedSitePackagesSnapshotFilePath,
            streamlitConfig: options.streamlitConfig,
        };
    }
    get loaded() {
        return this._loaded.promise;
    }
    connectWebSocket(path) {
        return this._asyncPostMessage({
            type: "websocket:connect",
            data: {
                path,
            },
        });
    }
    sendWebSocketMessage(payload) {
        return this._asyncPostMessage({
            type: "websocket:send",
            data: {
                payload,
            },
        });
    }
    onWebSocketMessage(handler) {
        this.handleWebSocketMessage = handler;
    }
    sendHttpRequest(request) {
        return this._asyncPostMessage({
            type: "http:request",
            data: {
                request,
            },
        }, "http:response").then((data) => {
            return data.response;
        });
    }
    writeFile(path, data, opts) {
        return this._asyncPostMessage({
            type: "file:write",
            data: {
                path,
                data,
                opts,
            },
        });
    }
    renameFile(oldPath, newPath) {
        return this._asyncPostMessage({
            type: "file:rename",
            data: {
                oldPath,
                newPath,
            },
        });
    }
    unlink(path) {
        return this._asyncPostMessage({
            type: "file:unlink",
            data: {
                path,
            },
        });
    }
    install(requirements) {
        return this._asyncPostMessage({
            type: "install",
            data: {
                requirements,
            },
        });
    }
    _asyncPostMessage(message, expectedReplyType = "reply") {
        return new Promise((resolve, reject) => {
            const channel = new MessageChannel();
            channel.port1.onmessage = (e) => {
                channel.port1.close();
                const msg = e.data;
                if (msg.error) {
                    reject(msg.error);
                }
                else {
                    if (msg.type !== expectedReplyType) {
                        throw new Error(`Unexpected reply type "${msg.type}"`);
                    }
                    resolve(msg.data);
                }
            };
            this._worker.postMessage(message, [channel.port2]);
        });
    }
    /**
     * Process a message coming from the pyodide web worker.
     *
     * @param msg The worker message to process.
     */
    _processWorkerMessage(msg) {
        switch (msg.type) {
            case "event:start": {
                this._worker.postMessage({
                    type: "initData",
                    data: this._workerInitData,
                });
                break;
            }
            case "event:progress": {
                this.onProgress && this.onProgress(msg.data.message);
                break;
            }
            case "event:error": {
                this.onError && this.onError(msg.data.error);
                break;
            }
            case "event:loaded": {
                this._loaded.resolve();
                this.onLoad && this.onLoad();
                break;
            }
            case "websocket:message": {
                const { payload } = msg.data;
                this.handleWebSocketMessage && this.handleWebSocketMessage(payload);
                break;
            }
        }
    }
    /**
     * Return whether the kernel is disposed.
     */
    get isDisposed() {
        return this._isDisposed;
    }
    /**
     * Dispose the kernel.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        this._worker.terminate();
        this._isDisposed = true;
    }
}
//# sourceMappingURL=kernel.js.map