"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.nonAsyncFunctionsReturningPromises = exports.toIgnore = exports.signatures = exports.ShellApiValueClass = exports.ShellApiWithMongoClass = exports.ShellApiClass = void 0;
exports.getShellApiType = getShellApiType;
exports.toShellResult = toShellResult;
exports.shellApiClassDefault = shellApiClassDefault;
exports.shellApiClassNoHelp = shellApiClassNoHelp;
exports.serverVersions = serverVersions;
exports.apiVersions = apiVersions;
exports.deprecated = deprecated;
exports.topologies = topologies;
exports.returnsPromise = returnsPromise;
exports.directShellCommand = directShellCommand;
exports.shellCommandCompleter = shellCommandCompleter;
exports.returnType = returnType;
exports.classDeprecated = classDeprecated;
exports.platforms = platforms;
exports.classPlatforms = classPlatforms;
exports.addSourceToResults = addSourceToResults;
exports.cursorChainable = cursorChainable;
const errors_1 = require("@mongosh/errors");
const enums_1 = require("./enums");
const help_1 = __importDefault(require("./help"));
const helpers_1 = require("./helpers");
const addSourceToResultsSymbol = Symbol.for('@@mongosh.addSourceToResults');
const resultSource = Symbol.for('@@mongosh.resultSource');
class ShellApiClass {
    get [enums_1.shellApiType]() {
        throw new errors_1.MongoshInternalError('Shell API Type did not use decorators');
    }
    set [enums_1.shellApiType](value) {
        (0, helpers_1.addHiddenDataProperty)(this, enums_1.shellApiType, value);
    }
    [enums_1.asPrintable]() {
        if (Array.isArray(this)) {
            return [...this];
        }
        return { ...this };
    }
}
exports.ShellApiClass = ShellApiClass;
class ShellApiWithMongoClass extends ShellApiClass {
    get _instanceState() {
        return this._mongo?._instanceState;
    }
}
exports.ShellApiWithMongoClass = ShellApiWithMongoClass;
class ShellApiValueClass extends ShellApiClass {
    get _mongo() {
        throw new errors_1.MongoshInternalError('Not supported on this value class');
    }
    get _instanceState() {
        throw new errors_1.MongoshInternalError('Not supported on this value class');
    }
}
exports.ShellApiValueClass = ShellApiValueClass;
function getShellApiType(rawValue) {
    return rawValue?.[enums_1.shellApiType] ?? null;
}
async function toShellResult(rawValue) {
    if ((typeof rawValue !== 'object' && typeof rawValue !== 'function') ||
        rawValue === null) {
        return {
            type: null,
            rawValue: rawValue,
            printable: rawValue,
        };
    }
    if ('then' in rawValue && typeof rawValue.then === 'function') {
        return toShellResult(await rawValue);
    }
    const type = getShellApiType(rawValue);
    const printable = typeof rawValue[enums_1.asPrintable] === 'function'
        ? await rawValue[enums_1.asPrintable]()
        : rawValue;
    const source = rawValue[resultSource] ?? undefined;
    const constructionOptions = rawValue._constructionOptions
        ? { options: rawValue._constructionOptions, chains: rawValue._chains }
        : undefined;
    return {
        type,
        rawValue,
        printable,
        source,
        constructionOptions,
    };
}
function wrapWithAddSourceToResult(fn) {
    function addSource(result, obj) {
        if (typeof result === 'object' && result !== null) {
            const resultSourceInformation = {
                namespace: obj[enums_1.namespaceInfo](),
            };
            (0, helpers_1.addHiddenDataProperty)(result, resultSource, resultSourceInformation);
            if (result[enums_1.shellApiType] === undefined &&
                fn.returnType) {
                (0, helpers_1.addHiddenDataProperty)(result, enums_1.shellApiType, fn.returnType);
            }
        }
        return result;
    }
    const wrapper = fn.returnsPromise
        ? markImplicitlyAwaited(async function (...args) {
            return addSource(await fn.call(this, ...args), this);
        })
        : function (...args) {
            return addSource(fn.call(this, ...args), this);
        };
    Object.setPrototypeOf(wrapper, Object.getPrototypeOf(fn));
    Object.defineProperties(wrapper, Object.getOwnPropertyDescriptors(fn));
    return wrapper;
}
function wrapWithApiChecks(fn, className) {
    const wrapper = fn.returnsPromise
        ? markImplicitlyAwaited(async function (...args) {
            const instanceState = getShellInstanceState(this);
            emitApiCallTelemetry(instanceState, className, fn, true);
            const interruptFlag = instanceState?.interrupted;
            interruptFlag?.checkpoint();
            const interrupt = interruptFlag?.asPromise();
            let result;
            try {
                if (instanceState) {
                    instanceState.apiCallDepth++;
                }
                result = await Promise.race([
                    interrupt?.promise ?? new Promise(() => { }),
                    fn.call(this, ...args),
                ]);
            }
            catch (e) {
                throw instanceState?.transformError(e) ?? e;
            }
            finally {
                if (instanceState) {
                    instanceState.apiCallDepth--;
                }
                if (interrupt) {
                    interrupt.destroy();
                }
            }
            interruptFlag?.checkpoint();
            return result;
        })
        : function (...args) {
            const instanceState = getShellInstanceState(this);
            emitApiCallTelemetry(instanceState, className, fn, false);
            const interruptFlag = instanceState?.interrupted;
            interruptFlag?.checkpoint();
            let result;
            try {
                if (instanceState) {
                    instanceState.apiCallDepth++;
                }
                result = fn.call(this, ...args);
            }
            catch (e) {
                throw instanceState?.transformError(e) ?? e;
            }
            finally {
                if (instanceState) {
                    instanceState.apiCallDepth--;
                }
            }
            interruptFlag?.checkpoint();
            return result;
        };
    Object.setPrototypeOf(wrapper, Object.getPrototypeOf(fn));
    Object.defineProperties(wrapper, Object.getOwnPropertyDescriptors(fn));
    return wrapper;
}
function emitApiCallTelemetry(instanceState, className, fn, isAsync) {
    instanceState?.emitApiCall?.({
        method: fn.name,
        class: className,
        deprecated: !!fn.deprecated,
        isAsync,
    });
}
function getShellInstanceState(apiObject) {
    if (!apiObject[enums_1.shellApiType]) {
        throw new errors_1.MongoshInternalError('getShellInstanceState can only be called for functions from shell API classes');
    }
    return apiObject._instanceState;
}
const signaturesGlobalIdentifier = '@@@mdb.signatures@@@';
if (!globalThis[signaturesGlobalIdentifier]) {
    globalThis[signaturesGlobalIdentifier] = {};
}
const signatures = globalThis[signaturesGlobalIdentifier];
exports.signatures = signatures;
signatures.Document = { type: 'Document', attributes: {} };
exports.toIgnore = ['constructor', 'help', 'toJSON'];
function shellApiClassGeneric(constructor, hasHelp, context) {
    const className = context.name;
    const classHelpKeyPrefix = `shell-api.classes.${className}.help`;
    const classHelp = {
        help: `${classHelpKeyPrefix}.description`,
        docs: `${classHelpKeyPrefix}.link`,
        attr: [],
    };
    const classSignature = {
        type: className,
        returnsPromise: constructor.prototype.returnsPromise || false,
        deprecated: constructor.prototype.deprecated || false,
        attributes: {},
    };
    const classAttributes = Object.getOwnPropertyNames(constructor.prototype);
    for (const propertyName of classAttributes) {
        const descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, propertyName);
        if (exports.toIgnore.includes(propertyName) || propertyName.startsWith('_')) {
            continue;
        }
        const isMethod = descriptor?.value && typeof descriptor.value === 'function';
        if (!isMethod) {
            const attributeHelpKeyPrefix = `${classHelpKeyPrefix}.attributes.${propertyName}`;
            classHelp.attr.push({
                name: propertyName,
                description: `${attributeHelpKeyPrefix}.description`,
            });
            continue;
        }
        let method = descriptor.value;
        if (constructor[addSourceToResultsSymbol]) {
            method = wrapWithAddSourceToResult(method);
        }
        method = wrapWithApiChecks(method, className);
        method.serverVersions = method.serverVersions || enums_1.ALL_SERVER_VERSIONS;
        method.apiVersions = method.apiVersions || enums_1.ALL_API_VERSIONS;
        method.topologies = method.topologies || enums_1.ALL_TOPOLOGIES;
        method.returnType = method.returnType || {
            type: 'unknown',
            attributes: {},
        };
        method.returnsPromise = method.returnsPromise || false;
        method.deprecated = method.deprecated || false;
        method.platforms = method.platforms || enums_1.ALL_PLATFORMS;
        method.isDirectShellCommand = method.isDirectShellCommand || false;
        method.acceptsRawInput = method.acceptsRawInput || false;
        method.shellCommandCompleter = method.shellCommandCompleter || undefined;
        method.newShellCommandCompleter =
            method.newShellCommandCompleter || undefined;
        classSignature.attributes[propertyName] = {
            type: 'function',
            serverVersions: method.serverVersions,
            apiVersions: method.apiVersions,
            topologies: method.topologies,
            returnType: method.returnType === 'this' ? className : method.returnType,
            returnsPromise: method.returnsPromise,
            deprecated: method.deprecated,
            platforms: method.platforms,
            isDirectShellCommand: method.isDirectShellCommand,
            acceptsRawInput: method.acceptsRawInput,
            shellCommandCompleter: method.shellCommandCompleter,
            newShellCommandCompleter: method.newShellCommandCompleter,
        };
        const attributeHelpKeyPrefix = `${classHelpKeyPrefix}.attributes.${propertyName}`;
        const attrHelp = {
            help: `${attributeHelpKeyPrefix}.example`,
            docs: `${attributeHelpKeyPrefix}.link`,
            attr: [{ description: `${attributeHelpKeyPrefix}.description` }],
        };
        const aHelp = new help_1.default(attrHelp);
        method.help = () => aHelp;
        Object.setPrototypeOf(method.help, aHelp);
        classHelp.attr.push({
            name: propertyName,
            description: `${attributeHelpKeyPrefix}.description`,
        });
        Object.defineProperty(constructor.prototype, propertyName, {
            ...descriptor,
            value: method,
        });
    }
    let superClass = constructor.prototype;
    while ((superClass = Object.getPrototypeOf(superClass)) !== null) {
        if (superClass.constructor.name === 'ShellApiClass' ||
            superClass.constructor === Array) {
            break;
        }
        const superClassHelpKeyPrefix = `shell-api.classes.${superClass.constructor.name}.help`;
        for (const propertyName of Object.getOwnPropertyNames(superClass)) {
            const descriptor = Object.getOwnPropertyDescriptor(superClass, propertyName);
            const isMethod = descriptor?.value && typeof descriptor.value === 'function';
            if (classAttributes.includes(propertyName) ||
                !isMethod ||
                exports.toIgnore.includes(propertyName) ||
                propertyName.startsWith('_'))
                continue;
            const method = descriptor.value;
            classSignature.attributes[propertyName] = {
                type: 'function',
                serverVersions: method.serverVersions,
                apiVersions: method.apiVersions,
                topologies: method.topologies,
                returnType: method.returnType === 'this' ? className : method.returnType,
                returnsPromise: method.returnsPromise,
                deprecated: method.deprecated,
                platforms: method.platforms,
                isDirectShellCommand: method.isDirectShellCommand,
                acceptsRawInput: method.acceptsRawInput,
                shellCommandCompleter: method.shellCommandCompleter,
                newShellCommandCompleter: method.newShellCommandCompleter,
                inherited: true,
            };
            const attributeHelpKeyPrefix = `${superClassHelpKeyPrefix}.attributes.${propertyName}`;
            classHelp.attr.push({
                name: propertyName,
                description: `${attributeHelpKeyPrefix}.description`,
            });
        }
    }
    const help = new help_1.default(classHelp);
    constructor.prototype.help = () => help;
    Object.setPrototypeOf(constructor.prototype.help, help);
    constructor.prototype[enums_1.asPrintable] =
        constructor.prototype[enums_1.asPrintable] || ShellApiClass.prototype[enums_1.asPrintable];
    (0, helpers_1.addHiddenDataProperty)(constructor.prototype, enums_1.shellApiType, className);
    if (hasHelp) {
        signatures[className] = classSignature;
    }
}
function shellApiClassDefault(constructor, context) {
    return shellApiClassGeneric(constructor, true, context);
}
function shellApiClassNoHelp(constructor, context) {
    return shellApiClassGeneric(constructor, false, context);
}
function markImplicitlyAwaited(orig) {
    function wrapper(...args) {
        const origResult = orig.call(this, ...args);
        return (0, helpers_1.addHiddenDataProperty)(origResult, Symbol.for('@@mongosh.syntheticPromise'), true);
    }
    Object.setPrototypeOf(wrapper, Object.getPrototypeOf(orig));
    Object.defineProperties(wrapper, Object.getOwnPropertyDescriptors(orig));
    return wrapper;
}
function serverVersions(serverVersions) {
    return function (value, context) {
        return Object.assign(value, { serverVersions });
    };
}
function apiVersions(versionArray) {
    return function (value, context) {
        if (versionArray.length === 0) {
            versionArray = [0, 0];
        }
        else if (versionArray.length === 1) {
            versionArray = [versionArray[0], Infinity];
        }
        return Object.assign(value, { apiVersions: versionArray });
    };
}
function deprecated(value, context) {
    return Object.assign(value, { deprecated: true });
}
function topologies(topologies) {
    return function (value, context) {
        return Object.assign(value, { topologies });
    };
}
exports.nonAsyncFunctionsReturningPromises = [];
function returnsPromise(originalFunction, context) {
    async function wrapper(...args) {
        try {
            return await originalFunction.call(this, ...args);
        }
        finally {
            if (typeof setTimeout === 'function' &&
                typeof setImmediate === 'function') {
                await new Promise(setImmediate);
            }
        }
    }
    Object.setPrototypeOf(wrapper, Object.getPrototypeOf(originalFunction));
    Object.defineProperties(wrapper, Object.getOwnPropertyDescriptors(originalFunction));
    if (originalFunction.constructor.name !== 'AsyncFunction') {
        exports.nonAsyncFunctionsReturningPromises.push(originalFunction.name);
    }
    return Object.assign(markImplicitlyAwaited(wrapper), {
        returnsPromise: true,
    });
}
function directShellCommand(value, context) {
    return Object.assign(value, { isDirectShellCommand: true });
}
function shellCommandCompleter(shellCommandCompleter, newShellCommandCompleter) {
    return function (value, context) {
        return Object.assign(value, {
            shellCommandCompleter,
            newShellCommandCompleter,
        });
    };
}
function returnType(returnType) {
    return function (value, context) {
        return Object.assign(value, { returnType });
    };
}
function classDeprecated(constructor, context) {
    constructor.prototype.deprecated = true;
}
function platforms(platforms) {
    return function (value, context) {
        return Object.assign(value, { platforms });
    };
}
function classPlatforms(platformsArray) {
    return function addSourceToResults(constructor, context) {
        constructor.prototype.platforms = platformsArray;
    };
}
function addSourceToResults(constructor, context) {
    constructor[addSourceToResultsSymbol] = true;
}
function cursorChainable(originalFunction, context) {
    function wrapper(...args) {
        this._chains.push({
            method: originalFunction.name,
            args: [...args],
        });
        return originalFunction.call(this, ...args);
    }
    Object.setPrototypeOf(wrapper, Object.getPrototypeOf(originalFunction));
    Object.defineProperties(wrapper, Object.getOwnPropertyDescriptors(originalFunction));
    return wrapper;
}
//# sourceMappingURL=decorators.js.map