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$1 = 2;
const Array$1 = 3;
const String$1 = 4;
const Boolean$1 = 5;
const Function$1 = 6;
const Null = 7;
const Unknown = 8;
const getType = value => {
  switch (typeof value) {
    case 'number':
      return Number$1;
    case 'function':
      return Function$1;
    case 'string':
      return String$1;
    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;
  }
};
const object = value => {
  const type = getType(value);
  if (type !== Object$1) {
    throw new AssertionError('expected value to be of type object');
  }
};
const number = value => {
  const type = getType(value);
  if (type !== Number$1) {
    throw new AssertionError('expected value to be of type number');
  }
};
const string = value => {
  const type = getType(value);
  if (type !== String$1) {
    throw new AssertionError('expected value to be of type string');
  }
};

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);
    }
  }
};
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;
      if (stack) {
        Object.defineProperty(cause, 'stack', {
          configurable: true,
          enumerable: false,
          value: stack,
          writable: true
        });
      }
      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$1 = key => {
  return commands[key];
};
const execute = (command, ...args) => {
  const fn = getCommand$1(command);
  if (!fn) {
    throw new CommandNotFoundError(command);
  }
  return fn(...args);
};

const Two$1 = '2.0';
const callbacks = Object.create(null);
const get$2 = 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) => {
  {
    return string.indexOf(NewLine);
  }
};
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 restoreExistingError = (error, currentStack) => {
  if (typeof error.stack === 'string') {
    error.stack = error.stack + NewLine + currentStack;
  }
  return error;
};
const restoreMethodNotFoundError = (error, currentStack) => {
  const restoredError = new JsonRpcError(error.message);
  const parentStack = getParentStack(error);
  restoredError.stack = parentStack + NewLine + currentStack;
  return restoredError;
};
const restoreStackFromData = (restoredError, error, currentStack) => {
  if (error.data.stack && error.data.type && error.message) {
    restoredError.stack = error.data.type + ': ' + error.message + NewLine + error.data.stack + NewLine + currentStack;
    return;
  }
  if (error.data.stack) {
    restoredError.stack = error.data.stack;
  }
};
const applyDataProperties = (restoredError, error) => {
  if (!error.data) {
    return;
  }
  restoreStackFromData(restoredError, error, getCurrentStack());
  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;
  }
};
const applyDirectProperties = (restoredError, error) => {
  if (error.stack) {
    const lowerStack = restoredError.stack || '';
    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;
  }
};
const restoreMessageError = (error, _currentStack) => {
  const restoredError = constructError(error.message, error.type, error.name);
  if (error.data) {
    applyDataProperties(restoredError, error);
  } else {
    applyDirectProperties(restoredError, error);
  }
  return restoredError;
};
const restoreJsonRpcError = error => {
  const currentStack = getCurrentStack();
  if (error && error instanceof Error) {
    return restoreExistingError(error, currentStack);
  }
  if (error && error.code && error.code === MethodNotFound) {
    return restoreMethodNotFoundError(error, currentStack);
  }
  if (error && error.message) {
    return restoreMessageError(error);
  }
  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 = (...args) => {
  console.warn(...args);
};
const resolve = (id, response) => {
  const fn = get$2(id);
  if (!fn) {
    console.log(response);
    warn(`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$a = (message, result) => {
  return {
    id: message.id,
    jsonrpc: Two$1,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$a(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$9 = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};

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

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

const registerPromise = map => {
  const id = create$7();
  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$8(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$9(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$6 = 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$5 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  const {
    port1,
    port2
  } = new MessageChannel();
  await send(port1);
  return create$6({
    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$4 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  return createSharedLazyRpc(() => {
    return create$5({
      commandMap,
      isMessagePortOpen,
      send
    });
  });
};

const create$3 = 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$3 = (id, rpc) => {
  rpcs[id] = rpc;
};
const get$1 = id => {
  return rpcs[id];
};
const remove = id => {
  delete rpcs[id];
};

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

const Div = 4;
const Span = 8;
const Text = 12;
const Img = 17;

const ClientX = 'event.clientX';
const ClientY = 'event.clientY';
const DeltaMode = 'event.deltaMode';
const DeltaY = 'event.deltaY';

const Enter = 3;
const Escape = 8;
const Space$1 = 9;
const End = 255;
const Home = 12;
const UpArrow = 14;
const DownArrow = 16;

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

const EditorWorker$1 = 99;
const ExtensionManagementWorker = 9006;

const {
  invoke: invoke$1,
  invokeAndTransfer,
  set: set$2
} = create$2(EditorWorker$1);
const sendMessagePortToExtensionManagementWorker = async port => {
  await invokeAndTransfer('SendMessagePortToExtensionManagementWorker.sendMessagePortToExtensionManagementWorker', port, 0);
};
const applyEdit$1 = async (editorUid, changes) => {
  // @ts-ignore
  await invoke$1('Editor.applyEdit2', editorUid, changes);
};
const closeWidget = async (editorUid, widgetId, widgetName, focusId) => {
  // @ts-ignore
  await invoke$1('Editor.closeWidget2', editorUid, widgetId, widgetName, focusId);
};
const getLines$1 = async editorUid => {
  const lines = await invoke$1('Editor.getLines2', editorUid);
  return lines;
};
const getPositionAtCursor$1 = async parentUid => {
  const position = await invoke$1('Editor.getPositionAtCursor', parentUid);
  return position;
};
const getOffsetAtCursor$1 = async editorId => {
  // @ts-ignore
  return await invoke$1('Editor.getOffsetAtCursor', editorId);
};
const getSelections$1 = async editorUid => {
  const selections = await invoke$1('Editor.getSelections2', editorUid);
  return selections;
};
const getWordAtOffset2 = async editorUid => {
  return invoke$1('Editor.getWordAtOffset2', editorUid);
};
const getWordBefore$1 = async (editorUid, rowIndex, columnIndex) => {
  return invoke$1('Editor.getWordBefore2', editorUid, rowIndex, columnIndex);
};
const getUri = async editorUid => {
  // @ts-ignore
  return invoke$1('Editor.getUri', editorUid);
};
const getLanguageId = async editorUid => {
  // @ts-ignore
  return invoke$1('Editor.getLanguageId', editorUid);
};

const EditorWorker = {
  __proto__: null,
  applyEdit: applyEdit$1,
  closeWidget,
  getLanguageId,
  getLines: getLines$1,
  getOffsetAtCursor: getOffsetAtCursor$1,
  getPositionAtCursor: getPositionAtCursor$1,
  getSelections: getSelections$1,
  getUri,
  getWordAtOffset2,
  getWordBefore: getWordBefore$1,
  invoke: invoke$1,
  invokeAndTransfer,
  sendMessagePortToExtensionManagementWorker,
  set: set$2
};

const {
  invoke,
  set: set$1
} = create$2(ExtensionManagementWorker);

const toCommandId = key => {
  const dotIndex = key.indexOf('.');
  return key.slice(dotIndex + 1);
};
const create$1 = () => {
  const states = Object.create(null);
  const commandMapRef = {};
  return {
    clear() {
      for (const key of Object.keys(states)) {
        delete states[key];
      }
    },
    diff(uid, modules, numbers) {
      const {
        newState,
        oldState
      } = states[uid];
      const diffResult = [];
      for (let i = 0; i < modules.length; i++) {
        const fn = modules[i];
        if (!fn(oldState, newState)) {
          diffResult.push(numbers[i]);
        }
      }
      return diffResult;
    },
    dispose(uid) {
      delete states[uid];
    },
    get(uid) {
      return states[uid];
    },
    getCommandIds() {
      const keys = Object.keys(commandMapRef);
      const ids = keys.map(toCommandId);
      return ids;
    },
    getKeys() {
      return Object.keys(states).map(key => {
        return Number.parseFloat(key);
      });
    },
    registerCommands(commandMap) {
      Object.assign(commandMapRef, commandMap);
    },
    set(uid, oldState, newState) {
      states[uid] = {
        newState,
        oldState
      };
    },
    wrapCommand(fn) {
      const wrapped = async (uid, ...args) => {
        const {
          newState,
          oldState
        } = states[uid];
        const newerState = await fn(newState, ...args);
        if (oldState === newerState || newState === newerState) {
          return;
        }
        const latestOld = states[uid];
        const latestNew = {
          ...latestOld.newState,
          ...newerState
        };
        states[uid] = {
          newState: latestNew,
          oldState: latestOld.oldState
        };
      };
      return wrapped;
    },
    wrapGetter(fn) {
      const wrapped = (uid, ...args) => {
        const {
          newState
        } = states[uid];
        return fn(newState, ...args);
      };
      return wrapped;
    },
    wrapLoadContent(fn) {
      const wrapped = async (uid, ...args) => {
        const {
          newState,
          oldState
        } = states[uid];
        const result = await fn(newState, ...args);
        const {
          error,
          state
        } = result;
        if (oldState === state || newState === state) {
          return {
            error
          };
        }
        const latestOld = states[uid];
        const latestNew = {
          ...latestOld.newState,
          ...state
        };
        states[uid] = {
          newState: latestNew,
          oldState: latestOld.oldState
        };
        return {
          error
        };
      };
      return wrapped;
    }
  };
};
const terminate = () => {
  globalThis.close();
};

const FocusEditorCompletions = 9;
const FocusEditorRename = 11;

const Completion = 3;

const close = async state => {
  const {
    editorUid
  } = state;
  await closeWidget(editorUid, Completion, 'Completions', FocusEditorCompletions);
  return state;
};

const {
  dispose: dispose$1,
  get,
  set,
  wrapCommand
} = create$1();

const create = (uid, x, y, width, height, editorUid, editorLanguageId) => {
  const state = {
    deltaY: 0,
    editorLanguageId,
    editorUid,
    finalDeltaY: 0,
    focused: false,
    focusedIndex: -1,
    headerHeight: 0,
    height: 0,
    itemHeight: 20,
    items: [],
    leadingWord: '',
    maxHeight: 150,
    maxItems: 8,
    maxLineY: 0,
    minLineY: 0,
    uid,
    unfilteredItems: [],
    version: 0,
    width: 0,
    x: 0,
    y: 0
  };
  set(uid, state, state);
};

const isEqual$4 = (oldState, newState) => {
  return oldState.x === newState.x && oldState.y === newState.y && oldState.width === newState.width && oldState.height === newState.height;
};

const isEqual$3 = (oldState, newState) => {
  return oldState.version === newState.version;
};

const isEqual$2 = (oldState, newState) => {
  return oldState.version === newState.version;
};

const isEqual$1 = (oldState, newState) => {
  return oldState.items === newState.items && oldState.focusedIndex === newState.focusedIndex && oldState.minLineY === newState.minLineY && oldState.maxLineY === newState.maxLineY;
};

const isEqual = (oldState, newState) => {
  return oldState.version === newState.version;
};

const RenderItems = 1;
const RenderBounds = 8;
const RenderEventListeners = 11;
const RenderUid = 12;
const RenderContent = 13;
const RenderFocusContext = 14;

const modules = [isEqual$3, isEqual$1, isEqual$4, isEqual, isEqual$2];
const numbers = [RenderEventListeners, RenderItems, RenderBounds, RenderUid, RenderFocusContext];

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 diff2 = uid => {
  const {
    newState,
    oldState
  } = get(uid);
  const diffResult = diff(oldState, newState);
  return diffResult;
};

const dispose = uid => {
  dispose$1(uid);
};

const focusIndex = (state, index) => {
  const newState = {
    ...state,
    focused: true,
    focusedIndex: index
  };
  return newState;
};

const focusFirst = state => {
  const firstIndex = 0;
  return focusIndex(state, firstIndex);
};

const focusNext = state => {
  const {
    focusedIndex
  } = state;
  const nextIndex = focusedIndex + 1;
  return focusIndex(state, nextIndex);
};

const focusPrevious = state => {
  const previousIndex = state.focusedIndex - 1;
  return focusIndex(state, previousIndex);
};

const openDetails = async state => {
  // TODO ask editor worker to add completion details widget
  return state;
};

const applyEdit = async (editorUid, changes) => {
  await applyEdit$1(editorUid, changes);
};

const {
  getLines
} = EditorWorker;

const {
  getSelections
} = EditorWorker;

const getSelectionPairs = (selections, i) => {
  const first = selections[i];
  const second = selections[i + 1];
  const third = selections[i + 2];
  const fourth = selections[i + 3];
  if (first > third || first === third && second >= fourth) {
    return [third, fourth, first, second, 1];
  }
  return [first, second, third, fourth, 0];
};

const getSelectionText = (lines, range) => {
  const startRowIndex = range.start.rowIndex;
  const startColumnIndex = range.start.columnIndex;
  const endRowIndex = Math.min(range.end.rowIndex, lines.length - 1);
  const endColumnIndex = range.end.columnIndex;
  if (startRowIndex === endRowIndex) {
    return [lines[startRowIndex].slice(startColumnIndex, endColumnIndex)];
  }
  const selectedLines = [lines[startRowIndex].slice(startColumnIndex), ...lines.slice(startRowIndex + 1, endRowIndex), lines[endRowIndex].slice(0, endColumnIndex)];
  return selectedLines;
};

const replaceRange = (lines, ranges, replacement, origin) => {
  const changes = [];
  const rangesLength = ranges.length;
  for (let i = 0; i < rangesLength; i += 4) {
    const [selectionStartRow, selectionStartColumn, selectionEndRow, selectionEndColumn] = getSelectionPairs(ranges, i);
    const start = {
      columnIndex: selectionStartColumn,
      rowIndex: selectionStartRow
    };
    const end = {
      columnIndex: selectionEndColumn,
      rowIndex: selectionEndRow
    };
    const selection = {
      end,
      start
    };
    changes.push({
      deleted: getSelectionText(lines, selection),
      end: end,
      inserted: replacement,
      origin,
      start: start
    });
  }
  return changes;
};

const getText = async editorUid => {
  const lines = await getLines$1(editorUid);
  return lines.join('\n');
};
const getTextDocument = async (editorUid, languageId) => {
  const [text, uri] = await Promise.all([getText(editorUid), getUri(editorUid)]);
  return {
    documentId: editorUid,
    languageId,
    text,
    uri
  };
};
const executeCompletionProvider = async (editorUid, editorLanguageId, offset) => {
  const textDocument = await getTextDocument(editorUid, editorLanguageId);
  return invoke('Extensions.executeCompletionProvider', textDocument, offset);
};
const executeResolveCompletionItem = async (editorUid, offset, name, completionItem) => {
  const editorLanguageId = await getLanguageId(editorUid);
  const textDocument = await getTextDocument(editorUid, editorLanguageId);
  return invoke('Extensions.executeResolveCompletionItemProvider', textDocument, offset, name, completionItem);
};

const getOffsetAtCursor = async editorUid => {
  return getOffsetAtCursor$1(editorUid);
};

// TODO don't send unnecessary parts of completion item like matches
const resolveCompletion = async (editorUid, name, completionItem) => {
  try {
    string(name);
    object(completionItem);
    const offset = await getOffsetAtCursor(editorUid);
    const resolvedCompletionItem = await executeResolveCompletionItem(editorUid, offset, name, completionItem);
    return resolvedCompletionItem;
  } catch {
    return undefined;
  }
};

const getEdits = async (editorUid, leadingWord, completionItem) => {
  const word = completionItem.label;
  const resolvedItem = await resolveCompletion(editorUid, word, completionItem);
  const inserted = resolvedItem ? resolvedItem.snippet : word;
  const lines = await getLines(editorUid);
  const selections = await getSelections(editorUid);
  const [startRowIndex, startColumnIndex] = selections;
  const leadingWordLength = leadingWord.length;
  const replaceRange$1 = new Uint32Array([startRowIndex, startColumnIndex - leadingWordLength, startRowIndex, startColumnIndex]);
  const changes = replaceRange(lines, replaceRange$1, [inserted], '');
  return changes;
};

const select = async (state, completionItem) => {
  const {
    editorUid,
    leadingWord
  } = state;
  const changes = await getEdits(editorUid, leadingWord, completionItem);
  await applyEdit(editorUid, changes);
  return close(state);
};

const selectIndex = async (state, index) => {
  const {
    items
  } = state;
  if (index === -1) {
    return state;
  }
  if (index > items.length) {
    throw new Error('index too large');
  }
  const actualIndex = index;
  const completionItem = items[actualIndex];
  return select(state, completionItem);
};

const selectCurrent = state => {
  const {
    focusedIndex
  } = state;
  return selectIndex(state, focusedIndex);
};

const commandIds = ['handleSliderPointerDown', 'handleSliderPointerMove', 'initialize'];

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

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

const px = value => {
  return `${value}px`;
};
const position = (x, y) => {
  return `${x}px ${y}px`;
};

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

const getCommand = shortId => {
  return {
    args: ['Completions', `Completions.${shortId}`, 0, Completion],
    command: 'Editor.executeWidgetCommand'
  };
};
const getKeyBindings = () => {
  return [{
    key: DownArrow,
    ...getCommand('focusNext'),
    when: FocusEditorCompletions
  }, {
    key: UpArrow,
    ...getCommand('focusPrevious'),
    when: FocusEditorCompletions
  }, {
    key: Enter,
    ...getCommand('selectCurrent'),
    when: FocusEditorCompletions
  }, {
    key: End,
    ...getCommand('focusLast'),
    when: FocusEditorCompletions
  }, {
    key: Home,
    ...getCommand('focusFirst'),
    when: FocusEditorCompletions
  }, {
    key: CtrlCmd | Space$1,
    ...getCommand('toggleDetails'),
    when: FocusEditorCompletions
  }, {
    key: Escape,
    ...getCommand('close'),
    when: FocusEditorCompletions
  }];
};

const Diagonal = 1;
const Left = 2;

// based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT)

const createTable = size => {
  const table = [];
  for (let i = 0; i < size; i++) {
    const row = new Uint8Array(size);
    table.push(row);
  }
  return table;
};
const EmptyMatches$1 = [];
const Dash = '-';
const Dot = '.';
const EmptyString$1 = '';
const Space = ' ';
const Underline = '_';
const T = 't';
const isLowerCase = char => {
  return char === char.toLowerCase();
};
const isUpperCase = char => {
  return char === char.toUpperCase();
};

// based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT)
const isGap = (columnCharBefore, columnChar) => {
  switch (columnCharBefore) {
    case Dash:
    case Dot:
    case EmptyString$1:
    case Space:
    case T:
    case Underline:
      return true;
  }
  return isLowerCase(columnCharBefore) && isUpperCase(columnChar);
};

// based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT)
const getScore = (rowCharLow, rowChar, columnCharBefore, columnCharLow, columnChar, isDiagonalMatch) => {
  if (rowCharLow !== columnCharLow) {
    return -1;
  }
  const isMatch = rowChar === columnChar;
  if (isMatch) {
    if (isDiagonalMatch) {
      return 8;
    }
    if (isGap(columnCharBefore, columnChar)) {
      return 8;
    }
    return 5;
  }
  if (isGap(columnCharBefore, columnChar)) {
    return 8;
  }
  return 5;
};

// based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT)

const isPatternInWord = (patternLow, patternPos, patternLen, wordLow, wordPos, wordLen) => {
  while (patternPos < patternLen && wordPos < wordLen) {
    if (patternLow[patternPos] === wordLow[wordPos]) {
      patternPos += 1;
    }
    wordPos += 1;
  }
  return patternPos === patternLen; // pattern must be exhausted
};

// based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT)
const traceHighlights = (table, arrows, patternLength, wordLength) => {
  const skipLeft = (rowStart, columnStart) => {
    let row = rowStart;
    let column = columnStart;
    while (row >= 1 && column >= 1 && arrows[row][column] === Diagonal) {
      row--;
      column--;
    }
    return [row, column];
  };
  let row = patternLength;
  let column = wordLength;
  const matches = [];
  while (row >= 1 && column >= 1) {
    const arrow = arrows[row][column];
    if (arrow === Left) {
      column--;
      continue;
    }
    if (arrow !== Diagonal) {
      break;
    }
    const start = column;
    [row, column] = skipLeft(row - 1, column - 1);
    matches.unshift(column, start);
  }
  matches.unshift(table[patternLength][wordLength - 1]);
  return matches;
};

// based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT)
const gridSize = 128;
const table = createTable(gridSize);
const arrows = createTable(gridSize);
const fillRow = (row, pattern, patternLower, word, wordLower, table, arrows, wordLength) => {
  const rowChar = pattern[row - 1];
  const rowCharLow = patternLower[row - 1];
  let strongMatch = false;
  for (let column = 1; column < wordLength + 1; column++) {
    const columnChar = word[column - 1];
    const columnCharLow = wordLower[column - 1];
    const columnCharBefore = word[column - 2] || '';
    const isDiagonalMatch = arrows[row - 1][column - 1] === Diagonal;
    const score = getScore(rowCharLow, rowChar, columnCharBefore, columnCharLow, columnChar, isDiagonalMatch);
    strongMatch ||= score > 5;
    let diagonalScore = score + table[row - 1][column - 1];
    if (isDiagonalMatch && score !== -1) {
      diagonalScore += 2;
    }
    const leftScore = table[row][column - 1];
    const useLeft = leftScore > diagonalScore;
    table[row][column] = useLeft ? leftScore : diagonalScore;
    arrows[row][column] = useLeft ? Left : Diagonal;
  }
  return strongMatch;
};
const fillTable = (pattern, patternLower, word, wordLower, table, arrows, patternLength, wordLength) => {
  let strongMatch = false;
  for (let row = 1; row < patternLength + 1; row++) {
    const isMatch = fillRow(row, pattern, patternLower, word, wordLower, table, arrows, wordLength);
    if (row === 1) {
      strongMatch ||= isMatch;
    }
  }
  return strongMatch;
};
const fuzzySearch = (pattern, word) => {
  const patternLength = Math.min(pattern.length, gridSize - 1);
  const wordLength = Math.min(word.length, gridSize - 1);
  const patternLower = pattern.toLowerCase();
  const wordLower = word.toLowerCase();
  if (!isPatternInWord(patternLower, 0, patternLength, wordLower, 0, wordLength)) {
    return EmptyMatches$1;
  }
  const strongMatch = fillTable(pattern, patternLower, word, wordLower, table, arrows, patternLength, wordLength);
  if (!strongMatch) {
    return EmptyMatches$1;
  }
  const highlights = traceHighlights(table, arrows, patternLength, wordLength);
  return highlights;
};

const EmptyMatches = [];

const addEmptyMatch = item => {
  return {
    ...item,
    matches: EmptyMatches
  };
};

const EmptyString = '';

const Deprecated = 1 << 0;

const filterCompletionItems = (completionItems, word) => {
  if (word === EmptyString) {
    return completionItems.map(addEmptyMatch);
  }
  const filteredCompletions = [];
  const deprecated = [];
  for (const completionItem of completionItems) {
    const {
      flags,
      label
    } = completionItem;
    const result = fuzzySearch(word, label);
    if (result.length > 0) {
      if (flags & Deprecated) {
        // TODO avoid mutation
        // @ts-ignore
        completionItem.matches = EmptyMatches;
        deprecated.push(completionItem);
      } else {
        // TODO avoid mutation
        // @ts-ignore
        completionItem.matches = result;
        filteredCompletions.push(completionItem);
      }
    }
  }
  if (deprecated.length > 0) {
    filteredCompletions.push(...deprecated);
  }
  return filteredCompletions;
};

const getListHeight = (itemsLength, itemHeight, maxHeight) => {
  number(itemsLength);
  number(itemHeight);
  number(maxHeight);
  if (itemsLength === 0) {
    return itemHeight;
  }
  const totalHeight = itemsLength * itemHeight;
  return Math.min(totalHeight, maxHeight);
};

const getPositionAtCursor = getPositionAtCursor$1;

const getWordAtOffset = getWordAtOffset2;

const handleEditorDeleteLeft = async state => {
  const {
    editorUid,
    itemHeight,
    maxHeight,
    maxItems,
    unfilteredItems
  } = state;
  const {
    x,
    y
  } = await getPositionAtCursor(editorUid);
  const wordAtOffset = await getWordAtOffset(editorUid);
  if (!wordAtOffset) {
    return {
      ...state,
      disposed: true
    };
  }
  const items = filterCompletionItems(unfilteredItems, wordAtOffset);
  const newMaxLineY = Math.min(items.length, maxItems);
  const height = getListHeight(items.length, itemHeight, maxHeight);
  return {
    ...state,
    height,
    items,
    leadingWord: wordAtOffset,
    maxLineY: newMaxLineY,
    x,
    y
  };
};

const {
  getWordBefore
} = EditorWorker;

const handleEditorType = async state => {
  const {
    editorUid,
    itemHeight,
    maxHeight,
    unfilteredItems
  } = state;
  const {
    columnIndex,
    rowIndex,
    x,
    y
  } = await getPositionAtCursor(editorUid);
  const wordAtOffset = await getWordBefore(editorUid, rowIndex, columnIndex);
  const items = filterCompletionItems(unfilteredItems, wordAtOffset);
  const newMinLineY = 0;
  const newMaxLineY = Math.min(items.length, 8);
  const height = getListHeight(items.length, itemHeight, maxHeight);
  const finalDeltaY = items.length * itemHeight - height;
  return {
    ...state,
    finalDeltaY,
    height,
    items,
    leadingWord: wordAtOffset,
    maxLineY: newMaxLineY,
    minLineY: newMinLineY,
    x,
    y
  };
};

const getIndexFromPosition = (y, clientY, itemHeight) => {
  const relativeY = clientY - y;
  const index = Math.floor(relativeY / itemHeight);
  return index;
};

const handlePointerDown = async (state, clientX, clientY) => {
  const {
    itemHeight,
    items,
    y
  } = state;
  const index = getIndexFromPosition(y, clientY, itemHeight);
  if (index < 0 || index >= items.length) {
    //  TODO close maybe?
    return state;
  }
  return selectIndex(state, index);
};

const clamp = (num, min, max) => {
  number(num);
  number(min);
  number(max);
  return Math.min(Math.max(num, min), max);
};

// TODO optimize this function to return the minimum number
// of visible items needed, e.g. when not scrolled 5 items with
// 20px fill 100px but when scrolled 6 items are needed
const getNumberOfVisibleItems = (listHeight, itemHeight) => {
  return Math.ceil(listHeight / itemHeight) + 1;
};

const getNewOffsets = (itemHeight, finalDeltaY, deltaY, height, headerHeight, value, itemCount) => {
  const listHeight = height - headerHeight;
  const newDeltaY = clamp(value, 0, finalDeltaY);
  if (deltaY === newDeltaY) {
    return {
      modified: false,
      newDeltaY: 0,
      newMaxLineY: 0,
      newMinLineY: 0
    };
  }
  // TODO when it only moves by one px, items don't need to be rerendered, only negative margin
  const minLineY = Math.floor(newDeltaY / itemHeight);
  const maxLineY = Math.min(minLineY + getNumberOfVisibleItems(listHeight, itemHeight), itemCount - 1);
  return {
    modified: true,
    newDeltaY: newDeltaY,
    newMaxLineY: maxLineY,
    newMinLineY: minLineY
  };
};

const setDeltaY = (state, value) => {
  object(state);
  number(value);
  const {
    deltaY,
    finalDeltaY,
    headerHeight,
    height,
    itemHeight,
    items
  } = state;
  const {
    modified,
    newDeltaY,
    newMaxLineY,
    newMinLineY
  } = getNewOffsets(itemHeight, finalDeltaY, deltaY, height, headerHeight, value, items.length);
  if (!modified) {
    return state;
  }
  return {
    ...state,
    deltaY: newDeltaY,
    maxLineY: newMaxLineY,
    minLineY: newMinLineY
  };
};

const handleWheel = (state, deltaMode, deltaY) => {
  number(deltaMode);
  number(deltaY);
  return setDeltaY(state, state.deltaY + deltaY);
};

const initialize = async () => {
  // Initialization happens during worker startup in Listen.listen.
};

const error = message => {
  // TODO send this to error worker for logging
  // @ts-ignore
  console.error(message);
};

// TODO possible to do this with events/state machine instead of promises -> enables canceling operations / concurrent calls
const getCompletions = async (editorUid, editorLanguageId) => {
  try {
    const offset = await getOffsetAtCursor(editorUid);
    const completions = await executeCompletionProvider(editorUid, editorLanguageId, offset);
    return completions;
  } catch (error$1) {
    error(`Failed to get completions:`);
    error(error$1);
    return [];
  }
};

const getFinalDeltaY = (height, itemHeight, itemsLength) => {
  const contentHeight = itemsLength * itemHeight;
  const finalDeltaY = Math.max(contentHeight - height, 0);
  return finalDeltaY;
};

const loadContent = async state => {
  const {
    editorLanguageId,
    editorUid,
    itemHeight,
    maxHeight
  } = state;
  const unfilteredItems = await getCompletions(editorUid, editorLanguageId);
  const wordAtOffset = await getWordAtOffset(editorUid);
  const items = filterCompletionItems(unfilteredItems, wordAtOffset);
  const {
    columnIndex,
    rowIndex,
    x,
    y
  } = await getPositionAtCursor(editorUid);
  const newMaxLineY = Math.min(items.length, 8);
  const itemsLength = items.length;
  const newFocusedIndex = itemsLength === 0 ? -1 : 0;
  const total = items.length;
  const height = getListHeight(items.length, itemHeight, maxHeight);
  const finalDeltaY = getFinalDeltaY(height, itemHeight, total);
  return {
    ...state,
    // @ts-ignore
    columnIndex,
    finalDeltaY,
    focusedIndex: newFocusedIndex,
    height,
    items,
    leadingWord: wordAtOffset,
    maxLineY: newMaxLineY,
    rowIndex,
    unfilteredItems,
    version: 1,
    width: 200,
    x,
    y
  };
};

const SetBounds = 'Viewlet.setBounds';
const SetDom2 = 'Viewlet.setDom2';
const SetUid = 'Viewlet.setUid';
const SetEventListeners = 'Viewlet.registerEventListeners';

const renderBounds = (oldState, newState) => {
  const {
    height,
    uid,
    width,
    x,
    y
  } = newState;
  return [SetBounds, uid, x, y, width, height];
};

const renderContent = (oldState, newState) => {
  const {
    uid
  } = newState;
  const dom = [];
  return [SetDom2, uid, dom];
};

const HandlePointerDown = 1;
const HandleWheel = 2;

const getEventListeners = () => {
  return [{
    name: HandleWheel,
    params: ['EditorCompletion.handleWheel', DeltaMode, DeltaY],
    passive: true
  }, {
    name: HandlePointerDown,
    params: ['EditorCompletion.handlePoinerDown', ClientX, ClientY],
    preventDefault: true
  }];
};

const renderEventListeners = state => {
  const {
    uid
  } = state;
  const eventListeners = getEventListeners();
  return [SetEventListeners, uid, eventListeners];
};

const renderFocusContext = (oldState, newState) => {
  return [/* method */'Viewlet.setFocusContext', FocusEditorRename];
};

const None = 'none';
const Option = 'option';
const ListBox = 'listbox';

const ColoredMaskIcon = 'ColoredMaskIcon';
const EditorCompletion = 'EditorCompletion';
const EditorCompletionItem = 'EditorCompletionItem';
const EditorCompletionItemDeprecated = 'EditorCompletionItemDeprecated';
const EditorCompletionItemFocused = 'EditorCompletionItemFocused';
const EditorCompletionItemHighlight = 'EditorCompletionItemHighlight';
const FileIcon = 'FileIcon';
const Label = 'Label';
const ListItems = 'ListItems';
const ScrollBar = 'ScrollBar';
const ScrollBarSmall = 'ScrollBarSmall';
const ScrollBarThumb = 'ScrollBarThumb';
const Viewlet = 'Viewlet';

const emptyObject = {};
const i18nString = (key, placeholders = emptyObject) => {
  if (placeholders === emptyObject) {
    return key;
  }
  let result = key;
  for (const [placeholder, replacement] of Object.entries(placeholders)) {
    result = result.split(`{${placeholder}}`).join(String(replacement));
  }
  return result;
};

const NoSuggestions = 'No Suggestions';
const Suggest = 'Suggest';

const noSuggestions = () => {
  return i18nString(NoSuggestions);
};
const suggest = () => {
  return i18nString(Suggest);
};

const getFileIconVirtualDom = icon => {
  return {
    childCount: 0,
    className: FileIcon,
    role: None,
    src: icon,
    type: Img
  };
};

const getIconDom = (fileIcon, symbolName) => {
  if (fileIcon) {
    return getFileIconVirtualDom(fileIcon);
  }
  return {
    childCount: 0,
    className: mergeClassNames(ColoredMaskIcon, symbolName),
    type: Div
  };
};

const label1 = {
  childCount: 1,
  className: Label,
  type: Div
};
const completionHighlight = {
  childCount: 1,
  className: EditorCompletionItemHighlight,
  type: Span
};
const getHighlightedLabelDom = (label, highlights) => {
  if (highlights.length === 0) {
    return [label1, text(label)];
  }
  const dom = [];
  const labelDom = {
    childCount: 0,
    className: Label,
    type: Div
  };
  dom.push(labelDom);
  let position = 0;
  for (let i = 0; i < highlights.length; i += 2) {
    const highlightStart = highlights[i];
    const highlightEnd = highlights[i + 1];
    if (position < highlightStart) {
      const beforeText = label.slice(position, highlightStart);
      labelDom.childCount++;
      dom.push(text(beforeText));
    }
    const highlightText = label.slice(highlightStart, highlightEnd);
    labelDom.childCount++;
    dom.push(completionHighlight, text(highlightText));
    position = highlightEnd;
  }
  if (position < label.length) {
    const afterText = label.slice(position);
    labelDom.childCount++;
    dom.push(text(afterText));
  }
  return dom;
};

const getCompletionItemClassName = (focused, deprecated) => {
  if (focused && deprecated) {
    return mergeClassNames(EditorCompletionItem, EditorCompletionItemFocused, EditorCompletionItemDeprecated);
  }
  if (focused) {
    return mergeClassNames(EditorCompletionItem, EditorCompletionItemFocused);
  }
  if (deprecated) {
    return mergeClassNames(EditorCompletionItem, EditorCompletionItemDeprecated);
  }
  return EditorCompletionItem;
};
const getCompletionItemVirtualDom = visibleItem => {
  const {
    deprecated,
    fileIcon,
    focused,
    highlights,
    label,
    symbolName,
    top
  } = visibleItem;
  const className = getCompletionItemClassName(focused, deprecated);
  return [{
    childCount: 2,
    className,
    role: Option,
    top,
    type: Div
  }, getIconDom(fileIcon, symbolName), ...getHighlightedLabelDom(label, highlights)];
};

const parentNode = {
  childCount: 1,
  type: Div
};
const getNoResultsVirtualDom = () => {
  return [parentNode, text(noSuggestions())];
};

const getCompletionItemsVirtualDom = visibleItems => {
  if (visibleItems.length === 0) {
    return getNoResultsVirtualDom();
  }
  return [{
    childCount: visibleItems.length,
    type: Div
  }, ...visibleItems.flatMap(getCompletionItemVirtualDom)];
};

const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
  const shouldShowScrollbar = scrollBarHeight > 0;
  if (!shouldShowScrollbar) {
    return [];
  }
  const heightString = px(scrollBarHeight);
  const translateString = position(0, scrollBarTop);
  return [{
    childCount: 1,
    className: mergeClassNames(ScrollBar, ScrollBarSmall),
    type: Div
  }, {
    childCount: 0,
    className: ScrollBarThumb,
    height: heightString,
    translate: translateString,
    type: Div
  }];
};

const Completions = 'Completions';

const getCompletionVirtualDom = (visibleItems, scrollBarHeight, scrollBarTop) => {
  const scrollBarDom = getScrollBarVirtualDom(scrollBarHeight, scrollBarTop);
  const childCount = scrollBarDom.length === 0 ? 1 : 2;
  return [{
    childCount: childCount,
    className: mergeClassNames(Viewlet, EditorCompletion),
    id: Completions,
    type: Div
  }, {
    ariaLabel: suggest(),
    childCount: 1,
    className: ListItems,
    onWheel: HandleWheel,
    role: ListBox,
    type: Div
  }, ...getCompletionItemsVirtualDom(visibleItems), ...scrollBarDom
  // TODO render scrollbar
  ];
};

const getScrollBarSize = (size, contentSize, minimumSliderSize) => {
  if (size >= contentSize) {
    return 0;
  }
  return Math.max(Math.round(size ** 2 / contentSize), minimumSliderSize);
};

const getScrollBarTop = (height, contentHeight, scrollTop) => {
  const scrollBarTop = Math.round(scrollTop / contentHeight * height);
  return scrollBarTop;
};

const Property = 1;
const Value = 2;
const Function = 3;
const Variable = 4;
const Keyword = 5;
const Folder = 6;
const File = 7;
const Field = 8;

const SymbolProperty = 'SymbolProperty';
const SymbolValue = 'SymbolValue';
const SymbolFunction = 'SymbolFunction';
const SymbolVariable = 'SymbolVariable';
const SymbolKeyword = 'SymbolKeyword';
const SymbolDefault = 'SymbolDefault';
const SymbolField = 'SymbolField';
const SymbolNone = '';

const getSymbolName = kind => {
  switch (kind) {
    case Field:
      return SymbolField;
    case File:
      return SymbolNone;
    case Function:
      return SymbolFunction;
    case Keyword:
      return SymbolKeyword;
    case Property:
      return SymbolProperty;
    case Value:
      return SymbolValue;
    case Variable:
      return SymbolVariable;
    default:
      return SymbolDefault;
  }
};

// TODO
const getCompletionFileIcon = kind => {
  switch (kind) {
    case File:
      return EmptyString;
    case Folder:
      return EmptyString;
    default:
      return EmptyString;
  }
};

const getHighlights = item => {
  const {
    matches
  } = item;
  return matches.slice(1);
};

const getLabel = item => {
  return item.label || '';
};

const getTop = (i, minLineY, itemHeight, relative) => {
  return (i - minLineY) * itemHeight - relative;
};

const getVisibleIem = (item, itemHeight, leadingWord, i, minLineY, focusedIndex, relative) => {
  return {
    deprecated: item.flags & Deprecated,
    fileIcon: getCompletionFileIcon(item.kind),
    focused: i === focusedIndex,
    highlights: getHighlights(item),
    label: getLabel(item),
    symbolName: getSymbolName(item.kind),
    top: getTop(i, minLineY, itemHeight, relative)
  };
};

const getVisibleItems = (filteredItems, itemHeight, leadingWord, minLineY, maxLineY, focusedIndex, deltaY) => {
  const visibleItems = [];
  const relative = deltaY % itemHeight;
  for (let i = minLineY; i < maxLineY; i++) {
    const filteredItem = filteredItems[i];
    visibleItems.push(getVisibleIem(filteredItem, itemHeight, leadingWord, i, minLineY, focusedIndex, relative));
  }
  return visibleItems;
};

const renderItems = (oldState, newState) => {
  const {
    deltaY,
    focusedIndex,
    height,
    itemHeight,
    items,
    leadingWord,
    maxLineY,
    minLineY,
    uid
  } = newState;
  const visibleItems = getVisibleItems(items, itemHeight, leadingWord, minLineY, maxLineY, focusedIndex, deltaY);
  const contentHeight = newState.items.length * newState.itemHeight;
  const scrollBarHeight = getScrollBarSize(height, contentHeight, 20);
  const scrollBarTop = getScrollBarTop(height, contentHeight, deltaY);
  const dom = getCompletionVirtualDom(visibleItems, scrollBarHeight, scrollBarTop);
  return [SetDom2, uid, dom];
};

const renderUid = (oldState, newState) => {
  const {
    editorUid,
    uid
  } = newState;
  return [SetUid, uid, editorUid];
};

const getRenderer = diffType => {
  switch (diffType) {
    case RenderBounds:
      return renderBounds;
    case RenderContent:
      return renderContent;
    case RenderEventListeners:
      return renderEventListeners;
    case RenderFocusContext:
      return renderFocusContext;
    case RenderItems:
      return renderItems;
    case RenderUid:
      return renderUid;
    default:
      throw new Error('unknown renderer');
  }
};

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

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

const commandMap = {
  'Completions.close': wrapCommand(close),
  'Completions.create': create,
  'Completions.diff2': diff2,
  'Completions.dispose': dispose,
  'Completions.focusFirst': wrapCommand(focusFirst),
  'Completions.focusIndex': wrapCommand(focusIndex),
  'Completions.focusNext': wrapCommand(focusNext),
  'Completions.focusPrevious': wrapCommand(focusPrevious),
  'Completions.getCommandIds': getCommandIds,
  'Completions.getKeyBindings': getKeyBindings,
  'Completions.handleEditorDeleteLeft': wrapCommand(handleEditorDeleteLeft),
  'Completions.handleEditorType': wrapCommand(handleEditorType),
  'Completions.handlePointerDown': wrapCommand(handlePointerDown),
  'Completions.handleWheel': wrapCommand(handleWheel),
  'Completions.initialize': initialize,
  'Completions.loadContent': wrapCommand(loadContent),
  'Completions.openDetails': wrapCommand(openDetails),
  'Completions.render2': render2,
  'Completions.selectCurrent': wrapCommand(selectCurrent),
  'Completions.selectIndex': wrapCommand(selectIndex),
  'Completions.terminate': terminate
};

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

const send = async port => {
  await sendMessagePortToExtensionManagementWorker(port);
};
const initializeExtensionManagementWorker = async () => {
  const rpc = await create$4({
    commandMap: commandMap,
    send
  });
  set$1(rpc);
};

const listen = async () => {
  await Promise.all([initializeEditorWorker(), initializeExtensionManagementWorker()]);
};

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

main();
