
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 { prepareQuery, pieceToQuery, scoreFuzzy2 } from '../../../../base/common/fuzzyScorer.js';
import { Disposable, DisposableStore, toDisposable, MutableDisposable } 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.service.js';
import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js';
import { localize } from '../../../../nls.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.service.js';
import { findLast } from '../../../../base/common/arraysFind.js';

var AbstractGotoSymbolQuickAccessProvider_1;
let AbstractGotoSymbolQuickAccessProvider = class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
    static {
        AbstractGotoSymbolQuickAccessProvider_1 = this;
    }
    static {
        this.PREFIX = "@";
    }
    static {
        this.SCOPE_PREFIX = ":";
    }
    static {
        this.PREFIX_BY_CATEGORY = `${this.PREFIX}${this.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, ( localize(
            1480,
            "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, ( localize(1481, "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, event.inBackground);
                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);
        const picksCts = disposables.add(( new MutableDisposable()));
        const updatePickerItems = async positionToEnclose => {
            picksCts?.value?.cancel();
            picker.busy = false;
            picksCts.value = ( new CancellationTokenSource());
            picker.busy = true;
            try {
                const query = prepareQuery(
                    picker.value.substr(AbstractGotoSymbolQuickAccessProvider_1.PREFIX.length).trim()
                );
                const items = await this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.value.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, ( localize(1482, "No matching editor symbols")));
                    } else {
                        this.provideLabelPick(picker, ( localize(1483, "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" ? ( localize(1484, "Open to the Side")) : ( localize(1485, "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: ( localize(1486, "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 = ( localize(1487, "properties ({0})"));
const NLS_SYMBOL_KIND_CACHE = {
    [SymbolKind.Method]: ( localize(1488, "methods ({0})")),
    [SymbolKind.Function]: ( localize(1489, "functions ({0})")),
    [SymbolKind.Constructor]: ( localize(1490, "constructors ({0})")),
    [SymbolKind.Variable]: ( localize(1491, "variables ({0})")),
    [SymbolKind.Class]: ( localize(1492, "classes ({0})")),
    [SymbolKind.Struct]: ( localize(1493, "structs ({0})")),
    [SymbolKind.Event]: ( localize(1494, "events ({0})")),
    [SymbolKind.Operator]: ( localize(1495, "operators ({0})")),
    [SymbolKind.Interface]: ( localize(1496, "interfaces ({0})")),
    [SymbolKind.Namespace]: ( localize(1497, "namespaces ({0})")),
    [SymbolKind.Package]: ( localize(1498, "packages ({0})")),
    [SymbolKind.TypeParameter]: ( localize(1499, "type parameters ({0})")),
    [SymbolKind.Module]: ( localize(1500, "modules ({0})")),
    [SymbolKind.Property]: ( localize(1487, "properties ({0})")),
    [SymbolKind.Enum]: ( localize(1501, "enumerations ({0})")),
    [SymbolKind.EnumMember]: ( localize(1502, "enumeration members ({0})")),
    [SymbolKind.String]: ( localize(1503, "strings ({0})")),
    [SymbolKind.File]: ( localize(1504, "files ({0})")),
    [SymbolKind.Array]: ( localize(1505, "arrays ({0})")),
    [SymbolKind.Number]: ( localize(1506, "numbers ({0})")),
    [SymbolKind.Boolean]: ( localize(1507, "booleans ({0})")),
    [SymbolKind.Object]: ( localize(1508, "objects ({0})")),
    [SymbolKind.Key]: ( localize(1509, "keys ({0})")),
    [SymbolKind.Field]: ( localize(1510, "fields ({0})")),
    [SymbolKind.Constant]: ( localize(1511, "constants ({0})"))
};

export { AbstractGotoSymbolQuickAccessProvider };
