import { expressionsAreEqualWithConstantSubstitution, implies } from "../../contextkey/common/contextkey.mjs";
class KeybindingResolver {
  constructor(defaultKeybindings, overrides, log) {
    this._log = log;
    this._defaultKeybindings = defaultKeybindings;
    this._defaultBoundCommands = /* @__PURE__ */ new Map();
    for (const defaultKeybinding of defaultKeybindings) {
      const command = defaultKeybinding.command;
      if (command && command.charAt(0) !== "-") {
        this._defaultBoundCommands.set(command, true);
      }
    }
    this._map = /* @__PURE__ */ new Map();
    this._lookupMap = /* @__PURE__ */ new Map();
    this._keybindings = KeybindingResolver.handleRemovals([].concat(defaultKeybindings).concat(overrides));
    for (let i = 0, len = this._keybindings.length; i < len; i++) {
      let k = this._keybindings[i];
      if (k.keypressParts.length === 0) {
        continue;
      }
      if (k.when && k.when.type === 0) {
        continue;
      }
      this._addKeyPress(k.keypressParts[0], k);
    }
  }
  static _isTargetedForRemoval(defaultKb, keypressFirstPart, keypressChordPart, when) {
    if (keypressFirstPart && defaultKb.keypressParts[0] !== keypressFirstPart) {
      return false;
    }
    if (keypressChordPart && defaultKb.keypressParts[1] !== keypressChordPart) {
      return false;
    }
    if (when) {
      if (!defaultKb.when) {
        return false;
      }
      if (!expressionsAreEqualWithConstantSubstitution(when, defaultKb.when)) {
        return false;
      }
    }
    return true;
  }
  /**
   * Looks for rules containing "-commandId" and removes them.
   */
  static handleRemovals(rules) {
    const removals = /* @__PURE__ */ new Map();
    for (let i = 0, len = rules.length; i < len; i++) {
      const rule = rules[i];
      if (rule.command && rule.command.charAt(0) === "-") {
        const command = rule.command.substring(1);
        if (!removals.has(command)) {
          removals.set(command, [rule]);
        } else {
          removals.get(command).push(rule);
        }
      }
    }
    if (removals.size === 0) {
      return rules;
    }
    const result = [];
    for (let i = 0, len = rules.length; i < len; i++) {
      const rule = rules[i];
      if (!rule.command || rule.command.length === 0) {
        result.push(rule);
        continue;
      }
      if (rule.command.charAt(0) === "-") {
        continue;
      }
      const commandRemovals = removals.get(rule.command);
      if (!commandRemovals || !rule.isDefault) {
        result.push(rule);
        continue;
      }
      let isRemoved = false;
      for (const commandRemoval of commandRemovals) {
        const keypressFirstPart = commandRemoval.keypressParts[0];
        const keypressChordPart = commandRemoval.keypressParts[1];
        const when = commandRemoval.when;
        if (this._isTargetedForRemoval(rule, keypressFirstPart, keypressChordPart, when)) {
          isRemoved = true;
          break;
        }
      }
      if (!isRemoved) {
        result.push(rule);
        continue;
      }
    }
    return result;
  }
  _addKeyPress(keypress, item) {
    const conflicts = this._map.get(keypress);
    if (typeof conflicts === "undefined") {
      this._map.set(keypress, [item]);
      this._addToLookupMap(item);
      return;
    }
    for (let i = conflicts.length - 1; i >= 0; i--) {
      let conflict = conflicts[i];
      if (conflict.command === item.command) {
        continue;
      }
      const conflictIsChord = conflict.keypressParts.length > 1;
      const itemIsChord = item.keypressParts.length > 1;
      if (conflictIsChord && itemIsChord && conflict.keypressParts[1] !== item.keypressParts[1]) {
        continue;
      }
      if (KeybindingResolver.whenIsEntirelyIncluded(conflict.when, item.when)) {
        this._removeFromLookupMap(conflict);
      }
    }
    conflicts.push(item);
    this._addToLookupMap(item);
  }
  _addToLookupMap(item) {
    if (!item.command) {
      return;
    }
    let arr = this._lookupMap.get(item.command);
    if (typeof arr === "undefined") {
      arr = [item];
      this._lookupMap.set(item.command, arr);
    } else {
      arr.push(item);
    }
  }
  _removeFromLookupMap(item) {
    if (!item.command) {
      return;
    }
    let arr = this._lookupMap.get(item.command);
    if (typeof arr === "undefined") {
      return;
    }
    for (let i = 0, len = arr.length; i < len; i++) {
      if (arr[i] === item) {
        arr.splice(i, 1);
        return;
      }
    }
  }
  /**
   * Returns true if it is provable `a` implies `b`.
   */
  static whenIsEntirelyIncluded(a, b) {
    if (!b || b.type === 1) {
      return true;
    }
    if (!a || a.type === 1) {
      return false;
    }
    return implies(a, b);
  }
  getKeybindings() {
    return this._keybindings;
  }
  lookupPrimaryKeybinding(commandId, context) {
    const items = this._lookupMap.get(commandId);
    if (typeof items === "undefined" || items.length === 0) {
      return null;
    }
    if (items.length === 1) {
      return items[0];
    }
    for (let i = items.length - 1; i >= 0; i--) {
      const item = items[i];
      if (context.contextMatchesRules(item.when)) {
        return item;
      }
    }
    return items[items.length - 1];
  }
  resolve(context, currentChord, keypress) {
    this._log(`| Resolving ${keypress}${currentChord ? ` chorded from ${currentChord}` : ``}`);
    let lookupMap = null;
    if (currentChord !== null) {
      const candidates = this._map.get(currentChord);
      if (typeof candidates === "undefined") {
        this._log(`\\ No keybinding entries.`);
        return null;
      }
      lookupMap = [];
      for (let i = 0, len = candidates.length; i < len; i++) {
        let candidate = candidates[i];
        if (candidate.keypressParts[1] === keypress) {
          lookupMap.push(candidate);
        }
      }
    } else {
      const candidates = this._map.get(keypress);
      if (typeof candidates === "undefined") {
        this._log(`\\ No keybinding entries.`);
        return null;
      }
      lookupMap = candidates;
    }
    let result = this._findCommand(context, lookupMap);
    if (!result) {
      this._log(`\\ From ${lookupMap.length} keybinding entries, no when clauses matched the context.`);
      return null;
    }
    if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) {
      this._log(`\\ From ${lookupMap.length} keybinding entries, matched chord, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
      return {
        enterChord: true,
        leaveChord: false,
        commandId: null,
        commandArgs: null,
        bubble: false
      };
    }
    this._log(`\\ From ${lookupMap.length} keybinding entries, matched ${result.command}, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
    return {
      enterChord: false,
      leaveChord: result.keypressParts.length > 1,
      commandId: result.command,
      commandArgs: result.commandArgs,
      bubble: result.bubble
    };
  }
  _findCommand(context, matches) {
    for (let i = matches.length - 1; i >= 0; i--) {
      let k = matches[i];
      if (!KeybindingResolver._contextMatchesRules(context, k.when)) {
        continue;
      }
      return k;
    }
    return null;
  }
  static _contextMatchesRules(context, rules) {
    if (!rules) {
      return true;
    }
    return rules.evaluate(context);
  }
}
function printWhenExplanation(when) {
  if (!when) {
    return `no when condition`;
  }
  return `${when.serialize()}`;
}
function printSourceExplanation(kb) {
  return kb.extensionId ? kb.isBuiltinExtension ? `built-in extension ${kb.extensionId}` : `user extension ${kb.extensionId}` : kb.isDefault ? `built-in` : `user`;
}
export {
  KeybindingResolver
};
