"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Original code forked from https://github.com/Quramy/ts-graphql-plugin
Object.defineProperty(exports, "__esModule", { value: true });
const vscode = require("vscode-languageserver-types");
const embeddedSupport_1 = require("./embeddedSupport");
const emptyCompletionList = {
    isIncomplete: false,
    items: [],
};
class CompletionsCache {
    getCached(context, position) {
        if (this._completions
            && context.fileName === this._cachedCompletionsFile
            && this._cachedCompletionsPosition && arePositionsEqual(position, this._cachedCompletionsPosition)
            && context.text === this._cachedCompletionsContent) {
            return this._completions;
        }
        return undefined;
    }
    updateCached(context, position, completions) {
        this._cachedCompletionsFile = context.fileName;
        this._cachedCompletionsPosition = position;
        this._cachedCompletionsContent = context.text;
        this._completions = completions;
    }
}
class HtmlTemplateLanguageService {
    constructor(typescript, configuration, virtualDocumentProvider, htmlLanguageService, styledLanguageService, _logger) {
        this.typescript = typescript;
        this.configuration = configuration;
        this.virtualDocumentProvider = virtualDocumentProvider;
        this.htmlLanguageService = htmlLanguageService;
        this.styledLanguageService = styledLanguageService;
        this._completionsCache = new CompletionsCache();
    }
    getCompletionsAtPosition(context, position) {
        const entry = this.getCompletionItems(context, position);
        if (entry.type === 'styled') {
            return entry.value;
        }
        return translateCompletionItemsToCompletionInfo(this.typescript, context, entry.value);
    }
    getCompletionEntryDetails(context, position, name) {
        const entry = this.getCompletionItems(context, position);
        if (entry.type === 'styled') {
            return this.styledLanguageService.getCompletionEntryDetails(context, position, name);
        }
        const item = entry.value.items.find(x => x.label === name);
        if (!item) {
            return {
                name,
                kind: this.typescript.ScriptElementKind.unknown,
                kindModifiers: '',
                tags: [],
                displayParts: toDisplayParts(name),
                documentation: [],
            };
        }
        return translateCompletionItemsToCompletionEntryDetails(this.typescript, item);
    }
    getQuickInfoAtPosition(context, position) {
        const document = this.virtualDocumentProvider.createVirtualDocument(context);
        const documentRegions = embeddedSupport_1.getDocumentRegions(this.htmlLanguageService, document);
        const languageId = documentRegions.getLanguageAtPosition(position);
        switch (languageId) {
            case 'html':
                const htmlDoc = this.htmlLanguageService.parseHTMLDocument(document);
                const hover = this.htmlLanguageService.doHover(document, position, htmlDoc);
                return hover ? this.translateHover(hover, position, context) : undefined;
            case 'css':
                return this.styledLanguageService.getQuickInfoAtPosition(context, position);
        }
        return undefined;
    }
    getFormattingEditsForRange(context, start, end, settings) {
        if (!this.configuration.format.enabled) {
            return [];
        }
        // Disable formatting for blocks that contain a style tag
        //
        // Formatting styled blocks gets complex since we want to preserve interpolations inside the output
        // but we can't format content with `{` property.
        // The best fix would be to add `style` to `contentUnformatted` but
        // https://github.com/Microsoft/vscode-html-languageservice/issues/29 is causing problems and I'm not sure how
        // to work around it well
        if (context.text.match(/<style/g)) {
            return [];
        }
        const document = this.virtualDocumentProvider.createVirtualDocument(context);
        // Make sure we don't get rid of leading newline
        const leading = context.text.match(/^\s*\n/);
        if (leading) {
            start += leading[0].length;
        }
        // or any trailing newlines
        const trailing = context.text.match(/\n\s*$/);
        if (trailing) {
            end -= trailing[0].length;
        }
        if (end <= start) {
            return [];
        }
        const range = this.toVsRange(context, start, end);
        const edits = this.htmlLanguageService.format(document, range, {
            tabSize: settings.tabSize,
            insertSpaces: !!settings.convertTabsToSpaces,
            wrapLineLength: 120,
            unformatted: '',
            contentUnformatted: 'pre,code,textarea',
            indentInnerHtml: false,
            preserveNewLines: true,
            maxPreserveNewLines: undefined,
            indentHandlebars: false,
            endWithNewline: false,
            extraLiners: 'head, body, /html',
            wrapAttributes: 'auto',
        });
        return edits.map(vsedit => toTsTextChange(context, vsedit));
    }
    getSignatureHelpItemsAtPosition(_context, _position) {
        // Html does not support sig help
        return undefined;
    }
    getOutliningSpans(context) {
        const document = this.virtualDocumentProvider.createVirtualDocument(context);
        const ranges = this.htmlLanguageService.getFoldingRanges(document);
        return ranges.map(range => this.translateOutliningSpan(context, range));
    }
    getSemanticDiagnostics(context) {
        return this.styledLanguageService.getSemanticDiagnostics(context);
    }
    getSupportedCodeFixes() {
        return this.styledLanguageService.getSupportedCodeFixes();
    }
    getCodeFixesAtPosition(context, start, end, errorCodes, format) {
        return this.styledLanguageService.getCodeFixesAtPosition(context, start, end, errorCodes, format);
    }
    getReferencesAtPosition(context, position) {
        const document = this.virtualDocumentProvider.createVirtualDocument(context);
        const htmlDoc = this.htmlLanguageService.parseHTMLDocument(document);
        const highlights = this.htmlLanguageService.findDocumentHighlights(document, position, htmlDoc);
        return highlights.map(highlight => ({
            fileName: context.fileName,
            textSpan: toTsSpan(context, highlight.range),
        }));
    }
    getJsxClosingTagAtPosition(context, position) {
        const document = this.virtualDocumentProvider.createVirtualDocument(context);
        const htmlDocument = this.htmlLanguageService.parseHTMLDocument(document);
        const tagComplete = this.htmlLanguageService.doTagComplete(document, position, htmlDocument);
        if (!tagComplete) {
            return undefined;
        }
        // Html returns completions with snippet placeholders. Strip these out.
        return {
            newText: tagComplete.replace(/\$\d/g, '')
        };
    }
    toVsRange(context, start, end) {
        return {
            start: context.toPosition(start),
            end: context.toPosition(end),
        };
    }
    getCompletionItems(context, position) {
        const cached = this._completionsCache.getCached(context, position);
        if (cached) {
            return cached;
        }
        const document = this.virtualDocumentProvider.createVirtualDocument(context);
        const documentRegions = embeddedSupport_1.getDocumentRegions(this.htmlLanguageService, document);
        const languageId = documentRegions.getLanguageAtPosition(position);
        switch (languageId) {
            case 'html':
                {
                    const htmlDoc = this.htmlLanguageService.parseHTMLDocument(document);
                    const htmlCompletions = {
                        type: 'html',
                        value: this.htmlLanguageService.doComplete(document, position, htmlDoc) || emptyCompletionList,
                    };
                    this._completionsCache.updateCached(context, position, htmlCompletions);
                    return htmlCompletions;
                }
            case 'css':
                {
                    const styledCompletions = {
                        type: 'styled',
                        value: this.styledLanguageService.getCompletionsAtPosition(context, position),
                    };
                    this._completionsCache.updateCached(context, position, styledCompletions);
                    return styledCompletions;
                }
        }
        const completions = {
            type: 'html',
            value: emptyCompletionList,
        };
        this._completionsCache.updateCached(context, position, completions);
        return completions;
    }
    translateHover(hover, position, context) {
        const header = [];
        const docs = [];
        const convertPart = (hoverContents) => {
            if (typeof hoverContents === 'string') {
                docs.push({ kind: 'unknown', text: hoverContents });
            }
            else if (Array.isArray(hoverContents)) {
                hoverContents.forEach(convertPart);
            }
            else {
                header.push({ kind: 'unknown', text: hoverContents.value });
            }
        };
        convertPart(hover.contents);
        const start = context.toOffset(hover.range ? hover.range.start : position);
        return {
            kind: this.typescript.ScriptElementKind.string,
            kindModifiers: '',
            textSpan: {
                start,
                length: hover.range ? context.toOffset(hover.range.end) - start : 1,
            },
            displayParts: header,
            documentation: docs,
            tags: [],
        };
    }
    translateOutliningSpan(context, range) {
        const startOffset = context.toOffset({ line: range.startLine, character: range.startCharacter || 0 });
        const endOffset = context.toOffset({ line: range.endLine, character: range.endCharacter || 0 });
        const span = {
            start: startOffset,
            length: endOffset - startOffset,
        };
        return {
            autoCollapse: false,
            kind: this.typescript.OutliningSpanKind.Code,
            bannerText: '',
            textSpan: span,
            hintSpan: span,
        };
    }
}
exports.default = HtmlTemplateLanguageService;
function translateCompletionItemsToCompletionInfo(typescript, context, items) {
    return {
        isGlobalCompletion: false,
        isMemberCompletion: false,
        isNewIdentifierLocation: false,
        entries: items.items.map(x => translateCompetionEntry(typescript, context, x)),
    };
}
function translateCompletionItemsToCompletionEntryDetails(typescript, item) {
    return {
        name: item.label,
        kindModifiers: 'declare',
        kind: item.kind ? translateionCompletionItemKind(typescript, item.kind) : typescript.ScriptElementKind.unknown,
        displayParts: toDisplayParts(item.detail),
        documentation: toDisplayParts(item.documentation),
        tags: [],
    };
}
function translateCompetionEntry(typescript, context, vsItem) {
    const kind = vsItem.kind ? translateionCompletionItemKind(typescript, vsItem.kind) : typescript.ScriptElementKind.unknown;
    const entry = {
        name: vsItem.label,
        kind,
        sortText: '0',
    };
    if (vsItem.textEdit) {
        entry.insertText = vsItem.textEdit.newText;
        entry.replacementSpan = toTsSpan(context, vsItem.textEdit.range);
    }
    return entry;
}
function translateionCompletionItemKind(typescript, kind) {
    switch (kind) {
        case vscode.CompletionItemKind.Method:
            return typescript.ScriptElementKind.memberFunctionElement;
        case vscode.CompletionItemKind.Function:
            return typescript.ScriptElementKind.functionElement;
        case vscode.CompletionItemKind.Constructor:
            return typescript.ScriptElementKind.constructorImplementationElement;
        case vscode.CompletionItemKind.Field:
        case vscode.CompletionItemKind.Variable:
            return typescript.ScriptElementKind.variableElement;
        case vscode.CompletionItemKind.Class:
            return typescript.ScriptElementKind.classElement;
        case vscode.CompletionItemKind.Interface:
            return typescript.ScriptElementKind.interfaceElement;
        case vscode.CompletionItemKind.Module:
            return typescript.ScriptElementKind.moduleElement;
        case vscode.CompletionItemKind.Property:
            return typescript.ScriptElementKind.memberVariableElement;
        case vscode.CompletionItemKind.Unit:
        case vscode.CompletionItemKind.Value:
            return typescript.ScriptElementKind.constElement;
        case vscode.CompletionItemKind.Enum:
            return typescript.ScriptElementKind.enumElement;
        case vscode.CompletionItemKind.Keyword:
            return typescript.ScriptElementKind.keyword;
        case vscode.CompletionItemKind.Color:
            return typescript.ScriptElementKind.constElement;
        case vscode.CompletionItemKind.Reference:
            return typescript.ScriptElementKind.alias;
        case vscode.CompletionItemKind.File:
            return typescript.ScriptElementKind.moduleElement;
        case vscode.CompletionItemKind.Snippet:
        case vscode.CompletionItemKind.Text:
        default:
            return typescript.ScriptElementKind.unknown;
    }
}
function toDisplayParts(text) {
    if (!text) {
        return [];
    }
    return [{
            kind: 'text',
            text: typeof text === 'string' ? text : text.value,
        }];
}
function arePositionsEqual(left, right) {
    return left.line === right.line && left.character === right.character;
}
function toTsSpan(context, range) {
    const editStart = context.toOffset(range.start);
    const editEnd = context.toOffset(range.end);
    return {
        start: editStart,
        length: editEnd - editStart,
    };
}
function toTsTextChange(context, vsedit) {
    return {
        span: toTsSpan(context, vsedit.range),
        newText: vsedit.newText,
    };
}
//# sourceMappingURL=html-template-language-service.js.map