"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isMemberIgniteUI = exports.getTypeDefinitionAtPosition = exports.createProjectService = exports.getLanguageService = exports.replaceMatch = exports.findMatches = exports.getRenamePositions = exports.getImportModulePositions = exports.getIdentifierPositions = exports.NG_LANG_SERVICE_PACKAGE_NAME = exports.IG_PACKAGE_NAME = void 0;
// eslint-disable-next-line import/no-extraneous-dependencies
const ts = require("typescript");
const tss = require("typescript/lib/tsserverlibrary");
const util_1 = require("./util");
const tsLogger_1 = require("./tsLogger");
exports.IG_PACKAGE_NAME = 'igniteui-angular';
exports.NG_LANG_SERVICE_PACKAGE_NAME = '@angular/language-service';
/** Returns an source file */
// export function getFileSource(sourceText: string): ts.SourceFile {
//     return ts.createSourceFile('', sourceText, ts.ScriptTarget.Latest, true);
// }
exports.getIdentifierPositions = (sourceText, name) => {
    const source = ts.createSourceFile('', sourceText, ts.ScriptTarget.Latest, true);
    const positions = [];
    const checkIdentifier = (node) => {
        if (node.kind !== ts.SyntaxKind.Identifier || !node.parent) {
            return false;
        }
        if (node.parent.kind === ts.SyntaxKind.PropertyDeclaration) {
            return false;
        }
        if (node.parent.kind === ts.SyntaxKind.PropertyAssignment ||
            node.parent.kind === ts.SyntaxKind.PropertySignature) {
            // make sure it's not prop assign  `= { IgxClass: "fake"}`
            //                  definition `prop: { IgxClass: string; }`
            //                                     name: initializer
            const propAssign = node.parent;
            if (propAssign.name.getText() === name) {
                return false;
            }
        }
        return node.text === name;
    };
    const findIdentifiers = (node) => {
        if (checkIdentifier(node)) {
            // Use `.getStart()` as node.pos includes the space(s) before the identifier text
            positions.push({ start: node.getStart(), end: node.end });
        }
        ts.forEachChild(node, findIdentifiers);
    };
    source.forEachChild(findIdentifiers);
    return positions;
};
/** Returns the positions of import from module string literals  */
exports.getImportModulePositions = (sourceText, startsWith) => {
    const source = ts.createSourceFile('', sourceText, ts.ScriptTarget.Latest, true);
    const positions = [];
    for (const statement of source.statements) {
        if (statement.kind === ts.SyntaxKind.ImportDeclaration) {
            const specifier = statement.moduleSpecifier;
            if (specifier.text.startsWith(startsWith)) {
                // string literal pos will include quotes, trim with 1
                positions.push({ start: specifier.getStart() + 1, end: specifier.end - 1 });
            }
        }
    }
    return positions;
};
/** Filters out statements to named imports (e.g. `import {x, y}`) from PACKAGE_IMPORT */
const namedImportFilter = (statement) => {
    if (statement.kind === ts.SyntaxKind.ImportDeclaration &&
        statement.moduleSpecifier.text.endsWith(exports.IG_PACKAGE_NAME)) {
        const clause = statement.importClause;
        return clause && clause.namedBindings && clause.namedBindings.kind === ts.SyntaxKind.NamedImports;
    }
    return false;
};
exports.getRenamePositions = (sourcePath, name, service) => {
    const source = service.getProgram().getSourceFile(sourcePath);
    const positions = [];
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    const imports = source.statements.filter(namedImportFilter);
    if (!imports.length) {
        return positions;
    }
    const elements = imports
        .map(x => x.importClause.namedBindings.elements)
        .reduce((prev, current) => prev.concat(current));
    for (const elem of elements) {
        if (elem.propertyName && elem.propertyName.text === name) {
            // alias imports `igxClass as smth` -> <propertyName> as <name>
            // other references are only for the name portion
            positions.push({ start: elem.propertyName.getStart(), end: elem.propertyName.getEnd() });
            break;
        }
        if (!elem.propertyName && elem.name.text === name) {
            const renames = service.findRenameLocations(sourcePath, elem.name.getStart(), false, false, false);
            if (renames) {
                const renamesPos = renames.map(x => ({ start: x.textSpan.start, end: x.textSpan.start + x.textSpan.length }));
                positions.push(...renamesPos);
            }
        }
    }
    return positions;
};
exports.findMatches = (content, change) => {
    let matches;
    const regex = new RegExp(util_1.escapeRegExp(change.member), 'g');
    const matchesPositions = [];
    do {
        matches = regex.exec(content);
        if (matches) {
            matchesPositions.push(matches.index);
        }
    } while (matches);
    return matchesPositions;
};
exports.replaceMatch = (content, toReplace, replaceWith, index) => content.substring(0, index) +
    replaceWith +
    content.substring(index + toReplace.length, content.length);
//#region Language Service
/**
 * Create a TypeScript language service
 *
 * @param serviceHost A TypeScript language service host
 */
exports.getLanguageService = (filePaths, host, options = {}) => {
    const fileVersions = new Map();
    patchHostOverwrite(host, fileVersions);
    const servicesHost = {
        getCompilationSettings: () => options,
        getScriptFileNames: () => filePaths,
        getScriptVersion: fileName => {
            // return host.actions.filter(x => x.path === fileName && x.kind !== 'c').length.toString();
            const version = fileVersions.get(fileName) || 0;
            return version.toString();
        },
        getScriptSnapshot: fileName => {
            if (!host.exists(fileName)) {
                return undefined;
            }
            return ts.ScriptSnapshot.fromString(host.read(fileName).toString());
        },
        getCurrentDirectory: () => process.cwd(),
        getDefaultLibFileName: opts => ts.getDefaultLibFilePath(opts),
        fileExists: fileName => filePaths.indexOf(fileName) !== -1
    };
    return ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
};
const patchHostOverwrite = (host, fileVersions) => {
    const original = host.overwrite;
    host.overwrite = (path, content) => {
        const version = fileVersions.get(path) || 0;
        fileVersions.set(path, version + 1);
        original.call(host, path, content);
    };
};
/**
 * Create a project service singleton that holds all projects within a directory tree
 *
 * @param serverHost Used by the tss to navigate the directory tree
 */
exports.createProjectService = (serverHost) => {
    // set traceToConsole to true to enable logging
    const logger = new tsLogger_1.Logger(false, tss.server.LogLevel.verbose);
    const projectService = new tss.server.ProjectService({
        host: serverHost,
        logger,
        /* not needed since we will run only migrations */
        cancellationToken: tss.server.nullCancellationToken,
        /* do not allow more than one InferredProject per project root */
        useSingleInferredProject: true,
        useInferredProjectPerProjectRoot: true,
        /* will load only global plug-ins */
        globalPlugins: [exports.NG_LANG_SERVICE_PACKAGE_NAME],
        allowLocalPluginLoads: false,
        typingsInstaller: tss.server.nullTypingsInstaller
    });
    projectService.setHostConfiguration({
        formatOptions: projectService.getHostFormatCodeOptions(),
        extraFileExtensions: [
            {
                extension: '.html',
                isMixedContent: false,
                scriptKind: tss.ScriptKind.External,
            }
        ]
    });
    projectService.configurePlugin({
        pluginName: exports.NG_LANG_SERVICE_PACKAGE_NAME,
        configuration: {
            angularOnly: false,
        },
    });
    return projectService;
};
/**
 * Get type information about a TypeScript identifier
 *
 * @param langServ TypeScript/Angular LanguageService
 * @param entryPath path to file
 * @param position Index of identifier
 */
exports.getTypeDefinitionAtPosition = (langServ, entryPath, position) => {
    var _a;
    const definition = (_a = langServ.getDefinitionAndBoundSpan(entryPath, position)) === null || _a === void 0 ? void 0 : _a.definitions[0];
    if (!definition) {
        return null;
    }
    // if the definition's kind is a reference, the identifier is a template variable referred in an internal/external template
    if (definition.kind.toString() === 'reference') {
        return langServ.getDefinitionAndBoundSpan(entryPath, definition.textSpan.start).definitions[0];
    }
    let typeDefs = langServ.getTypeDefinitionAtPosition(entryPath, definition.textSpan.start);
    // if there are no type definitions found, the identifier is a ts property, referred in an internal/external template
    // or is a reference in a decorator
    if (!typeDefs) {
        /*
         normally, the tsserver will consider non .ts files as external to the project
         however, we load .html files which we can handle with the Angular language service
         here we're only looking for definitions in a .ts source file
         we call the getSourceFile function which accesses a map of files, previously loaded by the tsserver
         at this point the map contains all .html files that we've included
         we have to ignore them, since the language service will attempt to parse them as .ts files
        */
        if (!definition.fileName.endsWith('.ts')) {
            return null;
        }
        const sourceFile = langServ.getProgram().getSourceFile(definition.fileName);
        if (!sourceFile) {
            return null;
        }
        const classDeclaration = sourceFile
            .statements
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            .filter((m => m.kind === tss.SyntaxKind.ClassDeclaration))
            .find(m => m.name.getText() === definition.containerName);
        // there must be at least one class declaration in the .ts file and the property must belong to it
        if (!classDeclaration) {
            return null;
        }
        const member = classDeclaration.members.find(m => m.name.getText() === definition.name);
        if (!(member === null || member === void 0 ? void 0 : member.name)) {
            return null;
        }
        typeDefs = langServ.getTypeDefinitionAtPosition(definition.fileName, member.name.getStart() + 1);
    }
    if (typeDefs === null || typeDefs === void 0 ? void 0 : typeDefs.length) {
        return typeDefs[0];
    }
    return null;
};
exports.isMemberIgniteUI = (change, langServ, entryPath, matchPosition) => {
    const typeDef = exports.getTypeDefinitionAtPosition(langServ, entryPath, matchPosition - 1);
    return !typeDef ? false : typeDef.fileName.includes(exports.IG_PACKAGE_NAME) && change.definedIn.indexOf(typeDef.name) !== -1;
};
//#endregion
