"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrpcReflection = void 0;
const Exceptions_1 = require("./Exceptions");
const protobufjs = require("protobufjs");
const Descriptor_1 = require("./Descriptor");
const descriptor_1 = require("protobufjs/ext/descriptor");
const lodash_1 = require("lodash");
const v1 = require("./Proto/v1");
const v1alpha = require("./Proto/v1alpha");
class GrpcReflection {
    constructor(host, credentials, options = {}, version = "v1alpha") {
        this.version = version;
        this.setProtoReflectionClient(host, credentials, options);
    }
    async listServices(prefix = '*', options = {}) {
        const response = await this.request({
            listServices: prefix
        }, options);
        return response.listServicesResponse.service.map(service => service.name);
    }
    async listMethods(service, options = {}) {
        const descriptor = await this.getDescriptorBySymbol(service, options);
        const packageObject = descriptor.getPackageObject({
            keepCase: true,
            enums: String,
            longs: String
        });
        return this.getServiceMethods(packageObject, service);
    }
    generateServicePath(service) {
        return service.split('.').reverse();
    }
    getServiceMethods(descriptor, service) {
        let anti_recusive = 0;
        let actualDescriptor = descriptor;
        let found = false;
        const path = this.generateServicePath(service);
        do {
            const service = path.pop();
            if (service && service in actualDescriptor) {
                actualDescriptor = actualDescriptor[service];
                found = true;
            }
            anti_recusive++;
        } while (anti_recusive < 100 || !found);
        if ('service' in actualDescriptor) {
            return Object.entries(actualDescriptor.service)
                .map(([methodName, methodDefinition]) => ({
                name: methodName,
                definition: methodDefinition
            }));
        }
        throw new Exceptions_1.ReflectionRequestException('Not found service');
    }
    async getDescriptorByFileName(file_name, options = {}) {
        const descriptor = await this.getProtoDescriptorByFileName(file_name, options);
        return await this.resolveFileDescriptorSet(descriptor, options);
    }
    async getDescriptorBySymbol(symbol, options = {}) {
        const descriptor = await this.getProtoDescriptorBySymbol(symbol, options);
        return await this.resolveFileDescriptorSet(descriptor, options);
    }
    async getAllExtensionNumbersOfType(package_type, options = {}) {
        const response = await this.request({
            allExtensionNumbersOfType: package_type
        }, options);
        return {
            base_type_name: response.allExtensionNumbersResponse.baseTypeName,
            extension_number: typeof (response.allExtensionNumbersResponse.extensionNumber) !== "undefined" ? response.allExtensionNumbersResponse.extensionNumber : []
        };
    }
    async request(payload, options) {
        return new Promise((resolve, reject) => {
            const payloadObject = this.reflectionRequestConstructor(payload);
            const call = this.client.ServerReflectionInfo(options);
            call.on('data', (data) => {
                if (data.errorResponse) {
                    reject(new Exceptions_1.ReflectionRequestException(data.errorResponse.errorMessage));
                    return;
                }
                resolve(data);
            });
            call.on('error', (err) => {
                reject(new Exceptions_1.ReflectionRequestException(err));
            });
            call.on('end', () => { });
            call.write(payloadObject);
            call.end();
        });
    }
    async resolveFileDescriptorSet(fileDescriptorProtoBytes, options) {
        const fileDescriptorSet = descriptor_1.FileDescriptorSet.create();
        const fileDescriptorProtos = await this.resolveDescriptorRecursive(fileDescriptorProtoBytes, options);
        (0, lodash_1.set)(fileDescriptorSet, 'file', Array.from(fileDescriptorProtos.values()));
        return new Descriptor_1.Descriptor(protobufjs.Root.fromDescriptor(fileDescriptorSet));
    }
    async resolveDescriptorRecursive(fileDescriptorProtoBytes, options) {
        let fileDescriptorProtos = new Map();
        let needsDependencyResolution = new Set();
        for (const item of fileDescriptorProtoBytes) {
            const fileDescriptorProto = descriptor_1.FileDescriptorProto.decode(item);
            if (fileDescriptorProto.dependency) {
                const dependencies = fileDescriptorProto.dependency;
                for (const dep of dependencies) {
                    needsDependencyResolution.add(dep);
                }
            }
            if (!fileDescriptorProtos.has(fileDescriptorProto.name)) {
                fileDescriptorProtos.set(fileDescriptorProto.name, fileDescriptorProto);
            }
        }
        for (const dep of needsDependencyResolution) {
            if (fileDescriptorProtos.has(dep)) {
                continue;
            }
            const depProtoBytes = await this.getProtoDescriptorByFileName(dep, options);
            const protoDependencies = await this.resolveDescriptorRecursive(depProtoBytes, options);
            fileDescriptorProtos = new Map([
                ...fileDescriptorProtos,
                ...protoDependencies,
            ]);
        }
        return fileDescriptorProtos;
    }
    async getProtoDescriptorBySymbol(symbol, options) {
        const response = await this.request({
            fileContainingSymbol: symbol
        }, options);
        return response.fileDescriptorResponse.fileDescriptorProto;
    }
    async getProtoDescriptorByFileName(file_name, options) {
        const response = await this.request({
            fileByFilename: file_name
        }, options);
        return response.fileDescriptorResponse.fileDescriptorProto;
    }
    setProtoReflectionClient(host, credentials, options = {}) {
        switch (this.version) {
            case 'v1':
                this.client = new v1.grpc.reflection.v1.ServerReflectionClient(host, credentials, options);
                this.reflectionRequestConstructor = v1.grpc.reflection.v1.ServerReflectionRequest.fromObject;
                break;
            case 'v1alpha':
                this.client = new v1alpha.grpc.reflection.v1alpha.ServerReflectionClient(host, credentials, options);
                this.reflectionRequestConstructor = v1alpha.grpc.reflection.v1alpha.ServerReflectionRequest.fromObject;
                break;
            default:
                throw new Exceptions_1.ReflectionRequestException('Unknown proto version available: [v1, v1alpha]');
        }
    }
}
exports.GrpcReflection = GrpcReflection;
//# sourceMappingURL=GrpcReflection.js.map