"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetadataManager = void 0;
const typescript_1 = require("typescript");
class MetadataManager {
    constructor(content) {
        this.content = content;
    }
    insert(metadata, symbol, staticOptions) {
        const source = (0, typescript_1.createSourceFile)('filename.ts', this.content, typescript_1.ScriptTarget.ES2017);
        const moduleDecoratorNode = this.findFirstDecoratorMetadata(source, 'Module');
        if (!moduleDecoratorNode) {
            return;
        }
        const matchingProperties = moduleDecoratorNode.properties
            .filter((prop) => prop.kind === typescript_1.SyntaxKind.PropertyAssignment)
            .filter((prop) => {
            const name = prop.name;
            switch (name.kind) {
                case typescript_1.SyntaxKind.Identifier:
                    return name.getText(source) === metadata;
                case typescript_1.SyntaxKind.StringLiteral:
                    return name.text === metadata;
                default:
                    return false;
            }
        });
        symbol = this.mergeSymbolAndExpr(symbol, staticOptions);
        const addBlankLinesIfDynamic = () => {
            symbol = staticOptions ? this.addBlankLines(symbol) : symbol;
        };
        if (matchingProperties.length === 0) {
            const expr = moduleDecoratorNode;
            if (expr.properties.length === 0) {
                addBlankLinesIfDynamic();
                return this.insertMetadataToEmptyModuleDecorator(expr, metadata, symbol);
            }
            else {
                addBlankLinesIfDynamic();
                return this.insertNewMetadataToDecorator(expr, source, metadata, symbol);
            }
        }
        else {
            return this.insertSymbolToMetadata(source, matchingProperties, symbol, staticOptions);
        }
    }
    findFirstDecoratorMetadata(source, identifier) {
        for (const node of this.getSourceNodes(source)) {
            const isDecoratorFactoryNode = node.kind === typescript_1.SyntaxKind.Decorator &&
                node.expression.kind === typescript_1.SyntaxKind.CallExpression;
            if (!isDecoratorFactoryNode)
                continue;
            const expr = node.expression;
            const isExpectedExpression = expr.arguments[0]?.kind === typescript_1.SyntaxKind.ObjectLiteralExpression;
            if (!isExpectedExpression)
                continue;
            if (expr.expression.kind === typescript_1.SyntaxKind.Identifier) {
                const escapedText = expr.expression.escapedText;
                const isTargetIdentifier = escapedText
                    ? escapedText.toLowerCase() === identifier.toLowerCase()
                    : true;
                if (isTargetIdentifier) {
                    return expr.arguments[0];
                }
            }
        }
    }
    getSourceNodes(sourceFile) {
        const nodes = [sourceFile];
        const result = [];
        while (nodes.length > 0) {
            const node = nodes.shift();
            if (node) {
                result.push(node);
                if (node.getChildCount(sourceFile) >= 0) {
                    nodes.unshift(...node.getChildren(sourceFile));
                }
            }
        }
        return result;
    }
    insertMetadataToEmptyModuleDecorator(expr, metadata, symbol) {
        const position = expr.getEnd() - 1;
        const toInsert = `  ${metadata}: [${symbol}]`;
        return this.content.split('').reduce((content, char, index) => {
            if (index === position) {
                return `${content}\n${toInsert}\n${char}`;
            }
            else {
                return `${content}${char}`;
            }
        }, '');
    }
    insertNewMetadataToDecorator(expr, source, metadata, symbol) {
        const node = expr.properties[expr.properties.length - 1];
        const position = node.getEnd();
        const text = node.getFullText(source);
        const matches = text.match(/^\r?\n\s*/);
        let toInsert;
        if (matches) {
            toInsert = `,${matches[0]}${metadata}: [${symbol}]`;
        }
        else {
            toInsert = `, ${metadata}: [${symbol}]`;
        }
        return this.content.split('').reduce((content, char, index) => {
            if (index === position) {
                return `${content}${toInsert}${char}`;
            }
            else {
                return `${content}${char}`;
            }
        }, '');
    }
    insertSymbolToMetadata(source, matchingProperties, symbol, staticOptions) {
        const assignment = matchingProperties[0];
        let node;
        const arrLiteral = assignment.initializer;
        if (!arrLiteral.elements) {
            return this.content;
        }
        if (arrLiteral.elements.length === 0) {
            node = arrLiteral;
        }
        else {
            node = arrLiteral.elements;
        }
        if (Array.isArray(node)) {
            const nodeArray = node;
            const symbolsArray = nodeArray.map((childNode) => childNode.getText(source));
            if (symbolsArray.includes(symbol)) {
                return this.content;
            }
            node = node[node.length - 1];
        }
        let toInsert;
        let position = node.getEnd();
        if (node.kind === typescript_1.SyntaxKind.ArrayLiteralExpression) {
            position--;
            toInsert = staticOptions ? this.addBlankLines(symbol) : `${symbol}`;
        }
        else {
            const text = node.getFullText(source);
            const itemSeparator = (text.match(/^\r?\n(\r?)\s+/) ||
                text.match(/^\r?\n/) ||
                ' ')[0];
            toInsert = `,${itemSeparator}${symbol}`;
        }
        return this.content.split('').reduce((content, char, index) => {
            if (index === position) {
                return `${content}${toInsert}${char}`;
            }
            else {
                return `${content}${char}`;
            }
        }, '');
    }
    mergeSymbolAndExpr(symbol, staticOptions) {
        if (!staticOptions) {
            return symbol;
        }
        const spacing = 6;
        let options = JSON.stringify(staticOptions.value, null, spacing);
        options = options.replace(/\"([^(\")"]+)\":/g, '$1:');
        options = options.replace(/\"/g, `'`);
        options = options.slice(0, options.length - 1) + '    }';
        symbol += `.${staticOptions.name}(${options})`;
        return symbol;
    }
    addBlankLines(expr) {
        return `\n    ${expr}\n  `;
    }
}
exports.MetadataManager = MetadataManager;
