import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { append, $ as $$1 } from '../../../../base/browser/dom.js';
import { asArray, compareBy, numberComparator } from '../../../../base/common/arrays.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { MarkdownString, isEmptyMarkdownString } from '../../../../base/common/htmlContent.js';
import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js';
import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js';
import { Range } from '../../../common/core/range.js';
import { ILanguageService } from '../../../common/languages/language.js';
import { HoverAnchorType, RenderedHoverParts } from './hoverTypes.js';
import { localizeWithPath } from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.service.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.service.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { EditorOption } from '../../../common/config/editorOptions.js';
import { HoverVerbosityAction } from '../../../common/languages.js';
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { onUnexpectedExternalError } from '../../../../base/common/errors.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.service.js';
import { ClickAction, KeyDownAction, HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { KeyCode } from '../../../../base/common/keyCodes.js';
import { WorkbenchHoverDelegate } from '../../../../platform/hover/browser/hover.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.service.js';
import { AsyncIterableObject } from '../../../../base/common/async.js';
import { getHoverProviderResultsAsAsyncIterable } from './getHover.js';
import { ICommandService } from '../../../../platform/commands/common/commands.service.js';
const _moduleId = "vs/editor/contrib/hover/browser/markdownHoverParticipant";
const $ = $$1;
const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, ( localizeWithPath(_moduleId, 0, 'Icon for increaseing hover verbosity.')));
const decreaseHoverVerbosityIcon = registerIcon('hover-decrease-verbosity', Codicon.remove, ( localizeWithPath(_moduleId, 1, 'Icon for decreasing hover verbosity.')));
class MarkdownHover {
    constructor(owner, range, contents, isBeforeContent, ordinal, source = undefined) {
        this.owner = owner;
        this.range = range;
        this.contents = contents;
        this.isBeforeContent = isBeforeContent;
        this.ordinal = ordinal;
        this.source = source;
    }
    isValidForHoverAnchor(anchor) {
        return (anchor.type === HoverAnchorType.Range
            && this.range.startColumn <= anchor.range.startColumn
            && this.range.endColumn >= anchor.range.endColumn);
    }
}
class HoverSource {
    constructor(hover, hoverProvider, hoverPosition) {
        this.hover = hover;
        this.hoverProvider = hoverProvider;
        this.hoverPosition = hoverPosition;
    }
    supportsVerbosityAction(hoverVerbosityAction) {
        switch (hoverVerbosityAction) {
            case HoverVerbosityAction.Increase:
                return this.hover.canIncreaseVerbosity ?? false;
            case HoverVerbosityAction.Decrease:
                return this.hover.canDecreaseVerbosity ?? false;
        }
    }
}
let MarkdownHoverParticipant = class MarkdownHoverParticipant {
    constructor(_editor, _languageService, _openerService, _configurationService, _languageFeaturesService, _keybindingService, _hoverService, _commandService) {
        this._editor = _editor;
        this._languageService = _languageService;
        this._openerService = _openerService;
        this._configurationService = _configurationService;
        this._languageFeaturesService = _languageFeaturesService;
        this._keybindingService = _keybindingService;
        this._hoverService = _hoverService;
        this._commandService = _commandService;
        this.hoverOrdinal = 3;
    }
    createLoadingMessage(anchor) {
        return (
             (new MarkdownHover(
                this,
                anchor.range,
                [( (new MarkdownString())).appendText(( localizeWithPath(_moduleId, 2, "Loading...")))],
                false,
                2000
            ))
        );
    }
    computeSync(anchor, lineDecorations) {
        if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) {
            return [];
        }
        const model = this._editor.getModel();
        const lineNumber = anchor.range.startLineNumber;
        const maxColumn = model.getLineMaxColumn(lineNumber);
        const result = [];
        let index = 1000;
        const lineLength = model.getLineLength(lineNumber);
        const languageId = model.getLanguageIdAtPosition(anchor.range.startLineNumber, anchor.range.startColumn);
        const stopRenderingLineAfter = this._editor.getOption(EditorOption.stopRenderingLineAfter);
        const maxTokenizationLineLength = this._configurationService.getValue('editor.maxTokenizationLineLength', {
            overrideIdentifier: languageId
        });
        let stopRenderingMessage = false;
        if (stopRenderingLineAfter >= 0 && lineLength > stopRenderingLineAfter && anchor.range.startColumn >= stopRenderingLineAfter) {
            stopRenderingMessage = true;
            result.push(( (new MarkdownHover(this, anchor.range, [{
                    value: ( localizeWithPath(
                        _moduleId,
                        3,
                        "Rendering paused for long line for performance reasons. This can be configured via `editor.stopRenderingLineAfter`."
                    ))
                }], false, index++))));
        }
        if (!stopRenderingMessage && typeof maxTokenizationLineLength === 'number' && lineLength >= maxTokenizationLineLength) {
            result.push(( (new MarkdownHover(this, anchor.range, [{
                    value: ( localizeWithPath(
                        _moduleId,
                        4,
                        "Tokenization is skipped for long lines for performance reasons. This can be configured via `editor.maxTokenizationLineLength`."
                    ))
                }], false, index++))));
        }
        let isBeforeContent = false;
        for (const d of lineDecorations) {
            const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
            const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
            const hoverMessage = d.options.hoverMessage;
            if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) {
                continue;
            }
            if (d.options.beforeContentClassName) {
                isBeforeContent = true;
            }
            const range = ( (new Range(
                anchor.range.startLineNumber,
                startColumn,
                anchor.range.startLineNumber,
                endColumn
            )));
            result.push(( (new MarkdownHover(this, range, asArray(hoverMessage), isBeforeContent, index++))));
        }
        return result;
    }
    computeAsync(anchor, lineDecorations, token) {
        if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) {
            return AsyncIterableObject.EMPTY;
        }
        const model = this._editor.getModel();
        const hoverProviderRegistry = this._languageFeaturesService.hoverProvider;
        if (!( (hoverProviderRegistry.has(model)))) {
            return AsyncIterableObject.EMPTY;
        }
        const markdownHovers = this._getMarkdownHovers(hoverProviderRegistry, model, anchor, token);
        return markdownHovers;
    }
    _getMarkdownHovers(hoverProviderRegistry, model, anchor, token) {
        const position = anchor.range.getStartPosition();
        const hoverProviderResults = getHoverProviderResultsAsAsyncIterable(hoverProviderRegistry, model, position, token);
        const markdownHovers = ( (hoverProviderResults.filter(item => !isEmptyMarkdownString(item.hover.contents))
            .map(item => {
            const range = item.hover.range ? Range.lift(item.hover.range) : anchor.range;
            const hoverSource = ( (new HoverSource(item.hover, item.provider, position)));
            return (
                 (new MarkdownHover(this, range, item.hover.contents, false, item.ordinal, hoverSource))
            );
        })));
        return markdownHovers;
    }
    renderHoverParts(context, hoverParts) {
        this._renderedHoverParts = ( (new MarkdownRenderedHoverParts(
            hoverParts,
            context.fragment,
            this,
            this._editor,
            this._languageService,
            this._openerService,
            this._commandService,
            this._keybindingService,
            this._hoverService,
            this._configurationService,
            context.onContentsChanged
        )));
        return this._renderedHoverParts;
    }
    getAccessibleContent(hoverPart) {
        return this._renderedHoverParts?.getAccessibleContent(hoverPart) ?? '';
    }
    doesMarkdownHoverAtIndexSupportVerbosityAction(index, action) {
        return this._renderedHoverParts?.doesMarkdownHoverAtIndexSupportVerbosityAction(index, action) ?? false;
    }
    updateMarkdownHoverVerbosityLevel(action, index, focus) {
        return Promise.resolve(this._renderedHoverParts?.updateMarkdownHoverPartVerbosityLevel(action, index, focus));
    }
};
MarkdownHoverParticipant = ( (__decorate([
    ( (__param(1, ILanguageService))),
    ( (__param(2, IOpenerService))),
    ( (__param(3, IConfigurationService))),
    ( (__param(4, ILanguageFeaturesService))),
    ( (__param(5, IKeybindingService))),
    ( (__param(6, IHoverService))),
    ( (__param(7, ICommandService)))
], MarkdownHoverParticipant)));
class RenderedMarkdownHoverPart {
    constructor(hoverPart, hoverElement, disposables) {
        this.hoverPart = hoverPart;
        this.hoverElement = hoverElement;
        this.disposables = disposables;
    }
    get hoverAccessibleContent() {
        return this.hoverElement.innerText.trim();
    }
    dispose() {
        this.disposables.dispose();
    }
}
class MarkdownRenderedHoverParts {
    constructor(hoverParts, hoverPartsContainer, _hoverParticipant, _editor, _languageService, _openerService, _commandService, _keybindingService, _hoverService, _configurationService, _onFinishedRendering) {
        this._hoverParticipant = _hoverParticipant;
        this._editor = _editor;
        this._languageService = _languageService;
        this._openerService = _openerService;
        this._commandService = _commandService;
        this._keybindingService = _keybindingService;
        this._hoverService = _hoverService;
        this._configurationService = _configurationService;
        this._onFinishedRendering = _onFinishedRendering;
        this._ongoingHoverOperations = ( (new Map()));
        this._disposables = ( (new DisposableStore()));
        this.renderedHoverParts = this._renderHoverParts(hoverParts, hoverPartsContainer, this._onFinishedRendering);
        this._disposables.add(toDisposable(() => {
            this.renderedHoverParts.forEach(renderedHoverPart => {
                renderedHoverPart.dispose();
            });
            this._ongoingHoverOperations.forEach(operation => {
                operation.tokenSource.dispose(true);
            });
        }));
    }
    _renderHoverParts(hoverParts, hoverPartsContainer, onFinishedRendering) {
        hoverParts.sort(compareBy(hover => hover.ordinal, numberComparator));
        return (
             (hoverParts.map(hoverPart => {
                const renderedHoverPart = this._renderHoverPart(hoverPart, onFinishedRendering);
                hoverPartsContainer.appendChild(renderedHoverPart.hoverElement);
                return renderedHoverPart;
            }))
        );
    }
    _renderHoverPart(hoverPart, onFinishedRendering) {
        const renderedMarkdownPart = this._renderMarkdownHover(hoverPart, onFinishedRendering);
        const renderedMarkdownElement = renderedMarkdownPart.hoverElement;
        const hoverSource = hoverPart.source;
        const disposables = ( (new DisposableStore()));
        disposables.add(renderedMarkdownPart);
        if (!hoverSource) {
            return (
                 (new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables))
            );
        }
        const canIncreaseVerbosity = hoverSource.supportsVerbosityAction(HoverVerbosityAction.Increase);
        const canDecreaseVerbosity = hoverSource.supportsVerbosityAction(HoverVerbosityAction.Decrease);
        if (!canIncreaseVerbosity && !canDecreaseVerbosity) {
            return (
                 (new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables))
            );
        }
        const actionsContainer = $('div.verbosity-actions');
        renderedMarkdownElement.prepend(actionsContainer);
        disposables.add(this._renderHoverExpansionAction(actionsContainer, HoverVerbosityAction.Increase, canIncreaseVerbosity));
        disposables.add(this._renderHoverExpansionAction(actionsContainer, HoverVerbosityAction.Decrease, canDecreaseVerbosity));
        return (
             (new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables))
        );
    }
    _renderMarkdownHover(markdownHover, onFinishedRendering) {
        const renderedMarkdownHover = renderMarkdownInContainer(this._editor, markdownHover, this._languageService, this._openerService, onFinishedRendering);
        return renderedMarkdownHover;
    }
    _renderHoverExpansionAction(container, action, actionEnabled) {
        const store = ( (new DisposableStore()));
        const isActionIncrease = action === HoverVerbosityAction.Increase;
        const actionElement = append(container, $(ThemeIcon.asCSSSelector(isActionIncrease ? increaseHoverVerbosityIcon : decreaseHoverVerbosityIcon)));
        actionElement.tabIndex = 0;
        const hoverDelegate = ( (new WorkbenchHoverDelegate(
            'mouse',
            false,
            { target: container, position: { hoverPosition: HoverPosition.LEFT } },
            this._configurationService,
            this._hoverService
        )));
        store.add(this._hoverService.setupManagedHover(hoverDelegate, actionElement, labelForHoverVerbosityAction(this._keybindingService, action)));
        if (!actionEnabled) {
            actionElement.classList.add('disabled');
            return store;
        }
        actionElement.classList.add('enabled');
        const actionFunction = () => this._commandService.executeCommand(action === HoverVerbosityAction.Increase ? INCREASE_HOVER_VERBOSITY_ACTION_ID : DECREASE_HOVER_VERBOSITY_ACTION_ID);
        store.add(( (new ClickAction(actionElement, actionFunction))));
        store.add(( (new KeyDownAction(actionElement, actionFunction, [KeyCode.Enter, KeyCode.Space]))));
        return store;
    }
    async updateMarkdownHoverPartVerbosityLevel(action, index, focus = true) {
        const model = this._editor.getModel();
        if (!model) {
            return undefined;
        }
        const hoverRenderedPart = this._getRenderedHoverPartAtIndex(index);
        const hoverSource = hoverRenderedPart?.hoverPart.source;
        if (!hoverRenderedPart || !hoverSource?.supportsVerbosityAction(action)) {
            return undefined;
        }
        const newHover = await this._fetchHover(hoverSource, model, action);
        if (!newHover) {
            return undefined;
        }
        const newHoverSource = ( (new HoverSource(newHover, hoverSource.hoverProvider, hoverSource.hoverPosition)));
        const initialHoverPart = hoverRenderedPart.hoverPart;
        const newHoverPart = ( (new MarkdownHover(
            this._hoverParticipant,
            initialHoverPart.range,
            newHover.contents,
            initialHoverPart.isBeforeContent,
            initialHoverPart.ordinal,
            newHoverSource
        )));
        const newHoverRenderedPart = this._renderHoverPart(newHoverPart, this._onFinishedRendering);
        this._replaceRenderedHoverPartAtIndex(index, newHoverRenderedPart, newHoverPart);
        if (focus) {
            this._focusOnHoverPartWithIndex(index);
        }
        return {
            hoverPart: newHoverPart,
            hoverElement: newHoverRenderedPart.hoverElement
        };
    }
    getAccessibleContent(hoverPart) {
        const renderedHoverPartIndex = this.renderedHoverParts.findIndex(renderedHoverPart => renderedHoverPart.hoverPart === hoverPart);
        if (renderedHoverPartIndex === -1) {
            return undefined;
        }
        const renderedHoverPart = this._getRenderedHoverPartAtIndex(renderedHoverPartIndex);
        if (!renderedHoverPart) {
            return undefined;
        }
        const hoverElementInnerText = renderedHoverPart.hoverElement.innerText;
        const accessibleContent = hoverElementInnerText.replace(/[^\S\n\r]+/gu, ' ');
        return accessibleContent;
    }
    doesMarkdownHoverAtIndexSupportVerbosityAction(index, action) {
        const hoverRenderedPart = this._getRenderedHoverPartAtIndex(index);
        const hoverSource = hoverRenderedPart?.hoverPart.source;
        if (!hoverRenderedPart || !hoverSource?.supportsVerbosityAction(action)) {
            return false;
        }
        return true;
    }
    async _fetchHover(hoverSource, model, action) {
        let verbosityDelta = action === HoverVerbosityAction.Increase ? 1 : -1;
        const provider = hoverSource.hoverProvider;
        const ongoingHoverOperation = this._ongoingHoverOperations.get(provider);
        if (ongoingHoverOperation) {
            ongoingHoverOperation.tokenSource.cancel();
            verbosityDelta += ongoingHoverOperation.verbosityDelta;
        }
        const tokenSource = ( (new CancellationTokenSource()));
        this._ongoingHoverOperations.set(provider, { verbosityDelta, tokenSource });
        const context = { verbosityRequest: { verbosityDelta, previousHover: hoverSource.hover } };
        let hover;
        try {
            hover = await Promise.resolve(provider.provideHover(model, hoverSource.hoverPosition, tokenSource.token, context));
        }
        catch (e) {
            onUnexpectedExternalError(e);
        }
        tokenSource.dispose();
        this._ongoingHoverOperations.delete(provider);
        return hover;
    }
    _replaceRenderedHoverPartAtIndex(index, renderedHoverPart, hoverPart) {
        if (index >= this.renderedHoverParts.length || index < 0) {
            return;
        }
        const currentRenderedHoverPart = this.renderedHoverParts[index];
        const currentRenderedMarkdown = currentRenderedHoverPart.hoverElement;
        const renderedMarkdown = renderedHoverPart.hoverElement;
        const renderedChildrenElements = Array.from(renderedMarkdown.children);
        currentRenderedMarkdown.replaceChildren(...renderedChildrenElements);
        const newRenderedHoverPart = ( (new RenderedMarkdownHoverPart(hoverPart, currentRenderedMarkdown, renderedHoverPart.disposables)));
        currentRenderedMarkdown.focus();
        currentRenderedHoverPart.dispose();
        this.renderedHoverParts[index] = newRenderedHoverPart;
    }
    _focusOnHoverPartWithIndex(index) {
        this.renderedHoverParts[index].hoverElement.focus();
    }
    _getRenderedHoverPartAtIndex(index) {
        return this.renderedHoverParts[index];
    }
    dispose() {
        this._disposables.dispose();
    }
}
function renderMarkdownHovers(context, markdownHovers, editor, languageService, openerService) {
    markdownHovers.sort(compareBy(hover => hover.ordinal, numberComparator));
    const renderedHoverParts = [];
    for (const markdownHover of markdownHovers) {
        renderedHoverParts.push(renderMarkdownInContainer(editor, markdownHover, languageService, openerService, context.onContentsChanged));
    }
    return (
         (new RenderedHoverParts(renderedHoverParts))
    );
}
function renderMarkdownInContainer(editor, markdownHover, languageService, openerService, onFinishedRendering) {
    const disposables = ( (new DisposableStore()));
    const renderedMarkdown = $('div.hover-row');
    const renderedMarkdownContents = $('div.hover-row-contents');
    renderedMarkdown.appendChild(renderedMarkdownContents);
    const markdownStrings = markdownHover.contents;
    for (const markdownString of markdownStrings) {
        if (isEmptyMarkdownString(markdownString)) {
            continue;
        }
        const markdownHoverElement = $('div.markdown-hover');
        const hoverContentsElement = append(markdownHoverElement, $('div.hover-contents'));
        const renderer = disposables.add(( (new MarkdownRenderer({ editor }, languageService, openerService))));
        disposables.add(renderer.onDidRenderAsync(() => {
            hoverContentsElement.className = 'hover-contents code-hover-contents';
            onFinishedRendering();
        }));
        const renderedContents = disposables.add(renderer.render(markdownString));
        hoverContentsElement.appendChild(renderedContents.element);
        renderedMarkdownContents.appendChild(markdownHoverElement);
    }
    const renderedHoverPart = {
        hoverPart: markdownHover,
        hoverElement: renderedMarkdown,
        dispose() { disposables.dispose(); }
    };
    return renderedHoverPart;
}
function labelForHoverVerbosityAction(keybindingService, action) {
    switch (action) {
        case HoverVerbosityAction.Increase: {
            const kb = keybindingService.lookupKeybinding(INCREASE_HOVER_VERBOSITY_ACTION_ID);
            return kb ?
                ( localizeWithPath(_moduleId, 5, "Increase Hover Verbosity ({0})", kb.getLabel())) :
                ( localizeWithPath(_moduleId, 6, "Increase Hover Verbosity"));
        }
        case HoverVerbosityAction.Decrease: {
            const kb = keybindingService.lookupKeybinding(DECREASE_HOVER_VERBOSITY_ACTION_ID);
            return kb ?
                ( localizeWithPath(_moduleId, 7, "Decrease Hover Verbosity ({0})", kb.getLabel())) :
                ( localizeWithPath(_moduleId, 8, "Decrease Hover Verbosity"));
        }
    }
}
export { MarkdownHover, MarkdownHoverParticipant, labelForHoverVerbosityAction, renderMarkdownHovers };
