/**
 * The stuff from project that the frontend queries.
 * Stuff like autocomplete is a good example.
 * Errors etc are pushed automatically from `activeProject` and do not belong here :)
 */

import { Project } from "./core/project";
import * as activeProject from "./activeProject";
let getProject = activeProject.GetProject.ifCurrentOrErrorOut;

import { Types } from "../../../socket/socketContract";
import * as types from "../../../common/types";

import * as utils from "../../../common/utils";
let { resolve } = utils;
import * as fsu from "../../utils/fsu";
import { errorsCache } from "./cache/tsErrorsCache";
import { getPathCompletionsForAutocomplete } from "./modules/getPathCompletions";

export function getCompletionsAtPosition(query: Types.GetCompletionsAtPositionQuery): Promise<Types.GetCompletionsAtPositionResponse> {
    const { filePath, position, prefix } = query;
    const project = getProject(query.filePath);
    const service = project.languageService;
    const languageServiceHost = project.languageServiceHost;

    const completions: ts.CompletionInfo = service.getCompletionsAtPosition(filePath, position, undefined);
    let completionList = completions ? completions.entries.filter(x => !!x) : [];
    const endsInPunctuation = utils.prefixEndsInPunctuation(prefix);

    /** Doing too many suggestions is slowing us down in some cases */
    let maxSuggestions = 10000;

    // limit to maxSuggestions
    if (completionList.length > maxSuggestions) completionList = completionList.slice(0, maxSuggestions);

    let completionsToReturn: Types.Completion[] = completionList.map((c, index) => {
        return {
            name: c.name,
            kind: c.kind,
            comment: '',
            display: ''
        };
    });

    /**
     * Add function signature help
     */
    if (query.prefix == '(') {
        const signatures = service.getSignatureHelpItems(query.filePath, query.position);
        if (signatures && signatures.items) {
            signatures.items.forEach((item, index) => {
                const template: string = item.parameters.map((p, i) => {
                    const display = '{{' + (i + 1) + ':' + ts.displayPartsToString(p.displayParts) + '}}';
                    return display;
                }).join(ts.displayPartsToString(item.separatorDisplayParts));

                const name: string = item.parameters.map((p) => ts.displayPartsToString(p.displayParts))
                    .join(ts.displayPartsToString(item.separatorDisplayParts));

                // e.g. test(something:string):any;
                // prefix: test(
                // template: {{something}}
                // suffix: ): any;
                const description: string =
                    ts.displayPartsToString(item.prefixDisplayParts)
                    + template
                    + ts.displayPartsToString(item.suffixDisplayParts);

                completionsToReturn.unshift({
                    kind: types.completionKindSnippet,
                    /** We use `(sig)` prefix to make sure its sorted by monaco to be at the top */
                    name: '(sig) ' + name,
                    insertText: template,

                    display: 'function signature',
                    comment: `Overload ${index + 1} of ${signatures.items.length}`
                });
            });
        }
    }

    /**
     * Add file path completions
     */
    const pathCompletions = getPathCompletionsForAutocomplete({
        position,
        project,
        filePath,
        prefix
    });
    if (pathCompletions.length) {
        completionsToReturn = pathCompletions.map(f => {
            const result: types.Completion = {
                kind: types.completionKindPath,
                name: f.relativePath,
                display: f.fileName,
                comment: f.fullPath,

                textEdit: {
                    from: languageServiceHost.getLineAndCharacterOfPosition(filePath, f.pathStringRange.from),
                    to: languageServiceHost.getLineAndCharacterOfPosition(filePath, f.pathStringRange.to),
                    newText: f.relativePath
                }
            };
            return result;
        }).concat(completionsToReturn);
    }

    return resolve({
        completions: completionsToReturn,
        endsInPunctuation: endsInPunctuation
    });
}

export function getCompletionEntryDetails(query: Types.GetCompletionEntryDetailsQuery): Promise<Types.GetCompletionEntryDetailsResponse> {
    const project = getProject(query.filePath);
    const service = project.languageService;
    const { filePath, position, label } = query;

    const completionDetails = project.languageService.getCompletionEntryDetails(filePath, position, label, undefined, undefined);

    /**
     * For JS Projects, TS will add all sorts of globals as members (because it cannot know for sure)
     * However if you try to `getCompletionEntryDetails` for them, you will get `undefined`.
     */
    if (!completionDetails) return resolve({ display: label, comment: '' });

    const comment = ts.displayPartsToString(completionDetails.documentation || []);
    const display = ts.displayPartsToString(completionDetails.displayParts || []);

    const result = { display: display, comment: comment };
    return resolve(result);
}

export function quickInfo(query: Types.QuickInfoQuery): Promise<Types.QuickInfoResponse> {
    let project = getProject(query.filePath);
    const { languageServiceHost } = project;
    const errors = positionErrors({ filePath: query.filePath, position: query.position });
    var info = project.languageService.getQuickInfoAtPosition(query.filePath, query.position);
    if (!info && !errors.length) {
        return Promise.resolve({ valid: false });
    } else {
        return resolve({
            valid: true,
            info: info && {
                name: ts.displayPartsToString(info.displayParts || []),
                comment: ts.displayPartsToString(info.documentation || []),
                range: {
                    from: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, info.textSpan.start),
                    to: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, info.textSpan.start + info.textSpan.length),
                }
            },
            errors: errors
        });
    }
}

/** Utility */
function positionErrors(query: Types.FilePathPositionQuery): types.CodeError[] {
    let project = getProject(query.filePath);
    if (!project.includesSourceFile(query.filePath)) {
        return [];
    }

    let editorPos = project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, query.position);
    let errors = errorsCache.getErrorsForFilePath(query.filePath);
    errors = errors.filter(e =>
        // completely contained in the multiline
        (e.from.line < editorPos.line && e.to.line > editorPos.line)
        // error is single line and on the same line and characters match
        || (e.from.line == e.to.line && e.from.line == editorPos.line && e.from.ch <= editorPos.ch && e.to.ch >= editorPos.ch)
    );

    return errors;
}

export function getRenameInfo(query: Types.GetRenameInfoQuery): Promise<Types.GetRenameInfoResponse> {
    let project = getProject(query.filePath);
    var findInStrings = false, findInComments = false;
    var info = project.languageService.getRenameInfo(query.filePath, query.position);
    if (info && info.canRename) {
        var locations: { [filePath: string]: ts.TextSpan[] } = {};
        project.languageService.findRenameLocations(query.filePath, query.position, findInStrings, findInComments)
            .forEach(loc => {
                if (!locations[loc.fileName]) locations[loc.fileName] = [];

                // Using unshift makes them with maximum value on top ;)
                locations[loc.fileName].unshift(loc.textSpan);
            });
        return resolve({
            canRename: true,
            localizedErrorMessage: info.localizedErrorMessage,
            displayName: info.displayName,
            fullDisplayName: info.fullDisplayName,
            kind: info.kind,
            kindModifiers: info.kindModifiers,
            triggerSpan: info.triggerSpan,
            locations: locations
        });
    }
    else {
        return resolve({
            canRename: false
        });
    }
}

export function getDefinitionsAtPosition(query: Types.GetDefinitionsAtPositionQuery): Promise<Types.GetDefinitionsAtPositionResponse> {
    let project = getProject(query.filePath);
    var definitions = project.languageService.getDefinitionAtPosition(query.filePath, query.position);
    var projectFileDirectory = project.configFile.projectFileDirectory;
    if (!definitions || !definitions.length) return resolve({ projectFileDirectory: projectFileDirectory, definitions: [] });

    return resolve({
        projectFileDirectory: projectFileDirectory,
        definitions: definitions.map(d => {
            // If we can get the filename *we are in the same program :P*
            var pos = project.languageServiceHost.getLineAndCharacterOfPosition(d.fileName, d.textSpan.start);
            return {
                filePath: d.fileName,
                position: pos,
                span: d.textSpan,
            };
        })
    });
}

import { getLangHelp } from "./modules/langHelp";
export function getDoctorInfo(query: Types.GetDoctorInfoQuery): Promise<Types.GetDoctorInfoResponse> {
    let project = getProject(query.filePath);
    let filePath = query.filePath;
    let position = project.languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);

    // Get langHelp
    const program = project.languageService.getProgram();
    const sourceFile = program.getSourceFile(query.filePath);
    const positionNode = ts.getTokenAtPosition(sourceFile, position, true);
    const langHelp = getLangHelp(positionNode)

    // Just collect other responses
    let defPromised = getDefinitionsAtPosition({ filePath, position });
    let quickInfoPromised = quickInfo({ filePath, position: position });

    return defPromised.then((defRes) => {
        return quickInfoPromised.then((infoRes) => {
            return getReferences({ filePath, position }).then(refRes => {
                const valid = !!defRes.definitions.length || infoRes.valid || !!refRes.references.length || !!langHelp;
                return {
                    valid,
                    definitions: defRes.definitions,
                    quickInfo: infoRes.valid && infoRes.info.name ? {
                        name: infoRes.info.name,
                        comment: infoRes.info.comment
                    } : null,
                    langHelp,
                    references: refRes.references
                }
            });
        });
    });
}

export function getReferences(query: Types.GetReferencesQuery): Promise<Types.GetReferencesResponse> {
    let project = getProject(query.filePath);
    var languageService = project.languageService;

    var references: ReferenceDetails[] = [];
    var refs = languageService.getReferencesAtPosition(query.filePath, query.position) || [];

    references = refs.map(r => {
        const position = project.languageServiceHost.getLineAndCharacterOfPosition(r.fileName, r.textSpan.start);
        return { filePath: r.fileName, position: position, span: r.textSpan }
    });

    return resolve({
        references
    })
}

/**
 * Formatting
 */
import * as formatting from "./modules/formatting";
export function formatDocument(query: Types.FormatDocumentQuery): Promise<Types.FormatDocumentResponse> {
    let project = getProject(query.filePath);
    const { languageServiceHost, languageService } = project;

    let tsresult = formatting.formatDocument(project, query.filePath, query.editorOptions);
    const edits = tsresult.map(res => {
        const result: Types.FormattingEdit = {
            from: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start),
            to: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start + res.span.length),
            newText: res.newText
        };
        return result;
    });

    return resolve({ edits });
}
export function formatDocumentRange(query: Types.FormatDocumentRangeQuery): Promise<Types.FormatDocumentRangeResponse> {
    let project = getProject(query.filePath);
    const { languageServiceHost, languageService } = project;

    let tsresult = formatting.formatDocumentRange(project, query.filePath, query.from, query.to, query.editorOptions);
    const edits = tsresult.map(res => {
        const result: Types.FormattingEdit = {
            from: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start),
            to: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start + res.span.length),
            newText: res.newText
        };
        return result;
    });

    return resolve({ edits });
}

export function getFormattingEditsAfterKeystroke(query: Types.FormattingEditsAfterKeystrokeQuery): Promise<Types.FormattingEditsAfterKeystrokeResponse> {
    let project = getProject(query.filePath);
    const { languageServiceHost, languageService } = project;
    const position = languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
    const options = formatting.completeFormatCodeOptions(query.editorOptions, project.configFile.project.formatCodeOptions);

    const tsresult = languageService.getFormattingEditsAfterKeystroke(query.filePath, position, query.key, options) || [];
    const edits = tsresult.map(res => {
        const result: Types.FormattingEdit = {
            from: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start),
            to: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start + res.span.length),
            newText: res.newText
        };
        return result;
    });

    return resolve({ edits });
}

import { removeUnusedImports as removeUnusedImportsCore } from './modules/removeUnusedImports';
export function removeUnusedImports(query: Types.FilePathQuery): Promise<types.RefactoringsByFilePath> {
    let project = getProject(query.filePath);
    const { languageServiceHost, languageService } = project;
    return resolve(removeUnusedImportsCore(query.filePath, languageService));
}

/**
 * Symbol search
 */
//--------------------------------------------------------------------------
//  getNavigateToItems
//--------------------------------------------------------------------------

// Look at
// https://github.com/Microsoft/TypeScript/blob/master/src/services/navigateTo.ts
// for inspiration
// Reason for forking:
//  didn't give all results
//  gave results from lib.d.ts
//  I wanted the practice

function getSymbolsForFile(project: Project, sourceFile: ts.SourceFile): types.NavigateToItem[] {
    let getNodeKind = ts.getNodeKind;
    function getDeclarationName(declaration: ts.Declaration): string {
        let result = ts.getNameOfDeclaration(declaration);
        if (result === undefined) {
            return '';
        }
        return result.getText();
    }
    function getTextOfIdentifierOrLiteral(node: ts.Node) {
        if (node.kind === ts.SyntaxKind.Identifier ||
            node.kind === ts.SyntaxKind.StringLiteral ||
            node.kind === ts.SyntaxKind.NumericLiteral) {

            return (<ts.Identifier | ts.LiteralExpression>node).text;
        }

        return undefined;
    }

    var items: types.NavigateToItem[] = [];
    let declarations = sourceFile.getNamedDeclarations();
    declarations.forEach((value: ts.Declaration[], key: string) => {
        value.forEach(declaration => {
            let item: types.NavigateToItem = {
                name: getDeclarationName(declaration),
                kind: getNodeKind(declaration),
                filePath: sourceFile.fileName,
                fileName: utils.getFileName(sourceFile.fileName),
                position: project.languageServiceHost.getLineAndCharacterOfPosition(sourceFile.fileName, declaration.getStart())
            }
            items.push(item);
        })
    });
    return items;
}

export function getNavigateToItems(query: {}): Promise<types.GetNavigateToItemsResponse> {
    let project = activeProject.GetProject.getCurrentIfAny();
    var languageService = project.languageService;

    let items: types.NavigateToItem[] = [];
    for (let file of project.getProjectSourceFiles()) {
        getSymbolsForFile(project, file).forEach(i => items.push(i));
    }

    return utils.resolve({ items });
}

export function getNavigateToItemsForFilePath(query: { filePath: string }): Promise<types.GetNavigateToItemsResponse> {
    let project = activeProject.GetProject.getCurrentIfAny();
    var languageService = project.languageService;

    const file = project.getSourceFile(query.filePath)
    const items = getSymbolsForFile(project, file);

    return utils.resolve({ items });
}

/**
 * Dependency View
 */
import { getProgramDependencies } from "./modules/programDependencies";
export function getDependencies(query: {}): Promise<Types.GetDependenciesResponse> {
    let project = activeProject.GetProject.getCurrentIfAny();
    var links = getProgramDependencies(project.configFile, project.languageService.getProgram());
    return resolve({ links });
}

/**
 * AST View
 */
import { astToText, astToTextFull } from "./modules/astToText";
export function getAST(query: Types.GetASTQuery): Promise<Types.GetASTResponse> {
    let project = getProject(query.filePath);
    var service = project.languageService;

    var files = service.getProgram().getSourceFiles().filter(x => x.fileName == query.filePath);
    if (!files.length) resolve({});

    var sourceFile = files[0];

    let root = query.mode === Types.ASTMode.visitor
        ? astToText(sourceFile)
        : astToTextFull(sourceFile);

    return resolve({ root });
}


/**
 * JS Ouput
 */
import { getRawJsOutput } from "./modules/building";
export function getJSOutputStatus(query: Types.FilePathQuery, autoEmit = true): types.GetJSOutputStatusResponse {
    const project = activeProject.GetProject.ifCurrent(query.filePath);
    if (!project) {
        return {
            inActiveProject: false
        }
    }
    const jsFile = getRawJsOutput(project, query.filePath);

    /**
     * We just read/write from disk for now
     * Would be better if it interacted with master
     */
    const getContents = (filePath: string) => fsu.existsSync(filePath) ? fsu.readFile(filePath) : '';
    const setContents = fsu.writeFile;

    /**
     * Note: If we have compileOnSave as false then the output status isn't relevant
     */
    const noJsFile = (project.configFile.project.compileOnSave === false)
        || (project.configFile.inMemory === true)
        || !jsFile;

    let state
        = noJsFile
            ? types.JSOutputState.NoJSFile
            : getContents(jsFile.filePath) === jsFile.contents
                ? types.JSOutputState.JSUpToDate
                : types.JSOutputState.JSOutOfDate;

    /**
     * If the state is JSOutOfDate we can easily fix that to bring it up to date for `compileOnSave`
     */
    if (autoEmit && state === types.JSOutputState.JSOutOfDate && project.configFile.project.compileOnSave !== false) {
        setContents(jsFile.filePath, jsFile.contents);
        state = types.JSOutputState.JSUpToDate;
    }

    const outputStatus: types.JSOutputStatus = {
        inputFilePath: query.filePath,
        state,
        outputFilePath: jsFile && jsFile.filePath
    };

    return {
        inActiveProject: true,
        outputStatus
    };
}

/**
 * Get Quick Fix
 */
import { QuickFix, QuickFixQueryInformation } from "./quickFix/quickFix";
import * as qf from "./quickFix/quickFix";
import { allQuickFixes } from "./quickFix/quickFixRegistry";
function getDiagnositcsByFilePath(query: Types.FilePathQuery) {
    let project = getProject(query.filePath);
    var diagnostics = project.languageService.getSyntacticDiagnostics(query.filePath);
    if (diagnostics.length === 0) {
        diagnostics = project.languageService.getSemanticDiagnostics(query.filePath);
    }
    return diagnostics;
}
function getInfoForQuickFixAnalysis(query: Types.GetQuickFixesQuery): QuickFixQueryInformation {
    let project = getProject(query.filePath);
    let program = project.languageService.getProgram();
    let sourceFile = program.getSourceFile(query.filePath);
    let sourceFileText: string,
        fileErrors: ts.Diagnostic[],
        positionErrors: ts.Diagnostic[],
        positionErrorMessages: string[],
        positionNode: ts.Node;
    if (project.includesSourceFile(query.filePath)) {
        sourceFileText = sourceFile.getFullText();
        fileErrors = getDiagnositcsByFilePath(query);
        /** We want errors that are *touching* and thefore expand the query position by one */
        positionErrors = fileErrors.filter(e => ((e.start - 1) < query.position) && (e.start + e.length + 1) > query.position);
        positionErrorMessages = positionErrors.map(e => ts.flattenDiagnosticMessageText(e.messageText, '\n'));
        positionNode = ts.getTokenAtPosition(sourceFile, query.position, true);
    } else {
        sourceFileText = "";
        fileErrors = [];
        positionErrors = [];
        positionErrorMessages = [];
        positionNode = undefined;
    }

    let service = project.languageService;
    let typeChecker = program.getTypeChecker();

    return {
        project,
        program,
        sourceFile,
        sourceFileText,
        fileErrors,
        positionErrors,
        positionErrorMessages,
        position: query.position,
        positionNode,
        service,
        typeChecker,
        filePath: query.filePath,
        formatOptions: {
            indentSize: query.indentSize
        }
    };
}

const tsCodefixPrefix = 'CodeFix:';
export function getQuickFixes(query: Types.GetQuickFixesQuery): Promise<Types.GetQuickFixesResponse> {
    const project = getProject(query.filePath);
    const info = getInfoForQuickFixAnalysis(query);

    // We let the quickFix determine if it wants provide any fixes for this file
    const fixes = allQuickFixes
        .map(x => {
            var canProvide = x.canProvideFix(info);
            if (!canProvide)
                return;
            else
                return { key: x.key, display: canProvide.display };
        })
        .filter(x => !!x);

    /**
     * TS Code fixes
     * They comes with the `changes` on query. So we use that on `get` as well as `apply`
     */
    const tsCodeFixes = project.languageService.getCodeFixesAtPosition(query.filePath, query.position, query.position, info.positionErrors.map(e => e.code), info.formatOptions);
    if (tsCodeFixes.length) {
        tsCodeFixes.forEach((fix, i) => {
            fixes.unshift({
                key: `${tsCodefixPrefix}${i}`, display: fix.description
            })
        })
    }

    return resolve({ fixes });
}
export function applyQuickFix(query: Types.ApplyQuickFixQuery): Promise<Types.ApplyQuickFixResponse> {
    const info = getInfoForQuickFixAnalysis(query);

    /**
     * If TS Code fix
     */
    if (query.key.startsWith(tsCodefixPrefix)) {
        /** Find the code fix */
        let project = getProject(query.filePath);
        const tsCodeFixes = project.languageService.getCodeFixesAtPosition(query.filePath, query.position, query.position, info.positionErrors.map(e => e.code), info.formatOptions);
        const index = +query.key.substr(tsCodefixPrefix.length);
        const tsCodeFix = tsCodeFixes[index];

        /** Map code fix to refactoring */
        const refactorings: types.Refactoring[] = [];
        tsCodeFix.changes.forEach(change => {
            change.textChanges.forEach(tc => {
                const res: types.Refactoring = {
                    filePath: change.fileName,
                    newText: tc.newText,
                    span: tc.span
                };
                refactorings.push(res);
            });
        });

        return resolve({ refactorings: qf.getRefactoringsByFilePath(refactorings) });
    }
    const fix = allQuickFixes.filter(x => x.key == query.key)[0];
    const res = fix.provideFix(info);
    const refactorings = qf.getRefactoringsByFilePath(res);
    return resolve({ refactorings });
}

/**
 * Semantic Tree
 */
function sortNavbarItemsBySpan(items: ts.NavigationBarItem[]) {
    items.sort((a, b) => a.spans[0].start - b.spans[0].start);

    // sort children recursively
    for (let item of items) {
        if (item.childItems) {
            sortNavbarItemsBySpan(item.childItems);
        }
    }
}
function flattenNavBarItems(items: ts.NavigationBarItem[]): ts.NavigationBarItem[] {
    if (!items.length) return [];
    const root = items[0];

    /**
     * Where we store the final good ones
     */
    const results: ts.NavigationBarItem[] = [];

    /** Just to remove the dupes for different keys */
    const resultMapBig: { [key: string]: ts.NavigationBarItem } = Object.create(null);
    /**
     * The same items apprear with differnt indent, and different `spans`
     * But at least one span seems to match, hence this key(s) function
     */
    const getKeys = (item: ts.NavigationBarItem): string[] => {
        return item.spans.map(span => {
            return `${span.start}-${item.text}`
        });
    };


    /** This is used to unflatten the resulting map */
    const getParentMapKey = (item: ts.NavigationBarItem): string => {
        return `${item.spans[0].start}-${item.text}`
    };
    const parentMap: { [key: string]: ts.NavigationBarItem } = {};

    /**
     * First create a map of everything
     */
    const addToMap = (item: ts.NavigationBarItem, parent: ts.NavigationBarItem) => {
        const keys = getKeys(item);

        const previous = keys.some(key => !!resultMapBig[key]);
        // If we already have it no need to add it.
        // This is because the first time it gets added the parent then is the best one
        if (!previous) {
            keys.forEach(key => resultMapBig[key] = item);
            results.push(item);
            if (item !== root) {
                const parentMapKey = getParentMapKey(item)
                parentMap[parentMapKey] = parent;
            }
        }

        // Whatever is in the final map is the version we want to use for parent pointers
        const itemToUseAsParent = resultMapBig[getParentMapKey(item)];
        // Also visit all children
        item.childItems && item.childItems.forEach((child) => addToMap(child, itemToUseAsParent));

        // Now delete the childItems as they are supposed to be restored by `parentMap`
        delete item.childItems;
    }

    // Flatten into the map
    items.forEach(item => addToMap(item, root));

    // Now restore based on child pointers
    results.forEach(item => {
        if (item == root) return;

        const key = getParentMapKey(item);
        const parent = parentMap[key];

        if (!parent.childItems) parent.childItems = [];
        parent.childItems.push(item);
    });

    // Now we only need the children of the root :)
    return results[0].childItems || [];
}
function navigationBarItemToSemanticTreeNode(item: ts.NavigationBarItem, project: Project, query: Types.FilePathQuery): Types.SemanticTreeNode {
    let toReturn: Types.SemanticTreeNode = {
        text: item.text,
        kind: item.kind,
        kindModifiers: item.kindModifiers,
        start: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, item.spans[0].start),
        end: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, item.spans[0].start + item.spans[0].length),
        subNodes: item.childItems ? item.childItems.map(ci => navigationBarItemToSemanticTreeNode(ci, project, query)) : []
    }
    return toReturn;
}
export function getSemanticTree(query: Types.GetSemanticTreeQuery): Promise<Types.GetSemanticTreeReponse> {
    let project = getProject(query.filePath);

    let navBarItems = project.languageService.getNavigationBarItems(query.filePath);

    // The nav bar from the language service has nodes at various levels (with duplication)
    // We want a flat version
    navBarItems = flattenNavBarItems(navBarItems);

    // Sort items by first spans:
    sortNavbarItemsBySpan(navBarItems);

    // convert to SemanticTreeNodes
    let nodes = navBarItems.map(nbi => navigationBarItemToSemanticTreeNode(nbi, project, query));
    return resolve({ nodes });
}

/**
 * Document highlights
 */
export function getOccurrencesAtPosition(query: Types.GetOccurancesAtPositionQuery): Promise<Types.GetOccurancesAtPositionResponse> {
    let project = getProject(query.filePath);
    const { languageServiceHost } = project;
    const position = languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
    const tsresults = project.languageService.getOccurrencesAtPosition(query.filePath, position) || [];
    const results: Types.GetOccurancesAtPositionResult[] = tsresults.map(res => {
        const result: Types.GetOccurancesAtPositionResult = {
            filePath: res.fileName,
            isWriteAccess: res.isWriteAccess,
            start: project.languageServiceHost.getLineAndCharacterOfPosition(res.fileName, res.textSpan.start),
            end: project.languageServiceHost.getLineAndCharacterOfPosition(res.fileName, res.textSpan.start + res.textSpan.length),
        }
        return result;
    });
    return resolve({ results });
}
