"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DecoratorPlugin = void 0;
const ts = require("typescript");
const types_1 = require("../../models/types");
const components_1 = require("../components");
const converter_1 = require("../converter");
/**
 * A plugin that detects decorators.
 */
let DecoratorPlugin = class DecoratorPlugin extends components_1.ConverterComponent {
    constructor() {
        super(...arguments);
        this.usages = new Map();
    }
    /**
     * Create a new ImplementsPlugin instance.
     */
    initialize() {
        this.listenTo(this.owner, {
            [converter_1.Converter.EVENT_CREATE_DECLARATION]: this.onDeclaration,
            [converter_1.Converter.EVENT_CREATE_PARAMETER]: this.onDeclaration,
            [converter_1.Converter.EVENT_RESOLVE]: this.onBeginResolve,
            [converter_1.Converter.EVENT_END]: () => this.usages.clear(),
        });
    }
    /**
     * Create an object describing the arguments a decorator is set with.
     *
     * @param args  The arguments resolved from the decorator's call expression.
     * @param signature  The signature definition of the decorator being used.
     * @returns An object describing the decorator parameters,
     */
    extractArguments(args, signature) {
        const result = {};
        args.forEach((arg, index) => {
            if (index < signature.parameters.length) {
                const parameter = signature.parameters[index];
                result[parameter.name] = arg.getText();
            }
            else {
                if (!result["..."]) {
                    result["..."] = [];
                }
                result["..."].push(arg.getText());
            }
        });
        return result;
    }
    /**
     * Triggered when the converter has created a declaration or signature reflection.
     *
     * @param context  The context object describing the current state the converter is in.
     * @param reflection  The reflection that is currently processed.
     * @param node  The node that is currently processed if available.
     */
    onDeclaration(context, reflection, node) {
        var _a;
        (_a = node === null || node === void 0 ? void 0 : node.decorators) === null || _a === void 0 ? void 0 : _a.forEach((decorator) => {
            var _a, _b;
            let callExpression;
            let identifier;
            switch (decorator.expression.kind) {
                case ts.SyntaxKind.Identifier:
                    identifier = decorator.expression;
                    break;
                case ts.SyntaxKind.CallExpression:
                    callExpression = decorator.expression;
                    identifier = callExpression.expression;
                    break;
                default:
                    return;
            }
            const info = {
                name: identifier.getText(),
            };
            const type = context.checker.getTypeAtLocation(identifier);
            if (type && type.symbol) {
                info.type = types_1.ReferenceType.createSymbolReference(context.resolveAliasedSymbol(type.symbol), context, info.name);
                if (callExpression && callExpression.arguments) {
                    const signature = context.checker.getResolvedSignature(callExpression);
                    if (signature) {
                        info.arguments = this.extractArguments(callExpression.arguments, signature);
                    }
                }
                const usages = (_a = this.usages.get(type.symbol)) !== null && _a !== void 0 ? _a : [];
                usages.push(types_1.ReferenceType.createResolvedReference(reflection.name, reflection, context.project));
                this.usages.set(type.symbol, usages);
            }
            (_b = reflection.decorators) !== null && _b !== void 0 ? _b : (reflection.decorators = []);
            reflection.decorators.push(info);
        });
    }
    onBeginResolve(context) {
        for (const [symbol, ref] of this.usages) {
            const reflection = context.project.getReflectionFromSymbol(symbol);
            if (reflection) {
                reflection.decorates = ref;
            }
        }
    }
};
DecoratorPlugin = __decorate([
    (0, components_1.Component)({ name: "decorator" })
], DecoratorPlugin);
exports.DecoratorPlugin = DecoratorPlugin;
