"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const typescript_1 = require("typescript");
const flatten_1 = require("../common/flatten");
const dependencies_1 = require("../typescript/dependencies");
const traverseNode_1 = require("../typescript/traverseNode");
function parseTestMetadata(sourceFiles, 
// tslint:disable-next-line:ban-types
typedTestSymbol, typeChecker) {
    const testNodes = findAllTestNodes(sourceFiles, typedTestSymbol, typeChecker);
    const exportedTestIdentifiers = findExportedTestIndentifiers(testNodes);
    const exportedTestNames = exportedTestIdentifiers.map(x => x.exported);
    const testDeclarationNodes = testNodes.filter(isVariableDeclarationNodeSource(exportedTestNames));
    return testDeclarationNodes.map(findTestMetadataFromNodeSource(exportedTestIdentifiers, testNodes));
}
exports.parseTestMetadata = parseTestMetadata;
function isVariableDeclarationNodeSource(exportNames) {
    return (x) => typescript_1.isVariableDeclaration(x.node) && exportNames.includes(findIdentifingName(x.node));
}
function findTestMetadataFromNodeSource(testIdentifiers, testNodes) {
    return (nodeSource) => {
        const { node, sourceFile } = nodeSource;
        const statement = node.parent.parent;
        const declarationName = findVariableDeclarationName(node);
        const exportNames = testIdentifiers
            .filter(x => x.sourceFile.fileName === sourceFile.fileName && x.local === declarationName)
            .map(x => x.exported);
        const subTestNodes = testNodes.filter(findSubTests(nodeSource));
        const subTests = subTestNodes
            .map(findNodeMetadata)
            .sort(({ position: a }, { position: b }) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
        const meta = Object.assign({}, findNodeMetadata({ node: statement, sourceFile }), { exportNames, filePath: sourceFile.fileName, additionalTests: [] });
        return findAdditional(meta, subTests);
    };
}
function findAdditional(metadata, nodes) {
    const possibleTests = nodes
        .filter(x => isContainedBy(metadata, x))
        .sort(({ position: a }, { position: b }) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    if (possibleTests.length === 0) {
        return metadata;
    }
    const testsWithAddtional = possibleTests
        .map((x, i) => findAdditional(x, possibleTests.slice(i)))
        .filter(x => x.additionalTests.length > 0);
    const others = possibleTests
        .map((x, i) => findAdditional(x, possibleTests.slice(i)))
        .filter(x => !testsWithAddtional.some(y => isContainedBy(y, x)));
    const testsToUse = [
        ...others,
        ...testsWithAddtional.filter(x => !testsWithAddtional.some(y => y === x || isContainedBy(y, x))),
    ];
    return Object.assign({}, metadata, { additionalTests: testsToUse.sort(({ position: a }, { position: b }) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)) });
}
function isContainedBy(parent, child) {
    return parent.position[0] < child.position[0] && parent.position[1] > child.position[1];
}
function findSubTests({ node, sourceFile }) {
    return (x) => x.sourceFile === sourceFile &&
        x.node !== node &&
        x.node.getStart() > node.getStart() &&
        x.node.getEnd() < node.getEnd();
}
function findNodeMetadata({ node, sourceFile }) {
    const subPosition = [node.getStart(), node.getEnd()];
    const subLine = sourceFile.text.slice(0, subPosition[0]).split(/\n/g).length;
    const subText = node.getText();
    const subLines = subText.split(/\n/g).length;
    return {
        line: subLine,
        lines: subLines,
        position: subPosition,
        text: subText,
        additionalTests: [],
    };
}
function findAllTestNodes(sourceFiles, symbol, typeChecker) {
    return flatten_1.flatten(sourceFiles.map(sourceFile => {
        if (sourceFile.isDeclarationFile || !dependencies_1.hasExports(sourceFile)) {
            return [];
        }
        return traverseNode_1.traverseNode(findTestNodes(symbol, typeChecker), [], sourceFile).map(node => ({
            node,
            sourceFile,
        }));
    }));
}
function findExportedTestIndentifiers(nodes) {
    return nodes
        .filter(x => isExportedVariableDeclaration(x.node) || isExportedIdentifier(x.node))
        .map(x => typescript_1.isVariableDeclaration(x.node)
        ? {
            exported: findVariableDeclarationName(x.node),
            local: findVariableDeclarationName(x.node),
            sourceFile: x.sourceFile,
        }
        : Object.assign({}, findIdentifierName(x.node), { sourceFile: x.sourceFile }));
}
function isExportedVariableDeclaration(x) {
    return ((typescript_1.isVariableDeclaration(x) &&
        x.parent &&
        typescript_1.isVariableDeclarationList(x.parent) &&
        x.parent.parent &&
        typescript_1.isVariableStatement(x.parent.parent) &&
        x.parent.parent.modifiers &&
        x.parent.parent.modifiers.some(m => m.kind === typescript_1.SyntaxKind.ExportKeyword)) ||
        false);
}
function isExportedIdentifier(x) {
    if (typescript_1.isIdentifier(x) && x.parent) {
        if (typescript_1.isExportAssignment(x.parent) || typescript_1.isExportSpecifier(x.parent)) {
            return true;
        }
    }
    return false;
}
function findTestNodes(testSymbol, typeChecker) {
    return (testNodes, node) => {
        const types = tryGetTypes(node, typeChecker);
        if (!types) {
            return testNodes;
        }
        return types.map(tryGetSymbol).some(x => x === testSymbol) ? testNodes.concat(node) : testNodes;
    };
}
function findIdentifingName(x) {
    return typescript_1.isVariableDeclaration(x) ? findVariableDeclarationName(x) : findIdentifierName(x).exported;
}
function findIdentifierName(x) {
    if (x.parent && typescript_1.isExportAssignment(x.parent) && x.parent.getText().includes('export default ')) {
        return { exported: 'default', local: x.text };
    }
    return { exported: x.text, local: x.text };
}
function findVariableDeclarationName(x) {
    const nodes = dependencies_1.getNodes(x);
    for (const node of nodes) {
        if (typescript_1.isIdentifier(node)) {
            return node.text;
        }
    }
    if (typescript_1.isIdentifier(x.name)) {
        return x.name.text;
    }
    throw new Error('Unabled to find test name');
}
function tryGetTypes(node, typeChecker) {
    try {
        const type = typeChecker.getWidenedType(typeChecker.getTypeAtLocation(node));
        const types = [type];
        const baseTypes = type.getBaseTypes();
        if (baseTypes) {
            types.push(...baseTypes);
        }
        if (typescript_1.isCallLikeExpression(node)) {
            const returnType = typeChecker.getReturnTypeOfSignature(typeChecker.getResolvedSignature(node));
            types.push(returnType);
        }
        return types;
    }
    catch (_a) {
        return undefined;
    }
}
function tryGetSymbol(type) {
    if (!type) {
        return;
    }
    try {
        return type.getSymbol();
    }
    catch (_a) {
        return undefined;
    }
}
//# sourceMappingURL=parseTestMetadata.js.map