import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import './media/languageStatus.css.js';
import { getWindow, addDisposableListener, append } from '../../../../base/browser/dom.js';
import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
import { DisposableStore, dispose, toDisposable, Disposable } from '../../../../base/common/lifecycle.js';
import Severity$1 from '../../../../base/common/severity.js';
import { getCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { localizeWithPath, localize2WithPath } from '../../../../nls.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { Extensions } from '../../../common/contributions.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { ILanguageStatusService } from '../../../services/languageStatus/common/languageStatusService.js';
import { ShowTooltipCommand, IStatusbarService } from '../../../services/statusbar/browser/statusbar.js';
import { parseLinkedText } from '../../../../base/common/linkedText.js';
import { Link } from '../../../../platform/opener/browser/link.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { MarkdownString } from '../../../../base/common/htmlContent.js';
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
import { Action } from '../../../../base/common/actions.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { IStorageService } from '../../../../platform/storage/common/storage.js';
import { equals } from '../../../../base/common/arrays.js';
import { URI } from '../../../../base/common/uri.js';
import { registerAction2, Action2 } from '../../../../platform/actions/common/actions.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
import { nativeHoverDelegate } from '../../../../platform/hover/browser/hover.js';
var LanguageStatus_1;
class LanguageStatusViewModel {
    constructor(combined, dedicated) {
        this.combined = combined;
        this.dedicated = dedicated;
    }
    isEqual(other) {
        return equals(this.combined, other.combined) && equals(this.dedicated, other.dedicated);
    }
}
let StoredCounter = class StoredCounter {
    constructor(_storageService, _key) {
        this._storageService = _storageService;
        this._key = _key;
    }
    get value() {
        return this._storageService.getNumber(this._key, 0 , 0);
    }
    increment() {
        const n = this.value + 1;
        this._storageService.store(this._key, n, 0 , 1 );
        return n;
    }
};
StoredCounter = ( __decorate([
    ( __param(0, IStorageService))
], StoredCounter));
let LanguageStatusContribution = class LanguageStatusContribution extends Disposable {
    constructor(instantiationService, editorGroupService, editorService) {
        super();
        const mainInstantiationService = instantiationService.createChild(( new ServiceCollection([IEditorService, editorService.createScoped('main', this._store)])));
        this._register(mainInstantiationService.createInstance(LanguageStatus));
        this._register(editorGroupService.onDidCreateAuxiliaryEditorPart(({ instantiationService, disposables }) => {
            disposables.add(instantiationService.createInstance(LanguageStatus));
        }));
    }
};
LanguageStatusContribution = ( __decorate([
    ( __param(0, IInstantiationService)),
    ( __param(1, IEditorGroupsService)),
    ( __param(2, IEditorService))
], LanguageStatusContribution));
let LanguageStatus = class LanguageStatus {
    static { LanguageStatus_1 = this; }
    static { this._id = 'status.languageStatus'; }
    static { this._keyDedicatedItems = 'languageStatus.dedicated'; }
    constructor(_languageStatusService, _statusBarService, _editorService, _openerService, _storageService) {
        this._languageStatusService = _languageStatusService;
        this._statusBarService = _statusBarService;
        this._editorService = _editorService;
        this._openerService = _openerService;
        this._storageService = _storageService;
        this._disposables = ( new DisposableStore());
        this._dedicated = ( new Set());
        this._dedicatedEntries = ( new Map());
        this._renderDisposables = ( new DisposableStore());
        _storageService.onDidChangeValue(0 , LanguageStatus_1._keyDedicatedItems, this._disposables)(this._handleStorageChange, this, this._disposables);
        this._restoreState();
        this._interactionCounter = ( new StoredCounter(_storageService, 'languageStatus.interactCount'));
        _languageStatusService.onDidChange(this._update, this, this._disposables);
        _editorService.onDidActiveEditorChange(this._update, this, this._disposables);
        this._update();
        _statusBarService.onDidChangeEntryVisibility(e => {
            if (!e.visible && ( this._dedicated.has(e.id))) {
                this._dedicated.delete(e.id);
                this._update();
                this._storeState();
            }
        }, this._disposables);
    }
    dispose() {
        this._disposables.dispose();
        this._combinedEntry?.dispose();
        dispose(( this._dedicatedEntries.values()));
        this._renderDisposables.dispose();
    }
    _handleStorageChange() {
        this._restoreState();
        this._update();
    }
    _restoreState() {
        const raw = this._storageService.get(LanguageStatus_1._keyDedicatedItems, 0 , '[]');
        try {
            const ids = JSON.parse(raw);
            this._dedicated = ( new Set(ids));
        }
        catch {
            this._dedicated.clear();
        }
    }
    _storeState() {
        if (this._dedicated.size === 0) {
            this._storageService.remove(LanguageStatus_1._keyDedicatedItems, 0 );
        }
        else {
            const raw = JSON.stringify(Array.from(( this._dedicated.keys())));
            this._storageService.store(LanguageStatus_1._keyDedicatedItems, raw, 0 , 0 );
        }
    }
    _createViewModel(editor) {
        if (!editor?.hasModel()) {
            return ( new LanguageStatusViewModel([], []));
        }
        const all = this._languageStatusService.getLanguageStatus(editor.getModel());
        const combined = [];
        const dedicated = [];
        for (const item of all) {
            if (( this._dedicated.has(item.id))) {
                dedicated.push(item);
            }
            combined.push(item);
        }
        return ( new LanguageStatusViewModel(combined, dedicated));
    }
    _update() {
        const editor = getCodeEditor(this._editorService.activeTextEditorControl);
        const model = this._createViewModel(editor);
        if (this._model?.isEqual(model)) {
            return;
        }
        this._renderDisposables.clear();
        this._model = model;
        editor?.onDidChangeModelLanguage(this._update, this, this._renderDisposables);
        if (model.combined.length === 0) {
            this._combinedEntry?.dispose();
            this._combinedEntry = undefined;
        }
        else {
            const [first] = model.combined;
            const showSeverity = first.severity >= Severity$1.Warning;
            const text = LanguageStatus_1._severityToComboCodicon(first.severity);
            let isOneBusy = false;
            const ariaLabels = [];
            const element = document.createElement('div');
            for (const status of model.combined) {
                const isPinned = model.dedicated.includes(status);
                element.appendChild(this._renderStatus(status, showSeverity, isPinned, this._renderDisposables));
                ariaLabels.push(LanguageStatus_1._accessibilityInformation(status).label);
                isOneBusy = isOneBusy || (!isPinned && status.busy);
            }
            const props = {
                name: ( localizeWithPath(
                    'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                    'langStatus.name',
                    "Editor Language Status"
                )),
                ariaLabel: ( localizeWithPath(
                    'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                    'langStatus.aria',
                    "Editor Language Status: {0}",
                    ariaLabels.join(', next: ')
                )),
                tooltip: element,
                command: ShowTooltipCommand,
                text: isOneBusy ? `${text}\u00A0\u00A0$(sync~spin)` : text,
            };
            if (!this._combinedEntry) {
                this._combinedEntry = this._statusBarService.addEntry(props, LanguageStatus_1._id, 1 , { id: 'status.editor.mode', alignment: 0 , compact: true });
            }
            else {
                this._combinedEntry.update(props);
            }
            const userHasInteractedWithStatus = this._interactionCounter.value >= 3;
            const targetWindow = getWindow(editor?.getContainerDomNode());
            const node = targetWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus A>SPAN.codicon');
            const container = targetWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus');
            if (node instanceof HTMLElement && container) {
                const _wiggle = 'wiggle';
                const _flash = 'flash';
                if (!isOneBusy) {
                    node.classList.toggle(_wiggle, showSeverity || !userHasInteractedWithStatus);
                    this._renderDisposables.add(addDisposableListener(node, 'animationend', _e => node.classList.remove(_wiggle)));
                    container.classList.toggle(_flash, showSeverity);
                    this._renderDisposables.add(addDisposableListener(container, 'animationend', _e => container.classList.remove(_flash)));
                }
                else {
                    node.classList.remove(_wiggle);
                    container.classList.remove(_flash);
                }
            }
            if (!userHasInteractedWithStatus) {
                const hoverTarget = targetWindow.document.querySelector('.monaco-workbench .context-view');
                if (hoverTarget instanceof HTMLElement) {
                    const observer = ( new MutationObserver(() => {
                        if (targetWindow.document.contains(element)) {
                            this._interactionCounter.increment();
                            observer.disconnect();
                        }
                    }));
                    observer.observe(hoverTarget, { childList: true, subtree: true });
                    this._renderDisposables.add(toDisposable(() => observer.disconnect()));
                }
            }
        }
        const newDedicatedEntries = ( new Map());
        for (const status of model.dedicated) {
            const props = LanguageStatus_1._asStatusbarEntry(status);
            let entry = this._dedicatedEntries.get(status.id);
            if (!entry) {
                entry = this._statusBarService.addEntry(props, status.id, 1 , { id: 'status.editor.mode', alignment: 1  });
            }
            else {
                entry.update(props);
                this._dedicatedEntries.delete(status.id);
            }
            newDedicatedEntries.set(status.id, entry);
        }
        dispose(( this._dedicatedEntries.values()));
        this._dedicatedEntries = newDedicatedEntries;
    }
    _renderStatus(status, showSeverity, isPinned, store) {
        const parent = document.createElement('div');
        parent.classList.add('hover-language-status');
        const severity = document.createElement('div');
        severity.classList.add('severity', `sev${status.severity}`);
        severity.classList.toggle('show', showSeverity);
        const severityText = LanguageStatus_1._severityToSingleCodicon(status.severity);
        append(severity, ...renderLabelWithIcons(severityText));
        parent.appendChild(severity);
        const element = document.createElement('div');
        element.classList.add('element');
        parent.appendChild(element);
        const left = document.createElement('div');
        left.classList.add('left');
        element.appendChild(left);
        const label = document.createElement('span');
        label.classList.add('label');
        const labelValue = typeof status.label === 'string' ? status.label : status.label.value;
        append(label, ...renderLabelWithIcons(status.busy ? `$(sync~spin)\u00A0\u00A0${labelValue}` : labelValue));
        left.appendChild(label);
        const detail = document.createElement('span');
        detail.classList.add('detail');
        this._renderTextPlus(detail, status.detail, store);
        left.appendChild(detail);
        const right = document.createElement('div');
        right.classList.add('right');
        element.appendChild(right);
        const { command } = status;
        if (command) {
            store.add(( new Link(right, {
                label: command.title,
                title: command.tooltip,
                href: ( ( URI.from({
                    scheme: 'command', path: command.id, query: command.arguments && JSON.stringify(command.arguments)
                })).toString())
            }, { hoverDelegate: nativeHoverDelegate }, this._openerService)));
        }
        const actionBar = ( new ActionBar(right, { hoverDelegate: nativeHoverDelegate }));
        store.add(actionBar);
        let action;
        if (!isPinned) {
            action = ( new Action('pin', ( localizeWithPath(
                'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                'pin',
                "Add to Status Bar"
            )), ThemeIcon.asClassName(Codicon.pin), true, () => {
                this._dedicated.add(status.id);
                this._statusBarService.updateEntryVisibility(status.id, true);
                this._update();
                this._storeState();
            }));
        }
        else {
            action = ( new Action('unpin', ( localizeWithPath(
                'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                'unpin',
                "Remove from Status Bar"
            )), ThemeIcon.asClassName(Codicon.pinned), true, () => {
                this._dedicated.delete(status.id);
                this._statusBarService.updateEntryVisibility(status.id, false);
                this._update();
                this._storeState();
            }));
        }
        actionBar.push(action, { icon: true, label: false });
        store.add(action);
        return parent;
    }
    static _severityToComboCodicon(sev) {
        switch (sev) {
            case Severity$1.Error: return '$(bracket-error)';
            case Severity$1.Warning: return '$(bracket-dot)';
            default: return '$(bracket)';
        }
    }
    static _severityToSingleCodicon(sev) {
        switch (sev) {
            case Severity$1.Error: return '$(error)';
            case Severity$1.Warning: return '$(info)';
            default: return '$(check)';
        }
    }
    _renderTextPlus(target, text, store) {
        for (const node of parseLinkedText(text).nodes) {
            if (typeof node === 'string') {
                const parts = renderLabelWithIcons(node);
                append(target, ...parts);
            }
            else {
                store.add(( new Link(target, node, undefined, this._openerService)));
            }
        }
    }
    static _accessibilityInformation(status) {
        if (status.accessibilityInfo) {
            return status.accessibilityInfo;
        }
        const textValue = typeof status.label === 'string' ? status.label : status.label.value;
        if (status.detail) {
            return { label: ( localizeWithPath(
                'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                'aria.1',
                '{0}, {1}',
                textValue,
                status.detail
            )) };
        }
        else {
            return { label: ( localizeWithPath(
                'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                'aria.2',
                '{0}',
                textValue
            )) };
        }
    }
    static _asStatusbarEntry(item) {
        let kind;
        if (item.severity === Severity$1.Warning) {
            kind = 'warning';
        }
        else if (item.severity === Severity$1.Error) {
            kind = 'error';
        }
        const textValue = typeof item.label === 'string' ? item.label : item.label.shortValue;
        return {
            name: ( localizeWithPath(
                'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                'name.pattern',
                '{0} (Language Status)',
                item.name
            )),
            text: item.busy ? `${textValue}\u00A0\u00A0$(sync~spin)` : textValue,
            ariaLabel: LanguageStatus_1._accessibilityInformation(item).label,
            role: item.accessibilityInfo?.role,
            tooltip: item.command?.tooltip || ( new MarkdownString(item.detail, { isTrusted: true, supportThemeIcons: true })),
            kind,
            command: item.command
        };
    }
};
LanguageStatus = LanguageStatus_1 = ( __decorate([
    ( __param(0, ILanguageStatusService)),
    ( __param(1, IStatusbarService)),
    ( __param(2, IEditorService)),
    ( __param(3, IOpenerService)),
    ( __param(4, IStorageService))
], LanguageStatus));
( Registry.as(Extensions.Workbench)).registerWorkbenchContribution(LanguageStatusContribution, 3 );
registerAction2(class extends Action2 {
    constructor() {
        super({
            id: 'editor.inlayHints.Reset',
            title: ( localize2WithPath(
                'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution',
                'reset',
                "Reset Language Status Interaction Counter"
            )),
            category: Categories.View,
            f1: true
        });
    }
    run(accessor) {
        accessor.get(IStorageService).remove('languageStatus.interactCount', 0 );
    }
});
