
import { __decorate, __param } from '../../../../../../external/tslib/tslib.es6.js';
import { onDidRegisterWindow, trackActiveElement, isEditableElement, getActiveElement, trackFocus, getActiveDocument } from '../../../base/browser/dom.js';
import { mainWindow } from '../../../base/browser/window.js';
import { PauseableEmitter, Event } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { Disposable, MutableDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { MarshalledId } from '../../../base/common/marshallingIds.js';
import { equals, distinct, cloneAndChange } from '../../../base/common/objects.js';
import { TernarySearchTree } from '../../../base/common/ternarySearchTree.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { CommandsRegistry } from '../../commands/common/commands.js';
import { ConfigurationTarget } from '../../configuration/common/configuration.js';
import { IConfigurationService } from '../../configuration/common/configuration.service.js';
import { RawContextKey } from '../common/contextkey.js';
import { IContextKeyService } from '../common/contextkey.service.js';
import { InputFocusedContext } from '../common/contextkeys.js';

const KEYBINDING_CONTEXT_ATTR = "data-keybinding-context";
class Context {
    constructor(id, parent) {
        this._id = id;
        this._parent = parent;
        this._value = Object.create(null);
        this._value["_contextId"] = id;
    }
    get value() {
        return {
            ...this._value
        };
    }
    setValue(key, value) {
        if (!equals(this._value[key], value)) {
            this._value[key] = value;
            return true;
        }
        return false;
    }
    removeValue(key) {
        if (key in this._value) {
            delete this._value[key];
            return true;
        }
        return false;
    }
    getValue(key) {
        const ret = this._value[key];
        if (typeof ret === "undefined" && this._parent) {
            return this._parent.getValue(key);
        }
        return ret;
    }
    updateParent(parent) {
        this._parent = parent;
    }
    collectAllValues() {
        let result = this._parent ? this._parent.collectAllValues() : Object.create(null);
        result = {
            ...result,
            ...this._value
        };
        delete result["_contextId"];
        return result;
    }
}
class NullContext extends Context {
    static {
        this.INSTANCE = ( new NullContext());
    }
    constructor() {
        super(-1, null);
    }
    setValue(key, value) {
        return false;
    }
    removeValue(key) {
        return false;
    }
    getValue(key) {
        return undefined;
    }
    collectAllValues() {
        return Object.create(null);
    }
}
class ConfigAwareContextValuesContainer extends Context {
    static {
        this._keyPrefix = "config.";
    }
    constructor(id, _configurationService, emitter) {
        super(id, null);
        this._configurationService = _configurationService;
        this._values = TernarySearchTree.forConfigKeys();
        this._listener = this._configurationService.onDidChangeConfiguration(event => {
            if (event.source === ConfigurationTarget.DEFAULT) {
                const allKeys = Array.from(this._values, ([k]) => k);
                this._values.clear();
                emitter.fire(( new ArrayContextKeyChangeEvent(allKeys)));
            } else {
                const changedKeys = [];
                for (const configKey of event.affectedKeys) {
                    const contextKey = `config.${configKey}`;
                    const cachedItems = this._values.findSuperstr(contextKey);
                    if (cachedItems !== undefined) {
                        changedKeys.push(...( Iterable.map(cachedItems, ([key]) => key)));
                        this._values.deleteSuperstr(contextKey);
                    }
                    if (( this._values.has(contextKey))) {
                        changedKeys.push(contextKey);
                        this._values.delete(contextKey);
                    }
                }
                emitter.fire(( new ArrayContextKeyChangeEvent(changedKeys)));
            }
        });
    }
    dispose() {
        this._listener.dispose();
    }
    getValue(key) {
        if (key.indexOf(ConfigAwareContextValuesContainer._keyPrefix) !== 0) {
            return super.getValue(key);
        }
        if (( this._values.has(key))) {
            return this._values.get(key);
        }
        const configKey = key.substr(ConfigAwareContextValuesContainer._keyPrefix.length);
        const configValue = this._configurationService.getValue(configKey);
        let value = undefined;
        switch (typeof configValue) {
        case "number":
        case "boolean":
        case "string":
            value = configValue;
            break;
        default:
            if (Array.isArray(configValue)) {
                value = JSON.stringify(configValue);
            } else {
                value = configValue;
            }
        }
        this._values.set(key, value);
        return value;
    }
    setValue(key, value) {
        return super.setValue(key, value);
    }
    removeValue(key) {
        return super.removeValue(key);
    }
    collectAllValues() {
        const result = Object.create(null);
        this._values.forEach((value, index) => result[index] = value);
        return {
            ...result,
            ...super.collectAllValues()
        };
    }
}
class ContextKey {
    constructor(service, key, defaultValue) {
        this._service = service;
        this._key = key;
        this._defaultValue = defaultValue;
        this.reset();
    }
    set(value) {
        this._service.setContext(this._key, value);
    }
    reset() {
        if (typeof this._defaultValue === "undefined") {
            this._service.removeContext(this._key);
        } else {
            this._service.setContext(this._key, this._defaultValue);
        }
    }
    get() {
        return this._service.getContextKeyValue(this._key);
    }
}
class SimpleContextKeyChangeEvent {
    constructor(key) {
        this.key = key;
    }
    affectsSome(keys) {
        return ( keys.has(this.key));
    }
    allKeysContainedIn(keys) {
        return this.affectsSome(keys);
    }
}
class ArrayContextKeyChangeEvent {
    constructor(keys) {
        this.keys = keys;
    }
    affectsSome(keys) {
        for (const key of this.keys) {
            if (( keys.has(key))) {
                return true;
            }
        }
        return false;
    }
    allKeysContainedIn(keys) {
        return this.keys.every(key => ( keys.has(key)));
    }
}
class CompositeContextKeyChangeEvent {
    constructor(events) {
        this.events = events;
    }
    affectsSome(keys) {
        for (const e of this.events) {
            if (e.affectsSome(keys)) {
                return true;
            }
        }
        return false;
    }
    allKeysContainedIn(keys) {
        return this.events.every(evt => evt.allKeysContainedIn(keys));
    }
}
function allEventKeysInContext(event, context) {
    return event.allKeysContainedIn(( new Set(( Object.keys(context)))));
}
class AbstractContextKeyService extends Disposable {
    get onDidChangeContext() {
        return this._onDidChangeContext.event;
    }
    constructor(myContextId) {
        super();
        this._onDidChangeContext = this._register(( new PauseableEmitter({
            merge: input => ( new CompositeContextKeyChangeEvent(input))
        })));
        this._isDisposed = false;
        this._myContextId = myContextId;
    }
    get contextId() {
        return this._myContextId;
    }
    createKey(key, defaultValue) {
        if (this._isDisposed) {
            throw ( new Error(`AbstractContextKeyService has been disposed`));
        }
        return ( new ContextKey(this, key, defaultValue));
    }
    bufferChangeEvents(callback) {
        this._onDidChangeContext.pause();
        try {
            callback();
        } finally {
            this._onDidChangeContext.resume();
        }
    }
    createScoped(domNode) {
        if (this._isDisposed) {
            throw ( new Error(`AbstractContextKeyService has been disposed`));
        }
        return ( new ScopedContextKeyService(this, domNode));
    }
    createOverlay(overlay = Iterable.empty()) {
        if (this._isDisposed) {
            throw ( new Error(`AbstractContextKeyService has been disposed`));
        }
        return ( new OverlayContextKeyService(this, overlay));
    }
    contextMatchesRules(rules) {
        if (this._isDisposed) {
            throw ( new Error(`AbstractContextKeyService has been disposed`));
        }
        const context = this.getContextValuesContainer(this._myContextId);
        const result = (rules ? rules.evaluate(context) : true);
        return result;
    }
    getContextKeyValue(key) {
        if (this._isDisposed) {
            return undefined;
        }
        return this.getContextValuesContainer(this._myContextId).getValue(key);
    }
    setContext(key, value) {
        if (this._isDisposed) {
            return;
        }
        const myContext = this.getContextValuesContainer(this._myContextId);
        if (!myContext) {
            return;
        }
        if (myContext.setValue(key, value)) {
            this._onDidChangeContext.fire(( new SimpleContextKeyChangeEvent(key)));
        }
    }
    removeContext(key) {
        if (this._isDisposed) {
            return;
        }
        if (this.getContextValuesContainer(this._myContextId).removeValue(key)) {
            this._onDidChangeContext.fire(( new SimpleContextKeyChangeEvent(key)));
        }
    }
    getContext(target) {
        if (this._isDisposed) {
            return NullContext.INSTANCE;
        }
        return this.getContextValuesContainer(findContextAttr(target));
    }
    dispose() {
        super.dispose();
        this._isDisposed = true;
    }
}
let ContextKeyService = class ContextKeyService extends AbstractContextKeyService {
    constructor(configurationService) {
        super(0);
        this._contexts = ( new Map());
        this._lastContextId = 0;
        this.inputFocusedContext = InputFocusedContext.bindTo(this);
        const myContext = this._register(( new ConfigAwareContextValuesContainer(this._myContextId, configurationService, this._onDidChangeContext)));
        this._contexts.set(this._myContextId, myContext);
        this._register(Event.runAndSubscribe(onDidRegisterWindow, (
            {
                window,
                disposables
            }
        ) => {
            const onFocusDisposables = disposables.add(( new MutableDisposable()));
            const activeElementTracker = disposables.add(trackActiveElement(window));
            activeElementTracker.onDidChangeActiveElement(activeElement => {
                onFocusDisposables.value = ( new DisposableStore());
                this.updateInputContextKeys(window.document, activeElement, onFocusDisposables.value);
            });
        }, {
            window: mainWindow,
            disposables: this._store
        }));
    }
    updateInputContextKeys(ownerDocument, activeElement, disposables) {
        function activeElementIsInput(activeElement = getActiveElement(ownerDocument)) {
            return !!activeElement && isEditableElement(activeElement);
        }
        const isInputFocused = activeElementIsInput(activeElement);
        this.inputFocusedContext.set(isInputFocused);
        if (isInputFocused) {
            const tracker = disposables.add(trackFocus(activeElement));
            Event.once(tracker.onDidBlur)(() => {
                if (getActiveDocument() === ownerDocument) {
                    this.inputFocusedContext.set(activeElementIsInput());
                }
                tracker.dispose();
            }, undefined, disposables);
        }
    }
    getContextValuesContainer(contextId) {
        if (this._isDisposed) {
            return NullContext.INSTANCE;
        }
        return this._contexts.get(contextId) || NullContext.INSTANCE;
    }
    createChildContext(parentContextId = this._myContextId) {
        if (this._isDisposed) {
            throw ( new Error(`ContextKeyService has been disposed`));
        }
        const id = (++this._lastContextId);
        this._contexts.set(id, ( new Context(id, this.getContextValuesContainer(parentContextId))));
        return id;
    }
    disposeContext(contextId) {
        if (!this._isDisposed) {
            this._contexts.delete(contextId);
        }
    }
    updateParent(_parentContextKeyService) {
        throw ( new Error("Cannot update parent of root ContextKeyService"));
    }
};
ContextKeyService = ( __decorate([( __param(0, IConfigurationService))], ContextKeyService));
class ScopedContextKeyService extends AbstractContextKeyService {
    constructor(parent, domNode) {
        super(parent.createChildContext());
        this._parentChangeListener = this._register(( new MutableDisposable()));
        this._parent = parent;
        this._updateParentChangeListener();
        this._domNode = domNode;
        if (this._domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) {
            let extraInfo = "";
            if (this._domNode.classList) {
                extraInfo = Array.from(( this._domNode.classList.values())).join(", ");
            }
            console.error(
                `Element already has context attribute${extraInfo ? ": " + extraInfo : ""}`
            );
        }
        this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId));
    }
    _updateParentChangeListener() {
        this._parentChangeListener.value = this._parent.onDidChangeContext(e => {
            const thisContainer = this._parent.getContextValuesContainer(this._myContextId);
            const thisContextValues = thisContainer.value;
            if (!allEventKeysInContext(e, thisContextValues)) {
                this._onDidChangeContext.fire(e);
            }
        });
    }
    dispose() {
        if (this._isDisposed) {
            return;
        }
        this._parentChangeListener.clear();
        this._parent.disposeContext(this._myContextId);
        this._domNode.removeAttribute(KEYBINDING_CONTEXT_ATTR);
        super.dispose();
    }
    getContextValuesContainer(contextId) {
        if (this._isDisposed) {
            return NullContext.INSTANCE;
        }
        return this._parent.getContextValuesContainer(contextId);
    }
    createChildContext(parentContextId = this._myContextId) {
        if (this._isDisposed) {
            throw ( new Error(`ScopedContextKeyService has been disposed`));
        }
        return this._parent.createChildContext(parentContextId);
    }
    disposeContext(contextId) {
        this._parent.disposeContext(contextId);
    }
    updateParent(parentContextKeyService) {
        if (this._parent === parentContextKeyService) {
            return;
        }
        const thisContainer = this._parent.getContextValuesContainer(this._myContextId);
        const oldAllValues = thisContainer.collectAllValues();
        this._parent = parentContextKeyService;
        this._updateParentChangeListener();
        const newParentContainer = this._parent.getContextValuesContainer(this._parent.contextId);
        thisContainer.updateParent(newParentContainer);
        const newAllValues = thisContainer.collectAllValues();
        const allValuesDiff = {
            ...distinct(oldAllValues, newAllValues),
            ...distinct(newAllValues, oldAllValues)
        };
        const changedKeys = ( Object.keys(allValuesDiff));
        this._onDidChangeContext.fire(( new ArrayContextKeyChangeEvent(changedKeys)));
    }
}
class OverlayContext {
    constructor(parent, overlay) {
        this.parent = parent;
        this.overlay = overlay;
    }
    getValue(key) {
        return ( this.overlay.has(key)) ? this.overlay.get(key) : this.parent.getValue(key);
    }
}
class OverlayContextKeyService {
    get contextId() {
        return this.parent.contextId;
    }
    get onDidChangeContext() {
        return this.parent.onDidChangeContext;
    }
    constructor(parent, overlay) {
        this.parent = parent;
        this.overlay = ( new Map(overlay));
    }
    bufferChangeEvents(callback) {
        this.parent.bufferChangeEvents(callback);
    }
    createKey() {
        throw ( new Error("Not supported."));
    }
    getContext(target) {
        return ( new OverlayContext(this.parent.getContext(target), this.overlay));
    }
    getContextValuesContainer(contextId) {
        const parentContext = this.parent.getContextValuesContainer(contextId);
        return ( new OverlayContext(parentContext, this.overlay));
    }
    contextMatchesRules(rules) {
        const context = this.getContextValuesContainer(this.contextId);
        const result = (rules ? rules.evaluate(context) : true);
        return result;
    }
    getContextKeyValue(key) {
        return ( this.overlay.has(key)) ? this.overlay.get(key) : this.parent.getContextKeyValue(key);
    }
    createScoped() {
        throw ( new Error("Not supported."));
    }
    createOverlay(overlay = Iterable.empty()) {
        return ( new OverlayContextKeyService(this, overlay));
    }
    updateParent() {
        throw ( new Error("Not supported."));
    }
}
function findContextAttr(domNode) {
    while (domNode) {
        if (domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) {
            const attr = domNode.getAttribute(KEYBINDING_CONTEXT_ATTR);
            if (attr) {
                return parseInt(attr, 10);
            }
            return NaN;
        }
        domNode = domNode.parentElement;
    }
    return 0;
}
function setContext(accessor, contextKey, contextValue) {
    const contextKeyService = accessor.get(IContextKeyService);
    contextKeyService.createKey(String(contextKey), stringifyURIs(contextValue));
}
function stringifyURIs(contextValue) {
    return cloneAndChange(contextValue, obj => {
        if (typeof obj === "object" && obj.$mid === MarshalledId.Uri) {
            return ( URI.revive(obj).toString());
        }
        if (obj instanceof URI) {
            return ( obj.toString());
        }
        return undefined;
    });
}
CommandsRegistry.registerCommand("_setContext", setContext);
CommandsRegistry.registerCommand({
    id: "getContextKeyInfo",
    handler() {
        return [...RawContextKey.all()].sort((a, b) => a.key.localeCompare(b.key));
    },
    metadata: {
        description: ( localize(1879, "A command that returns information about context keys")),
        args: []
    }
});
CommandsRegistry.registerCommand("_generateContextKeyInfo", function() {
    const result = [];
    const seen = ( new Set());
    for (const info of RawContextKey.all()) {
        if (!( seen.has(info.key))) {
            seen.add(info.key);
            result.push(info);
        }
    }
    result.sort((a, b) => a.key.localeCompare(b.key));
    console.log(JSON.stringify(result, undefined, 2));
});

export { AbstractContextKeyService, Context, ContextKeyService, setContext };
