var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _CommonActorRef_runtime, _CommonActorRef_hostname, _CommonRuntime_instances, _CommonRuntime_supervisorClient, _CommonRuntime_workerErrorLogger, _CommonRuntime_started, _CommonRuntime_dispatcher, _CommonRuntime_provides, _CommonRuntime_inspectorChanges, _CommonRuntime_systemWorkerSpecs, _CommonRuntime_workersByHostname, _CommonRuntime_bindAndProvide, _CommonRuntime_unprovide, _CommonRuntime_notifyInspector;
import { Definition } from '@devvit/protos/lib/Types.js';
import { InspectorDefinition, InspectorDescription, InspectorDescriptions, InspectorUpdate, InspectorUpdateType, } from '@devvit/protos/types/devvit/plugin/inspector/inspector.js';
import { BootstrapActorDefinition, } from '@devvit/protos/types/devvit/runtime/actor/bootstrap.js';
import { RuntimeActorDefinition, } from '@devvit/protos/types/devvit/runtime/actor/runtime.js';
import { SupervisorDefinition, UnloadRequest, } from '@devvit/protos/types/devvit/runtime/actor/supervisor.js';
import { Empty } from '@devvit/protos/types/google/protobuf/empty.js';
import { DEBUG_CONFIG } from '@devvit/shared-types/debugConfig.js';
import { resolveActorHostname, resolvePluginDefinitionHostname, resolveSystemHostname, RUNTIME_CNAME, SUPERVISOR_CNAME, } from '@devvit/shared-types/HostnameUtil.js';
import { NonNull } from '@devvit/shared-types/NonNull.js';
import { startWith, Subject } from 'rxjs';
import UnknownActorError from '../../common/errors/UnknownActorError.js';
import { defaultSystemWorkerSpec } from '../../workers/SystemWorkerSpec.js';
import { CentralDispatcher } from '../envelope/dispatcher/CentralDispatcher.js';
import { EnvelopeClientBuilder } from '../envelope/EnvelopeClientBuilder.js';
import { EnvelopeServerBuilder } from '../envelope/EnvelopeServerBuilder.js';
import { Never } from './GCPolicy.js';
import { WorkerEnvelopeRecipient } from './WorkerEnvelopeRecipient.js';
class CommonActorRef {
    constructor(runtime, hostname) {
        var _a;
        _CommonActorRef_runtime.set(this, void 0);
        _CommonActorRef_hostname.set(this, void 0);
        __classPrivateFieldSet(this, _CommonActorRef_runtime, runtime, "f");
        __classPrivateFieldSet(this, _CommonActorRef_hostname, hostname, "f");
        (_a = __classPrivateFieldGet(this, _CommonActorRef_runtime, "f").refCounts)[hostname] ?? (_a[hostname] = 0);
        __classPrivateFieldGet(this, _CommonActorRef_runtime, "f").refCounts[hostname] += 1;
    }
    // to-do: make it harder to do the wrong thing. This is confusing because it's
    // so similar to getClient() but many call getClient() directly instead of
    // this ref counter.
    As(desc) {
        const out = __classPrivateFieldGet(this, _CommonActorRef_runtime, "f").getClient(__classPrivateFieldGet(this, _CommonActorRef_hostname, "f"), desc);
        if (!out) {
            throw new UnknownActorError(__classPrivateFieldGet(this, _CommonActorRef_hostname, "f"));
        }
        return out;
    }
    async dispose() {
        __classPrivateFieldGet(this, _CommonActorRef_runtime, "f").refCounts[__classPrivateFieldGet(this, _CommonActorRef_hostname, "f")] -= 1;
        return __classPrivateFieldGet(this, _CommonActorRef_runtime, "f").policy.onDispose(__classPrivateFieldGet(this, _CommonActorRef_hostname, "f"));
    }
}
_CommonActorRef_runtime = new WeakMap(), _CommonActorRef_hostname = new WeakMap();
export class CommonRuntime {
    get hostname() {
        return __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").hostname;
    }
    /** Get the dispatcher, only meant to be used in a debug mode REPL */
    get debugDispatcher() {
        return DEBUG_CONFIG.addDebugUtilsToEnv ? __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f") : undefined;
    }
    constructor(namespace, policyFactory = Never, workerErrorLogger = () => { }, systemWorkerSpecs = defaultSystemWorkerSpec) {
        _CommonRuntime_instances.add(this);
        _CommonRuntime_supervisorClient.set(this, void 0);
        _CommonRuntime_workerErrorLogger.set(this, void 0);
        _CommonRuntime_started.set(this, false);
        _CommonRuntime_dispatcher.set(this, void 0);
        this.refCounts = Object.create(null);
        _CommonRuntime_provides.set(this, Object.create(null));
        _CommonRuntime_inspectorChanges.set(this, new Subject());
        _CommonRuntime_systemWorkerSpecs.set(this, void 0);
        _CommonRuntime_workersByHostname.set(this, {});
        __classPrivateFieldSet(this, _CommonRuntime_workerErrorLogger, workerErrorLogger, "f");
        this.namespace = namespace;
        __classPrivateFieldSet(this, _CommonRuntime_dispatcher, new CentralDispatcher(namespace), "f");
        this.policy = policyFactory.create(this);
        __classPrivateFieldSet(this, _CommonRuntime_systemWorkerSpecs, systemWorkerSpecs.reduce((acc, spec) => ({ ...acc, [resolveSystemHostname(spec.cname, this)]: spec }), Object.create(null)), "f");
        if (!__classPrivateFieldGet(this, _CommonRuntime_systemWorkerSpecs, "f")[resolveSystemHostname(SUPERVISOR_CNAME, this)]) {
            throw new Error('must provide a Supervisor implementation in system workers.');
        }
    }
    /**
     * Bind a recipient to the dispatcher. Allow a recipient to send and receive
     * envelopes but not to be used (dependend on).
     */
    bind(recipient) {
        __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").bind(recipient);
    }
    async release(recipient) {
        await __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").release(recipient);
    }
    unbind(recipient) {
        __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").unbind(recipient);
    }
    post(caller, envelope) {
        __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").post(caller, envelope);
    }
    onBind(dispatcher) {
        __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").onBind(dispatcher);
    }
    onUnbind(dispatcher) {
        __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").onUnbind(dispatcher);
    }
    /**
     * Return the path to the worker.bootstrap.cjs file
     */
    getBootstrapPath() {
        return this.pathForName('bootstrap');
    }
    /**
     * Starts the  Supervisor and loads Plugins
     */
    async start() {
        if (__classPrivateFieldGet(this, _CommonRuntime_started, "f"))
            return;
        __classPrivateFieldSet(this, _CommonRuntime_supervisorClient, this.getClient(resolveSystemHostname(SUPERVISOR_CNAME, this), SupervisorDefinition), "f");
        __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").bindSupervisor(__classPrivateFieldGet(this, _CommonRuntime_supervisorClient, "f"));
        this.provide(InspectorDefinition, this);
        // CommonRuntime embeds RuntimeActor. to-do: should this be a plugin? If so,
        // the few lines can become this.provide().
        const runtimeActor = EnvelopeServerBuilder.build(resolveSystemHostname(RUNTIME_CNAME, this), [RuntimeActorDefinition], this);
        __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_bindAndProvide).call(this, runtimeActor, RuntimeActorDefinition);
        for (const spec of Object.values(__classPrivateFieldGet(this, _CommonRuntime_systemWorkerSpecs, "f"))) {
            const hostname = resolveSystemHostname(spec.cname, this);
            const worker = new WorkerEnvelopeRecipient(hostname, this.pathForName(spec.workerFileBaseName), __classPrivateFieldGet(this, _CommonRuntime_workerErrorLogger, "f"));
            __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_bindAndProvide).call(this, worker, ...spec.provides);
        }
        await this.loadPlugins(__classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f"));
        __classPrivateFieldSet(this, _CommonRuntime_started, true, "f");
    }
    /**
     * Teardown the Runtime, terminating all Workers and connections
     */
    async dispose() {
        __classPrivateFieldSet(this, _CommonRuntime_supervisorClient, undefined, "f");
        await __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").dispose();
    }
    /**
     * Load an app and all dependencies from the given bundle
     */
    async load(bundle) {
        await this.start();
        await __classPrivateFieldGet(this, _CommonRuntime_supervisorClient, "f").Load(bundle);
    }
    async loadAndGetActorRef(bundle) {
        await this.start();
        await this.load(bundle);
        return this.getActorRef(bundle.hostname);
    }
    async forceUnload(hostname) {
        await this.start();
        await __classPrivateFieldGet(this, _CommonRuntime_supervisorClient, "f")?.Unload(UnloadRequest.fromPartial({ hostname, force: true }));
    }
    /**
     * Get an ActorRef by hostname.
     */
    getActorRef(hostname) {
        return new CommonActorRef(this, hostname);
    }
    /**
     * Returns an RPC connection to a plugin
     */
    getPlugin(def) {
        // Plugins must be local, so lets check.
        if (!__classPrivateFieldGet(this, _CommonRuntime_provides, "f")[resolvePluginDefinitionHostname(def, this)]) {
            throw new UnknownActorError(resolvePluginDefinitionHostname(def, this));
        }
        return this.getClient(resolvePluginDefinitionHostname(def, this), def);
    }
    /**
     * Returns the inspector, if available. to-do: how do we get callers to unbind
     * clients when they're done with them?
     */
    getInspector() {
        return this.getClient(resolvePluginDefinitionHostname(InspectorDefinition, this), InspectorDefinition);
    }
    // to-do: bindService?
    /**
     * Add a Definition instance as an addressable and usable service.
     */
    provide(definition, service) {
        const server = EnvelopeServerBuilder.build(resolvePluginDefinitionHostname(definition, this), [definition], service);
        __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_bindAndProvide).call(this, server, definition);
    }
    /**
     * Returns true if there is an active connection to the hostname.
     */
    isBound(recipient) {
        return __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").isBound(recipient);
    }
    /**
     * Create a client to the server at hostname. The server is assumed to exist.
     * Each client has a unique hostname that doesn't collide even when there are
     * multiple clients to the same server. The client hostname is under the
     * namespace of the runtime and has no resemblance to the server. Eg, the
     * server may answer to logger.plugins.local and the client may call as
     * 123.local.
     *
     * A client should be unbound when no longer used. to-do: reference count
     * with a FinalizationRegistry or a WeakMap.
     */
    getClient(serverHostname, definition) {
        const client = EnvelopeClientBuilder.build(serverHostname, [definition]);
        this.bind(client);
        return client;
    }
    async NewWorker(bundle) {
        if (bundle.hostname in __classPrivateFieldGet(this, _CommonRuntime_workersByHostname, "f"))
            throw Error(`Worker already at ${bundle.hostname}.`);
        const worker = new WorkerEnvelopeRecipient(bundle.hostname, this.getBootstrapPath(), __classPrivateFieldGet(this, _CommonRuntime_workerErrorLogger, "f"));
        __classPrivateFieldGet(this, _CommonRuntime_workersByHostname, "f")[bundle.hostname] = worker;
        __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_bindAndProvide).call(this, worker, ...bundle.provides.map((definition) => Definition.fromSerializable(definition)));
        // The bootstrap service itself is a remote (isolated in the worker) so like
        // remote runtimes, we leave it to the worker to resolve and don't advertise
        // BootstrapDefinition as a provide in this runtime. We assume it exists
        // here.
        const bootstrap = this.getClient(resolveActorHostname('bootstrap', bundle), BootstrapActorDefinition);
        try {
            // On error, the supervisor will invoke TerminateWorker() which clears
            // #workersByHostname and #provides state.
            await bootstrap.LoadActor(bundle);
        }
        finally {
            await this.release(bootstrap);
        }
        return Empty.fromPartial({});
    }
    // RPC
    async TerminateWorker(query) {
        const worker = __classPrivateFieldGet(this, _CommonRuntime_workersByHostname, "f")[query.hostname];
        if (worker == null)
            return Empty.fromPartial({});
        await __classPrivateFieldGet(this, _CommonRuntime_dispatcher, "f").release(worker);
        __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_unprovide).call(this, query.hostname);
        delete __classPrivateFieldGet(this, _CommonRuntime_workersByHostname, "f")[query.hostname];
        return Empty.fromPartial({});
    }
    // RPC
    async ListActors(request) {
        const descriptions = [];
        for (const hostname of Object.keys(__classPrivateFieldGet(this, _CommonRuntime_provides, "f"))) {
            if (hostname.includes(request.hostname.toLocaleLowerCase())) {
                if (__classPrivateFieldGet(this, _CommonRuntime_provides, "f")[hostname].some((provide) => provide.fullName.includes(request.type))) {
                    descriptions.push(InspectorDescription.fromPartial({
                        hostname,
                        provides: [...__classPrivateFieldGet(this, _CommonRuntime_provides, "f")[hostname]],
                    }));
                }
            }
        }
        return InspectorDescriptions.fromPartial({ descriptions });
    }
    // RPC
    ActorChanges(_request) {
        return __classPrivateFieldGet(this, _CommonRuntime_inspectorChanges, "f").pipe(startWith(InspectorUpdate.fromPartial({ type: InspectorUpdateType.SUBSCRIBED })));
    }
}
_CommonRuntime_supervisorClient = new WeakMap(), _CommonRuntime_workerErrorLogger = new WeakMap(), _CommonRuntime_started = new WeakMap(), _CommonRuntime_dispatcher = new WeakMap(), _CommonRuntime_provides = new WeakMap(), _CommonRuntime_inspectorChanges = new WeakMap(), _CommonRuntime_systemWorkerSpecs = new WeakMap(), _CommonRuntime_workersByHostname = new WeakMap(), _CommonRuntime_instances = new WeakSet(), _CommonRuntime_bindAndProvide = function _CommonRuntime_bindAndProvide(recipient, ...definitions) {
    this.bind(recipient);
    const hostname = NonNull(recipient.hostname);
    const provides = definitions.map((definition) => Definition.toSerializable(definition));
    __classPrivateFieldGet(this, _CommonRuntime_provides, "f")[hostname] = provides;
    __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_notifyInspector).call(this, provides, 'ADDED', hostname);
}, _CommonRuntime_unprovide = function _CommonRuntime_unprovide(hostname) {
    const provides = __classPrivateFieldGet(this, _CommonRuntime_provides, "f")[hostname];
    if (provides == null)
        return;
    __classPrivateFieldGet(this, _CommonRuntime_instances, "m", _CommonRuntime_notifyInspector).call(this, provides, 'REMOVED', hostname);
    delete __classPrivateFieldGet(this, _CommonRuntime_provides, "f")[hostname];
}, _CommonRuntime_notifyInspector = function _CommonRuntime_notifyInspector(provides, type, hostname) {
    __classPrivateFieldGet(this, _CommonRuntime_inspectorChanges, "f").next(InspectorUpdate.fromPartial({
        type: InspectorUpdateType[type],
        description: { hostname, provides: [...provides] },
    }));
};
