const normalizeLine = line => {
  if (line.startsWith('Error: ')) {
    return line.slice('Error: '.length);
  }
  if (line.startsWith('VError: ')) {
    return line.slice('VError: '.length);
  }
  return line;
};
const getCombinedMessage = (error, message) => {
  const stringifiedError = normalizeLine(`${error}`);
  if (message) {
    return `${message}: ${stringifiedError}`;
  }
  return stringifiedError;
};
const NewLine$2 = '\n';
const getNewLineIndex$1 = (string, startIndex = undefined) => {
  return string.indexOf(NewLine$2, startIndex);
};
const mergeStacks = (parent, child) => {
  if (!child) {
    return parent;
  }
  const parentNewLineIndex = getNewLineIndex$1(parent);
  const childNewLineIndex = getNewLineIndex$1(child);
  if (childNewLineIndex === -1) {
    return parent;
  }
  const parentFirstLine = parent.slice(0, parentNewLineIndex);
  const childRest = child.slice(childNewLineIndex);
  const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
  if (parentFirstLine.includes(childFirstLine)) {
    return parentFirstLine + childRest;
  }
  return child;
};
class VError extends Error {
  constructor(error, message) {
    const combinedMessage = getCombinedMessage(error, message);
    super(combinedMessage);
    this.name = 'VError';
    if (error instanceof Error) {
      this.stack = mergeStacks(this.stack, error.stack);
    }
    if (error.codeFrame) {
      // @ts-ignore
      this.codeFrame = error.codeFrame;
    }
    if (error.code) {
      // @ts-ignore
      this.code = error.code;
    }
  }
}

class AssertionError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AssertionError';
  }
}
const Object$1 = 1;
const Number = 2;
const Array$1 = 3;
const String = 4;
const Boolean$1 = 5;
const Function = 6;
const Null = 7;
const Unknown$1 = 8;
const getType = value => {
  switch (typeof value) {
    case 'number':
      return Number;
    case 'function':
      return Function;
    case 'string':
      return String;
    case 'object':
      if (value === null) {
        return Null;
      }
      if (Array.isArray(value)) {
        return Array$1;
      }
      return Object$1;
    case 'boolean':
      return Boolean$1;
    default:
      return Unknown$1;
  }
};
const number = value => {
  const type = getType(value);
  if (type !== Number) {
    throw new AssertionError('expected value to be of type number');
  }
};

const isMessagePort = value => {
  return value && value instanceof MessagePort;
};
const isMessagePortMain = value => {
  return value && value.constructor && value.constructor.name === 'MessagePortMain';
};
const isOffscreenCanvas = value => {
  return typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas;
};
const isInstanceOf = (value, constructorName) => {
  return value?.constructor?.name === constructorName;
};
const isSocket = value => {
  return isInstanceOf(value, 'Socket');
};
const transferrables = [isMessagePort, isMessagePortMain, isOffscreenCanvas, isSocket];
const isTransferrable = value => {
  for (const fn of transferrables) {
    if (fn(value)) {
      return true;
    }
  }
  return false;
};
const walkValue = (value, transferrables, isTransferrable) => {
  if (!value) {
    return;
  }
  if (isTransferrable(value)) {
    transferrables.push(value);
    return;
  }
  if (Array.isArray(value)) {
    for (const item of value) {
      walkValue(item, transferrables, isTransferrable);
    }
    return;
  }
  if (typeof value === 'object') {
    for (const property of Object.values(value)) {
      walkValue(property, transferrables, isTransferrable);
    }
    return;
  }
};
const getTransferrables = value => {
  const transferrables = [];
  walkValue(value, transferrables, isTransferrable);
  return transferrables;
};
const attachEvents = that => {
  const handleMessage = (...args) => {
    const data = that.getData(...args);
    that.dispatchEvent(new MessageEvent('message', {
      data
    }));
  };
  that.onMessage(handleMessage);
  const handleClose = event => {
    that.dispatchEvent(new Event('close'));
  };
  that.onClose(handleClose);
};
class Ipc extends EventTarget {
  constructor(rawIpc) {
    super();
    this._rawIpc = rawIpc;
    attachEvents(this);
  }
}
const E_INCOMPATIBLE_NATIVE_MODULE = 'E_INCOMPATIBLE_NATIVE_MODULE';
const E_MODULES_NOT_SUPPORTED_IN_ELECTRON = 'E_MODULES_NOT_SUPPORTED_IN_ELECTRON';
const ERR_MODULE_NOT_FOUND = 'ERR_MODULE_NOT_FOUND';
const NewLine$1 = '\n';
const joinLines$1 = lines => {
  return lines.join(NewLine$1);
};
const RE_AT = /^\s+at/;
const RE_AT_PROMISE_INDEX = /^\s*at async Promise.all \(index \d+\)$/;
const isNormalStackLine = line => {
  return RE_AT.test(line) && !RE_AT_PROMISE_INDEX.test(line);
};
const getDetails = lines => {
  const index = lines.findIndex(isNormalStackLine);
  if (index === -1) {
    return {
      actualMessage: joinLines$1(lines),
      rest: []
    };
  }
  let lastIndex = index - 1;
  while (++lastIndex < lines.length) {
    if (!isNormalStackLine(lines[lastIndex])) {
      break;
    }
  }
  return {
    actualMessage: lines[index - 1],
    rest: lines.slice(index, lastIndex)
  };
};
const splitLines$1 = lines => {
  return lines.split(NewLine$1);
};
const RE_MESSAGE_CODE_BLOCK_START = /^Error: The module '.*'$/;
const RE_MESSAGE_CODE_BLOCK_END = /^\s* at/;
const isMessageCodeBlockStartIndex = line => {
  return RE_MESSAGE_CODE_BLOCK_START.test(line);
};
const isMessageCodeBlockEndIndex = line => {
  return RE_MESSAGE_CODE_BLOCK_END.test(line);
};
const getMessageCodeBlock = stderr => {
  const lines = splitLines$1(stderr);
  const startIndex = lines.findIndex(isMessageCodeBlockStartIndex);
  const endIndex = startIndex + lines.slice(startIndex).findIndex(isMessageCodeBlockEndIndex, startIndex);
  const relevantLines = lines.slice(startIndex, endIndex);
  const relevantMessage = relevantLines.join(' ').slice('Error: '.length);
  return relevantMessage;
};
const isModuleNotFoundMessage = line => {
  return line.includes('[ERR_MODULE_NOT_FOUND]');
};
const getModuleNotFoundError = stderr => {
  const lines = splitLines$1(stderr);
  const messageIndex = lines.findIndex(isModuleNotFoundMessage);
  const message = lines[messageIndex];
  return {
    code: ERR_MODULE_NOT_FOUND,
    message
  };
};
const isModuleNotFoundError = stderr => {
  if (!stderr) {
    return false;
  }
  return stderr.includes('ERR_MODULE_NOT_FOUND');
};
const isModulesSyntaxError = stderr => {
  if (!stderr) {
    return false;
  }
  return stderr.includes('SyntaxError: Cannot use import statement outside a module');
};
const RE_NATIVE_MODULE_ERROR = /^innerError Error: Cannot find module '.*.node'/;
const RE_NATIVE_MODULE_ERROR_2 = /was compiled against a different Node.js version/;
const isUnhelpfulNativeModuleError = stderr => {
  return RE_NATIVE_MODULE_ERROR.test(stderr) && RE_NATIVE_MODULE_ERROR_2.test(stderr);
};
const getNativeModuleErrorMessage = stderr => {
  const message = getMessageCodeBlock(stderr);
  return {
    code: E_INCOMPATIBLE_NATIVE_MODULE,
    message: `Incompatible native node module: ${message}`
  };
};
const getModuleSyntaxError = () => {
  return {
    code: E_MODULES_NOT_SUPPORTED_IN_ELECTRON,
    message: `ES Modules are not supported in electron`
  };
};
const getHelpfulChildProcessError = (stdout, stderr) => {
  if (isUnhelpfulNativeModuleError(stderr)) {
    return getNativeModuleErrorMessage(stderr);
  }
  if (isModulesSyntaxError(stderr)) {
    return getModuleSyntaxError();
  }
  if (isModuleNotFoundError(stderr)) {
    return getModuleNotFoundError(stderr);
  }
  const lines = splitLines$1(stderr);
  const {
    actualMessage,
    rest
  } = getDetails(lines);
  return {
    code: '',
    message: actualMessage,
    stack: rest
  };
};
class IpcError extends VError {
  // @ts-ignore
  constructor(betterMessage, stdout = '', stderr = '') {
    if (stdout || stderr) {
      // @ts-ignore
      const {
        code,
        message,
        stack
      } = getHelpfulChildProcessError(stdout, stderr);
      const cause = new Error(message);
      // @ts-ignore
      cause.code = code;
      cause.stack = stack;
      super(cause, betterMessage);
    } else {
      super(betterMessage);
    }
    // @ts-ignore
    this.name = 'IpcError';
    // @ts-ignore
    this.stdout = stdout;
    // @ts-ignore
    this.stderr = stderr;
  }
}
const readyMessage = 'ready';
const getData$2 = event => {
  return event.data;
};
const listen$7 = () => {
  // @ts-ignore
  if (typeof WorkerGlobalScope === 'undefined') {
    throw new TypeError('module is not in web worker scope');
  }
  return globalThis;
};
const signal$8 = global => {
  global.postMessage(readyMessage);
};
class IpcChildWithModuleWorker extends Ipc {
  getData(event) {
    return getData$2(event);
  }
  send(message) {
    // @ts-ignore
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    // @ts-ignore
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    // ignore
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
}
const wrap$f = global => {
  return new IpcChildWithModuleWorker(global);
};
const waitForFirstMessage = async port => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  port.addEventListener('message', resolve, {
    once: true
  });
  const event = await promise;
  // @ts-ignore
  return event.data;
};
const listen$6 = async () => {
  const parentIpcRaw = listen$7();
  signal$8(parentIpcRaw);
  const parentIpc = wrap$f(parentIpcRaw);
  const firstMessage = await waitForFirstMessage(parentIpc);
  if (firstMessage.method !== 'initialize') {
    throw new IpcError('unexpected first message');
  }
  const type = firstMessage.params[0];
  if (type === 'message-port') {
    parentIpc.send({
      id: firstMessage.id,
      jsonrpc: '2.0',
      result: null
    });
    parentIpc.dispose();
    const port = firstMessage.params[1];
    return port;
  }
  return globalThis;
};
class IpcChildWithModuleWorkerAndMessagePort extends Ipc {
  getData(event) {
    return getData$2(event);
  }
  send(message) {
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    if (this._rawIpc.close) {
      this._rawIpc.close();
    }
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
    this._rawIpc.start();
  }
}
const wrap$e = port => {
  return new IpcChildWithModuleWorkerAndMessagePort(port);
};
const IpcChildWithModuleWorkerAndMessagePort$1 = {
  __proto__: null,
  listen: listen$6,
  wrap: wrap$e
};
const addListener = (emitter, type, callback) => {
  if ('addEventListener' in emitter) {
    emitter.addEventListener(type, callback);
  } else {
    emitter.on(type, callback);
  }
};
const removeListener = (emitter, type, callback) => {
  if ('removeEventListener' in emitter) {
    emitter.removeEventListener(type, callback);
  } else {
    emitter.off(type, callback);
  }
};
const getFirstEvent = (eventEmitter, eventMap) => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  const listenerMap = Object.create(null);
  const cleanup = value => {
    for (const event of Object.keys(eventMap)) {
      removeListener(eventEmitter, event, listenerMap[event]);
    }
    resolve(value);
  };
  for (const [event, type] of Object.entries(eventMap)) {
    const listener = event => {
      cleanup({
        event,
        type
      });
    };
    addListener(eventEmitter, event, listener);
    listenerMap[event] = listener;
  }
  return promise;
};
const Message$1 = 3;
const create$5$1 = async ({
  isMessagePortOpen,
  messagePort
}) => {
  if (!isMessagePort(messagePort)) {
    throw new IpcError('port must be of type MessagePort');
  }
  if (isMessagePortOpen) {
    return messagePort;
  }
  const eventPromise = getFirstEvent(messagePort, {
    message: Message$1
  });
  messagePort.start();
  const {
    event,
    type
  } = await eventPromise;
  if (type !== Message$1) {
    throw new IpcError('Failed to wait for ipc message');
  }
  if (event.data !== readyMessage) {
    throw new IpcError('unexpected first message');
  }
  return messagePort;
};
const signal$1 = messagePort => {
  messagePort.start();
};
class IpcParentWithMessagePort extends Ipc {
  getData = getData$2;
  send(message) {
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    this._rawIpc.close();
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
  onClose(callback) {}
}
const wrap$5 = messagePort => {
  return new IpcParentWithMessagePort(messagePort);
};
const IpcParentWithMessagePort$1 = {
  __proto__: null,
  create: create$5$1,
  signal: signal$1,
  wrap: wrap$5
};

class CommandNotFoundError extends Error {
  constructor(command) {
    super(`Command not found ${command}`);
    this.name = 'CommandNotFoundError';
  }
}
const commands = Object.create(null);
const register = commandMap => {
  Object.assign(commands, commandMap);
};
const getCommand = key => {
  return commands[key];
};
const execute = (command, ...args) => {
  const fn = getCommand(command);
  if (!fn) {
    throw new CommandNotFoundError(command);
  }
  return fn(...args);
};

const Two$1 = '2.0';
const callbacks = Object.create(null);
const get$3 = id => {
  return callbacks[id];
};
const remove$1 = id => {
  delete callbacks[id];
};
class JsonRpcError extends Error {
  constructor(message) {
    super(message);
    this.name = 'JsonRpcError';
  }
}
const NewLine = '\n';
const DomException = 'DOMException';
const ReferenceError$1 = 'ReferenceError';
const SyntaxError$1 = 'SyntaxError';
const TypeError$1 = 'TypeError';
const getErrorConstructor = (message, type) => {
  if (type) {
    switch (type) {
      case DomException:
        return DOMException;
      case ReferenceError$1:
        return ReferenceError;
      case SyntaxError$1:
        return SyntaxError;
      case TypeError$1:
        return TypeError;
      default:
        return Error;
    }
  }
  if (message.startsWith('TypeError: ')) {
    return TypeError;
  }
  if (message.startsWith('SyntaxError: ')) {
    return SyntaxError;
  }
  if (message.startsWith('ReferenceError: ')) {
    return ReferenceError;
  }
  return Error;
};
const constructError = (message, type, name) => {
  const ErrorConstructor = getErrorConstructor(message, type);
  if (ErrorConstructor === DOMException && name) {
    return new ErrorConstructor(message, name);
  }
  if (ErrorConstructor === Error) {
    const error = new Error(message);
    if (name && name !== 'VError') {
      error.name = name;
    }
    return error;
  }
  return new ErrorConstructor(message);
};
const joinLines = lines => {
  return lines.join(NewLine);
};
const splitLines = lines => {
  return lines.split(NewLine);
};
const getCurrentStack = () => {
  const stackLinesToSkip = 3;
  const currentStack = joinLines(splitLines(new Error().stack || '').slice(stackLinesToSkip));
  return currentStack;
};
const getNewLineIndex = (string, startIndex = undefined) => {
  return string.indexOf(NewLine, startIndex);
};
const getParentStack = error => {
  let parentStack = error.stack || error.data || error.message || '';
  if (parentStack.startsWith('    at')) {
    parentStack = error.message + NewLine + parentStack;
  }
  return parentStack;
};
const MethodNotFound = -32601;
const Custom = -32001;
const restoreJsonRpcError = error => {
  const currentStack = getCurrentStack();
  if (error && error instanceof Error) {
    if (typeof error.stack === 'string') {
      error.stack = error.stack + NewLine + currentStack;
    }
    return error;
  }
  if (error && error.code && error.code === MethodNotFound) {
    const restoredError = new JsonRpcError(error.message);
    const parentStack = getParentStack(error);
    restoredError.stack = parentStack + NewLine + currentStack;
    return restoredError;
  }
  if (error && error.message) {
    const restoredError = constructError(error.message, error.type, error.name);
    if (error.data) {
      if (error.data.stack && error.data.type && error.message) {
        restoredError.stack = error.data.type + ': ' + error.message + NewLine + error.data.stack + NewLine + currentStack;
      } else if (error.data.stack) {
        restoredError.stack = error.data.stack;
      }
      if (error.data.codeFrame) {
        // @ts-ignore
        restoredError.codeFrame = error.data.codeFrame;
      }
      if (error.data.code) {
        // @ts-ignore
        restoredError.code = error.data.code;
      }
      if (error.data.type) {
        // @ts-ignore
        restoredError.name = error.data.type;
      }
    } else {
      if (error.stack) {
        const lowerStack = restoredError.stack || '';
        // @ts-ignore
        const indexNewLine = getNewLineIndex(lowerStack);
        const parentStack = getParentStack(error);
        // @ts-ignore
        restoredError.stack = parentStack + lowerStack.slice(indexNewLine);
      }
      if (error.codeFrame) {
        // @ts-ignore
        restoredError.codeFrame = error.codeFrame;
      }
    }
    return restoredError;
  }
  if (typeof error === 'string') {
    return new Error(`JsonRpc Error: ${error}`);
  }
  return new Error(`JsonRpc Error: ${error}`);
};
const unwrapJsonRpcResult = responseMessage => {
  if ('error' in responseMessage) {
    const restoredError = restoreJsonRpcError(responseMessage.error);
    throw restoredError;
  }
  if ('result' in responseMessage) {
    return responseMessage.result;
  }
  throw new JsonRpcError('unexpected response message');
};
const warn$1 = (...args) => {
  console.warn(...args);
};
const resolve = (id, response) => {
  const fn = get$3(id);
  if (!fn) {
    console.log(response);
    warn$1(`callback ${id} may already be disposed`);
    return;
  }
  fn(response);
  remove$1(id);
};
const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND';
const getErrorType = prettyError => {
  if (prettyError && prettyError.type) {
    return prettyError.type;
  }
  if (prettyError && prettyError.constructor && prettyError.constructor.name) {
    return prettyError.constructor.name;
  }
  return undefined;
};
const isAlreadyStack = line => {
  return line.trim().startsWith('at ');
};
const getStack = prettyError => {
  const stackString = prettyError.stack || '';
  const newLineIndex = stackString.indexOf('\n');
  if (newLineIndex !== -1 && !isAlreadyStack(stackString.slice(0, newLineIndex))) {
    return stackString.slice(newLineIndex + 1);
  }
  return stackString;
};
const getErrorProperty = (error, prettyError) => {
  if (error && error.code === E_COMMAND_NOT_FOUND) {
    return {
      code: MethodNotFound,
      data: error.stack,
      message: error.message
    };
  }
  return {
    code: Custom,
    data: {
      code: prettyError.code,
      codeFrame: prettyError.codeFrame,
      name: prettyError.name,
      stack: getStack(prettyError),
      type: getErrorType(prettyError)
    },
    message: prettyError.message
  };
};
const create$1$1 = (id, error) => {
  return {
    error,
    id,
    jsonrpc: Two$1
  };
};
const getErrorResponse = (id, error, preparePrettyError, logError) => {
  const prettyError = preparePrettyError(error);
  logError(error, prettyError);
  const errorProperty = getErrorProperty(error, prettyError);
  return create$1$1(id, errorProperty);
};
const create$9 = (message, result) => {
  return {
    id: message.id,
    jsonrpc: Two$1,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$9(message, resultProperty);
};
const getErrorResponseSimple = (id, error) => {
  return {
    error: {
      code: Custom,
      data: error,
      // @ts-ignore
      message: error.message
    },
    id,
    jsonrpc: Two$1
  };
};
const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => {
  try {
    const result = requiresSocket(message.method) ? await execute(message.method, ipc, ...message.params) : await execute(message.method, ...message.params);
    return getSuccessResponse(message, result);
  } catch (error) {
    if (ipc.canUseSimpleErrorResponse) {
      return getErrorResponseSimple(message.id, error);
    }
    return getErrorResponse(message.id, error, preparePrettyError, logError);
  }
};
const defaultPreparePrettyError = error => {
  return error;
};
const defaultLogError = () => {
  // ignore
};
const defaultRequiresSocket = () => {
  return false;
};
const defaultResolve = resolve;

// TODO maybe remove this in v6 or v7, only accept options object to simplify the code
const normalizeParams = args => {
  if (args.length === 1) {
    const options = args[0];
    return {
      execute: options.execute,
      ipc: options.ipc,
      logError: options.logError || defaultLogError,
      message: options.message,
      preparePrettyError: options.preparePrettyError || defaultPreparePrettyError,
      requiresSocket: options.requiresSocket || defaultRequiresSocket,
      resolve: options.resolve || defaultResolve
    };
  }
  return {
    execute: args[2],
    ipc: args[0],
    logError: args[5],
    message: args[1],
    preparePrettyError: args[4],
    requiresSocket: args[6],
    resolve: args[3]
  };
};
const handleJsonRpcMessage = async (...args) => {
  const options = normalizeParams(args);
  const {
    execute,
    ipc,
    logError,
    message,
    preparePrettyError,
    requiresSocket,
    resolve
  } = options;
  if ('id' in message) {
    if ('method' in message) {
      const response = await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
      try {
        ipc.send(response);
      } catch (error) {
        const errorResponse = getErrorResponse(message.id, error, preparePrettyError, logError);
        ipc.send(errorResponse);
      }
      return;
    }
    resolve(message.id, message);
    return;
  }
  if ('method' in message) {
    await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
    return;
  }
  throw new JsonRpcError('unexpected message');
};

const Two = '2.0';

const create$8 = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};

const create$7 = (id, method, params) => {
  const message = {
    id,
    jsonrpc: Two,
    method,
    params
  };
  return message;
};

let id$a = 0;
const create$6 = () => {
  return ++id$a;
};

const registerPromise = map => {
  const id = create$6();
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  map[id] = resolve;
  return {
    id,
    promise
  };
};

const invokeHelper = async (callbacks, ipc, method, params, useSendAndTransfer) => {
  const {
    id,
    promise
  } = registerPromise(callbacks);
  const message = create$7(id, method, params);
  if (useSendAndTransfer && ipc.sendAndTransfer) {
    ipc.sendAndTransfer(message);
  } else {
    ipc.send(message);
  }
  const responseMessage = await promise;
  return unwrapJsonRpcResult(responseMessage);
};
const createRpc = ipc => {
  const callbacks = Object.create(null);
  ipc._resolve = (id, response) => {
    const fn = callbacks[id];
    if (!fn) {
      console.warn(`callback ${id} may already be disposed`);
      return;
    }
    fn(response);
    delete callbacks[id];
  };
  const rpc = {
    async dispose() {
      await ipc?.dispose();
    },
    invoke(method, ...params) {
      return invokeHelper(callbacks, ipc, method, params, false);
    },
    invokeAndTransfer(method, ...params) {
      return invokeHelper(callbacks, ipc, method, params, true);
    },
    // @ts-ignore
    ipc,
    /**
     * @deprecated
     */
    send(method, ...params) {
      const message = create$8(method, params);
      ipc.send(message);
    }
  };
  return rpc;
};

const requiresSocket = () => {
  return false;
};
const preparePrettyError = error => {
  return error;
};
const logError = () => {
  // handled by renderer worker
};
const handleMessage = event => {
  const actualRequiresSocket = event?.target?.requiresSocket || requiresSocket;
  const actualExecute = event?.target?.execute || execute;
  return handleJsonRpcMessage(event.target, event.data, actualExecute, event.target._resolve, preparePrettyError, logError, actualRequiresSocket);
};

const handleIpc = ipc => {
  if ('addEventListener' in ipc) {
    ipc.addEventListener('message', handleMessage);
  } else if ('on' in ipc) {
    // deprecated
    ipc.on('message', handleMessage);
  }
};

const listen$1 = async (module, options) => {
  const rawIpc = await module.listen(options);
  if (module.signal) {
    module.signal(rawIpc);
  }
  const ipc = module.wrap(rawIpc);
  return ipc;
};

const create$5 = async ({
  commandMap,
  isMessagePortOpen = true,
  messagePort
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const rawIpc = await IpcParentWithMessagePort$1.create({
    isMessagePortOpen,
    messagePort
  });
  const ipc = IpcParentWithMessagePort$1.wrap(rawIpc);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  messagePort.start();
  return rpc;
};

const create$4 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  const {
    port1,
    port2
  } = new MessageChannel();
  await send(port1);
  return create$5({
    commandMap,
    isMessagePortOpen,
    messagePort: port2
  });
};

const createSharedLazyRpc = factory => {
  let rpcPromise;
  const getOrCreate = () => {
    if (!rpcPromise) {
      rpcPromise = factory();
    }
    return rpcPromise;
  };
  return {
    async dispose() {
      const rpc = await getOrCreate();
      await rpc.dispose();
    },
    async invoke(method, ...params) {
      const rpc = await getOrCreate();
      return rpc.invoke(method, ...params);
    },
    async invokeAndTransfer(method, ...params) {
      const rpc = await getOrCreate();
      return rpc.invokeAndTransfer(method, ...params);
    },
    async send(method, ...params) {
      const rpc = await getOrCreate();
      rpc.send(method, ...params);
    }
  };
};

const create$3 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  return createSharedLazyRpc(() => {
    return create$4({
      commandMap,
      isMessagePortOpen,
      send
    });
  });
};

const create$2 = async ({
  commandMap
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const ipc = await listen$1(IpcChildWithModuleWorkerAndMessagePort$1);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  return rpc;
};

const createMockRpc = ({
  commandMap
}) => {
  const invocations = [];
  const invoke = (method, ...params) => {
    invocations.push([method, ...params]);
    const command = commandMap[method];
    if (!command) {
      throw new Error(`command ${method} not found`);
    }
    return command(...params);
  };
  const mockRpc = {
    invocations,
    invoke,
    invokeAndTransfer: invoke
  };
  return mockRpc;
};

const rpcs = Object.create(null);
const set$4 = (id, rpc) => {
  rpcs[id] = rpc;
};
const get$2 = id => {
  return rpcs[id];
};
const remove = id => {
  delete rpcs[id];
};

/* eslint-disable  @typescript-eslint/explicit-function-return-type */
const create$1 = rpcId => {
  return {
    async dispose() {
      const rpc = get$2(rpcId);
      await rpc.dispose();
    },
    // @ts-ignore
    invoke(method, ...params) {
      const rpc = get$2(rpcId);
      // @ts-ignore
      return rpc.invoke(method, ...params);
    },
    // @ts-ignore
    invokeAndTransfer(method, ...params) {
      const rpc = get$2(rpcId);
      // @ts-ignore
      return rpc.invokeAndTransfer(method, ...params);
    },
    registerMockRpc(commandMap) {
      const mockRpc = createMockRpc({
        commandMap
      });
      set$4(rpcId, mockRpc);
      // @ts-ignore
      mockRpc[Symbol.dispose] = () => {
        remove(rpcId);
      };
      // @ts-ignore
      return mockRpc;
    },
    set(rpc) {
      set$4(rpcId, rpc);
    }
  };
};

const Div$1 = 4;
const Text = 12;

const Button = 'event.button';
const ClientX = 'event.clientX';
const ClientY = 'event.clientY';

const Backspace$1 = 1;
const Tab$1 = 2;
const Enter$1 = 3;
const Escape$1 = 8;
const Space$1 = 9;
const PageUp$1 = 10;
const PageDown$1 = 11;
const End$1 = 255;
const Home$1 = 12;
const LeftArrow$1 = 13;
const UpArrow$1 = 14;
const RightArrow$1 = 15;
const DownArrow$1 = 16;
const Insert$1 = 17;
const Delete$1 = 18;
const Digit0$1 = 19;
const Digit1$1 = 20;
const Digit2$1 = 21;
const Digit3$1 = 22;
const Digit4$1 = 23;
const Digit5$1 = 24;
const Digit6$1 = 25;
const Digit7$1 = 26;
const Digit8$1 = 27;
const Digit9$1 = 28;
const KeyA$1 = 29;
const KeyB$1 = 30;
const KeyC$1 = 31;
const KeyD$1 = 32;
const KeyE$1 = 33;
const KeyF$1 = 34;
const KeyG$1 = 35;
const KeyH$1 = 36;
const KeyI$1 = 37;
const KeyJ$1 = 38;
const KeyK$1 = 39;
const KeyL$1 = 40;
const KeyM$1 = 41;
const KeyN$1 = 42;
const KeyO$1 = 43;
const KeyP$1 = 44;
const KeyQ$1 = 45;
const KeyR$1 = 46;
const KeyS$1 = 47;
const KeyT$1 = 48;
const KeyU$1 = 49;
const KeyV$1 = 50;
const KeyW$1 = 51;
const KeyX$1 = 52;
const KeyY$1 = 53;
const KeyZ$1 = 54;
const F1$1 = 57;
const F2$1 = 58;
const F3$1 = 59;
const F4$1 = 60;
const F5$1 = 61;
const F6$1 = 62;
const Equal$1 = 84;
const Comma$1 = 85;
const Minus$1 = 86;
const Backquote$1 = 89;
const Backslash$1 = 91;
const Star$1 = 131;
const Plus$1 = 132;

const Unknown = 'Unknown';
const Backspace = 'Backspace';
const Tab = 'Tab';
const Enter = 'Enter';
const Escape = 'Escape';
const Space = 'Space';
const PageUp = 'PageUp';
const PageDown = 'PageDown';
const End = 'End';
const Home = 'Home';
const LeftArrow = 'LeftArrow';
const UpArrow = 'UpArrow';
const RightArrow = 'RightArrow';
const DownArrow = 'DownArrow';
const Insert = 'Insert';
const Delete = 'Delete';
const Digit0 = '0';
const Digit1 = '1';
const Digit2 = '2';
const Digit3 = '3';
const Digit4 = '4';
const Digit5 = '5';
const Digit6 = '6';
const Digit7 = '7';
const Digit8 = '8';
const Digit9 = '9';
const KeyA = 'a';
const KeyB = 'b';
const KeyC = 'c';
const KeyD = 'd';
const KeyE = 'e';
const KeyF = 'f';
const KeyG = 'g';
const KeyH = 'h';
const KeyI = 'i';
const KeyJ = 'j';
const KeyK = 'k';
const KeyL = 'l';
const KeyM = 'm';
const KeyN = 'n';
const KeyO = 'o';
const KeyP = 'p';
const KeyQ = 'q';
const KeyR = 'r';
const KeyS = 's';
const KeyT = 't';
const KeyU = 'u';
const KeyV = 'v';
const KeyW = 'w';
const KeyX = 'x';
const KeyY = 'y';
const KeyZ = 'z';
const F1 = 'F1';
const F2 = 'F2';
const F3 = 'F3';
const F4 = 'F4';
const F5 = 'F5';
const F6 = 'F6';
const Equal = '=';
const Comma = ',';
const Minus = 'Minus';
const Backquote = 'Backquote';
const Backslash = 'Backslash';
const Star = '*';
const Plus = '+';

const getKeyCodeString = keyCode => {
  switch (keyCode) {
    case Backquote$1:
      return Backquote;
    case Backslash$1:
      return Backslash;
    case Backspace$1:
      return Backspace;
    case Comma$1:
      return Comma;
    case Delete$1:
      return Delete;
    case Digit0$1:
      return Digit0;
    case Digit1$1:
      return Digit1;
    case Digit2$1:
      return Digit2;
    case Digit3$1:
      return Digit3;
    case Digit4$1:
      return Digit4;
    case Digit5$1:
      return Digit5;
    case Digit6$1:
      return Digit6;
    case Digit7$1:
      return Digit7;
    case Digit8$1:
      return Digit8;
    case Digit9$1:
      return Digit9;
    case DownArrow$1:
      return DownArrow;
    case End$1:
      return End;
    case Enter$1:
      return Enter;
    case Equal$1:
      return Equal;
    case Escape$1:
      return Escape;
    case F1$1:
      return F1;
    case F2$1:
      return F2;
    case F3$1:
      return F3;
    case F4$1:
      return F4;
    case F5$1:
      return F5;
    case F6$1:
      return F6;
    case Home$1:
      return Home;
    case Insert$1:
      return Insert;
    case KeyA$1:
      return KeyA;
    case KeyB$1:
      return KeyB;
    case KeyC$1:
      return KeyC;
    case KeyD$1:
      return KeyD;
    case KeyE$1:
      return KeyE;
    case KeyF$1:
      return KeyF;
    case KeyG$1:
      return KeyG;
    case KeyH$1:
      return KeyH;
    case KeyI$1:
      return KeyI;
    case KeyJ$1:
      return KeyJ;
    case KeyK$1:
      return KeyK;
    case KeyL$1:
      return KeyL;
    case KeyM$1:
      return KeyM;
    case KeyN$1:
      return KeyN;
    case KeyO$1:
      return KeyO;
    case KeyP$1:
      return KeyP;
    case KeyQ$1:
      return KeyQ;
    case KeyR$1:
      return KeyR;
    case KeyS$1:
      return KeyS;
    case KeyT$1:
      return KeyT;
    case KeyU$1:
      return KeyU;
    case KeyV$1:
      return KeyV;
    case KeyW$1:
      return KeyW;
    case KeyX$1:
      return KeyX;
    case KeyY$1:
      return KeyY;
    case KeyZ$1:
      return KeyZ;
    case LeftArrow$1:
      return LeftArrow;
    case Minus$1:
      return Minus;
    case PageDown$1:
      return PageDown;
    case PageUp$1:
      return PageUp;
    case Plus$1:
      return Plus;
    case RightArrow$1:
      return RightArrow;
    case Space$1:
      return Space;
    case Star$1:
      return Star;
    case Tab$1:
      return Tab;
    case UpArrow$1:
      return UpArrow;
    default:
      return Unknown;
  }
};

const CtrlCmd = 1 << 11 >>> 0;
const Shift = 1 << 10 >>> 0;

const Separator$2 = 1;
const None$1 = 0;
const SubMenu$1 = 4;
const Checked$1 = 2;
const Unchecked$1 = 3;
const Disabled$1 = 5;
const RestoreFocus$1 = 6;
const Ignore$1 = 7;

const parseKey = rawKey => {
  const isCtrl = Boolean(rawKey & CtrlCmd);
  const isShift = Boolean(rawKey & Shift);
  const keyCode = rawKey & 0x00_00_00_ff;
  const key = getKeyCodeString(keyCode);
  return {
    isCtrl,
    isShift,
    key
  };
};

const DebugWorker = 55;
const RendererProcess = 1670;
const RendererWorker$1 = 1;

const FocusSelector = 'Viewlet.focusSelector';

const {
  invoke: invoke$3,
  set: set$3
} = create$1(RendererProcess);

const {
  invoke: invoke$2,
  invokeAndTransfer,
  set: set$2
} = create$1(RendererWorker$1);
const sendMessagePortToRendererProcess = async port => {
  const command = 'HandleMessagePort.handleMessagePort';
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToRendererProcess', port, command, DebugWorker);
};
const sendMessagePortToTextMeasurementWorker = async port => {
  const command = 'TextMeasurement.handleMessagePort';
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToTextMeasurementWorker', port, command, 0);
};

const RendererWorker = {
  __proto__: null,
  invoke: invoke$2,
  invokeAndTransfer,
  sendMessagePortToRendererProcess,
  sendMessagePortToTextMeasurementWorker,
  set: set$2
};

const send$1 = port => {
  return sendMessagePortToRendererProcess(port);
};
const createRendererProcessRpc = async () => {
  try {
    const rpc = await create$4({
      commandMap: {},
      send: send$1
    });
    return rpc;
  } catch (error) {
    throw new VError(error, `Failed to create renderer process rpc`);
  }
};

const initializeRendererProcess = async () => {
  const rpc = await createRendererProcessRpc();
  set$3(rpc);
};

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
// TODO lazyload menuEntries and use Command.execute (maybe)

// TODO difference between focusing with mouse or keyboard
// with mouse -> open submenu
// with keyboard -> don't open submenu, only focus

// TODO this code seems a bit too complicated, maybe it can be simplified

const state$1 = {
  latestTimeStamp: 0,
  menus: []
};
const addMenuInternal = menu => {
  if (state$1.menus.length > 5) {
    throw new Error('too many menus');
  }
  state$1.menus.push(menu);
  return menu;
};
const getCount = () => {
  return state$1.menus.length;
};
const reset = () => {
  state$1.menus = [];
};
const get$1 = index => {
  return state$1.menus[index];
};
const set$1 = menus => {
  state$1.menus = menus;
};
const getAll = () => {
  return state$1.menus;
};
const setTimestamp = timestamp => {
  state$1.latestTimeStamp = timestamp;
};
const getLatestTimestamp = () => {
  return state$1.latestTimeStamp;
};

const closeSubMenu = async () => {
  const menus = getAll();
  const parentMenu = menus.at(-1);
  // @ts-ignore
  await invoke$3(/* Menu.hideSubMenu */7903, /* parentIndex */parentMenu.focusedIndex);
};

const send = port => {
  return sendMessagePortToTextMeasurementWorker(port);
};
const createTextMeasurementWorkerRpc = async () => {
  try {
    const rpc = await create$3({
      commandMap: {},
      send
    });
    return rpc;
  } catch (error) {
    throw new VError(error, `Failed to create text measurement worker rpc`);
  }
};

let rpcPromise;
const getRpc = () => {
  rpcPromise ||= createTextMeasurementWorkerRpc();
  return rpcPromise;
};
const measureTextWidths = async (texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonospaceFont, charWidth) => {
  const rpc = await getRpc();
  return rpc.invoke('TextMeasurement.measureTextWidths', texts, fontWeight, fontSize, fontFamily, letterSpacing, isMonospaceFont, charWidth);
};

const FontWeight = 400;
const FontSize = 13;
const FontFamily = 'system-ui, Ubuntu, Droid Sans, sans-serif';
const LetterSpacing = 0;
const IsMonospaceFont = false;
const CharWidth = 8;
const getLabels = items => {
  return items.map(item => item.label).filter(label => typeof label === 'string' && label);
};
const getMenuLabelWidths = async items => {
  const labels = getLabels(items);
  return measureTextWidths(labels, FontWeight, FontSize, FontFamily, LetterSpacing, IsMonospaceFont, CharWidth);
};

const MENU_MIN_WIDTH = 150;
const MENU_ITEM_HORIZONTAL_PADDING = 56;
const MENU_ITEM_HORIZONTAL_MARGIN = 8;
const getMenuMeasuredWidth = async items => {
  const labelWidths = await getMenuLabelWidths(items);
  const maxLabelWidth = Math.max(0, ...labelWidths);
  return Math.ceil(Math.max(MENU_MIN_WIDTH, maxLabelWidth + MENU_ITEM_HORIZONTAL_PADDING + MENU_ITEM_HORIZONTAL_MARGIN));
};

const Separator$1 = 1;
const None = 0;
const SubMenu = 4;
const Checked = 2;
const Unchecked = 3;
const Disabled = 5;
const RestoreFocus = 6;
const Ignore = 7;

// TODO lazyload menuEntries and use Command.execute (maybe)
const MENU_TARGET_GAP = 5;

// TODO difference between focusing with mouse or keyboard
// with mouse -> open submenu
// with keyboard -> don't open submenu, only focus

// TODO this code seems a bit too complicated, maybe it can be simplified

// TODO handle printable letter and focus item that starts with that letter

// TODO pageup / pagedown keys

// TODO more tests

const CONTEXT_MENU_ITEM_HEIGHT$1 = 26;
const CONTEXT_MENU_SEPARATOR_HEIGHT$1 = 11;
const CONTEXT_MENU_PADDING$1 = 8;
const getMenuHeight$1 = items => {
  let height = CONTEXT_MENU_PADDING$1;
  for (const item of items) {
    switch (item.flags) {
      case Separator$1:
        height += CONTEXT_MENU_SEPARATOR_HEIGHT$1;
        break;
      default:
        height += CONTEXT_MENU_ITEM_HEIGHT$1;
        break;
    }
  }
  return height;
};
const getMenuBounds = async (x, y, items) => {
  const menuWidth = await getMenuMeasuredWidth(items);
  const menuHeight = getMenuHeight$1(items);
  const layoutState = {
    points: [0, 0]
  };
  const windowWidth = layoutState.points[0];
  const windowHeight = layoutState.points[1];
  // TODO maybe only send labels and keybindings to ui (id not needed on ui)
  // TODO what about separators?

  if (x + menuWidth > windowWidth) {
    x -= menuWidth + MENU_TARGET_GAP;
    x = Math.max(x, 0);
  }
  if (y + menuHeight > windowHeight) {
    y -= menuHeight;
    y = Math.max(y, 0);
  }
  return {
    height: menuHeight,
    width: menuWidth,
    x,
    y
  };
};

const matchesArgs = (a, b) => {
  if (!a && !b) {
    return true;
  }
  if (!a && b || !b && a) {
    return false;
  }
  if (a.length !== b.length) {
    return false;
  }
  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) {
      return false;
    }
  }
  return true;
};
const getCommandKeyBinding = (keyBindings, command, args) => {
  for (const keyBinding of keyBindings) {
    if (keyBinding.command === command && matchesArgs(keyBinding.args, args)) {
      return keyBinding;
    }
  }
  return undefined;
};

/* eslint-disable no-console */
const warn = (...args) => {
  console.warn(...args);
};

const invoke$1 = async (method, ...params) => {
  // @ts-ignore
  await invoke$2('WebView.compatRendererProcessInvoke', method, ...params);
};

const state = {
  /**
   * @type {any}
   */
  matchingKeyBindings: []
};

const emptyObject = {};
const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
const i18nString = (key, placeholders = emptyObject) => {
  if (placeholders === emptyObject) {
    return key;
  }
  const replacer = (match, rest) => {
    return placeholders[rest];
  };
  return key.replaceAll(RE_PLACEHOLDER, replacer);
};

/**
 * @enum {string}
 */
const UiStrings$2 = {
  Copy: 'Copy',
  CopyLineDown: 'Copy Line Down',
  CopyLineUp: 'Copy Line Up',
  Cut: 'Cut',
  MoveLineDown: 'Move Line Down',
  MoveLineUp: 'Move Line Up',
  Paste: 'Paste',
  Redo: 'Redo',
  SelectAll: 'Select All',
  ToggleBlockComment: 'Toggle Block Comment',
  ToggleLineComment: 'Toggle Line Comment',
  Undo: 'Undo'
};
const cut = () => {
  return i18nString(UiStrings$2.Cut);
};
const copy = () => {
  return i18nString(UiStrings$2.Copy);
};
const paste = () => {
  return i18nString(UiStrings$2.Paste);
};
const undo = () => {
  return i18nString(UiStrings$2.Undo);
};
const redo = () => {
  return i18nString(UiStrings$2.Redo);
};
const toggleLineComment = () => {
  return i18nString(UiStrings$2.ToggleLineComment);
};
const toggleBlockComment = () => {
  return i18nString(UiStrings$2.ToggleBlockComment);
};
const selectAll = () => {
  return i18nString(UiStrings$2.SelectAll);
};
const copyLineUp = () => {
  return i18nString(UiStrings$2.CopyLineUp);
};
const copyLineDown = () => {
  return i18nString(UiStrings$2.CopyLineDown);
};
const moveLineUp = () => {
  return i18nString(UiStrings$2.MoveLineUp);
};
const moveLineDown = () => {
  return i18nString(UiStrings$2.MoveLineDown);
};

const Edit$1 = 2;
const File$2 = 5;
const Go$1 = 6;
const Help$1 = 7;
const OpenRecent = 9;
const Run$1 = 10;
const Selection$1 = 11;
const Terminal$1 = 14;
const TitleBar = 15;
const View$1 = 16;

const menuEntrySeparator = {
  command: '',
  flags: Separator$2,
  id: 'separator',
  label: ''
};

const id$9 = Edit$1;
const getMenuEntries$d = () => {
  return [{
    command: /* TODO */'-1',
    flags: Disabled,
    id: 'undo',
    label: undo()
  }, {
    command: /* TODO */'-1',
    flags: Disabled,
    id: 'redo',
    label: redo()
  }, menuEntrySeparator, {
    command: /* Editor.cut */'Editor.cut',
    flags: None,
    id: 'cut',
    label: cut()
  }, {
    command: /* Editor.copy */'Editor.copy',
    flags: None,
    id: 'copy',
    label: copy()
  }, {
    command: /* Editor.paste */'Editor.paste',
    flags: None,
    id: 'paste',
    label: paste()
  }, menuEntrySeparator, {
    command: /* Editor.toggleLineComment */'Editor.toggleLineComment',
    flags: None,
    id: 'toggle-line-comment',
    label: toggleLineComment()
  }, {
    command: /* Editor.toggleBlockComment */'Editor.toggleBlockComment',
    flags: None,
    id: 'toggle-block-comment',
    label: toggleBlockComment()
  }];
};

const MenuEntriesEdit = {
  __proto__: null,
  getMenuEntries: getMenuEntries$d,
  id: id$9
};

/**
 * @enum {string}
 */
const UiStrings$1 = {
  Exit: 'Exit',
  NewFile: 'New File',
  NewWindow: 'New Window',
  OpenFile: 'Open File',
  OpenFolder: 'Open Folder',
  OpenRecent: 'Open Recent',
  Save: 'Save',
  SaveAll: 'Save All'};
const newFile = () => {
  return i18nString(UiStrings$1.NewFile);
};
const newWindow = () => {
  return i18nString(UiStrings$1.NewWindow);
};
const openFile = () => {
  return i18nString(UiStrings$1.OpenFile);
};
const openFolder = () => {
  return i18nString(UiStrings$1.OpenFolder);
};
const openRecent = () => {
  return i18nString(UiStrings$1.OpenRecent);
};
const save = () => {
  return i18nString(UiStrings$1.Save);
};
const saveAll = () => {
  return i18nString(UiStrings$1.SaveAll);
};
const exit = () => {
  return i18nString(UiStrings$1.Exit);
};

const Web = 1;
const Electron = 2;

const id$8 = File$2;
const getMenuEntries$c = platform => {
  const entries = [{
    command: '-1',
    flags: None,
    id: 'newFile',
    label: newFile()
  }, {
    command: /* Window.openNew */'Window.openNew',
    flags: None,
    id: 'newWindow',
    label: newWindow()
  }, menuEntrySeparator, {
    command: 'Dialog.openFile',
    flags: None,
    id: 'openFile',
    label: openFile()
  }, {
    command: 'Dialog.openFolder',
    flags: RestoreFocus,
    id: 'openFolder',
    label: openFolder()
  }, {
    command: '',
    flags: SubMenu,
    id: OpenRecent,
    label: openRecent()
  }, menuEntrySeparator, {
    command: 'Main.save',
    flags: Disabled,
    id: 'save',
    label: save()
  }, {
    command: 'Main.saveAll',
    flags: Disabled,
    id: 'saveAll',
    label: saveAll()
  }];
  if (platform === Electron) {
    entries.push(menuEntrySeparator, {
      command: 'Chrome.exit',
      flags: Ignore,
      id: 'exit',
      label: exit()
    });
  }
  return entries;
};

const MenuEntriesFile = {
  __proto__: null,
  getMenuEntries: getMenuEntries$c,
  id: id$8
};

const id$7 = Go$1;
const getMenuEntries$b = () => {
  return [];
};

const MenuEntriesGo = {
  __proto__: null,
  getMenuEntries: getMenuEntries$b,
  id: id$7
};

const About = 'About';
const Edit = 'Edit';
const File$1 = 'File';
const Go = 'Go';
const Help = 'Help';
const OpenProcessExplorer = 'Open Process Explorer';
const Run = 'Run';
const Selection = 'Selection';
const Terminal = 'Terminal';
const ToggleDeveloperTools = 'Toggle Developer Tools';
const View = 'View';

const toggleDeveloperTools = () => {
  return i18nString(ToggleDeveloperTools);
};
const openProcessExplorer = () => {
  return i18nString(OpenProcessExplorer);
};
const about = () => {
  return i18nString(About);
};

const id$6 = Help$1;
const getMenuEntries$a = async platform => {
  const entries = [];
  if (platform !== Web) {
    entries.push({
      command: 'Developer.toggleDeveloperTools',
      flags: None,
      id: 'toggleDeveloperTools',
      label: toggleDeveloperTools()
    }, {
      command: 'Developer.openProcessExplorer',
      flags: RestoreFocus,
      id: 'openProcessExplorer',
      label: openProcessExplorer()
    });
  }
  if (entries.length > 0) {
    entries.push(menuEntrySeparator);
  }
  entries.push({
    command: 'About.showAbout',
    flags: None,
    id: 'about',
    label: about()
  });
  return entries;
};

const MenuEntriesHelp = {
  __proto__: null,
  getMenuEntries: getMenuEntries$a,
  id: id$6
};

const getRecentlyOpened = async () => {
  const recentlyOpened = await invoke$2(/* RecentlyOpened.getRecentlyOpened */'RecentlyOpened.getRecentlyOpened');
  return recentlyOpened;
};

const File = 'file://';

const getTitle = (homeDir, uri) => {
  if (!uri) {
    return '';
  }
  if (uri.startsWith(File)) {
    return uri.slice(File.length);
  }
  return uri;
};

const getHomeDir = () => {
  return '';
};

const MAX_MENU_RECENT_ENTRIES = 10;
const toMenuItem = folder => {
  const homeDir = getHomeDir();
  const label = getTitle(homeDir, folder);
  return {
    args: [folder],
    command: 'Workspace.setPath',
    flags: None,
    label
  };
};
const id$5 = OpenRecent;
const getMenuEntries$9 = async () => {
  const allItems = await getRecentlyOpened();
  const itemsToShow = allItems.slice(0, MAX_MENU_RECENT_ENTRIES);
  const items = [];
  if (itemsToShow.length > 0) {
    items.push(...itemsToShow.map(toMenuItem), menuEntrySeparator);
  }
  items.push({
    command: 'QuickPick.showRecent',
    flags: None,
    id: 'more',
    label: '...'
  }, menuEntrySeparator, {
    command: 'RecentlyOpened.clearRecentlyOpened',
    flags: None,
    id: 'clearRecentlyOpened',
    label: 'clear'
  });
  return items;
};

const MenuEntriesOpenRecent = {
  __proto__: null,
  getMenuEntries: getMenuEntries$9,
  id: id$5
};

const id$4 = Run$1;
const getMenuEntries$8 = () => {
  return [];
};

const MenuEntriesRun = {
  __proto__: null,
  getMenuEntries: getMenuEntries$8,
  id: id$4
};

const id$3 = Selection$1;
const getMenuEntries$7 = () => {
  return [{
    command: 'Editor.selectAll',
    flags: None,
    id: 'selectAll',
    label: selectAll()
  }, {
    command: 'Editor.copyLineUp',
    flags: None,
    id: 'copyLineUp',
    label: copyLineUp()
  }, {
    command: 'Editor.copyLineDown',
    flags: None,
    id: 'copyLineDown',
    label: copyLineDown()
  }, {
    command: 'Editor.moveLineUp',
    flags: Disabled,
    id: 'moveLineUp',
    label: moveLineUp()
  }, {
    command: 'Editor.moveLineDown',
    flags: Disabled,
    id: 'moveLineDown',
    label: moveLineDown()
  }];
};

const MenuEntriesSelection = {
  __proto__: null,
  getMenuEntries: getMenuEntries$7,
  id: id$3
};

/**
 * @enum {string}
 */
const UiStrings = {
  NewTerminal: 'New Terminal'};
const newTerminal = () => {
  return i18nString(UiStrings.NewTerminal);
};

const id$2 = Terminal$1;
const getMenuEntries$6 = () => {
  return [{
    args: ['Terminal'],
    command: 'Layout.togglePanel',
    flags: None,
    id: 'newTerminal',
    label: newTerminal()
  }];
};

const MenuEntriesTerminal = {
  __proto__: null,
  getMenuEntries: getMenuEntries$6,
  id: id$2
};

const file = () => {
  return i18nString(File$1);
};
const edit = () => {
  return i18nString(Edit);
};
const selection = () => {
  return i18nString(Selection);
};
const view = () => {
  return i18nString(View);
};
const go = () => {
  return i18nString(Go);
};
const run = () => {
  return i18nString(Run);
};
const terminal = () => {
  return i18nString(Terminal);
};
const help = () => {
  return i18nString(Help);
};

const getMenuEntries$5 = () => {
  return [{
    flags: SubMenu,
    id: File$2,
    label: file()
  }, {
    flags: SubMenu,
    id: Edit$1,
    label: edit()
  }, {
    flags: SubMenu,
    id: Selection$1,
    label: selection()
  }, {
    flags: SubMenu,
    id: View$1,
    label: view()
  }, {
    flags: SubMenu,
    id: Go$1,
    label: go()
  }, {
    flags: SubMenu,
    id: Run$1,
    keyboardShortCut: 'Alt+r',
    label: run()
  }, {
    flags: SubMenu,
    id: Terminal$1,
    keyboardShortCut: 'Alt+t',
    label: terminal()
  }, {
    flags: SubMenu,
    id: Help$1,
    keyboardShortCut: 'Alt+h',
    label: help()
  }];
};

const getMenuEntries$4 = () => {
  return [{
    flags: None,
    id: File$2,
    label: file()
  }, {
    flags: None,
    id: Edit$1,
    label: edit()
  }, {
    flags: None,
    id: Selection$1,
    label: selection()
  }, {
    flags: None,
    id: View$1,
    label: view()
  }, {
    flags: None,
    id: Go$1,
    label: go()
  }, {
    flags: None,
    id: Help$1,
    label: help()
  }];
};

const getFn = platform => {
  switch (platform) {
    case Web:
      return getMenuEntries$4;
    default:
      return getMenuEntries$5;
  }
};
const id$1 = TitleBar;
const getMenuEntries$3 = async platform => {
  const fn = getFn(platform);
  return fn();
};

const MenuEntriesTitleBar = {
  __proto__: null,
  getMenuEntries: getMenuEntries$3,
  id: id$1
};

const id = View$1;
const getMenuEntries$2 = () => {
  return [];
};

const MenuEntriesView = {
  __proto__: null,
  getMenuEntries: getMenuEntries$2,
  id
};

const menus$1 = [MenuEntriesEdit, MenuEntriesFile, MenuEntriesGo, MenuEntriesHelp, MenuEntriesRun, MenuEntriesSelection, MenuEntriesTerminal, MenuEntriesTitleBar, MenuEntriesView, MenuEntriesOpenRecent];
const getMenus = () => {
  return menus$1;
};
const getMenuEntries$1 = async (id, ...args) => {
  // @ts-ignore
  return invoke$2('Menu.getMenuEntries', id, ...args);
};
const getMenuEntries2 = async (uid, menuId, ...args) => {
  // @ts-ignore
  return invoke$2('Menu.getMenuEntries2', uid, menuId, ...args);
};

const addKeyBindings = menuEntries => {
  const keyBindings = state.matchingKeyBindings;
  const newMenuEntries = [];
  for (const menuEntry of menuEntries) {
    const keyBinding = getCommandKeyBinding(keyBindings, menuEntry.command, menuEntry.args);
    const key = keyBinding ? keyBinding.key : 0;
    const newMenuEntry = {
      ...menuEntry,
      key
    };
    newMenuEntries.push(newMenuEntry);
  }
  return newMenuEntries;
};
const getMenuEntriesWithKeyBindings = async (id, ...args) => {
  const menuEntries = await getMenuEntries$1(id, ...args);
  const newMenuEntries = addKeyBindings(menuEntries);
  return newMenuEntries;
};
const getMenuEntriesWithKeyBindings2 = async (uid, menuId, ...args) => {
  // @ts-ignore
  const menuEntries = await getMenuEntries2(uid, menuId, ...args);
  const newMenuEntries = addKeyBindings(menuEntries);
  return newMenuEntries;
};

const Menu$1 = 'menu';
const MenuItem$1 = 'menuitem';
const MenuItemCheckBox = 'menuitemcheckbox';
const Separator = 'separator';

const MaskIconCheck = 'MaskIconCheck';
const Menu = 'Menu';
const MenuItem = 'MenuItem';
const MenuItemCheckMark = 'MenuItemCheckMark';
const MenuItemCheckmarkIcon = 'MenuItemCheckmarkIcon';
const MenuItemDisabled = 'MenuItemDisabled';
const MenuItemFocused = 'MenuItemFocused';
const MenuItemKeyBinding = 'MenuItemKeyBinding';
const MenuItemSeparator = 'MenuItemSeparator';
const MenuItemSeparatorLine = 'MenuItemSeparatorLine';
const MenuItemSubMenu = 'MenuItemSubMenu';
const MenuItemSubMenuArrowRight = 'MenuItemSubMenuArrowRight';

const mergeClassNames = (...classNames) => {
  return classNames.filter(Boolean).join(' ');
};

const text = data => {
  return {
    childCount: 0,
    text: data,
    type: Text
  };
};

const Div = 4;
const Span = 8;

const checkboxChecked = {
  ariaChecked: true,
  childCount: 2,
  className: mergeClassNames(MenuItem, MenuItemCheckMark),
  role: MenuItemCheckBox,
  tabIndex: -1,
  type: Div
};
const checkMark = {
  className: mergeClassNames(MenuItemCheckmarkIcon, MaskIconCheck),
  type: Div
};
const getMenuItemCheckedDom = menuItem => {
  const {
    label
  } = menuItem;
  return [checkboxChecked, checkMark, text(label)];
};

const getKeyBindingString = (key, altKey, ctrlKey, shiftKey, metaKey) => {
  let string = '';
  if (ctrlKey) {
    string += 'Ctrl+';
  }
  if (shiftKey) {
    string += 'Shift+';
  }
  string += key.toUpperCase();
  return string;
};

const getMenuItemDefaultDom = menuItem => {
  const {
    isFocused,
    key,
    label
  } = menuItem;
  let className = MenuItem;
  if (isFocused) {
    className += ' ' + MenuItemFocused;
  }
  const dom = [{
    childCount: 1,
    className,
    role: MenuItem$1,
    tabIndex: -1,
    type: Div
  }, text(label)];
  if (key) {
    dom[0].childCount++;
    const parsedKey = parseKey(key);
    const keyBindingsString = getKeyBindingString(parsedKey.key, false, parsedKey.isCtrl, parsedKey.isShift);
    dom.push({
      childCount: 1,
      className: MenuItemKeyBinding,
      type: Span
    }, text(keyBindingsString));
  }
  return dom;
};

const disabled = {
  ariaDisabled: true,
  childCount: 1,
  className: `${MenuItem} ${MenuItemDisabled}`,
  disabled: true,
  role: MenuItem$1,
  tabIndex: -1,
  type: Div
};
const getMenuItemDisabledDom = menuItem => {
  const {
    label
  } = menuItem;
  return [disabled, text(label)];
};

const getMenuItemsNoopDom = () => {
  return [{
    childCount: 1,
    type: Div$1
  }, text(`Unsupport menu item type`)];
};

const separator = {
  childCount: 1,
  className: MenuItemSeparator,
  role: Separator,
  type: Div
};
const separatorLine = {
  childCount: 0,
  className: MenuItemSeparatorLine,
  type: Div
};
const getMenuItemSeparatorDom = menuItem => {
  return [separator, separatorLine];
};

const arrowRight = {
  childCount: 0,
  className: MenuItemSubMenuArrowRight,
  type: Div
};
const getMenuItemSubMenuDom = menuItem => {
  const {
    isExpanded,
    isFocused,
    label,
    level
  } = menuItem;
  let className = MenuItem;
  className += ' ' + MenuItemSubMenu;
  if (isFocused) {
    className += ' ' + MenuItemFocused;
  }
  return [{
    ariaExpanded: isExpanded,
    ariaHasPopup: true,
    ariaOwns: isExpanded ? `Menu-${level + 1}` : undefined,
    childCount: 2,
    className,
    role: MenuItem$1,
    tabIndex: -1,
    type: Div
  }, text(label), arrowRight];
};

const checkboxUnchecked = {
  ariaChecked: false,
  childCount: 1,
  className: MenuItem,
  role: MenuItemCheckBox,
  tabIndex: -1,
  type: Div
};
const getMenuItemUncheckedDom = menuItem => {
  const {
    label
  } = menuItem;
  return [checkboxUnchecked, text(label)];
};

const getMenuItemRenderer = flags => {
  switch (flags) {
    case Checked:
      return getMenuItemCheckedDom;
    case Disabled:
      return getMenuItemDisabledDom;
    case Ignore:
    case None:
    case RestoreFocus:
      return getMenuItemDefaultDom;
    case Separator$1:
      return getMenuItemSeparatorDom;
    case SubMenu:
      return getMenuItemSubMenuDom;
    case Unchecked:
      return getMenuItemUncheckedDom;
    default:
      return getMenuItemsNoopDom;
  }
};

const getMenuItemVirtualDom = menuItem => {
  const {
    flags
  } = menuItem;
  const fn = getMenuItemRenderer(flags);
  return fn(menuItem);
};

const getMenuVirtualDom = menuItems => {
  const dom = [{
    childCount: menuItems.length,
    className: Menu,
    role: Menu$1,
    tabIndex: -1,
    type: Div
  }, ...menuItems.flatMap(getMenuItemVirtualDom)];
  return dom;
};

const getVisible = (items, focusedIndex, expanded, level) => {
  const visibleItems = [];
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const {
      flags,
      label
    } = item;
    visibleItems.push({
      flags,
      isExpanded: i === focusedIndex && expanded,
      isFocused: i === focusedIndex,
      key: item.key,
      label,
      level
    });
  }
  return visibleItems;
};

const CONTEXT_MENU_WIDTH = 250;
const getMenuWidth = () => {
  return CONTEXT_MENU_WIDTH;
};

// TODO lazyload menuEntries and use Command.execute (maybe)
const CONTEXT_MENU_ITEM_HEIGHT = 26;
const CONTEXT_MENU_SEPARATOR_HEIGHT = 11;
const CONTEXT_MENU_PADDING = 8;
const getMenuHeight = items => {
  let height = CONTEXT_MENU_PADDING;
  for (const item of items) {
    switch (item.flags) {
      case Separator$1:
        height += CONTEXT_MENU_SEPARATOR_HEIGHT;
        break;
      default:
        height += CONTEXT_MENU_ITEM_HEIGHT;
        break;
    }
  }
  return height;
};

const getMenuHideCommands = async (restoreFocus = true) => {
  if (getCount() === 0) {
    return {
      commands: [],
      newMenus: []
    };
  }
  reset();
  return {
    commands: [/* Menu.hide */'Menu.hide', /* restoreFocus */restoreFocus],
    newMenus: []
  };
};

const hide = async (restoreFocus = true) => {
  const {
    commands
  } = await getMenuHideCommands(restoreFocus);
  if (commands.length > 0) {
    // @ts-ignore
    await invoke$3(...commands);
  }
};

// TODO lazyload menuEntries and use Command.execute (maybe)
const MENU_WIDTH = 150;

// TODO difference between focusing with mouse or keyboard
// with mouse -> open submenu
// with keyboard -> don't open submenu, only focus

// TODO handle printable letter and focus item that starts with that letter

// TODO pageup / pagedown keys

// TODO more tests

const show$2 = async (x, y, id, mouseBlocking = false, ...args) => {
  const items = await getMenuEntriesWithKeyBindings(id, ...args);
  const bounds = await getMenuBounds(x, y, items);
  const menu = addMenuInternal({
    args,
    focusedIndex: -1,
    id,
    items,
    level: getCount(),
    x: bounds.x,
    y: bounds.y
  });
  const visible = getVisible(menu.items, -1, false, menu.level);
  const dom = getMenuVirtualDom(visible).slice(1);
  await invoke$1(/* Menu.show */'Menu.showMenu', /* x */bounds.x, /* y */bounds.y, /* width */bounds.width, /* height */bounds.height, /* items */menu.items, /* level */menu.level, /* parentIndex */-1, /* dom */dom, /* mouseBlocking */mouseBlocking);
};
const show2$2 = async (uid, menuId, x, y, mouseBlocking = false, ...args) => {
  const items = await getMenuEntriesWithKeyBindings2(uid, menuId, ...args);
  const bounds = await getMenuBounds(x, y, items);
  const menu = addMenuInternal({
    args,
    focusedIndex: -1,
    id: menuId,
    items,
    level: getCount(),
    uid,
    x: bounds.x,
    y: bounds.y
  });
  const visible = getVisible(menu.items, -1, false, menu.level);
  const dom = getMenuVirtualDom(visible).slice(1);
  await invoke$1(/* Menu.show */'Menu.showMenu', /* x */bounds.x, /* y */bounds.y, /* width */bounds.width, /* height */bounds.height, /* items */menu.items, /* level */menu.level, /* parentIndex */-1, /* dom */dom, /* mouseBlocking */mouseBlocking);
};

const show$1 = async (x, y, id, ...args) => {
  // @ts-ignore
  await hide();
  // TODO handle error
  // TODO race condition
  await show$2(x, y, id, /* mouseBlocking */true, ...args);
  // TODO maybe only send labels and keybindings to ui (id not needed on ui)
  // TODO what about separators?
};
const show2$1 = async (uid, menuId, x, y, ...args) => {
  // @ts-ignore
  await hide();
  // TODO handle error
  // TODO race condition
  await show2$2(uid, menuId, x, y, /* mouseBlocking */true, ...args);
  // TODO maybe only send labels and keybindings to ui (id not needed on ui)
  // TODO what about separators?
};

const ContextMenuBrowser = {
  __proto__: null,
  show: show$1,
  show2: show2$1
};

const getModule = async platform => {
  return ContextMenuBrowser;
};
const show = async (x, y, id, ...args) => {
  number(x);
  number(y);
  number(id);
  const module = await getModule();
  return module.show(x, y, id, ...args);
};
const show2 = async (uid, menuId, x, y, ...args) => {
  number(uid);
  number(menuId);
  number(x);
  number(y);
  const module = await getModule();
  // @ts-ignore
  return module.show2(uid, menuId, x, y, ...args);
};

const RenderEntries = 1;
const RenderFocusedIndex = 2;
const RenderMenus = 3;

const diffType$2 = RenderEntries;
const isEqual$2 = (oldState, newState) => {
  return oldState.titleBarEntries === newState.titleBarEntries && oldState.width === newState.width && oldState.focusedIndex === newState.focusedIndex && oldState.isMenuOpen === newState.isMenuOpen;
};

const diffType$1 = RenderFocusedIndex;
const isEqual$1 = (oldState, newState) => {
  return oldState.focusedIndex === newState.focusedIndex && oldState.isMenuOpen === newState.isMenuOpen;
};

const diffType = RenderMenus;
const isEqual = (oldState, newState) => {
  return oldState.menus === newState.menus;
};

const modules = [isEqual$2, isEqual$1, isEqual];
const numbers = [diffType$2, diffType$1, diffType];

const diff = (oldState, newState) => {
  const diffResult = [];
  for (let i = 0; i < modules.length; i++) {
    const fn = modules[i];
    if (!fn(oldState, newState)) {
      diffResult.push(numbers[i]);
    }
  }
  return diffResult;
};

const create = () => {
  const states = Object.create(null);
  return {
    get(uid) {
      return states[uid];
    },
    set(uid, oldState, newState) {
      states[uid] = {
        newState,
        oldState
      };
    }
  };
};

const {
  get,
  set
} = create();

const diff2 = uid => {
  const {
    newState,
    oldState
  } = get(uid);
  return diff(oldState, newState);
};

const canBeFocused = item => {
  switch (item.flags) {
    case Disabled$1:
    case Separator$2:
      return false;
    default:
      return true;
  }
};

const focusIndex = async (menu, index) => {
  if (menu.items.length === 0) {
    return;
  }
  const oldFocusedIndex = menu.focusedIndex;
  menu.focusedIndex = index;
  await invoke$3('Menu.focusIndex', /* level */menu.level, /* oldFocusedIndex */oldFocusedIndex, /* newFocusedIndex */index);
};

const getCurrentMenu = () => {
  if (getCount() === 0) {
    warn('menu not available');
  }
  return getAll().at(-1);
};
const getIndexToFocusNextStartingAt = (items, startIndex) => {
  for (let i = startIndex; i < startIndex + items.length; i++) {
    const index = i % items.length;
    const item = items[index];
    if (canBeFocused(item)) {
      return index;
    }
  }
  return -1;
};
const getIndexToFocusFirst = items => {
  return getIndexToFocusNextStartingAt(items, 0);
};
const focusFirst = async () => {
  const menu = getCurrentMenu();
  if (getCount() === 0) {
    return;
  }
  const indexToFocus = getIndexToFocusFirst(menu.items);
  await focusIndex(menu, indexToFocus);
};

const getIndexToFocusPreviousStartingAt$1 = (items, startIndex) => {
  for (let i = startIndex; i > startIndex - items.length; i--) {
    const index = (i + items.length) % items.length;
    const item = items[index];
    if (canBeFocused(item)) {
      return index;
    }
  }
  return -1;
};
const focusLast = async () => {
  const menu = getCurrentMenu();
  const indexToFocus = getIndexToFocusPreviousStartingAt$1(menu.items, menu.items.length - 1);
  await focusIndex(menu, indexToFocus);
};

const getIndexToFocusNext = menu => {
  const startIndex = menu.focusedIndex + 1;
  return getIndexToFocusNextStartingAt(menu.items, startIndex);
};
const focusNext = async () => {
  const menu = getCurrentMenu();
  if (menu.items.length === 0) {
    return;
  }
  const indexToFocus = getIndexToFocusNext(menu);
  await focusIndex(menu, indexToFocus);
};

// TODO this code seems a bit too complicated, maybe it can be simplified
const getIndexToFocusPreviousStartingAt = (items, startIndex) => {
  for (let i = startIndex; i > startIndex - items.length; i--) {
    const index = (i + items.length) % items.length;
    const item = items[index];
    if (canBeFocused(item)) {
      return index;
    }
  }
  return -1;
};
const getIndexToFocusPrevious = menu => {
  const startIndex = menu.focusedIndex === -1 ? menu.items.length - 1 : menu.focusedIndex - 1;
  return getIndexToFocusPreviousStartingAt(menu.items, startIndex);
};
const focusPrevious = async () => {
  const menu = getCurrentMenu();
  if (menu.items.length === 0) {
    return;
  }
  const indexToFocus = getIndexToFocusPrevious(menu);
  await focusIndex(menu, indexToFocus);
};

const commandsIds = ['closeMenu', 'handleClickAt', 'focus', 'focusFirst', 'focusIndex', 'focusLast', 'focusNext', 'focusPrevious', 'handleClick', 'handleFocus', 'handleFocusOut', 'handleKeyArrowDown', 'handleKeyArrowLeft', 'handleKeyArrowRight', 'handleKeyArrowUp', 'handleKeyEnd', 'handleKeyEnter', 'handleKeyEscape', 'handleKeyHome', 'handleKeySpace', 'handleMenuClick', 'handleMenuMouseOver', 'handleMouseOut', 'handleMouseOver', 'handlePointerOver', 'handlePointerOut', 'toggleIndex', 'toggleMenu'];

const getCommandIds = () => {
  return commandsIds;
};

const FocusTitleBarMenuBar = 26;

const getKeyBindings = () => {
  return [{
    command: 'TitleBarMenuBar.handleKeyArrowDown',
    key: DownArrow$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeyArrowUp',
    key: UpArrow$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeyArrowRight',
    key: RightArrow$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeyArrowLeft',
    key: LeftArrow$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeySpace',
    key: Space$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeyHome',
    key: Home$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeyEnd',
    key: End$1,
    when: FocusTitleBarMenuBar
  }, {
    command: 'TitleBarMenuBar.handleKeyEscape',
    key: Escape$1,
    when: FocusTitleBarMenuBar
  }];
};

const menus = [MenuEntriesEdit, MenuEntriesFile, MenuEntriesGo, MenuEntriesHelp, MenuEntriesRun, MenuEntriesSelection, MenuEntriesTerminal, MenuEntriesTitleBar, MenuEntriesView, MenuEntriesOpenRecent];

const getMenuIds = () => {
  return menus.map(menu => menu.id);
};
const getMenuEntries = (id, platform) => {
  const menu = menus.find(item => item.id === id);
  if (!menu) {
    return [];
  }
  return menu.getMenuEntries(platform);
};

// TODO lazyload menuEntries and use Command.execute (maybe)

// TODO difference between focusing with mouse or keyboard
// with mouse -> open submenu
// with keyboard -> don't open submenu, only focus

// TODO this code seems a bit too complicated, maybe it can be simplified

// TODO handle printable letter and focus item that starts with that letter

// TODO pageup / pagedown keys

// TODO more tests

const getMenuShowCommands = async (items, menuId, x, y, mouseBlocking = false) => {
  const bounds = await getMenuBounds(x, y, items);
  const menu = addMenuInternal({
    focusedIndex: -1,
    id: menuId,
    items,
    level: getCount(),
    x: bounds.x,
    y: bounds.y
  });
  const visible = getVisible(menu.items, -1, false, menu.level);
  const dom = getMenuVirtualDom(visible).slice(1);
  return {
    commands: [/* Menu.show */'Menu.showMenu', /* x */bounds.x, /* y */bounds.y, /* width */bounds.width, /* height */bounds.height, /* items */menu.items, /* level */menu.level, /* parentIndex */-1, /* dom */dom, /* mouseBlocking */mouseBlocking],
    menu,
    menus: getAll()
  };
};

const handleContextMenu = state => {
  return state;
};

const hideSubMenus = async level => {
  if (level < getCount()) {
    set$1(getAll().slice(0, level + 1));
    await invoke$3('Menu.hideSubMenu', /* level */level + 1);
  }
};

const MENU_DELAY_TRIANGLE = 300;
const resolveAfterTimeout = async () => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  setTimeout(resolve, MENU_DELAY_TRIANGLE);
  await promise;
};

const getSubMenuItems = async (parentMenu, item) => {
  const args = item.args || parentMenu.args || [];
  if (typeof parentMenu.uid === 'number') {
    return getMenuEntries2(parentMenu.uid, item.id, ...args);
  }
  return getMenuEntries$1(item.id, ...args);
};
const getOpenSubMenuToLeft = parentMenu => {
  return parentMenu.openSubMenuToLeft === true || parentMenu.args?.[0]?.openSubMenuToLeft === true;
};
const getSubMenuX = (parentMenu, openSubMenuToLeft) => {
  return openSubMenuToLeft ? parentMenu.x - MENU_WIDTH : parentMenu.x + MENU_WIDTH;
};
const showSubMenuAtEnter = async (level, index, enterX, enterY) => {
  const parentMenu = get$1(level);
  const item = parentMenu.items[index];
  const existingSubMenu = getAll()[level + 1];
  if (existingSubMenu?.parentIndex === index && existingSubMenu.id === item.id) {
    return;
  }
  set$1(getAll().slice(0, level + 1));
  const subMenuItems = await getSubMenuItems(parentMenu, item);
  const openSubMenuToLeft = getOpenSubMenuToLeft(parentMenu);
  const subMenu = addMenuInternal({
    args: item.args || parentMenu.args || [],
    enterX,
    enterY,
    focusedIndex: -1,
    id: item.id,
    items: subMenuItems,
    level: getCount(),
    openSubMenuToLeft,
    parentIndex: index,
    uid: parentMenu.uid,
    x: getSubMenuX(parentMenu, openSubMenuToLeft),
    y: parentMenu.y + index * 25
  });
  const width = getMenuWidth();
  const height = getMenuHeight(subMenuItems);
  const visible = getVisible(subMenu.items, -1, false, subMenu.level);
  const dom = getMenuVirtualDom(visible).slice(1);
  await invoke$3(/* Menu.showMenu */'Menu.showMenu', /* x */subMenu.x, /* y */subMenu.y, /* width */width, /* height */height, /* items */subMenu.items, /* level */subMenu.level, /* parentIndex */index, /* dom */dom);
};
const showSubMenu = async (level, index) => {
  return showSubMenuAtEnter(level, index, -1, -1);
};

const handleMouseEnter = async (level, index, enterX, enterY, enterTimeStamp) => {
  setTimestamp(enterTimeStamp);
  if (level >= getCount()) {
    return;
  }
  const menu = get$1(level);
  if (menu.focusedIndex === index) {
    return;
  }
  if (level < getCount() - 1) {
    const subMenu = get$1(level + 1);
    const subMenuEnterX = subMenu.enterX;
    // TODO should check for triangle position here
    if (enterX >= subMenuEnterX) {
      await resolveAfterTimeout();
      if (getLatestTimestamp() !== enterTimeStamp) {
        return;
      }
    }
  }
  const item = menu.items[index];
  await focusIndex(menu, index);
  switch (item.flags) {
    case SubMenu:
      await showSubMenuAtEnter(menu.level, index, enterX, enterY);
      break;
    default:
      await hideSubMenus(menu.level);
      break;
  }
};

const handleMouseLeave = async () => {
  const menu = get$1(0);
  if (menu.items.length === 0) {
    return;
  }
  const oldFocusedIndex = menu.focusedIndex;
  menu.focusedIndex = -1;
  return {
    commands: [/* Menu.focusIndex */'Menu.focusIndex', /* level */menu.level, /* oldFocusedIndex */oldFocusedIndex, /* newFocusedIndex */-1],
    menus: getAll()
  };
};

const loadContent = async (state, titleBarEntries) => {
  return {
    ...state
  };
};

const renderFocusedIndex = (oldState, newState) => {
  if (newState.focusedIndex === -1) {
    return [];
  }
  return [FocusSelector, '.ViewletTitleBarMenuBar'];
};

const SetMenus = 'setMenus';

const renderMenus = (oldState, newState) => {
  const oldMenus = oldState.menus;
  const newMenus = newState.menus;
  const oldLength = oldMenus.length;
  const newLength = newMenus.length;
  const commonLength = Math.min(oldLength, newLength);
  const changes = [];
  for (let i = 0; i < commonLength; i++) {
    const oldMenu = oldMenus[i];
    const newMenu = newMenus[i];
    if (oldMenu !== newMenu) {
      const visible = getVisible(newMenu.items, newMenu.focusedIndex, newMenu.expanded, newMenu.level);
      const dom = getMenuVirtualDom(visible).slice(1);
      changes.push([/* method */'updateMenu', newMenu, /* newLength */newLength, dom]);
    }
  }
  const difference = newLength - oldLength;
  if (difference > 0) {
    const newMenu = newMenus.at(-1);
    const visible = getVisible(newMenu.items, newMenu.focusedIndex, newMenu.expanded, newMenu.level);
    const dom = getMenuVirtualDom(visible).slice(1);
    changes.push(['addMenu', newMenu, dom]);
  } else if (difference < 0) {
    changes.push(['closeMenus', newLength]);
  }
  return ['Viewlet.send', newState.uid, /* method */SetMenus, /* changes */changes, newState.uid];
};

const getRenderer = diffType => {
  switch (diffType) {
    case RenderFocusedIndex:
      return renderFocusedIndex;
    case RenderMenus:
      return renderMenus;
    default:
      throw new Error('unknown renderer');
  }
};

const applyRender = (oldState, newState, diffResult) => {
  const commands = [];
  for (const item of diffResult) {
    const fn = getRenderer(item);
    const result = fn(oldState, newState);
    if (result.length > 0) {
      commands.push(result);
    }
  }
  return commands;
};

const render2 = async (uid, diffResult) => {
  const {
    newState,
    oldState
  } = get(uid);
  set(uid, newState, newState);
  const commands = applyRender(oldState, newState, diffResult);
  return commands;
};

const HandleClick = 'handleClick';
const HandleFocusIn = 'handleFocusIn';
const HandlePointerOut = 'handlePointerOut';
const HandlePointerOver = 'handlePointerOver';
const HandleMenuClick = 'handleMenuClick';
const HandleMenuMouseOver = 'handleMenuMouseOver';

const renderEventListeners = () => {
  return [{
    name: HandleFocusIn,
    params: ['handleFocus']
  }, {
    name: HandleMenuClick,
    params: ['handleMenuClick', ClientX, ClientY]
  }, {
    name: HandleMenuMouseOver,
    params: ['handleMenuMouseOver', ClientX, ClientY]
  }, {
    name: HandleClick,
    params: ['handleClickAt', Button, ClientX, ClientY]
  }, {
    name: HandlePointerOut,
    params: ['handlePointerOut', ClientX, ClientY]
  }, {
    name: HandlePointerOver,
    params: ['handlePointerOver', ClientX, ClientY]
  }];
};

const saveState = uid => {
  return {
    x: 1
  };
};

const selectIndexDefault = async () => {};

const {
  invoke} = RendererWorker;

const executeMenuItemCommand = async item => {
  await invoke(item.command, ...(item.args || []));
};

const selectIndexIgnore = async (menu, item) => {
  await executeMenuItemCommand(item);
};

const selectIndexNone = async (menu, item) => {
  await hide(/* restoreFocus */false);
  await executeMenuItemCommand(item);
};

const selectIndexRestoreFocus = async (menu, item) => {
  await hide(/* restoreFocus */true);
  await executeMenuItemCommand(item);
};

const selectIndexSubMenu = async (menu, item, index) => {
  if (menu.focusedIndex === index) {
    return;
  }
  await showSubMenu(menu.level, menu.focusedIndex);
};

const getSelectIndexFunction = flags => {
  switch (flags) {
    case Checked$1:
      return selectIndexRestoreFocus;
    case Ignore$1:
      return selectIndexIgnore;
    case None$1:
      return selectIndexNone;
    case RestoreFocus$1:
      return selectIndexRestoreFocus;
    case SubMenu$1:
      return selectIndexSubMenu;
    case Unchecked$1:
      return selectIndexRestoreFocus;
    default:
      return selectIndexDefault;
  }
};

const selectIndex = async (level, index) => {
  const menu = get$1(level);
  // TODO avoid assignment
  menu.focusedIndex = index;
  const item = menu.items[menu.focusedIndex];
  const fn = getSelectIndexFunction(item.flags);
  await fn(menu, item, index);
};

const selectItem = async text => {
  for (let level = 0; level < getCount(); level++) {
    const menu = get$1(level);
    for (let i = 0; i < menu.items.length; i++) {
      const item = menu.items[i];
      if (item.label === text) {
        return selectIndex(level, i);
      }
    }
  }
  throw new Error(`menu item not found: ${text}`);
};

const wrapCommand = fn => {
  const wrapped = async (uid, ...args) => {
    const {
      newState
    } = get(uid);
    const newerState = await fn(newState, ...args);
    if (newState === newerState) {
      return;
    }
    const latest = get(uid);
    set(uid, latest.oldState, newerState);
  };
  return wrapped;
};

const commandMap = {
  'Menu.closeSubMenu': closeSubMenu,
  'Menu.diff2': diff2,
  'Menu.focusFirst': focusFirst,
  'Menu.focusIndex': focusIndex,
  'Menu.focusLast': focusLast,
  'Menu.focusNext': focusNext,
  'Menu.focusPrevious': focusPrevious,
  'Menu.getCommands': getCommandIds,
  'Menu.getHideCommands': getMenuHideCommands,
  'Menu.getKeyBindings': getKeyBindings,
  'Menu.getMenuEntries': getMenuEntries,
  'Menu.getMenuIds': getMenuIds,
  'Menu.getMenus': getMenus,
  'Menu.getShowCommands': getMenuShowCommands,
  'Menu.handleContextMenu': handleContextMenu,
  'Menu.handleMouseEnter': handleMouseEnter,
  'Menu.handleMouseLeave': handleMouseLeave,
  'Menu.hide': hide,
  'Menu.hideSubMenus': hideSubMenus,
  'Menu.loadContent': wrapCommand(loadContent),
  'Menu.render2': render2,
  'Menu.renderEventListeners': renderEventListeners,
  'Menu.saveState': saveState,
  'Menu.selectIndex': selectIndex,
  'Menu.selectItem': selectItem,
  'Menu.show': show,
  'Menu.show2': show2,
  'Menu.showSubMenu': showSubMenu
};

const initializeRendererWorker = async () => {
  const rpc = await create$2({
    commandMap: commandMap
  });
  set$2(rpc);
};

const listen = async () => {
  await initializeRendererWorker();
  await initializeRendererProcess();
};

const main = async () => {
  await listen();
};

main();
