"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleKindModifiers = exports.register = void 0;
const semver = __importStar(require("semver"));
const getUserPreferences_1 = require("../../configs/getUserPreferences");
const PConst = __importStar(require("../../protocol.const"));
const modifiers_1 = require("../../utils/modifiers");
const shared_1 = require("../../shared");
function register(ctx) {
    const { ts } = ctx;
    const lt_320 = semver.lt(ts.version, '3.2.0');
    const gte_300 = semver.gte(ts.version, '3.0.0');
    return async (uri, position, options) => {
        const document = ctx.getTextDocument(uri);
        if (!document)
            return;
        const preferences = await (0, getUserPreferences_1.getUserPreferences)(ctx, document);
        const fileName = ctx.env.uriToFileName(document.uri);
        const offset = document.offsetAt(position);
        const completionContext = (0, shared_1.safeCall)(() => ctx.typescript.languageService.getCompletionsAtPosition(fileName, offset, {
            ...preferences,
            ...options,
        }));
        if (completionContext === undefined)
            return;
        const wordRange = completionContext.optionalReplacementSpan ? {
            start: document.positionAt(completionContext.optionalReplacementSpan.start),
            end: document.positionAt(completionContext.optionalReplacementSpan.start + completionContext.optionalReplacementSpan.length),
        } : undefined;
        let line = document.getText({
            start: { line: position.line, character: 0 },
            end: { line: position.line + 1, character: 0 },
        });
        if (line.endsWith('\n')) {
            line = line.substring(0, line.length - 1);
        }
        const dotAccessorContext = getDotAccessorContext(document);
        const entries = completionContext.entries
            .map(tsEntry => toVScodeItem(tsEntry, document));
        return {
            isIncomplete: !!completionContext.isIncomplete,
            items: entries,
        };
        function toVScodeItem(tsEntry, document) {
            const item = { label: tsEntry.name };
            item.kind = convertKind(tsEntry.kind);
            if (tsEntry.source && tsEntry.hasAction) {
                // De-prioritize auto-imports
                // https://github.com/microsoft/vscode/issues/40311
                item.sortText = '\uffff' + tsEntry.sortText;
            }
            else {
                item.sortText = tsEntry.sortText;
            }
            const { sourceDisplay, isSnippet, labelDetails } = tsEntry;
            if (sourceDisplay) {
                item.labelDetails ??= {};
                item.labelDetails.description = ts.displayPartsToString(sourceDisplay);
            }
            if (labelDetails) {
                item.labelDetails ??= {};
                Object.assign(item.labelDetails, labelDetails);
            }
            item.preselect = tsEntry.isRecommended;
            let range = getRangeFromReplacementSpan(tsEntry, document);
            item.commitCharacters = getCommitCharacters(tsEntry, {
                isNewIdentifierLocation: completionContext.isNewIdentifierLocation,
                isInValidCommitCharacterContext: isInValidCommitCharacterContext(document, position),
                enableCallCompletions: true, // TODO: suggest.completeFunctionCalls
            });
            item.insertText = tsEntry.insertText;
            item.insertTextFormat = isSnippet ? 2 : 1;
            item.filterText = getFilterText(tsEntry, wordRange, line, tsEntry.insertText);
            if (completionContext?.isMemberCompletion && dotAccessorContext && !isSnippet) {
                item.filterText = dotAccessorContext.text + (item.insertText || item.label);
                if (!range) {
                    const replacementRange = wordRange;
                    if (replacementRange) {
                        range = {
                            inserting: dotAccessorContext.range,
                            replacing: rangeUnion(dotAccessorContext.range, replacementRange),
                        };
                    }
                    else {
                        range = dotAccessorContext.range;
                    }
                    item.insertText = item.filterText;
                }
            }
            handleKindModifiers(item, tsEntry);
            if (!range && wordRange) {
                range = {
                    inserting: { start: wordRange.start, end: position },
                    replacing: wordRange,
                };
            }
            if (range) {
                if ('start' in range) {
                    item.textEdit = {
                        range,
                        newText: item.insertText || item.label,
                    };
                }
                else {
                    item.textEdit = {
                        insert: range.inserting,
                        replace: range.replacing,
                        newText: item.insertText || item.label,
                    };
                }
            }
            return {
                ...item,
                data: {
                    uri,
                    fileName,
                    offset,
                    originalItem: {
                        name: tsEntry.name,
                        source: tsEntry.source,
                        data: tsEntry.data,
                        labelDetails: tsEntry.labelDetails,
                    },
                },
            };
        }
        function getDotAccessorContext(document) {
            let dotAccessorContext;
            if (gte_300) {
                if (!completionContext)
                    return;
                const isMemberCompletion = completionContext.isMemberCompletion;
                if (isMemberCompletion) {
                    const dotMatch = line.slice(0, position.character).match(/\??\.\s*$/) || undefined;
                    if (dotMatch) {
                        const range = {
                            start: { line: position.line, character: position.character - dotMatch[0].length },
                            end: position,
                        };
                        const text = document.getText(range);
                        dotAccessorContext = { range, text };
                    }
                }
            }
            return dotAccessorContext;
        }
        // from vscode typescript
        function getRangeFromReplacementSpan(tsEntry, document) {
            if (!tsEntry.replacementSpan) {
                return;
            }
            let replaceRange = {
                start: document.positionAt(tsEntry.replacementSpan.start),
                end: document.positionAt(tsEntry.replacementSpan.start + tsEntry.replacementSpan.length),
            };
            // Make sure we only replace a single line at most
            if (replaceRange.start.line !== replaceRange.end.line) {
                replaceRange = {
                    start: {
                        line: replaceRange.start.line,
                        character: replaceRange.start.character,
                    },
                    end: {
                        line: replaceRange.start.line,
                        character: document.positionAt(document.offsetAt({ line: replaceRange.start.line + 1, character: 0 }) - 1).character,
                    },
                };
            }
            // If TS returns an explicit replacement range, we should use it for both types of completion
            return {
                inserting: replaceRange,
                replacing: replaceRange,
            };
        }
        function getFilterText(tsEntry, wordRange, line, insertText) {
            // Handle private field completions
            if (tsEntry.name.startsWith('#')) {
                const wordStart = wordRange ? line.charAt(wordRange.start.character) : undefined;
                if (insertText) {
                    if (insertText.startsWith('this.#')) {
                        return wordStart === '#' ? insertText : insertText.replace(/^this\.#/, '');
                    }
                    else {
                        return insertText;
                    }
                }
                else {
                    return wordStart === '#' ? undefined : tsEntry.name.replace(/^#/, '');
                }
            }
            // For `this.` completions, generally don't set the filter text since we don't want them to be overly prioritized. #74164
            if (insertText?.startsWith('this.')) {
                return undefined;
            }
            // Handle the case:
            // ```
            // const xyz = { 'ab c': 1 };
            // xyz.ab|
            // ```
            // In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of
            // the bracketed insert text.
            else if (insertText?.startsWith('[')) {
                return insertText.replace(/^\[['"](.+)[['"]\]$/, '.$1');
            }
            // In all other cases, fallback to using the insertText
            return insertText;
        }
        function convertKind(kind) {
            switch (kind) {
                case PConst.Kind.primitiveType:
                case PConst.Kind.keyword:
                    return 14;
                case PConst.Kind.const:
                case PConst.Kind.let:
                case PConst.Kind.variable:
                case PConst.Kind.localVariable:
                case PConst.Kind.alias:
                case PConst.Kind.parameter:
                    return 6;
                case PConst.Kind.memberVariable:
                case PConst.Kind.memberGetAccessor:
                case PConst.Kind.memberSetAccessor:
                    return 5;
                case PConst.Kind.function:
                case PConst.Kind.localFunction:
                    return 3;
                case PConst.Kind.method:
                case PConst.Kind.constructSignature:
                case PConst.Kind.callSignature:
                case PConst.Kind.indexSignature:
                    return 2;
                case PConst.Kind.enum:
                    return 13;
                case PConst.Kind.enumMember:
                    return 20;
                case PConst.Kind.module:
                case PConst.Kind.externalModuleName:
                    return 9;
                case PConst.Kind.class:
                case PConst.Kind.type:
                    return 7;
                case PConst.Kind.interface:
                    return 8;
                case PConst.Kind.warning:
                    return 1;
                case PConst.Kind.script:
                    return 17;
                case PConst.Kind.directory:
                    return 19;
                case PConst.Kind.string:
                    return 21;
                default:
                    return 10;
            }
        }
        function getCommitCharacters(entry, context) {
            if (entry.kind === PConst.Kind.warning) { // Ambient JS word based suggestion
                return undefined;
            }
            if (context.isNewIdentifierLocation || !context.isInValidCommitCharacterContext) {
                return undefined;
            }
            const commitCharacters = ['.', ',', ';'];
            if (context.enableCallCompletions) {
                commitCharacters.push('(');
            }
            return commitCharacters;
        }
        function isInValidCommitCharacterContext(document, position) {
            if (lt_320) {
                // Workaround for https://github.com/microsoft/TypeScript/issues/27742
                // Only enable dot completions when the previous character is not a dot preceded by whitespace.
                // Prevents incorrectly completing while typing spread operators.
                if (position.character > 1) {
                    const preText = document.getText({
                        start: { line: position.line, character: 0 },
                        end: position,
                    });
                    return preText.match(/(\s|^)\.$/ig) === null;
                }
            }
            return true;
        }
    };
}
exports.register = register;
function handleKindModifiers(item, tsEntry) {
    if (tsEntry.kindModifiers) {
        const kindModifiers = (0, modifiers_1.parseKindModifier)(tsEntry.kindModifiers);
        if (kindModifiers.has(PConst.KindModifiers.optional)) {
            if (!item.insertText) {
                item.insertText = item.label;
            }
            if (!item.filterText) {
                item.filterText = item.label;
            }
            item.label += '?';
        }
        if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
            item.tags = [1];
        }
        if (kindModifiers.has(PConst.KindModifiers.color)) {
            item.kind = 16;
        }
        if (tsEntry.kind === PConst.Kind.script) {
            for (const extModifier of PConst.KindModifiers.fileExtensionKindModifiers) {
                if (kindModifiers.has(extModifier)) {
                    if (tsEntry.name.toLowerCase().endsWith(extModifier)) {
                        item.detail = tsEntry.name;
                    }
                    else {
                        item.detail = tsEntry.name + extModifier;
                    }
                    break;
                }
            }
        }
    }
}
exports.handleKindModifiers = handleKindModifiers;
function rangeUnion(a, b) {
    const start = (a.start.line < b.start.line || (a.start.line === b.start.line && a.start.character < b.start.character)) ? a.start : b.start;
    const end = (a.end.line > b.end.line || (a.end.line === b.end.line && a.end.character > b.end.character)) ? a.end : b.end;
    return { start, end };
}
//# sourceMappingURL=basic.js.map