"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTypeParamReflection = exports.convertTypeParameterNodes = exports.convertParameterNodes = exports.createSignature = void 0;
const ts = require("typescript");
const assert = require("assert");
const models_1 = require("../../models");
const converter_events_1 = require("../converter-events");
const convert_expression_1 = require("../convert-expression");
const reflections_1 = require("../utils/reflections");
function createSignature(context, kind, signature, declaration, commentDeclaration) {
    var _a;
    var _b;
    assert(context.scope instanceof models_1.DeclarationReflection);
    // signature.getDeclaration might return undefined.
    // https://github.com/microsoft/TypeScript/issues/30014
    declaration !== null && declaration !== void 0 ? declaration : (declaration = signature.getDeclaration());
    if (!commentDeclaration &&
        declaration &&
        (ts.isArrowFunction(declaration) ||
            ts.isFunctionExpression(declaration))) {
        commentDeclaration = declaration.parent;
    }
    commentDeclaration !== null && commentDeclaration !== void 0 ? commentDeclaration : (commentDeclaration = declaration);
    const sigRef = new models_1.SignatureReflection(kind == models_1.ReflectionKind.ConstructorSignature
        ? `new ${context.scope.parent.name}`
        : context.scope.name, kind, context.scope);
    sigRef.typeParameters = convertTypeParameters(context, sigRef, signature.typeParameters);
    const parameterSymbols = signature.thisParameter
        ? [signature.thisParameter, ...signature.parameters]
        : signature.parameters;
    sigRef.parameters = convertParameters(context, sigRef, parameterSymbols, declaration === null || declaration === void 0 ? void 0 : declaration.parameters);
    const predicate = context.checker.getTypePredicateOfSignature(signature);
    if (predicate) {
        sigRef.type = convertPredicate(predicate, context.withScope(sigRef));
    }
    else if (kind == models_1.ReflectionKind.SetSignature) {
        sigRef.type = new models_1.IntrinsicType("void");
    }
    else {
        sigRef.type = context.converter.convertType(context.withScope(sigRef), ((declaration === null || declaration === void 0 ? void 0 : declaration.kind) === ts.SyntaxKind.FunctionDeclaration &&
            declaration.type) ||
            signature.getReturnType());
    }
    context.registerReflection(sigRef, undefined);
    switch (kind) {
        case models_1.ReflectionKind.GetSignature:
            context.scope.getSignature = sigRef;
            break;
        case models_1.ReflectionKind.SetSignature:
            context.scope.setSignature = sigRef;
            break;
        case models_1.ReflectionKind.CallSignature:
        case models_1.ReflectionKind.ConstructorSignature:
            (_a = (_b = context.scope).signatures) !== null && _a !== void 0 ? _a : (_b.signatures = []);
            context.scope.signatures.push(sigRef);
            break;
    }
    context.trigger(converter_events_1.ConverterEvents.CREATE_SIGNATURE, sigRef, commentDeclaration);
}
exports.createSignature = createSignature;
function convertParameters(context, sigRef, parameters, parameterNodes) {
    return parameters.map((param, i) => {
        const declaration = param.valueDeclaration;
        assert(!declaration ||
            ts.isParameter(declaration) ||
            ts.isJSDocParameterTag(declaration));
        const paramRefl = new models_1.ParameterReflection(/__\d+/.test(param.name) ? "__namedParameters" : param.name, models_1.ReflectionKind.Parameter, sigRef);
        context.registerReflection(paramRefl, param);
        context.trigger(converter_events_1.ConverterEvents.CREATE_PARAMETER, paramRefl, declaration);
        let type;
        if (declaration) {
            type = context.checker.getTypeOfSymbolAtLocation(param, declaration);
        }
        else {
            type = param.type;
        }
        paramRefl.type = context.converter.convertType(context.withScope(paramRefl), type);
        let isOptional = false;
        if (declaration) {
            isOptional = ts.isParameter(declaration)
                ? !!declaration.questionToken
                : declaration.isBracketed;
        }
        if (isOptional) {
            paramRefl.type = (0, reflections_1.removeUndefined)(paramRefl.type);
        }
        paramRefl.defaultValue = (0, convert_expression_1.convertDefaultValue)(parameterNodes === null || parameterNodes === void 0 ? void 0 : parameterNodes[i]);
        paramRefl.setFlag(models_1.ReflectionFlag.Optional, isOptional);
        // If we have no declaration, then this is an implicitly defined parameter in JS land
        // because the method body uses `arguments`... which is always a rest argument
        let isRest = true;
        if (declaration) {
            isRest = ts.isParameter(declaration)
                ? !!declaration.dotDotDotToken
                : !!declaration.typeExpression &&
                    ts.isJSDocVariadicType(declaration.typeExpression.type);
        }
        paramRefl.setFlag(models_1.ReflectionFlag.Rest, isRest);
        return paramRefl;
    });
}
function convertParameterNodes(context, sigRef, parameters) {
    return parameters.map((param) => {
        var _a;
        const paramRefl = new models_1.ParameterReflection(/__\d+/.test(param.name.getText())
            ? "__namedParameters"
            : param.name.getText(), models_1.ReflectionKind.Parameter, sigRef);
        context.registerReflection(paramRefl, context.getSymbolAtLocation(param));
        paramRefl.type = context.converter.convertType(context.withScope(paramRefl), ts.isParameter(param) ? param.type : (_a = param.typeExpression) === null || _a === void 0 ? void 0 : _a.type);
        const isOptional = ts.isParameter(param)
            ? !!param.questionToken
            : param.isBracketed;
        if (isOptional) {
            paramRefl.type = (0, reflections_1.removeUndefined)(paramRefl.type);
        }
        paramRefl.defaultValue = (0, convert_expression_1.convertDefaultValue)(param);
        paramRefl.setFlag(models_1.ReflectionFlag.Optional, isOptional);
        paramRefl.setFlag(models_1.ReflectionFlag.Rest, ts.isParameter(param)
            ? !!param.dotDotDotToken
            : !!param.typeExpression &&
                ts.isJSDocVariadicType(param.typeExpression.type));
        return paramRefl;
    });
}
exports.convertParameterNodes = convertParameterNodes;
function convertTypeParameters(context, parent, parameters) {
    return parameters === null || parameters === void 0 ? void 0 : parameters.map((param) => {
        var _a, _b, _c;
        const constraintT = param.getConstraint();
        const defaultT = param.getDefault();
        const constraint = constraintT
            ? context.converter.convertType(context, constraintT)
            : void 0;
        const defaultType = defaultT
            ? context.converter.convertType(context, defaultT)
            : void 0;
        // There's no way to determine directly from a ts.TypeParameter what it's variance modifiers are
        // so unfortunately we have to go back to the node for this...
        const variance = getVariance((_c = (_b = (_a = param.getSymbol()) === null || _a === void 0 ? void 0 : _a.declarations) === null || _b === void 0 ? void 0 : _b.find(ts.isTypeParameterDeclaration)) === null || _c === void 0 ? void 0 : _c.modifiers);
        const paramRefl = new models_1.TypeParameterReflection(param.symbol.name, constraint, defaultType, parent, variance);
        context.registerReflection(paramRefl, param.getSymbol());
        context.trigger(converter_events_1.ConverterEvents.CREATE_TYPE_PARAMETER, paramRefl);
        return paramRefl;
    });
}
function convertTypeParameterNodes(context, parameters) {
    return parameters === null || parameters === void 0 ? void 0 : parameters.map((param) => createTypeParamReflection(param, context));
}
exports.convertTypeParameterNodes = convertTypeParameterNodes;
function createTypeParamReflection(param, context) {
    const constraint = param.constraint
        ? context.converter.convertType(context, param.constraint)
        : void 0;
    const defaultType = param.default
        ? context.converter.convertType(context, param.default)
        : void 0;
    const paramRefl = new models_1.TypeParameterReflection(param.name.text, constraint, defaultType, context.scope, getVariance(param.modifiers));
    context.registerReflection(paramRefl, param.symbol);
    context.trigger(converter_events_1.ConverterEvents.CREATE_TYPE_PARAMETER, paramRefl, param);
    return paramRefl;
}
exports.createTypeParamReflection = createTypeParamReflection;
function getVariance(modifiers) {
    const hasIn = modifiers === null || modifiers === void 0 ? void 0 : modifiers.some((mod) => mod.kind === ts.SyntaxKind.InKeyword);
    const hasOut = modifiers === null || modifiers === void 0 ? void 0 : modifiers.some((mod) => mod.kind === ts.SyntaxKind.OutKeyword);
    if (hasIn && hasOut) {
        return models_1.VarianceModifier.inOut;
    }
    if (hasIn) {
        return models_1.VarianceModifier.in;
    }
    if (hasOut) {
        return models_1.VarianceModifier.out;
    }
}
function convertPredicate(predicate, context) {
    let name;
    switch (predicate.kind) {
        case ts.TypePredicateKind.This:
        case ts.TypePredicateKind.AssertsThis:
            name = "this";
            break;
        case ts.TypePredicateKind.Identifier:
        case ts.TypePredicateKind.AssertsIdentifier:
            name = predicate.parameterName;
            break;
    }
    let asserts;
    switch (predicate.kind) {
        case ts.TypePredicateKind.This:
        case ts.TypePredicateKind.Identifier:
            asserts = false;
            break;
        case ts.TypePredicateKind.AssertsThis:
        case ts.TypePredicateKind.AssertsIdentifier:
            asserts = true;
            break;
    }
    return new models_1.PredicateType(name, asserts, predicate.type
        ? context.converter.convertType(context, predicate.type)
        : void 0);
}
