// Copyright 2021-2026 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 { wktPublicImportPaths } from "@bufbuild/protobuf/codegenv2";
import { nestedTypes } from "@bufbuild/protobuf/reflect";
import { safeIdentifier } from "./safe-identifier.js";
/**
 * Return a file path for the give file descriptor.
 */
export function generateFilePath(file, bootstrapWkt, filesToGenerate) {
    // Well-known types are published with the runtime package. We usually want
    // the generated code to import them from the runtime package, with the
    // following exceptions:
    // 1. We are bootstrapping the runtime package via the plugin option
    //    "bootstrap_wkt". In that case, we cannot refer to the runtime package
    //    itself.
    // 2. We were explicitly asked to generate the well-known type.
    const wktFrom = wktPublicImportPaths[file.proto.name];
    if (wktFrom !== undefined &&
        !bootstrapWkt &&
        !filesToGenerate.find((f) => f.name === file.name)) {
        return wktFrom;
    }
    return "./" + file.name + "_pb.js";
}
/**
 * Return a safe identifier for a generated descriptor.
 */
export function generatedDescName(desc) {
    const file = desc.kind == "file" ? desc : desc.file;
    const { descNames } = allNames(file);
    const name = descNames.get(desc);
    if (name === undefined) {
        throw new Error(`unable to determine unique descriptor name for ${desc.toString()}`);
    }
    return name;
}
/**
 * Return a safe identifier for a generated shape.
 */
export function generatedShapeName(desc) {
    const { shapeNames } = allNames(desc.file);
    const name = shapeNames.get(desc);
    if (name === undefined) {
        throw new Error(`unable to determine unique shape name for ${desc.toString()}`);
    }
    return name;
}
/**
 * Return a safe identifier for a generated JSON type.
 */
export function generatedJsonTypeName(desc) {
    const { jsonTypeNames } = allNames(desc.file);
    const name = jsonTypeNames.get(desc);
    if (name === undefined) {
        throw new Error(`unable to determine unique json type name for ${desc.toString()}`);
    }
    return name;
}
/**
 * Return a safe identifier for a generated Valid type.
 */
export function generatedValidTypeName(desc) {
    const { validTypeNames } = allNames(desc.file);
    const name = validTypeNames.get(desc);
    if (name === undefined) {
        throw new Error(`unable to determine unique valid type name for ${desc.toString()}`);
    }
    return name;
}
/**
 * Compute the ideal name for a generated descriptor.
 */
function idealDescName(desc, i) {
    const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`;
    if (desc.kind == "file") {
        const name = "file_" + desc.name.replace(/[^a-zA-Z0-9_]+/g, "_");
        return safeIdentifier(name + salt);
    }
    switch (desc.kind) {
        case "enum":
            return safeIdentifier(identifier(desc) + "Schema" + salt);
        case "message":
            return safeIdentifier(identifier(desc) + "Schema" + salt);
        case "extension":
            return safeIdentifier(identifier(desc) + salt);
        case "service":
            return safeIdentifier(identifier(desc) + salt);
    }
}
/**
 * Compute the ideal name for a generated shape.
 */
function idealShapeName(desc, i) {
    const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`;
    return safeIdentifier(identifier(desc) + salt);
}
/**
 * Compute the ideal name for a generated JSON type.
 */
function idealJsonTypeName(desc, i) {
    const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`;
    return safeIdentifier(identifier(desc) + "Json" + salt);
}
/**
 * Compute the ideal name for a generated Valid type.
 */
function idealValidTypeName(desc, i) {
    const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`;
    return safeIdentifier(identifier(desc) + "Valid" + salt);
}
/**
 * Return an identifier for the given descriptor based on its type name.
 *
 * The type name for a protobuf message is the package name (if any), plus
 * the names of parent messages it is nested in (if any), plus the name of
 * the element, separated by dots. For example: foo.bar.ParentMsg.MyEnum.
 *
 * ECMAScript does not have packages or namespaces, so we need a single
 * identifier. Our convention is to drop the package name, and to join other
 * parts of the name with an underscore. For example: ParentMsg_MyEnum.
 */
function identifier(desc) {
    const pkg = desc.file.proto.package;
    const offset = pkg.length > 0 ? pkg.length + 1 : 0;
    const nameWithoutPkg = desc.typeName.substring(offset);
    return nameWithoutPkg.replace(/\./g, "_");
}
/**
 * Compute all ideal names for the elements in the file, resolving name clashes.
 */
function allNames(file) {
    const taken = new Set();
    // In the first pass, register shape names
    const shapeNames = new Map();
    for (const desc of nestedTypes(file)) {
        if (desc.kind != "enum" && desc.kind != "message") {
            continue;
        }
        let name;
        for (let i = 0;; i++) {
            name = idealShapeName(desc, i);
            if (!taken.has(name)) {
                break;
            }
        }
        taken.add(name);
        shapeNames.set(desc, name);
    }
    // In the second pass, register desc names
    const descNames = new Map();
    for (const desc of [file, ...nestedTypes(file)]) {
        let name;
        switch (desc.kind) {
            case "enum":
            case "message": {
                for (let i = 0;; i++) {
                    name = idealDescName(desc, i);
                    if (!taken.has(name)) {
                        break;
                    }
                }
                break;
            }
            default: {
                for (let i = 0;; i++) {
                    name = idealDescName(desc, i);
                    if (!taken.has(name)) {
                        break;
                    }
                }
                break;
            }
        }
        taken.add(name);
        descNames.set(desc, name);
    }
    // In the third pass, register json type names
    const jsonTypeNames = new Map();
    for (const desc of nestedTypes(file)) {
        if (desc.kind != "enum" && desc.kind != "message") {
            continue;
        }
        let name;
        for (let i = 0;; i++) {
            name = idealJsonTypeName(desc, i);
            if (!taken.has(name)) {
                break;
            }
        }
        taken.add(name);
        jsonTypeNames.set(desc, name);
    }
    const validTypeNames = new Map();
    for (const desc of nestedTypes(file)) {
        if (desc.kind != "enum" && desc.kind != "message") {
            continue;
        }
        let name;
        for (let i = 0;; i++) {
            name = idealValidTypeName(desc, i);
            if (!taken.has(name)) {
                break;
            }
        }
        taken.add(name);
        validTypeNames.set(desc, name);
    }
    return { shapeNames, jsonTypeNames, validTypeNames, descNames };
}
