// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Edition, FeatureSet_RepeatedFieldEncoding, FeatureSetDefaults, FieldDescriptorProto_Label, FieldDescriptorProto_Type, FieldOptions_JSType, FileDescriptorProto, FileDescriptorSet, MethodDescriptorProto, MethodOptions_IdempotencyLevel, ServiceDescriptorProto, SourceCodeInfo, } from "./google/protobuf/descriptor_pb.js";
import { assert } from "./private/assert.js";
import { MethodIdempotency, MethodKind } from "./service-type.js";
import { fieldJsonName, findEnumSharedPrefix } from "./private/names.js";
import { parseTextFormatEnumValue, parseTextFormatScalarValue, } from "./private/text-format.js";
import { createFeatureResolver } from "./private/feature-set.js";
import { LongType, ScalarType } from "./scalar.js";
import { isMessage } from "./is-message.js";
/**
 * Create a DescriptorSet, a convenient interface for working with a set of
 * google.protobuf.FileDescriptorProto.
 *
 * Note that files must be given in topological order, so each file appears
 * before any file that imports it. Protocol buffer compilers always produce
 * files in topological order.
 */
export function createDescriptorSet(input, options) {
    var _a;
    const cart = {
        files: [],
        enums: new Map(),
        messages: new Map(),
        services: new Map(),
        extensions: new Map(),
        mapEntries: new Map(),
    };
    const fileDescriptors = isMessage(input, FileDescriptorSet)
        ? input.file
        : input instanceof Uint8Array
            ? FileDescriptorSet.fromBinary(input).file
            : input;
    const resolverByEdition = new Map();
    for (const proto of fileDescriptors) {
        const edition = (_a = proto.edition) !== null && _a !== void 0 ? _a : parseFileSyntax(proto.syntax, proto.edition).edition;
        let resolveFeatures = resolverByEdition.get(edition);
        if (resolveFeatures === undefined) {
            resolveFeatures = createFeatureResolver(edition, options === null || options === void 0 ? void 0 : options.featureSetDefaults, options === null || options === void 0 ? void 0 : options.serializationOptions);
            resolverByEdition.set(edition, resolveFeatures);
        }
        addFile(proto, cart, resolveFeatures);
    }
    return cart;
}
/**
 * Create a descriptor for a file.
 */
function addFile(proto, cart, resolveFeatures) {
    var _a, _b;
    assert(proto.name, `invalid FileDescriptorProto: missing name`);
    const file = Object.assign(Object.assign({ kind: "file", proto, deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false }, parseFileSyntax(proto.syntax, proto.edition)), { name: proto.name.replace(/\.proto/, ""), dependencies: findFileDependencies(proto, cart), enums: [], messages: [], extensions: [], services: [], toString() {
            // eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- we asserted above
            return `file ${this.proto.name}`;
        },
        getSyntaxComments() {
            return findComments(this.proto.sourceCodeInfo, [
                FieldNumber.FileDescriptorProto_Syntax,
            ]);
        },
        getPackageComments() {
            return findComments(this.proto.sourceCodeInfo, [
                FieldNumber.FileDescriptorProto_Package,
            ]);
        },
        getFeatures() {
            var _a;
            return resolveFeatures((_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
        } });
    cart.mapEntries.clear(); // map entries are local to the file, we can safely discard
    for (const enumProto of proto.enumType) {
        addEnum(enumProto, file, undefined, cart, resolveFeatures);
    }
    for (const messageProto of proto.messageType) {
        addMessage(messageProto, file, undefined, cart, resolveFeatures);
    }
    for (const serviceProto of proto.service) {
        addService(serviceProto, file, cart, resolveFeatures);
    }
    addExtensions(file, cart, resolveFeatures);
    for (const mapEntry of cart.mapEntries.values()) {
        addFields(mapEntry, cart, resolveFeatures);
    }
    for (const message of file.messages) {
        addFields(message, cart, resolveFeatures);
        addExtensions(message, cart, resolveFeatures);
    }
    cart.mapEntries.clear(); // map entries are local to the file, we can safely discard
    cart.files.push(file);
}
/**
 * Create descriptors for extensions, and add them to the message / file,
 * and to our cart.
 * Recurses into nested types.
 */
function addExtensions(desc, cart, resolveFeatures) {
    switch (desc.kind) {
        case "file":
            for (const proto of desc.proto.extension) {
                const ext = newExtension(proto, desc, undefined, cart, resolveFeatures);
                desc.extensions.push(ext);
                cart.extensions.set(ext.typeName, ext);
            }
            break;
        case "message":
            for (const proto of desc.proto.extension) {
                const ext = newExtension(proto, desc.file, desc, cart, resolveFeatures);
                desc.nestedExtensions.push(ext);
                cart.extensions.set(ext.typeName, ext);
            }
            for (const message of desc.nestedMessages) {
                addExtensions(message, cart, resolveFeatures);
            }
            break;
    }
}
/**
 * Create descriptors for fields and oneof groups, and add them to the message.
 * Recurses into nested types.
 */
function addFields(message, cart, resolveFeatures) {
    const allOneofs = message.proto.oneofDecl.map((proto) => newOneof(proto, message, resolveFeatures));
    const oneofsSeen = new Set();
    for (const proto of message.proto.field) {
        const oneof = findOneof(proto, allOneofs);
        const field = newField(proto, message.file, message, oneof, cart, resolveFeatures);
        message.fields.push(field);
        if (oneof === undefined) {
            message.members.push(field);
        }
        else {
            oneof.fields.push(field);
            if (!oneofsSeen.has(oneof)) {
                oneofsSeen.add(oneof);
                message.members.push(oneof);
            }
        }
    }
    for (const oneof of allOneofs.filter((o) => oneofsSeen.has(o))) {
        message.oneofs.push(oneof);
    }
    for (const child of message.nestedMessages) {
        addFields(child, cart, resolveFeatures);
    }
}
/**
 * Create a descriptor for an enumeration, and add it our cart and to the
 * parent type, if any.
 */
function addEnum(proto, file, parent, cart, resolveFeatures) {
    var _a, _b, _c;
    assert(proto.name, `invalid EnumDescriptorProto: missing name`);
    const desc = {
        kind: "enum",
        proto,
        deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
        file,
        parent,
        name: proto.name,
        typeName: makeTypeName(proto, parent, file),
        values: [],
        sharedPrefix: findEnumSharedPrefix(proto.name, proto.value.map((v) => { var _a; return (_a = v.name) !== null && _a !== void 0 ? _a : ""; })),
        toString() {
            return `enum ${this.typeName}`;
        },
        getComments() {
            const path = this.parent
                ? [
                    ...this.parent.getComments().sourcePath,
                    FieldNumber.DescriptorProto_EnumType,
                    this.parent.proto.enumType.indexOf(this.proto),
                ]
                : [
                    FieldNumber.FileDescriptorProto_EnumType,
                    this.file.proto.enumType.indexOf(this.proto),
                ];
            return findComments(file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a, _b;
            return resolveFeatures((_a = parent === null || parent === void 0 ? void 0 : parent.getFeatures()) !== null && _a !== void 0 ? _a : file.getFeatures(), (_b = proto.options) === null || _b === void 0 ? void 0 : _b.features);
        },
    };
    cart.enums.set(desc.typeName, desc);
    proto.value.forEach((proto) => {
        var _a, _b;
        assert(proto.name, `invalid EnumValueDescriptorProto: missing name`);
        assert(proto.number !== undefined, `invalid EnumValueDescriptorProto: missing number`);
        desc.values.push({
            kind: "enum_value",
            proto,
            deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
            parent: desc,
            name: proto.name,
            number: proto.number,
            toString() {
                return `enum value ${desc.typeName}.${this.name}`;
            },
            declarationString() {
                var _a;
                let str = `${this.name} = ${this.number}`;
                if (((_a = this.proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) === true) {
                    str += " [deprecated = true]";
                }
                return str;
            },
            getComments() {
                const path = [
                    ...this.parent.getComments().sourcePath,
                    FieldNumber.EnumDescriptorProto_Value,
                    this.parent.proto.value.indexOf(this.proto),
                ];
                return findComments(file.proto.sourceCodeInfo, path);
            },
            getFeatures() {
                var _a;
                return resolveFeatures(desc.getFeatures(), (_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
            },
        });
    });
    ((_c = parent === null || parent === void 0 ? void 0 : parent.nestedEnums) !== null && _c !== void 0 ? _c : file.enums).push(desc);
}
/**
 * Create a descriptor for a message, including nested types, and add it to our
 * cart. Note that this does not create descriptors fields.
 */
function addMessage(proto, file, parent, cart, resolveFeatures) {
    var _a, _b, _c, _d;
    assert(proto.name, `invalid DescriptorProto: missing name`);
    const desc = {
        kind: "message",
        proto,
        deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
        file,
        parent,
        name: proto.name,
        typeName: makeTypeName(proto, parent, file),
        fields: [],
        oneofs: [],
        members: [],
        nestedEnums: [],
        nestedMessages: [],
        nestedExtensions: [],
        toString() {
            return `message ${this.typeName}`;
        },
        getComments() {
            const path = this.parent
                ? [
                    ...this.parent.getComments().sourcePath,
                    FieldNumber.DescriptorProto_NestedType,
                    this.parent.proto.nestedType.indexOf(this.proto),
                ]
                : [
                    FieldNumber.FileDescriptorProto_MessageType,
                    this.file.proto.messageType.indexOf(this.proto),
                ];
            return findComments(file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a, _b;
            return resolveFeatures((_a = parent === null || parent === void 0 ? void 0 : parent.getFeatures()) !== null && _a !== void 0 ? _a : file.getFeatures(), (_b = proto.options) === null || _b === void 0 ? void 0 : _b.features);
        },
    };
    if (((_c = proto.options) === null || _c === void 0 ? void 0 : _c.mapEntry) === true) {
        cart.mapEntries.set(desc.typeName, desc);
    }
    else {
        ((_d = parent === null || parent === void 0 ? void 0 : parent.nestedMessages) !== null && _d !== void 0 ? _d : file.messages).push(desc);
        cart.messages.set(desc.typeName, desc);
    }
    for (const enumProto of proto.enumType) {
        addEnum(enumProto, file, desc, cart, resolveFeatures);
    }
    for (const messageProto of proto.nestedType) {
        addMessage(messageProto, file, desc, cart, resolveFeatures);
    }
}
/**
 * Create a descriptor for a service, including methods, and add it to our
 * cart.
 */
function addService(proto, file, cart, resolveFeatures) {
    var _a, _b;
    assert(proto.name, `invalid ServiceDescriptorProto: missing name`);
    const desc = {
        kind: "service",
        proto,
        deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
        file,
        name: proto.name,
        typeName: makeTypeName(proto, undefined, file),
        methods: [],
        toString() {
            return `service ${this.typeName}`;
        },
        getComments() {
            const path = [
                FieldNumber.FileDescriptorProto_Service,
                this.file.proto.service.indexOf(this.proto),
            ];
            return findComments(file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a;
            return resolveFeatures(file.getFeatures(), (_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
        },
    };
    file.services.push(desc);
    cart.services.set(desc.typeName, desc);
    for (const methodProto of proto.method) {
        desc.methods.push(newMethod(methodProto, desc, cart, resolveFeatures));
    }
}
/**
 * Create a descriptor for a method.
 */
function newMethod(proto, parent, cart, resolveFeatures) {
    var _a, _b, _c;
    assert(proto.name, `invalid MethodDescriptorProto: missing name`);
    assert(proto.inputType, `invalid MethodDescriptorProto: missing input_type`);
    assert(proto.outputType, `invalid MethodDescriptorProto: missing output_type`);
    let methodKind;
    if (proto.clientStreaming === true && proto.serverStreaming === true) {
        methodKind = MethodKind.BiDiStreaming;
    }
    else if (proto.clientStreaming === true) {
        methodKind = MethodKind.ClientStreaming;
    }
    else if (proto.serverStreaming === true) {
        methodKind = MethodKind.ServerStreaming;
    }
    else {
        methodKind = MethodKind.Unary;
    }
    let idempotency;
    switch ((_a = proto.options) === null || _a === void 0 ? void 0 : _a.idempotencyLevel) {
        case MethodOptions_IdempotencyLevel.IDEMPOTENT:
            idempotency = MethodIdempotency.Idempotent;
            break;
        case MethodOptions_IdempotencyLevel.NO_SIDE_EFFECTS:
            idempotency = MethodIdempotency.NoSideEffects;
            break;
        case MethodOptions_IdempotencyLevel.IDEMPOTENCY_UNKNOWN:
        case undefined:
            idempotency = undefined;
            break;
    }
    const input = cart.messages.get(trimLeadingDot(proto.inputType));
    const output = cart.messages.get(trimLeadingDot(proto.outputType));
    assert(input, `invalid MethodDescriptorProto: input_type ${proto.inputType} not found`);
    assert(output, `invalid MethodDescriptorProto: output_type ${proto.inputType} not found`);
    const name = proto.name;
    return {
        kind: "rpc",
        proto,
        deprecated: (_c = (_b = proto.options) === null || _b === void 0 ? void 0 : _b.deprecated) !== null && _c !== void 0 ? _c : false,
        parent,
        name,
        methodKind,
        input,
        output,
        idempotency,
        toString() {
            return `rpc ${parent.typeName}.${name}`;
        },
        getComments() {
            const path = [
                ...this.parent.getComments().sourcePath,
                FieldNumber.ServiceDescriptorProto_Method,
                this.parent.proto.method.indexOf(this.proto),
            ];
            return findComments(parent.file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a;
            return resolveFeatures(parent.getFeatures(), (_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
        },
    };
}
/**
 * Create a descriptor for a oneof group.
 */
function newOneof(proto, parent, resolveFeatures) {
    assert(proto.name, `invalid OneofDescriptorProto: missing name`);
    return {
        kind: "oneof",
        proto,
        deprecated: false,
        parent,
        fields: [],
        name: proto.name,
        toString() {
            return `oneof ${parent.typeName}.${this.name}`;
        },
        getComments() {
            const path = [
                ...this.parent.getComments().sourcePath,
                FieldNumber.DescriptorProto_OneofDecl,
                this.parent.proto.oneofDecl.indexOf(this.proto),
            ];
            return findComments(parent.file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a;
            return resolveFeatures(parent.getFeatures(), (_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
        },
    };
}
/**
 * Create a descriptor for a field.
 */
function newField(proto, file, parent, oneof, cart, resolveFeatures) {
    var _a, _b, _c;
    assert(proto.name, `invalid FieldDescriptorProto: missing name`);
    assert(proto.number, `invalid FieldDescriptorProto: missing number`);
    assert(proto.type, `invalid FieldDescriptorProto: missing type`);
    const common = {
        proto,
        deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false,
        name: proto.name,
        number: proto.number,
        parent,
        oneof,
        optional: isOptionalField(proto, file.syntax),
        packedByDefault: isPackedFieldByDefault(proto, resolveFeatures),
        packed: isPackedField(file, parent, proto, resolveFeatures),
        jsonName: proto.jsonName === fieldJsonName(proto.name) ? undefined : proto.jsonName,
        scalar: undefined,
        longType: undefined,
        message: undefined,
        enum: undefined,
        mapKey: undefined,
        mapValue: undefined,
        declarationString,
        // toString, getComments, getFeatures are overridden in newExtension
        toString() {
            return `field ${this.parent.typeName}.${this.name}`;
        },
        getComments() {
            const path = [
                ...this.parent.getComments().sourcePath,
                FieldNumber.DescriptorProto_Field,
                this.parent.proto.field.indexOf(this.proto),
            ];
            return findComments(file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a;
            return resolveFeatures(parent.getFeatures(), (_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
        },
    };
    const repeated = proto.label === FieldDescriptorProto_Label.REPEATED;
    switch (proto.type) {
        case FieldDescriptorProto_Type.MESSAGE:
        case FieldDescriptorProto_Type.GROUP: {
            assert(proto.typeName, `invalid FieldDescriptorProto: missing type_name`);
            const mapEntry = cart.mapEntries.get(trimLeadingDot(proto.typeName));
            if (mapEntry !== undefined) {
                assert(repeated, `invalid FieldDescriptorProto: expected map entry to be repeated`);
                return Object.assign(Object.assign(Object.assign({}, common), { kind: "field", fieldKind: "map", repeated: false }), getMapFieldTypes(mapEntry));
            }
            const message = cart.messages.get(trimLeadingDot(proto.typeName));
            assert(message !== undefined, `invalid FieldDescriptorProto: type_name ${proto.typeName} not found`);
            return Object.assign(Object.assign({}, common), { kind: "field", fieldKind: "message", repeated,
                message });
        }
        case FieldDescriptorProto_Type.ENUM: {
            assert(proto.typeName, `invalid FieldDescriptorProto: missing type_name`);
            const e = cart.enums.get(trimLeadingDot(proto.typeName));
            assert(e !== undefined, `invalid FieldDescriptorProto: type_name ${proto.typeName} not found`);
            return Object.assign(Object.assign({}, common), { kind: "field", fieldKind: "enum", getDefaultValue,
                repeated, enum: e });
        }
        default: {
            const scalar = fieldTypeToScalarType[proto.type];
            assert(scalar, `invalid FieldDescriptorProto: unknown type ${proto.type}`);
            return Object.assign(Object.assign({}, common), { kind: "field", fieldKind: "scalar", getDefaultValue,
                repeated,
                scalar, longType: ((_c = proto.options) === null || _c === void 0 ? void 0 : _c.jstype) == FieldOptions_JSType.JS_STRING
                    ? LongType.STRING
                    : LongType.BIGINT });
        }
    }
}
/**
 * Create a descriptor for an extension field.
 */
function newExtension(proto, file, parent, cart, resolveFeatures) {
    assert(proto.extendee, `invalid FieldDescriptorProto: missing extendee`);
    const field = newField(proto, file, null, // to safe us many lines of duplicated code, we trick the type system
    undefined, cart, resolveFeatures);
    const extendee = cart.messages.get(trimLeadingDot(proto.extendee));
    assert(extendee, `invalid FieldDescriptorProto: extendee ${proto.extendee} not found`);
    return Object.assign(Object.assign({}, field), { kind: "extension", typeName: makeTypeName(proto, parent, file), parent,
        file,
        extendee,
        // Must override toString, getComments, getFeatures from newField, because we
        // call newField with parent undefined.
        toString() {
            return `extension ${this.typeName}`;
        },
        getComments() {
            const path = this.parent
                ? [
                    ...this.parent.getComments().sourcePath,
                    FieldNumber.DescriptorProto_Extension,
                    this.parent.proto.extension.indexOf(proto),
                ]
                : [
                    FieldNumber.FileDescriptorProto_Extension,
                    this.file.proto.extension.indexOf(proto),
                ];
            return findComments(file.proto.sourceCodeInfo, path);
        },
        getFeatures() {
            var _a;
            return resolveFeatures((parent !== null && parent !== void 0 ? parent : file).getFeatures(), (_a = proto.options) === null || _a === void 0 ? void 0 : _a.features);
        } });
}
/**
 * Parse the "syntax" and "edition" fields, stripping test editions.
 */
function parseFileSyntax(syntax, edition) {
    let e;
    let s;
    switch (syntax) {
        case undefined:
        case "proto2":
            s = "proto2";
            e = Edition.EDITION_PROTO2;
            break;
        case "proto3":
            s = "proto3";
            e = Edition.EDITION_PROTO3;
            break;
        case "editions":
            s = "editions";
            switch (edition) {
                case undefined:
                case Edition.EDITION_1_TEST_ONLY:
                case Edition.EDITION_2_TEST_ONLY:
                case Edition.EDITION_99997_TEST_ONLY:
                case Edition.EDITION_99998_TEST_ONLY:
                case Edition.EDITION_99999_TEST_ONLY:
                case Edition.EDITION_UNKNOWN:
                    e = Edition.EDITION_UNKNOWN;
                    break;
                default:
                    e = edition;
                    break;
            }
            break;
        default:
            throw new Error(`invalid FileDescriptorProto: unsupported syntax: ${syntax}`);
    }
    if (syntax === "editions" && edition === Edition.EDITION_UNKNOWN) {
        throw new Error(`invalid FileDescriptorProto: syntax ${syntax} cannot have edition ${String(edition)}`);
    }
    return {
        syntax: s,
        edition: e,
    };
}
/**
 * Resolve dependencies of FileDescriptorProto to DescFile.
 */
function findFileDependencies(proto, cart) {
    return proto.dependency.map((wantName) => {
        const dep = cart.files.find((f) => f.proto.name === wantName);
        assert(dep);
        return dep;
    });
}
/**
 * Create a fully qualified name for a protobuf type or extension field.
 *
 * The fully qualified name for messages, enumerations, and services is
 * constructed by concatenating the package name (if present), parent
 * message names (for nested types), and the type name. We omit the leading
 * dot added by protobuf compilers. Examples:
 * - mypackage.MyMessage
 * - mypackage.MyMessage.NestedMessage
 *
 * The fully qualified name for extension fields is constructed by
 * concatenating the package name (if present), parent message names (for
 * extensions declared within a message), and the field name. Examples:
 * - mypackage.extfield
 * - mypackage.MyMessage.extfield
 */
function makeTypeName(proto, parent, file) {
    assert(proto.name, `invalid ${proto.getType().typeName}: missing name`);
    let typeName;
    if (parent) {
        typeName = `${parent.typeName}.${proto.name}`;
    }
    else if (file.proto.package !== undefined) {
        typeName = `${file.proto.package}.${proto.name}`;
    }
    else {
        typeName = `${proto.name}`;
    }
    return typeName;
}
/**
 * Remove the leading dot from a fully qualified type name.
 */
function trimLeadingDot(typeName) {
    return typeName.startsWith(".") ? typeName.substring(1) : typeName;
}
function getMapFieldTypes(mapEntry) {
    var _a, _b;
    assert((_a = mapEntry.proto.options) === null || _a === void 0 ? void 0 : _a.mapEntry, `invalid DescriptorProto: expected ${mapEntry.toString()} to be a map entry`);
    assert(mapEntry.fields.length === 2, `invalid DescriptorProto: map entry ${mapEntry.toString()} has ${mapEntry.fields.length} fields`);
    const keyField = mapEntry.fields.find((f) => f.proto.number === 1);
    assert(keyField, `invalid DescriptorProto: map entry ${mapEntry.toString()} is missing key field`);
    const mapKey = keyField.scalar;
    assert(mapKey !== undefined &&
        mapKey !== ScalarType.BYTES &&
        mapKey !== ScalarType.FLOAT &&
        mapKey !== ScalarType.DOUBLE, `invalid DescriptorProto: map entry ${mapEntry.toString()} has unexpected key type ${(_b = keyField.proto.type) !== null && _b !== void 0 ? _b : -1}`);
    const valueField = mapEntry.fields.find((f) => f.proto.number === 2);
    assert(valueField, `invalid DescriptorProto: map entry ${mapEntry.toString()} is missing value field`);
    switch (valueField.fieldKind) {
        case "scalar":
            return {
                mapKey,
                mapValue: Object.assign(Object.assign({}, valueField), { kind: "scalar" }),
            };
        case "message":
            return {
                mapKey,
                mapValue: Object.assign(Object.assign({}, valueField), { kind: "message" }),
            };
        case "enum":
            return {
                mapKey,
                mapValue: Object.assign(Object.assign({}, valueField), { kind: "enum" }),
            };
        default:
            throw new Error("invalid DescriptorProto: unsupported map entry value field");
    }
}
/**
 * Did the user put the field in a oneof group?
 * This handles proto3 optionals.
 */
function findOneof(proto, allOneofs) {
    var _a;
    const oneofIndex = proto.oneofIndex;
    if (oneofIndex === undefined) {
        return undefined;
    }
    let oneof;
    if (proto.proto3Optional !== true) {
        oneof = allOneofs[oneofIndex];
        assert(oneof, `invalid FieldDescriptorProto: oneof #${oneofIndex} for field #${(_a = proto.number) !== null && _a !== void 0 ? _a : -1} not found`);
    }
    return oneof;
}
/**
 * Did the user use the `optional` keyword?
 * This handles proto3 optionals.
 */
function isOptionalField(proto, syntax) {
    switch (syntax) {
        case "proto2":
            return (proto.oneofIndex === undefined &&
                proto.label === FieldDescriptorProto_Label.OPTIONAL);
        case "proto3":
            return proto.proto3Optional === true;
        case "editions":
            return false;
    }
}
/**
 * Is this field packed by default? Only valid for repeated enum fields, and
 * for repeated scalar fields except BYTES and STRING.
 *
 * In proto3 syntax, fields are packed by default. In proto2 syntax, fields
 * are unpacked by default. With editions, the default is whatever the edition
 * specifies as a default. In edition 2023, fields are packed by default.
 */
function isPackedFieldByDefault(proto, resolveFeatures) {
    const { repeatedFieldEncoding } = resolveFeatures();
    if (repeatedFieldEncoding != FeatureSet_RepeatedFieldEncoding.PACKED) {
        return false;
    }
    // From the proto3 language guide:
    // > In proto3, repeated fields of scalar numeric types are packed by default.
    // This information is incomplete - according to the conformance tests, BOOL
    // and ENUM are packed by default as well. This means only STRING and BYTES
    // are not packed by default, which makes sense because they are length-delimited.
    switch (proto.type) {
        case FieldDescriptorProto_Type.STRING:
        case FieldDescriptorProto_Type.BYTES:
        case FieldDescriptorProto_Type.GROUP:
        case FieldDescriptorProto_Type.MESSAGE:
            return false;
        default:
            return true;
    }
}
/**
 * Pack this repeated field?
 *
 * Respects field type, proto2/proto3 defaults and the `packed` option, or
 * edition defaults and the edition features.repeated_field_encoding options.
 */
function isPackedField(file, parent, proto, resolveFeatures) {
    var _a, _b, _c, _d, _e, _f;
    switch (proto.type) {
        case FieldDescriptorProto_Type.STRING:
        case FieldDescriptorProto_Type.BYTES:
        case FieldDescriptorProto_Type.GROUP:
        case FieldDescriptorProto_Type.MESSAGE:
            // length-delimited types cannot be packed
            return false;
        default:
            switch (file.edition) {
                case Edition.EDITION_PROTO2:
                    return (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.packed) !== null && _b !== void 0 ? _b : false;
                case Edition.EDITION_PROTO3:
                    return (_d = (_c = proto.options) === null || _c === void 0 ? void 0 : _c.packed) !== null && _d !== void 0 ? _d : true;
                default: {
                    const { repeatedFieldEncoding } = resolveFeatures((_e = parent === null || parent === void 0 ? void 0 : parent.getFeatures()) !== null && _e !== void 0 ? _e : file.getFeatures(), (_f = proto.options) === null || _f === void 0 ? void 0 : _f.features);
                    return (repeatedFieldEncoding == FeatureSet_RepeatedFieldEncoding.PACKED);
                }
            }
    }
}
/**
 * Map from a compiler-generated field type to our ScalarType, which is a
 * subset of field types declared by protobuf enum google.protobuf.FieldDescriptorProto.
 */
const fieldTypeToScalarType = {
    [FieldDescriptorProto_Type.DOUBLE]: ScalarType.DOUBLE,
    [FieldDescriptorProto_Type.FLOAT]: ScalarType.FLOAT,
    [FieldDescriptorProto_Type.INT64]: ScalarType.INT64,
    [FieldDescriptorProto_Type.UINT64]: ScalarType.UINT64,
    [FieldDescriptorProto_Type.INT32]: ScalarType.INT32,
    [FieldDescriptorProto_Type.FIXED64]: ScalarType.FIXED64,
    [FieldDescriptorProto_Type.FIXED32]: ScalarType.FIXED32,
    [FieldDescriptorProto_Type.BOOL]: ScalarType.BOOL,
    [FieldDescriptorProto_Type.STRING]: ScalarType.STRING,
    [FieldDescriptorProto_Type.GROUP]: undefined,
    [FieldDescriptorProto_Type.MESSAGE]: undefined,
    [FieldDescriptorProto_Type.BYTES]: ScalarType.BYTES,
    [FieldDescriptorProto_Type.UINT32]: ScalarType.UINT32,
    [FieldDescriptorProto_Type.ENUM]: undefined,
    [FieldDescriptorProto_Type.SFIXED32]: ScalarType.SFIXED32,
    [FieldDescriptorProto_Type.SFIXED64]: ScalarType.SFIXED64,
    [FieldDescriptorProto_Type.SINT32]: ScalarType.SINT32,
    [FieldDescriptorProto_Type.SINT64]: ScalarType.SINT64,
};
/**
 * Find comments.
 */
function findComments(sourceCodeInfo, sourcePath) {
    if (!sourceCodeInfo) {
        return {
            leadingDetached: [],
            sourcePath,
        };
    }
    for (const location of sourceCodeInfo.location) {
        if (location.path.length !== sourcePath.length) {
            continue;
        }
        if (location.path.some((value, index) => sourcePath[index] !== value)) {
            continue;
        }
        return {
            leadingDetached: location.leadingDetachedComments,
            leading: location.leadingComments,
            trailing: location.trailingComments,
            sourcePath,
        };
    }
    return {
        leadingDetached: [],
        sourcePath,
    };
}
/**
 * The following field numbers are used to find comments in
 * google.protobuf.SourceCodeInfo.
 */
var FieldNumber;
(function (FieldNumber) {
    FieldNumber[FieldNumber["FileDescriptorProto_Package"] = 2] = "FileDescriptorProto_Package";
    FieldNumber[FieldNumber["FileDescriptorProto_MessageType"] = 4] = "FileDescriptorProto_MessageType";
    FieldNumber[FieldNumber["FileDescriptorProto_EnumType"] = 5] = "FileDescriptorProto_EnumType";
    FieldNumber[FieldNumber["FileDescriptorProto_Service"] = 6] = "FileDescriptorProto_Service";
    FieldNumber[FieldNumber["FileDescriptorProto_Extension"] = 7] = "FileDescriptorProto_Extension";
    FieldNumber[FieldNumber["FileDescriptorProto_Syntax"] = 12] = "FileDescriptorProto_Syntax";
    FieldNumber[FieldNumber["DescriptorProto_Field"] = 2] = "DescriptorProto_Field";
    FieldNumber[FieldNumber["DescriptorProto_NestedType"] = 3] = "DescriptorProto_NestedType";
    FieldNumber[FieldNumber["DescriptorProto_EnumType"] = 4] = "DescriptorProto_EnumType";
    FieldNumber[FieldNumber["DescriptorProto_Extension"] = 6] = "DescriptorProto_Extension";
    FieldNumber[FieldNumber["DescriptorProto_OneofDecl"] = 8] = "DescriptorProto_OneofDecl";
    FieldNumber[FieldNumber["EnumDescriptorProto_Value"] = 2] = "EnumDescriptorProto_Value";
    FieldNumber[FieldNumber["ServiceDescriptorProto_Method"] = 2] = "ServiceDescriptorProto_Method";
})(FieldNumber || (FieldNumber = {}));
/**
 * Return a string that matches the definition of a field in the protobuf
 * source. Does not take custom options into account.
 */
function declarationString() {
    var _a, _b, _c;
    const parts = [];
    if (this.repeated) {
        parts.push("repeated");
    }
    if (this.optional) {
        parts.push("optional");
    }
    const file = this.kind === "extension" ? this.file : this.parent.file;
    if (file.syntax == "proto2" &&
        this.proto.label === FieldDescriptorProto_Label.REQUIRED) {
        parts.push("required");
    }
    let type;
    switch (this.fieldKind) {
        case "scalar":
            type = ScalarType[this.scalar].toLowerCase();
            break;
        case "enum":
            type = this.enum.typeName;
            break;
        case "message":
            type = this.message.typeName;
            break;
        case "map": {
            const k = ScalarType[this.mapKey].toLowerCase();
            let v;
            switch (this.mapValue.kind) {
                case "scalar":
                    v = ScalarType[this.mapValue.scalar].toLowerCase();
                    break;
                case "enum":
                    v = this.mapValue.enum.typeName;
                    break;
                case "message":
                    v = this.mapValue.message.typeName;
                    break;
            }
            type = `map<${k}, ${v}>`;
            break;
        }
    }
    parts.push(`${type} ${this.name} = ${this.number}`);
    const options = [];
    if (((_a = this.proto.options) === null || _a === void 0 ? void 0 : _a.packed) !== undefined) {
        options.push(`packed = ${this.proto.options.packed.toString()}`);
    }
    let defaultValue = this.proto.defaultValue;
    if (defaultValue !== undefined) {
        if (this.proto.type == FieldDescriptorProto_Type.BYTES ||
            this.proto.type == FieldDescriptorProto_Type.STRING) {
            defaultValue = '"' + defaultValue.replace('"', '\\"') + '"';
        }
        options.push(`default = ${defaultValue}`);
    }
    if (this.jsonName !== undefined) {
        options.push(`json_name = "${this.jsonName}"`);
    }
    if (((_b = this.proto.options) === null || _b === void 0 ? void 0 : _b.jstype) !== undefined) {
        options.push(`jstype = ${FieldOptions_JSType[this.proto.options.jstype]}`);
    }
    if (((_c = this.proto.options) === null || _c === void 0 ? void 0 : _c.deprecated) === true) {
        options.push(`deprecated = true`);
    }
    if (options.length > 0) {
        parts.push("[" + options.join(", ") + "]");
    }
    return parts.join(" ");
}
/**
 * Parses a text-encoded default value (proto2) of a scalar or enum field.
 */
function getDefaultValue() {
    const d = this.proto.defaultValue;
    if (d === undefined) {
        return undefined;
    }
    switch (this.fieldKind) {
        case "enum":
            return parseTextFormatEnumValue(this.enum, d);
        case "scalar":
            return parseTextFormatScalarValue(this.scalar, d);
        default:
            return undefined;
    }
}
