import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { DeferredPromise } from '../../../../base/common/async.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { pieceToQuery, scoreFuzzy2, prepareQuery } from '../../../../base/common/fuzzyScorer.js';
import { Disposable, toDisposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { trim, format } from '../../../../base/common/strings.js';
import { Range } from '../../../common/core/range.js';
import { ScrollType } from '../../../common/editorCommon.js';
import { SymbolKind, SymbolKinds, SymbolTag, getAriaLabelForSymbol } from '../../../common/languages.js';
import { IOutlineModelService } from '../../documentSymbols/browser/outlineModel.js';
import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js';
import { localizeWithPath } from '../../../../nls.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { findLast } from '../../../../base/common/arraysFind.js';
var AbstractGotoSymbolQuickAccessProvider_1;
const _moduleId = "vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess";
let AbstractGotoSymbolQuickAccessProvider = class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
    static { AbstractGotoSymbolQuickAccessProvider_1 = this; }
    static { this.PREFIX = '@'; }
    static { this.SCOPE_PREFIX = ':'; }
    static { this.PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider_1.PREFIX}${AbstractGotoSymbolQuickAccessProvider_1.SCOPE_PREFIX}`; }
    constructor(_languageFeaturesService, _outlineModelService, options = Object.create(null)) {
        super(options);
        this._languageFeaturesService = _languageFeaturesService;
        this._outlineModelService = _outlineModelService;
        this.options = options;
        this.options.canAcceptInBackground = true;
    }
    provideWithoutTextEditor(picker) {
        this.provideLabelPick(picker, ( localizeWithPath(
            _moduleId,
            0,
            "To go to a symbol, first open a text editor with symbol information."
        )));
        return Disposable.None;
    }
    provideWithTextEditor(context, picker, token, runOptions) {
        const editor = context.editor;
        const model = this.getModel(editor);
        if (!model) {
            return Disposable.None;
        }
        if (( (this._languageFeaturesService.documentSymbolProvider.has(model)))) {
            return this.doProvideWithEditorSymbols(context, model, picker, token, runOptions);
        }
        return this.doProvideWithoutEditorSymbols(context, model, picker, token);
    }
    doProvideWithoutEditorSymbols(context, model, picker, token) {
        const disposables = ( (new DisposableStore()));
        this.provideLabelPick(picker, ( localizeWithPath(
            _moduleId,
            1,
            "The active text editor does not provide symbol information."
        )));
        (async () => {
            const result = await this.waitForLanguageSymbolRegistry(model, disposables);
            if (!result || token.isCancellationRequested) {
                return;
            }
            disposables.add(this.doProvideWithEditorSymbols(context, model, picker, token));
        })();
        return disposables;
    }
    provideLabelPick(picker, label) {
        picker.items = [{ label, index: 0, kind: SymbolKind.String }];
        picker.ariaLabel = label;
    }
    async waitForLanguageSymbolRegistry(model, disposables) {
        if (( (this._languageFeaturesService.documentSymbolProvider.has(model)))) {
            return true;
        }
        const symbolProviderRegistryPromise = ( (new DeferredPromise()));
        const symbolProviderListener = disposables.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => {
            if (( (this._languageFeaturesService.documentSymbolProvider.has(model)))) {
                symbolProviderListener.dispose();
                symbolProviderRegistryPromise.complete(true);
            }
        }));
        disposables.add(toDisposable(() => symbolProviderRegistryPromise.complete(false)));
        return symbolProviderRegistryPromise.p;
    }
    doProvideWithEditorSymbols(context, model, picker, token, runOptions) {
        const editor = context.editor;
        const disposables = ( (new DisposableStore()));
        disposables.add(picker.onDidAccept(event => {
            const [item] = picker.selectedItems;
            if (item && item.range) {
                this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground });
                runOptions?.handleAccept?.(item);
                if (!event.inBackground) {
                    picker.hide();
                }
            }
        }));
        disposables.add(picker.onDidTriggerItemButton(({ item }) => {
            if (item && item.range) {
                this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, forceSideBySide: true });
                picker.hide();
            }
        }));
        const symbolsPromise = this.getDocumentSymbols(model, token);
        let picksCts = undefined;
        const updatePickerItems = async (positionToEnclose) => {
            picksCts?.dispose(true);
            picker.busy = false;
            picksCts = ( (new CancellationTokenSource(token)));
            picker.busy = true;
            try {
                const query = prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider_1.PREFIX.length).trim());
                const items = await this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token, model);
                if (token.isCancellationRequested) {
                    return;
                }
                if (items.length > 0) {
                    picker.items = items;
                    if (positionToEnclose && query.original.length === 0) {
                        const candidate = findLast(items, item => Boolean(item.type !== 'separator' && item.range && Range.containsPosition(item.range.decoration, positionToEnclose)));
                        if (candidate) {
                            picker.activeItems = [candidate];
                        }
                    }
                }
                else {
                    if (query.original.length > 0) {
                        this.provideLabelPick(picker, ( localizeWithPath(_moduleId, 2, "No matching editor symbols")));
                    }
                    else {
                        this.provideLabelPick(picker, ( localizeWithPath(_moduleId, 3, "No editor symbols")));
                    }
                }
            }
            finally {
                if (!token.isCancellationRequested) {
                    picker.busy = false;
                }
            }
        };
        disposables.add(picker.onDidChangeValue(() => updatePickerItems(undefined)));
        updatePickerItems(editor.getSelection()?.getPosition());
        disposables.add(picker.onDidChangeActive(() => {
            const [item] = picker.activeItems;
            if (item && item.range) {
                editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth);
                this.addDecorations(editor, item.range.decoration);
            }
        }));
        return disposables;
    }
    async doGetSymbolPicks(symbolsPromise, query, options, token, model) {
        const symbols = await symbolsPromise;
        if (token.isCancellationRequested) {
            return [];
        }
        const filterBySymbolKind = query.original.indexOf(AbstractGotoSymbolQuickAccessProvider_1.SCOPE_PREFIX) === 0;
        const filterPos = filterBySymbolKind ? 1 : 0;
        let symbolQuery;
        let containerQuery;
        if (query.values && query.values.length > 1) {
            symbolQuery = pieceToQuery(query.values[0]);
            containerQuery = pieceToQuery(query.values.slice(1));
        }
        else {
            symbolQuery = query;
        }
        let buttons;
        const openSideBySideDirection = this.options?.openSideBySideDirection?.();
        if (openSideBySideDirection) {
            buttons = [{
                    iconClass: openSideBySideDirection === 'right' ? ThemeIcon.asClassName(Codicon.splitHorizontal) : ThemeIcon.asClassName(Codicon.splitVertical),
                    tooltip: openSideBySideDirection === 'right' ? ( localizeWithPath(_moduleId, 4, "Open to the Side")) : ( localizeWithPath(_moduleId, 5, "Open to the Bottom"))
                }];
        }
        const filteredSymbolPicks = [];
        for (let index = 0; index < symbols.length; index++) {
            const symbol = symbols[index];
            const symbolLabel = trim(symbol.name);
            const symbolLabelWithIcon = `$(${SymbolKinds.toIcon(symbol.kind).id}) ${symbolLabel}`;
            const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length;
            let containerLabel = symbol.containerName;
            if (options?.extraContainerLabel) {
                if (containerLabel) {
                    containerLabel = `${options.extraContainerLabel} • ${containerLabel}`;
                }
                else {
                    containerLabel = options.extraContainerLabel;
                }
            }
            let symbolScore = undefined;
            let symbolMatches = undefined;
            let containerScore = undefined;
            let containerMatches = undefined;
            if (query.original.length > filterPos) {
                let skipContainerQuery = false;
                if (symbolQuery !== query) {
                    [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, { ...query, values: undefined  }, filterPos, symbolLabelIconOffset);
                    if (typeof symbolScore === 'number') {
                        skipContainerQuery = true;
                    }
                }
                if (typeof symbolScore !== 'number') {
                    [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, filterPos, symbolLabelIconOffset);
                    if (typeof symbolScore !== 'number') {
                        continue;
                    }
                }
                if (!skipContainerQuery && containerQuery) {
                    if (containerLabel && containerQuery.original.length > 0) {
                        [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery);
                    }
                    if (typeof containerScore !== 'number') {
                        continue;
                    }
                    if (typeof symbolScore === 'number') {
                        symbolScore += containerScore;
                    }
                }
            }
            const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0;
            filteredSymbolPicks.push({
                index,
                kind: symbol.kind,
                score: symbolScore,
                label: symbolLabelWithIcon,
                ariaLabel: getAriaLabelForSymbol(symbol.name, symbol.kind),
                description: containerLabel,
                highlights: deprecated ? undefined : {
                    label: symbolMatches,
                    description: containerMatches
                },
                range: {
                    selection: Range.collapseToStart(symbol.selectionRange),
                    decoration: symbol.range
                },
                uri: model.uri,
                symbolName: symbolLabel,
                strikethrough: deprecated,
                buttons
            });
        }
        const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ?
            this.compareByKindAndScore(symbolA, symbolB) :
            this.compareByScore(symbolA, symbolB));
        let symbolPicks = [];
        if (filterBySymbolKind) {
            let lastSymbolKind = undefined;
            let lastSeparator = undefined;
            let lastSymbolKindCounter = 0;
            function updateLastSeparatorLabel() {
                if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) {
                    lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter);
                }
            }
            for (const symbolPick of sortedFilteredSymbolPicks) {
                if (lastSymbolKind !== symbolPick.kind) {
                    updateLastSeparatorLabel();
                    lastSymbolKind = symbolPick.kind;
                    lastSymbolKindCounter = 1;
                    lastSeparator = { type: 'separator' };
                    symbolPicks.push(lastSeparator);
                }
                else {
                    lastSymbolKindCounter++;
                }
                symbolPicks.push(symbolPick);
            }
            updateLastSeparatorLabel();
        }
        else if (sortedFilteredSymbolPicks.length > 0) {
            symbolPicks = [
                { label: ( localizeWithPath(_moduleId, 6, "symbols ({0})", filteredSymbolPicks.length)), type: 'separator' },
                ...sortedFilteredSymbolPicks
            ];
        }
        return symbolPicks;
    }
    compareByScore(symbolA, symbolB) {
        if (typeof symbolA.score !== 'number' && typeof symbolB.score === 'number') {
            return 1;
        }
        else if (typeof symbolA.score === 'number' && typeof symbolB.score !== 'number') {
            return -1;
        }
        if (typeof symbolA.score === 'number' && typeof symbolB.score === 'number') {
            if (symbolA.score > symbolB.score) {
                return -1;
            }
            else if (symbolA.score < symbolB.score) {
                return 1;
            }
        }
        if (symbolA.index < symbolB.index) {
            return -1;
        }
        else if (symbolA.index > symbolB.index) {
            return 1;
        }
        return 0;
    }
    compareByKindAndScore(symbolA, symbolB) {
        const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND;
        const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND;
        const result = kindA.localeCompare(kindB);
        if (result === 0) {
            return this.compareByScore(symbolA, symbolB);
        }
        return result;
    }
    async getDocumentSymbols(document, token) {
        const model = await this._outlineModelService.getOrCreate(document, token);
        return token.isCancellationRequested ? [] : model.asListOfDocumentSymbols();
    }
};
AbstractGotoSymbolQuickAccessProvider = AbstractGotoSymbolQuickAccessProvider_1 = ( (__decorate([
    ( (__param(0, ILanguageFeaturesService))),
    ( (__param(1, IOutlineModelService)))
], AbstractGotoSymbolQuickAccessProvider)));
const FALLBACK_NLS_SYMBOL_KIND = ( localizeWithPath(_moduleId, 7, "properties ({0})"));
const NLS_SYMBOL_KIND_CACHE = {
    [SymbolKind.Method]: ( localizeWithPath(_moduleId, 8, "methods ({0})")),
    [SymbolKind.Function]: ( localizeWithPath(_moduleId, 9, "functions ({0})")),
    [SymbolKind.Constructor]: ( localizeWithPath(_moduleId, 10, "constructors ({0})")),
    [SymbolKind.Variable]: ( localizeWithPath(_moduleId, 11, "variables ({0})")),
    [SymbolKind.Class]: ( localizeWithPath(_moduleId, 12, "classes ({0})")),
    [SymbolKind.Struct]: ( localizeWithPath(_moduleId, 13, "structs ({0})")),
    [SymbolKind.Event]: ( localizeWithPath(_moduleId, 14, "events ({0})")),
    [SymbolKind.Operator]: ( localizeWithPath(_moduleId, 15, "operators ({0})")),
    [SymbolKind.Interface]: ( localizeWithPath(_moduleId, 16, "interfaces ({0})")),
    [SymbolKind.Namespace]: ( localizeWithPath(_moduleId, 17, "namespaces ({0})")),
    [SymbolKind.Package]: ( localizeWithPath(_moduleId, 18, "packages ({0})")),
    [SymbolKind.TypeParameter]: ( localizeWithPath(_moduleId, 19, "type parameters ({0})")),
    [SymbolKind.Module]: ( localizeWithPath(_moduleId, 20, "modules ({0})")),
    [SymbolKind.Property]: ( localizeWithPath(_moduleId, 7, "properties ({0})")),
    [SymbolKind.Enum]: ( localizeWithPath(_moduleId, 21, "enumerations ({0})")),
    [SymbolKind.EnumMember]: ( localizeWithPath(_moduleId, 22, "enumeration members ({0})")),
    [SymbolKind.String]: ( localizeWithPath(_moduleId, 23, "strings ({0})")),
    [SymbolKind.File]: ( localizeWithPath(_moduleId, 24, "files ({0})")),
    [SymbolKind.Array]: ( localizeWithPath(_moduleId, 25, "arrays ({0})")),
    [SymbolKind.Number]: ( localizeWithPath(_moduleId, 26, "numbers ({0})")),
    [SymbolKind.Boolean]: ( localizeWithPath(_moduleId, 27, "booleans ({0})")),
    [SymbolKind.Object]: ( localizeWithPath(_moduleId, 28, "objects ({0})")),
    [SymbolKind.Key]: ( localizeWithPath(_moduleId, 29, "keys ({0})")),
    [SymbolKind.Field]: ( localizeWithPath(_moduleId, 30, "fields ({0})")),
    [SymbolKind.Constant]: ( localizeWithPath(_moduleId, 31, "constants ({0})"))
};
export { AbstractGotoSymbolQuickAccessProvider };
