"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultPayloadConverterWithProtobufs = exports.ProtobufJsonPayloadConverter = exports.ProtobufBinaryPayloadConverter = void 0;
const protoJsonSerializer = __importStar(require("proto3-json-serializer"));
const encoding_1 = require("../encoding");
const errors_1 = require("../errors");
const type_helpers_1 = require("../type-helpers");
const payload_converter_1 = require("./payload-converter");
const types_1 = require("./types");
const GLOBAL_BUFFER = globalThis.constructor.constructor('return globalThis.Buffer')();
class ProtobufPayloadConverter {
    root;
    // Don't use type Root here because root.d.ts doesn't export Root, so users would have to type assert
    constructor(root) {
        if (root) {
            if (!isRoot(root)) {
                throw new TypeError('root must be an instance of a protobufjs Root');
            }
            this.root = root;
        }
    }
    validatePayload(content) {
        if (content.data === undefined || content.data === null) {
            throw new errors_1.ValueError('Got payload with no data');
        }
        if (!content.metadata || !(types_1.METADATA_MESSAGE_TYPE_KEY in content.metadata)) {
            throw new errors_1.ValueError(`Got protobuf payload without metadata.${types_1.METADATA_MESSAGE_TYPE_KEY}`);
        }
        if (!this.root) {
            throw new errors_1.PayloadConverterError('Unable to deserialize protobuf message without `root` being provided');
        }
        const messageTypeName = (0, encoding_1.decode)(content.metadata[types_1.METADATA_MESSAGE_TYPE_KEY]);
        let messageType;
        try {
            messageType = this.root.lookupType(messageTypeName);
        }
        catch (e) {
            if ((0, type_helpers_1.errorMessage)(e)?.includes('no such type')) {
                throw new errors_1.PayloadConverterError(`Got a \`${messageTypeName}\` protobuf message but cannot find corresponding message class in \`root\``);
            }
            throw e;
        }
        return { messageType, data: content.data };
    }
    constructPayload({ messageTypeName, message }) {
        return {
            metadata: {
                [types_1.METADATA_ENCODING_KEY]: (0, encoding_1.encode)(this.encodingType),
                [types_1.METADATA_MESSAGE_TYPE_KEY]: (0, encoding_1.encode)(messageTypeName),
            },
            data: message,
        };
    }
}
/**
 * Converts between protobufjs Message instances and serialized Protobuf Payload
 */
class ProtobufBinaryPayloadConverter extends ProtobufPayloadConverter {
    encodingType = types_1.encodingTypes.METADATA_ENCODING_PROTOBUF;
    /**
     * @param root The value returned from {@link patchProtobufRoot}
     */
    constructor(root) {
        super(root);
    }
    toPayload(value) {
        if (!isProtobufMessage(value)) {
            return undefined;
        }
        return this.constructPayload({
            messageTypeName: getNamespacedTypeName(value.$type),
            message: value.$type.encode(value).finish(),
        });
    }
    fromPayload(content) {
        const { messageType, data } = this.validatePayload(content);
        // Wrap with Uint8Array from this context to ensure `instanceof` works
        const localData = data ? new Uint8Array(data.buffer, data.byteOffset, data.length) : data;
        return messageType.decode(localData);
    }
}
exports.ProtobufBinaryPayloadConverter = ProtobufBinaryPayloadConverter;
/**
 * Converts between protobufjs Message instances and serialized JSON Payload
 */
class ProtobufJsonPayloadConverter extends ProtobufPayloadConverter {
    encodingType = types_1.encodingTypes.METADATA_ENCODING_PROTOBUF_JSON;
    /**
     * @param root The value returned from {@link patchProtobufRoot}
     */
    constructor(root) {
        super(root);
    }
    toPayload(value) {
        if (!isProtobufMessage(value)) {
            return undefined;
        }
        const hasBufferChanged = setBufferInGlobal();
        try {
            const jsonValue = protoJsonSerializer.toProto3JSON(value);
            return this.constructPayload({
                messageTypeName: getNamespacedTypeName(value.$type),
                message: (0, encoding_1.encode)(JSON.stringify(jsonValue)),
            });
        }
        finally {
            resetBufferInGlobal(hasBufferChanged);
        }
    }
    fromPayload(content) {
        const hasBufferChanged = setBufferInGlobal();
        try {
            const { messageType, data } = this.validatePayload(content);
            const res = protoJsonSerializer.fromProto3JSON(messageType, JSON.parse((0, encoding_1.decode)(data)));
            if (Buffer.isBuffer(res)) {
                return new Uint8Array(res);
            }
            replaceBuffers(res);
            return res;
        }
        finally {
            resetBufferInGlobal(hasBufferChanged);
        }
    }
}
exports.ProtobufJsonPayloadConverter = ProtobufJsonPayloadConverter;
function replaceBuffers(obj) {
    const replaceBuffersImpl = (value, key, target) => {
        if (Buffer.isBuffer(value)) {
            target[key] = new Uint8Array(value);
        }
        else {
            replaceBuffers(value);
        }
    };
    if (obj != null && typeof obj === 'object') {
        // Performance optimization for large arrays
        if (Array.isArray(obj)) {
            obj.forEach(replaceBuffersImpl);
        }
        else {
            for (const [key, value] of Object.entries(obj)) {
                replaceBuffersImpl(value, key, obj);
            }
        }
    }
}
function setBufferInGlobal() {
    if (typeof globalThis.Buffer === 'undefined') {
        globalThis.Buffer = GLOBAL_BUFFER;
        return true;
    }
    return false;
}
function resetBufferInGlobal(hasChanged) {
    if (hasChanged) {
        delete globalThis.Buffer;
    }
}
function isProtobufType(type) {
    return ((0, type_helpers_1.isRecord)(type) &&
        // constructor.name may get mangled by minifiers; thanksfuly protobufjs also sets a className property
        type.constructor.className === 'Type' &&
        (0, type_helpers_1.hasOwnProperties)(type, ['parent', 'name', 'create', 'encode', 'decode']) &&
        typeof type.name === 'string' &&
        typeof type.create === 'function' &&
        typeof type.encode === 'function' &&
        typeof type.decode === 'function');
}
function isProtobufMessage(value) {
    return (0, type_helpers_1.isRecord)(value) && (0, type_helpers_1.hasOwnProperty)(value, '$type') && isProtobufType(value.$type);
}
function getNamespacedTypeName(node) {
    if (node.parent && !isRoot(node.parent)) {
        return getNamespacedTypeName(node.parent) + '.' + node.name;
    }
    else {
        return node.name;
    }
}
function isRoot(root) {
    // constructor.name may get mangled by minifiers; thanksfuly protobufjs also sets a className property
    return (0, type_helpers_1.isRecord)(root) && root.constructor.className === 'Root';
}
class DefaultPayloadConverterWithProtobufs extends payload_converter_1.CompositePayloadConverter {
    // Match the order used in other SDKs.
    //
    // Go SDK:
    // https://github.com/temporalio/sdk-go/blob/5e5645f0c550dcf717c095ae32c76a7087d2e985/converter/default_data_converter.go#L28
    constructor({ protobufRoot }) {
        super(new payload_converter_1.UndefinedPayloadConverter(), new payload_converter_1.BinaryPayloadConverter(), new ProtobufJsonPayloadConverter(protobufRoot), new ProtobufBinaryPayloadConverter(protobufRoot), new payload_converter_1.JsonPayloadConverter());
    }
}
exports.DefaultPayloadConverterWithProtobufs = DefaultPayloadConverterWithProtobufs;
//# sourceMappingURL=protobuf-payload-converters.js.map