import { Subscription, Unsubscribable } from 'rxjs';
import { WebExtHostExtensionServiceShape, tryCatchPromise } from '@workbench-stack/extension-api';


/** The WebWorker's global scope */
declare const self: any;

export class WebExtHostExtensionService implements WebExtHostExtensionServiceShape, Unsubscribable {
    /** Extension's deactivate functions. */
    private extensionDeactivate = new Map<string, (() => void | Promise<void>)>();

    /**
     * Proxy method invoked by the client to load an extension and invoke its `activate` function to start running it.
     *
     * It also sets up global hooks so that when the extension's code uses `require('cdecode/client')` and
     * `import 'cdecode/client'`, it gets the extension API handle (the value specified in
     * cdecode.d.ts).
     *
     * @param extensionID The extension ID of the extension to activate.
     * @param bundleURL The URL to the JavaScript source file (that exports an `activate` function) for
     * the extension.
     * @returns A promise that resolves when the extension's activation finishes (i.e., when it returns if it's synchronous,
     * or when the promise it returns resolves if it's async)
     */
    public async $activateExtension(extensionID: string, bundleURL: string): Promise<void> {
        // Load the extension bundle and retrieve the extension entrypoint module's exports on
        // the global `module` property.
        try {
            self.exports = {};
            self.module = {};
            self.importScripts(bundleURL);
            console.log("SELF.MODULE: ", self.module);
        } catch (err) {
            throw new Error(
                `error thrown while executing extension ${JSON.stringify(extensionID)}
                bundle (in importScripts of ${bundleURL}): ${err}`,
            );
        }
        const extensionExports = self.module.exports || {};
        delete self.exports;
        delete self.module;

        if (!extensionExports || !('activate' in extensionExports)) {
            throw new Error(
                `extension bundle for ${JSON.stringify(extensionID)} has no exported activate
                function (in ${bundleURL})`,
            );
        }

        // During extension deactivation, we fist call the extension's `deactivate` function and then unsubscribe
        // the Subscription passed to the `activate` function.
        const extensionSubscriptions = new Subscription();
        const extensionDeactivate =
            typeof extensionExports.deactivate === 'function' ? extensionExports.deactivate() : null;
        this.extensionDeactivate.set(extensionID, async () => {
            try {
                if (extensionDeactivate) {
                    await Promise.resolve(extensionDeactivate());
                }
            } finally {
                extensionSubscriptions.unsubscribe();
            }
        });

        // The behavior should be consistent for both sync and async activate functions that throw
        // errors or reject. Both cases should yield a rejected promise.
        //
        // TODO(sqs): Add timeouts to prevent long-running activate or deactivate functions from
        // significantly delaying other extensions.
        return tryCatchPromise(() => extensionExports.activate({ subscriptions: extensionSubscriptions })).catch(
            error => {
                throw Object.assign(
                    new Error(`error during extension ${JSON.stringify(extensionID)} activate function: ${error}`),
                    {
                        error,
                    },
                );
            },
        );
    }

    public async $deactivateExtension(extensionID: string): Promise<void> {
        const deactivate = this.extensionDeactivate.get(extensionID);
        if (deactivate) {
            this.extensionDeactivate.delete(extensionID);
            return Promise.resolve(deactivate());
        }
    }

    /**
     * Deactivates all activated extensions that have "deactivate" functions. It does not wait for
     * the deactivation to finish for an extension if its deactivate function is async. If any
     * deactivate functions throw an error or reject, the error is logged and not propagated.
     *
     * There is no guarantee that extensions' deactivate functions are called or that execution
     * continues until they are finished. The JavaScript execution context may be terminated before
     * deactivation is completed.
     */
    public unsubscribe(): void {
        for (const [extensionID, deactivate] of this.extensionDeactivate.entries()) {
            this.extensionDeactivate.delete(extensionID);
            tryCatchPromise(deactivate).catch(err => {
                console.warn(`Error deactivating extension ${JSON.stringify(extensionID)}:`, err);
            });
        }
    }

}



