import { CircuitBreakerResponse, isCircuitBreaker } from '@devvit/shared-types/CircuitBreaker.js';
import { getAppTitle } from '../client/ClientShared.js';
import { ActorCommand, ActorResponse } from '../types/Commands.js';
import { inspect } from '../utils/inspect.js';
/** Populate box with a RuntimeLite instance. */
export function assignRuntime(box, runtime) {
    Object.assign(box, {
        CircuitBreakerResponse,
        log,
        onmessage,
        onunhandledrejection,
        runtime,
    });
}
export function startRuntime(box) {
    box.postMessage([
        ActorResponse.READY,
        { start: performance.timeOrigin, end: performance.timeOrigin + performance.now() },
    ]);
}
function log(...args) {
    if (this.debugLogging) {
        console.debug.apply(null, args);
    }
}
async function onmessage(ev) {
    switch (ev.data[0]) {
        case ActorCommand.ENABLE_APP_LOGGING:
            this.runtime.enableAppLogging();
            this.log('<App logs enabled>');
            break;
        case ActorCommand.ENABLE_DEBUG_LOGGING:
            this.debugLogging = true;
            this.log('<Debug logs enabled>');
            break;
        case ActorCommand.ENABLE_PLUGINS: {
            const [, plugins] = ev.data;
            this.log('<Enabling local plugins:>');
            for (const plugin of plugins) {
                this.log(`  ${plugin}`);
            }
            this.runtime.setPluginList(plugins);
            this.postMessage([ActorResponse.PLUGINS_CONFIGURED]);
            break;
        }
        case ActorCommand.LOAD_BUNDLE: {
            const [, bundle] = ev.data;
            this.log(`Loading bundle for app ${getAppTitle(bundle)}`);
            try {
                this.runtime.loadBundle(bundle);
                this.postMessage([ActorResponse.BUNDLE_LOADED, this.runtime.getCallState().logs]);
            }
            catch (err) {
                this.postMessage([
                    ActorResponse.BUNDLE_LOAD_FAILED,
                    undefined,
                    err,
                    this.runtime.getCallState().logs,
                ]);
            }
            break;
        }
        case ActorCommand.CALL: {
            const [, id, method, args, meta, binary] = ev.data;
            this.log(`${id} → [Call] ${method}`);
            const response = binary
                ? await this.runtime.callBinary(method, args, meta)
                : await this.runtime.call(method, args, meta);
            if (response.success) {
                this.postMessage([
                    ActorResponse.RETURN,
                    id,
                    response.value,
                    this.runtime.getCallState().logs,
                ]);
            }
            else {
                if (isCircuitBreaker(response.error)) {
                    this.postMessage([
                        ActorResponse.CIRCUIT_BREAKER,
                        id,
                        response.value,
                        response.error.cause,
                    ]);
                }
                else {
                    this.postMessage([
                        ActorResponse.ERROR,
                        id,
                        response.error,
                        this.runtime.getCallState().logs,
                    ]);
                }
            }
            break;
        }
        case ActorCommand.PLUGIN_RESPONSE: {
            const [, id, response] = ev.data;
            this.runtime.onPluginResponse(id, response);
            break;
        }
        case ActorCommand.QUIT:
            this.log('<Shutdown>');
            this.runtime.quit();
            break;
    }
}
function onunhandledrejection(ev) {
    // Firefox v102.15.1 throws a DateCloneError for Error. Pass an Error-like
    // object if an instance of an Error or a string otherwise.
    let cloneable;
    if (ev.reason instanceof Error)
        cloneable = CloneableError(ev.reason);
    else if (ev.reason != null)
        cloneable = String(ev.reason);
    // The runtime doesn't know about unhandled rejections. Manually inject the
    // unhandeled rejection into the logs so it appears in the CLI.
    const logs = [...(this.runtime?.getCallState().logs ?? [])];
    logs.push({ method: 'error', args: [inspect(ev.reason)] });
    this.postMessage([ActorResponse.UNHANDLED_REJECTION, undefined, cloneable, logs]);
}
function CloneableError(err) {
    return {
        cause: err.cause == null ? undefined : String(err.cause),
        columnNumber: err.columnNumber,
        fileName: err.fileName,
        lineNumber: err.lineNumber,
        message: err.message,
        name: err.name,
        ...(err.stack && { stack: err.stack }),
    };
}
