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 __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 _MetadataEnvelopeFilter_instances, _MetadataEnvelopeFilter_storeByStreamID, _MetadataEnvelopeFilter_namespace, _MetadataEnvelopeFilter_onStoreEvictingUserCallback, _MetadataEnvelopeFilter_filterHeadersForStore, _MetadataEnvelopeFilter_onStoreEvicting;
import { Strings } from '@devvit/protos';
import { Header } from '@devvit/shared-types/Header.js';
import LRUCache from 'lru-cache';
import { filterHeadersForDestination, isSystemHeader, isTrustedLocation, } from '../../../lib/Header.js';
import { Log } from '../../../lib/loggers/Log.js';
import { EnvelopeUtil } from '../EnvelopeUtil.js';
/**
 * Metadata mutations for all filtered envelopes.
 *
 * It's usually correct to transform envelopes in a filter, not the
 * CentralDispatcher. In native runtimes, the only envelopes that pass through
 * the dispatcher are 1) unfiltered envelopes from the runtime for the
 * supervisor like Load/Unload(), and 2) envelopes to be filtered. For 2, these
 * are Envelopes that arrive in the CentralDispatcher as Envelopes wrapped in
 * Envelopes! In this case, the Envelope we actually care about transforming is
 * the payload, but the changes the CentralDispatcher makes like setting the
 * Envelope.id only apply to the outer Envelope, not the wrapped inner payload.
 */
export class MetadataEnvelopeFilter {
    constructor(namespace, onStoreEvicting = () => { }, size = 10000) {
        _MetadataEnvelopeFilter_instances.add(this);
        /**
         * Metadata by Envelope.streamId store. Each stream shares a record across
         * all requests and responses in the stream. The path forms a call graph where
         * each new call (Envelope) aggregates the metadata of any predecessor
         * streams.
         *
         * .
         * |\
         * o o
         *   |
         *   O
         *
         * If metadata is not passed on new stream creation, a new empty record is
         * made.
         *
         * Envelope.id does not match stream semantics which may have many requests
         * but only one response. There's no "push / pop" scheme for envelope IDs
         * which are each unique.
         *
         * Envelope.traceId also isn't ideal because it may be shared across many
         * successor streams. Successors could mutate a distant predecessor.
         *
         * Envelope.streamId unsurprisingly matches the stream mechanics. Each
         * metadata record is layered (copied) by stream so that when a new stream is
         * created, a new copy is made using the predecessor's stream, if any. We
         * don't really care if multiple requests within a stream change their own
         * stream's shared record.
         *
         * `Envelope.streamId` does not match the metadata stream ID in these cases:
         *
         * - New stream creation: no prior metadata / root request. The metadata
         *   stream ID is empty.
         * - New stream creation: no passed metadata by client. The metadata stream ID
         *   is empty.
         * - New stream creation: client passes metadata but the Envelope hasn't been
         *   filtered by MetadataEnvelopeFilter store yet. The metadata stream ID
         *   points to the predecessor's record.
         *
         * In other words, when this header comes into a EnvelopeServer it means,
         * "this is the stream that caused your invocation" and it matches
         * `Envelope.streamId` because its already been filtered by the time it
         * arrives at the server.
         *
         * Subsequent client calls (`EnvelopeClient.post()`) _never_ specify this
         * header manually. Instead, they pass (or not) an entire Metadata set. This
         * `devvit-stream-id` does not match `Envelope.streamId` because it's the
         * predecessor's stream ID. For requests, the header specifies which stream to
         * propagate metadata from, not what stream the envelope that's being post()ed
         * should belong to.
         */
        _MetadataEnvelopeFilter_storeByStreamID.set(this, void 0);
        _MetadataEnvelopeFilter_namespace.set(this, void 0);
        _MetadataEnvelopeFilter_onStoreEvictingUserCallback.set(this, void 0);
        __classPrivateFieldSet(this, _MetadataEnvelopeFilter_storeByStreamID, new LRUCache({
            max: size,
            dispose: __classPrivateFieldGet(this, _MetadataEnvelopeFilter_instances, "m", _MetadataEnvelopeFilter_onStoreEvicting).bind(this),
        }), "f");
        __classPrivateFieldSet(this, _MetadataEnvelopeFilter_namespace, namespace, "f");
        __classPrivateFieldSet(this, _MetadataEnvelopeFilter_onStoreEvictingUserCallback, onStoreEvicting, "f");
    }
    async filter(envelope) {
        if (envelope.streamId === '')
            throw Error(`Missing stream ID on ${EnvelopeUtil.toLogString(envelope)}.`);
        // The metadata currently contains the predecessor metadata store lookup ID,
        // if any.
        //
        // The Go worker filter is dedicated to a given worker and does not allow
        // unrequested responses in.
        const upID = envelope.metadata[Header.StreamID]?.values[0];
        const upMeta = upID == null
            ? undefined
            : // Refresh the LRU recency.
                __classPrivateFieldGet(this, _MetadataEnvelopeFilter_storeByStreamID, "f").get(upID)?.metadata;
        const meta = {
            ...upMeta,
            ...__classPrivateFieldGet(this, _MetadataEnvelopeFilter_instances, "m", _MetadataEnvelopeFilter_filterHeadersForStore).call(this, envelope.metadata, envelope.src),
            // When a successor client issues a request, this will be the metadata
            // store lookup ID.
            [Header.StreamID]: Strings.fromPartial({ values: [envelope.streamId] }),
            [Header.Caller]: Strings.fromPartial({ values: [envelope.src] }),
            // At any point in a trace, a client may opt-out. Always propagate the
            // current envelope's trace ID.
            [Header.TraceID]: Strings.fromPartial({ values: [envelope.traceId] }),
        };
        // to-do: forbid response metadata to match the Go implementation.
        if (envelope.request) {
            // Insert or replace. Only requests can write to the store. Devvit only
            // supports Message responses, not Metadata. Don't care who writes to the
            // store itself since every stream gets its own.
            __classPrivateFieldGet(this, _MetadataEnvelopeFilter_storeByStreamID, "f").set(envelope.streamId, {
                log: EnvelopeUtil.toLogString(envelope),
                metadata: meta,
            });
        }
        else if (envelope.complete) {
            __classPrivateFieldGet(this, _MetadataEnvelopeFilter_storeByStreamID, "f").delete(envelope.streamId);
        }
        envelope.metadata = filterHeadersForDestination(__classPrivateFieldGet(this, _MetadataEnvelopeFilter_namespace, "f"), {
            // Re-provide the original metadata, less hidden headers, with stored
            // metadata taking precedence. The original is necessary for headers
            // allowed to be read but not stored.
            ...filterHeadersForDestination(__classPrivateFieldGet(this, _MetadataEnvelopeFilter_namespace, "f"), envelope.metadata, envelope.src),
            ...meta,
        }, envelope.dst);
        return envelope;
    }
}
_MetadataEnvelopeFilter_storeByStreamID = new WeakMap(), _MetadataEnvelopeFilter_namespace = new WeakMap(), _MetadataEnvelopeFilter_onStoreEvictingUserCallback = new WeakMap(), _MetadataEnvelopeFilter_instances = new WeakSet(), _MetadataEnvelopeFilter_filterHeadersForStore = function _MetadataEnvelopeFilter_filterHeadersForStore(metadata, source) {
    if (isTrustedLocation(__classPrivateFieldGet(this, _MetadataEnvelopeFilter_namespace, "f"), source))
        return { ...metadata }; // No filtering.
    // Note: devvit-stream-id is extracted from the original Envelope.metadata
    // to identify the upstream's record but it can't be written to the store
    // directly.
    const allowedEntries = Object.entries(metadata).filter(([header]) => !isSystemHeader(header));
    return Object.fromEntries(allowedEntries);
}, _MetadataEnvelopeFilter_onStoreEvicting = function _MetadataEnvelopeFilter_onStoreEvicting(store, streamID, reason) {
    // Stream closed.
    if (reason === 'delete')
        return;
    // Multi-request stream updated; metadata overwritten.
    if (reason === 'set')
        return;
    __classPrivateFieldGet(this, _MetadataEnvelopeFilter_onStoreEvictingUserCallback, "f").call(this, store, streamID, reason);
    Log.warn(`Envelope metadata store overflow. Evicting least recently used ` +
        `metadata for stream ID ${streamID}. Last received ${store.log}.`);
};
