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$3 = '\n';
const getNewLineIndex$1 = (string, startIndex = undefined) => {
  return string.indexOf(NewLine$3, 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 = 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;
  }
};
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) {
    throw new AssertionError('expected value to be of type number');
  }
};
const string = value => {
  const type = getType(value);
  if (type !== String) {
    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);
    }
    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$2 = '\n';
const joinLines$1 = lines => {
  return lines.join(NewLine$2);
};
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$2);
};
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 Error$3 = 1;
const Open = 2;
const Close = 3;
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
};
const stringifyCompact = value => {
  return JSON.stringify(value);
};
const parse = content => {
  if (content === 'undefined') {
    return null;
  }
  try {
    return JSON.parse(content);
  } catch (error) {
    throw new VError(error, 'failed to parse json');
  }
};
const waitForWebSocketToBeOpen = webSocket => {
  return getFirstEvent(webSocket, {
    close: Close,
    error: Error$3,
    open: Open
  });
};
const create$6 = async ({
  webSocket
}) => {
  const firstWebSocketEvent = await waitForWebSocketToBeOpen(webSocket);
  if (firstWebSocketEvent.type === Error$3) {
    throw new IpcError(`WebSocket connection error`);
  }
  if (firstWebSocketEvent.type === Close) {
    throw new IpcError('Websocket connection was immediately closed');
  }
  return webSocket;
};
class IpcParentWithWebSocket extends Ipc {
  getData(event) {
    return parse(event.data);
  }
  send(message) {
    this._rawIpc.send(stringifyCompact(message));
  }
  sendAndTransfer(message) {
    throw new Error('sendAndTransfer not supported');
  }
  dispose() {
    this._rawIpc.close();
  }
  onClose(callback) {
    this._rawIpc.addEventListener('close', callback);
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
}
const wrap = webSocket => {
  return new IpcParentWithWebSocket(webSocket);
};
const IpcParentWithWebSocket$1 = {
  __proto__: null,
  create: create$6,
  wrap
};

const Two$1 = '2.0';
const callbacks = Object.create(null);
const get$5 = id => {
  return callbacks[id];
};
const remove$1 = id => {
  delete callbacks[id];
};
class JsonRpcError extends Error {
  constructor(message) {
    super(message);
    this.name = 'JsonRpcError';
  }
}
const NewLine$1 = '\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 TypeError$1:
        return TypeError;
      case SyntaxError$1:
        return SyntaxError;
      case ReferenceError$1:
        return ReferenceError;
      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$1);
};
const splitLines = lines => {
  return lines.split(NewLine$1);
};
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$1, startIndex);
};
const getParentStack = error => {
  let parentStack = error.stack || error.data || error.message || '';
  if (parentStack.startsWith('    at')) {
    parentStack = error.message + NewLine$1 + 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$1 + currentStack;
    }
    return error;
  }
  if (error && error.code && error.code === MethodNotFound) {
    const restoredError = new JsonRpcError(error.message);
    const parentStack = getParentStack(error);
    restoredError.stack = parentStack + NewLine$1 + 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$1 + error.data.stack + NewLine$1 + 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$5(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,
      message: error.message,
      data: error.stack
    };
  }
  return {
    code: Custom,
    message: prettyError.message,
    data: {
      stack: getStack(prettyError),
      codeFrame: prettyError.codeFrame,
      type: getErrorType(prettyError),
      code: prettyError.code,
      name: prettyError.name
    }
  };
};
const create$1$1 = (id, error) => {
  return {
    jsonrpc: Two$1,
    id,
    error
  };
};
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$4 = (message, result) => {
  return {
    jsonrpc: Two$1,
    id: message.id,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$4(message, resultProperty);
};
const getErrorResponseSimple = (id, error) => {
  return {
    jsonrpc: Two$1,
    id,
    error: {
      code: Custom,
      // @ts-ignore
      message: error.message,
      data: error
    }
  };
};
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 {
      ipc: options.ipc,
      message: options.message,
      execute: options.execute,
      resolve: options.resolve || defaultResolve,
      preparePrettyError: options.preparePrettyError || defaultPreparePrettyError,
      logError: options.logError || defaultLogError,
      requiresSocket: options.requiresSocket || defaultRequiresSocket
    };
  }
  return {
    ipc: args[0],
    message: args[1],
    execute: args[2],
    resolve: args[3],
    preparePrettyError: args[4],
    logError: args[5],
    requiresSocket: args[6]
  };
};
const handleJsonRpcMessage = async (...args) => {
  const options = normalizeParams(args);
  const {
    message,
    ipc,
    execute,
    resolve,
    preparePrettyError,
    logError,
    requiresSocket
  } = 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');
};

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 = '2.0';
const create$t = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};
const create$s = (id, method, params) => {
  const message = {
    id,
    jsonrpc: Two,
    method,
    params
  };
  return message;
};
let id$1 = 0;
const create$r = () => {
  return ++id$1;
};

/* eslint-disable n/no-unsupported-features/es-syntax */

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

// @ts-ignore
const invokeHelper = async (callbacks, ipc, method, params, useSendAndTransfer) => {
  const {
    id,
    promise
  } = registerPromise(callbacks);
  const message = create$s(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$t(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 Https = 'https:';
const Ws = 'ws:';
const Wss = 'wss:';
const getWebSocketProtocol = locationProtocol => {
  return locationProtocol === Https ? Wss : Ws;
};
const getWebSocketUrl = (type, host, locationProtocol) => {
  const wsProtocol = getWebSocketProtocol(locationProtocol);
  return `${wsProtocol}//${host}/websocket/${type}`;
};
const getHost = () => {
  return location.host;
};
const getProtocol = () => {
  return location.protocol;
};
const create$i = async ({
  commandMap,
  webSocket
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const rawIpc = await IpcParentWithWebSocket$1.create({
    webSocket
  });
  const ipc = IpcParentWithWebSocket$1.wrap(rawIpc);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  return rpc;
};
const create$h = async ({
  commandMap,
  type
}) => {
  const host = getHost();
  const protocol = getProtocol();
  const wsUrl = getWebSocketUrl(type, host, protocol);
  const webSocket = new WebSocket(wsUrl);
  const rpc = await create$i({
    commandMap,
    webSocket
  });
  return rpc;
};
const WebSocketRpcParent2 = {
  __proto__: null,
  create: create$h
};
const create$e = async ({
  commandMap,
  isMessagePortOpen,
  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);
  return rpc;
};
const MessagePortRpcParent = {
  __proto__: null,
  create: create$e
};
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 PlainMessagePortRpc = {
  __proto__: null,
  create: create$5
};
const create$3 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  const {
    port1,
    port2
  } = new MessageChannel();
  await send(port1);
  return create$5({
    commandMap,
    isMessagePortOpen,
    messagePort: port2
  });
};
const TransferMessagePortRpcParent = {
  __proto__: null,
  create: create$3
};
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 WebWorkerRpcClient = {
  __proto__: null,
  create: create$2
};
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 Web = 1;
const Electron = 2;
const Remote = 3;
const Test = 4;

const ExtensionHostWorker = 44;
const FileSystemWorker = 209;
const RendererWorker = 1;
const SharedProcess = 1492;

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

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

const {
  invoke: invoke$4,
  set: set$6
} = create$1(ExtensionHostWorker);

const {
  invoke: invoke$3,
  set: set$5
} = create$1(FileSystemWorker);
const readFile = async uri => {
  return invoke$3('FileSystem.readFile', uri);
};
const writeFile = async (uri, content) => {
  return invoke$3('FileSystem.writeFile', uri, content);
};
const exists = async uri => {
  return invoke$3('FileSystem.exists', uri);
};

const {
  invoke: invoke$2,
  invokeAndTransfer: invokeAndTransfer$1,
  set: set$4
} = create$1(RendererWorker);
const sendMessagePortToFileSystemWorker = async (port, rpcId) => {
  const command = 'FileSystem.handleMessagePort';
  // @ts-ignore
  await invokeAndTransfer$1('SendMessagePortToExtensionHostWorker.sendMessagePortToFileSystemWorker', port, command, rpcId);
};
const sendMessagePortToExtensionHostWorker = async (port, rpcId = 0) => {
  const command = 'HandleMessagePort.handleMessagePort2';
  await invokeAndTransfer$1('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
};
const sendMessagePortToSharedProcess = async port => {
  const command = 'HandleElectronMessagePort.handleElectronMessagePort';
  // @ts-ignore
  await invokeAndTransfer$1('SendMessagePortToExtensionHostWorker.sendMessagePortToSharedProcess', port, command, 0);
};

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

const create = () => {
  return {
    finished: false
  };
};
const cancel = token => {
  token.finished = true;
};
const isCanceled = token => {
  return token.finished;
};

const baseName = path => {
  const slashIndex = path.lastIndexOf('/');
  return path.slice(slashIndex + 1);
};
const getExtensionId = extension => {
  if (extension && extension.id) {
    return extension.id;
  }
  if (extension && extension.path) {
    return baseName(extension.path);
  }
  return '<unknown>';
};

const isImportErrorChrome = error => {
  return Boolean(error && error instanceof Error && error.message.startsWith('Failed to fetch dynamically imported module'));
};

const isImportErrorFirefox = error => {
  return Boolean(error && error instanceof TypeError && error.message === 'error loading dynamically imported module');
};

const isSyntaxError = error => {
  return error instanceof SyntaxError;
};

const isImportError = error => {
  return isImportErrorChrome(error) || isImportErrorFirefox(error) || isSyntaxError(error);
};

const states = Object.create(null);
const set$2 = status => {
  states[status.id] = status;
};
const get$3 = extensionId => {
  return states[extensionId];
};
const update$1 = (id, update) => {
  states[id] = {
    ...states[id],
    ...update
  };
};

const None = 0;
const Importing = 1;
const Activating = 2;
const Activated = 3;
const Error$1 = 4;

const sleep = duration => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  setTimeout(resolve, duration);
  return promise;
};

const NotFound = 404;

const tryToGetActualImportErrorMessage = async (url, error) => {
  let response;
  try {
    response = await fetch(url);
  } catch (error) {
    return `Failed to import ${url}: ${error}`;
  }
  if (response.ok) {
    throw new Error(`Failed to import ${url}: Unknown Error`);
  }
  switch (response.status) {
    case NotFound:
      throw new Error(`Failed to import ${url}: Not found (404)`);
    default:
      return `Failed to import ${url}: ${error}`;
  }
};

// TODO make activation timeout configurable or remove it.
// some extension might do workspace indexing which could take some time
const activationTimeout = 10_000;
const rejectAfterTimeout = async (timeout, token) => {
  await sleep(timeout);
  if (isCanceled(token)) {
    return;
  }
  throw new Error(`Activation timeout of ${timeout}ms exceeded`);
};
const activate = async (extensionId, extension) => {
  await invoke$4('ExtensionHost.activateExtension3', extensionId, extension);
};
const activateExtension2 = async (extensionId, extension, absolutePath) => {
  string(extensionId);
  object(extension);
  string(absolutePath);
  const token = create();
  try {
    const startTime = performance.now();
    update$1(extensionId, {
      activationStartTime: startTime,
      status: Activating
    });
    await Promise.race([activate(extensionId, extension), rejectAfterTimeout(activationTimeout, token)]);
    const endTime = performance.now();
    const time = endTime - startTime;
    update$1(extensionId, {
      activationEndTime: endTime,
      activationTime: time,
      status: Activated
    });
  } catch (error) {
    const id = getExtensionId(extension);
    if (isImportError(error)) {
      const actualErrorMessage = await tryToGetActualImportErrorMessage(absolutePath, error);
      throw new Error(`Failed to activate extension ${id}: ${actualErrorMessage}`);
    }
    update$1(extensionId, {
      status: Error$1 // TODO maybe store error also in runtime status state
    });
    throw new VError(error, `Failed to activate extension ${id}`);
  } finally {
    cancel(token);
  }
};

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

const getUrlPrefix = (platform, extensionPath) => {
  if (extensionPath.startsWith('http://') || extensionPath.startsWith('https://')) {
    return extensionPath;
  }
  if (platform === Web) {
    return extensionPath;
  }
  if (extensionPath.startsWith('/')) {
    return `/remote${extensionPath}`;
  }
  return `/remote/${extensionPath}`;
};

const handleRpcInfos = (extension, platform) => {
  try {
    if (!extension) {
      return;
    }
    const rpcs = extension.rpc;
    const urlPrefix = getUrlPrefix(platform, extension.path);
    if (!rpcs) {
      return;
    }
    if (!Array.isArray(rpcs)) {
      return;
    }
    for (const rpc of rpcs) {
      rpc.url = `${urlPrefix}/${rpc.url}`;
      add(rpc.id, rpc);
    }
  } catch (error) {
    console.warn(`Failed to handle extension rpcs: ${error}`);
  }
};

const importExtension = async (extensionId, absolutePath, activationEvent) => {
  try {
    string(absolutePath);
    const startTime = performance.now();
    set$2({
      activationEndTime: 0,
      activationEvent: activationEvent,
      activationStartTime: performance.now(),
      activationTime: 0,
      id: extensionId,
      importEndTime: 0,
      importStartTime: startTime,
      importTime: 0,
      status: Importing
    });
    try {
      await invoke$4('ExtensionHost.importExtension2', extensionId, absolutePath);
      const endTime = performance.now();
      const time = endTime - startTime;
      update$1(extensionId, {
        importEndTime: endTime,
        importTime: time
      });
    } catch (error) {
      update$1(extensionId, {
        status: Error$1 // TODO maybe store error also in runtime status state
      });
      if (isImportError(error)) {
        const actualErrorMessage = await tryToGetActualImportErrorMessage(absolutePath, error);
        throw new Error(actualErrorMessage);
      }
      throw error;
    }
  } catch (error) {
    throw new VError(error, `Failed to import extension ${extensionId}`);
  }
};

const NewLine = '\n';
const Slash = '/';

const interExtensionId = path => {
  const slashIndex = path.lastIndexOf(Slash);
  return path.slice(slashIndex + 1);
};

const activateExtension3 = async (extension, absolutePath, activationEvent, platform) => {
  handleRpcInfos(extension, platform);
  const extensionId = extension.id || interExtensionId(extension.uri);
  await importExtension(extensionId, absolutePath, activationEvent);
  await activateExtension2(extensionId, extension, absolutePath);
};

const state$1 = {
  webExtensions: []
};

const cache = Object.create(null);
const id = 1;
const clear = () => {
  delete cache[id];
};

const getJson$1 = async url => {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(response.statusText);
    }
    const json = await response.json();
    return json;
  } catch (error) {
    throw new VError(error, `Failed to get json`);
  }
};

const getWebExtensionManifest = async (path, manifestPath) => {
  try {
    const manifest = await getJson$1(manifestPath);
    return {
      ...manifest,
      path,
      uri: path
    };
  } catch (error) {
    const id = interExtensionId(path);
    throw new VError(error, `Failed to load extension manifest for ${id}`);
  }
};

const getWebManifestPath = path => {
  const manifestPath = `${path}/extension.json`;
  return manifestPath;
};

const addWebExtension = async path => {
  const manifestPath = getWebManifestPath(path);
  const manifest = await getWebExtensionManifest(path, manifestPath);
  // TODO avoid mutation if possible
  state$1.webExtensions.push(manifest);
  clear();
  return manifest;
};

const ModuleWorkerAndWorkaroundForChromeDevtoolsBug = 6;

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
const invoke = (method, ...params) => {
  return invoke$2(method, ...params);
};
const invokeAndTransfer = (method, ...params) => {
  return invokeAndTransfer$1(method, ...params);
};

const createWebViewWorkerRpc2 = async (rpcInfo, port) => {
  // TODO this function is called from the iframe worker to create a direct
  // connection between a webview/iframe and it's webworker. For this to work
  // the iframe worker creates a messagechannel and sends one messageport to the webview
  // and the other messageport to the webworker. This enables direct communication via
  // the two message ports

  // TODO have a way so that the worker already includes the webview api and the extension
  // host subworker doesn't need to import the other file
  await invokeAndTransfer('IpcParent.create', {
    method: ModuleWorkerAndWorkaroundForChromeDevtoolsBug,
    name: rpcInfo.name,
    port,
    raw: true,
    url: rpcInfo.url
  });
};

const getExtensionHostSubWorkerUrl = () => {
  return new URL('../../../../extension-host-sub-worker/src/extensionHostSubWorkerMain.js', import.meta.url).toString();
};
const extensionHostSubWorkerUrl = getExtensionHostSubWorkerUrl();

// TODO

/**
 * @deprecated use createWebViewWorkerRpc2 which passes the worker url as a parameter
 */
const createWebViewWorkerRpc = async (rpcInfo, port) => {
  object(rpcInfo);
  object(port);
  // TODO this function is called from the iframe worker to create a direct
  // connection between a webview/iframe and it's webworker. For this to work
  // the iframe worker creates a messagechannel and sends one messageport to the webview
  // and the other messageport to the webworker. This enables direct communication via
  // the two message ports

  // TODO have a way so that the worker already includes the webview api and the extension
  // host subworker doesn't need to import the other file
  await invokeAndTransfer$1('IpcParent.create', {
    method: ModuleWorkerAndWorkaroundForChromeDevtoolsBug,
    name: rpcInfo.name,
    port,
    raw: true,
    url: extensionHostSubWorkerUrl
  });
};

const ContentType = 'Content-Type';
const ContentLength = 'Content-Length';

const createResponseFromData = data => {
  const responseString = JSON.stringify(data);
  const response = new Response(responseString, {
    headers: {
      [ContentLength]: `${responseString.length}`,
      [ContentType]: 'application/json'
    }
  });
  return response;
};

const cacheName = 'Extensions'; // TODO

const getJson = async cacheKey => {
  const response = await caches.match(cacheKey, {
    cacheName
  });
  if (!response) {
    return undefined;
  }
  const json = await response.json();
  return json;
};
const setJson = async (cacheKey, data) => {
  try {
    const cache = await caches.open(cacheName);
    const response = createResponseFromData(data);
    await cache.put(cacheKey, response);
  } catch (error) {
    throw new VError(error, `Failed to add to cache`);
  }
};

const disabledExtensionsCacheKey = '/cache/disabledExtensions.json';

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */

let state = {
  activatedExtensions: Object.create(null),
  cachedActivationEvents: Object.create(null),
  disabledIds: [],
  platform: 0
};
const set$1 = newState => {
  state = newState;
};
const update = newState => {
  const fullNewState = {
    ...state,
    ...newState
  };
  state = fullNewState;
};
const get$1 = () => {
  return state;
};

const disableExtension2$1 = async (id, platform) => {
  const isTest = platform === Test;
  const isWeb = platform === Web;
  const oldState = get$1(); // TODO maybe pass in an application id? Would allow multiple editors with different extensions.
  if (isTest) {
    const newState = {
      ...oldState,
      disabledIds: [...oldState.disabledIds, id]
    };
    set$1(newState);
  } else if (isWeb) {
    const cached = await getJson(disabledExtensionsCacheKey);
    const oldDisabled = cached?.disabledExtensions || [];
    const newDisabled = [...oldDisabled, id];
    const newData = {
      disabledExtensions: newDisabled
    };
    await setJson(disabledExtensionsCacheKey, newData);
  } else {
    await invoke$1('ExtensionManagement.disable', id);
  }
};
const enableExtension2$1 = async (id, platform) => {
  const isTest = platform === Test;
  const isWeb = platform === Web;
  const oldState = get$1();
  if (isTest) {
    const newState = {
      ...oldState,
      disabledIds: oldState.disabledIds.filter(existing => existing !== id)
    };
    set$1(newState);
  } else if (isWeb) {
    const cached = await getJson(disabledExtensionsCacheKey);
    const oldDisabled = cached?.disabledExtensions || [];
    const newDisabled = oldDisabled.filter(item => item !== id);
    const newData = {
      disabledExtensions: newDisabled
    };
    await setJson(disabledExtensionsCacheKey, newData);
  }
};

const invalidateExtensionsCache = async () => {
  await invoke$2('ExtensionManagement.invalidateExtensionsCache');
};

const disableExtension2 = async (id, platform) => {
  string(id);
  number(platform);
  try {
    await disableExtension2$1(id, platform);
    await invalidateExtensionsCache();
    return undefined;
  } catch (error) {
    return error;
  }
};

const disableExtension = async (id, isTest) => {
  const oldState = get$1();
  return disableExtension2(id, oldState.platform);
};

const enableExtension2 = async (id, platform) => {
  string(id);
  number(platform);
  if (platform === Remote || platform === Electron) {
    const disabledExtensionsJsonPath = await invoke$2('WebView.compatSharedProcessInvoke', 'PlatformPaths.getDisabledExtensionsJsonUri');
    const exists$1 = await exists(disabledExtensionsJsonPath);
    if (!exists$1) {
      return undefined;
    }
    const content = await readFile(disabledExtensionsJsonPath);
    const parsed = JSON.parse(content);
    const oldDisabled = parsed.disabledExtensions || [];
    const newDisabled = oldDisabled.filter(item => item !== id);
    const newData = {
      disabledExtensions: newDisabled
    };
    const newContent = JSON.stringify(newData, null, 2) + '\n';
    await writeFile(disabledExtensionsJsonPath, newContent);
  }
  await enableExtension2$1(id, platform);
};

const enableExtension = async (id, isTest) => {
  const oldState = get$1();
  return enableExtension2(id, oldState.platform);
};

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
const warn = (...args) => {
  console.warn(...args);
};

const transparent = (color, factor) => {
  return color;
};
const toColorRule = ([key, value]) => {
  return `  --${key}: ${value};`;
};
const toTokenColorRule = tokenColor => {
  return `.${tokenColor.name} { color: ${tokenColor.foreground} }`;
};
const addFallbackColors = colors => {
  const newColors = {
    ...colors
  };
  if (!newColors.ActivityBarInactiveForeground) {
    // TODO don't assign, avoid mutation
    newColors.ActivityBarInactiveForeground = transparent(newColors.ActivityBarForeground);
  }
  newColors.CssVariableName ||= colors.VariableName;
  return newColors;
};
const createColorThemeFromJson = (colorThemeId, colorThemeJson) => {
  if (!colorThemeJson) {
    warn(`color theme json for "${colorThemeId}" is empty: "${colorThemeJson}"`);
    return '';
  }
  if (typeof colorThemeJson !== 'object') {
    warn(`color theme json for "${colorThemeId}" cannot be converted to css: "${colorThemeJson}"`);
    return '';
  }
  if (Array.isArray(colorThemeJson)) {
    warn(`color theme json for "${colorThemeId}" cannot be converted to css, it must be of type object but was of type array`);
    return '';
  }
  const {
    colors
  } = colorThemeJson;
  if (!colors) {
    return '';
  }
  const newColors = addFallbackColors(colors);
  const colorRules = Object.entries(newColors).map(toColorRule);
  const tokenColors = colorThemeJson.tokenColors || [];
  const tokenColorRules = tokenColors.map(toTokenColorRule);
  const extraRules = [];
  if (colors.ContrastBorder) {
    extraRules.push(`#ActivityBar, #SideBar {
  border-left: 1px solid var(--ContrastBorder);
}`, `#Panel {
  border-top: 1px solid var(--ContrastBorder);
}`, `#StatusBar {
  border-top: 1px solid var(--ContrastBorder);
}`, `.ActivityBarItemBadge {
  border: 1px solid var(--ContrastBorder);
}`, `#QuickPick {
  border: 1px solid var(--ContrastBorder);
}`);
  }
  const colorThemeCss = `:root {\n${colorRules.join(NewLine)}\n}\n\n${tokenColorRules.join(NewLine)}\n\n${extraRules.join(NewLine)}`;
  return colorThemeCss;
};

// for (let i = 0; i < 10000; i++) {
//   createColorThemeFromJson({
//     type: 'dark',
//     colors: {
//       ActivityBarBackground: 'rgb(40, 46, 47)',
//       ActivityBarForeground: '#878f8c',
//       ActivityBarActiveBackground: '#1f2727',

//       EditorBackGround: '#1e2324',
//       EditorScrollBarBackground: 'rgba(57, 71, 71, 0.6)',
//       EditorCursorBackground: '#a8df5a',

//       ListActiveSelectionBackground: '#515f59',
//       ListActiveSelectionForeground: '#ffffff',
//       ListHoverBackground: '#405c5033',
//       ListHoverForeground: '#e0e0e0',
//       ListInactiveSelectionBackground: '#3b474280',

//       MainBackground: '#1e2324',

//       PanelBackground: '#1b2020',
//       PanelBorderTopColor: 'rgba(128, 128, 128, 0.35)',

//       SideBarBackground: '#1b2020',

//       StatusBarBackground: 'rgb(40, 46, 47)',
//       StatusBarBorderTopColor: '#222222',

//       TabActiveBackground: '#24292a',
//       TabInactiveBackground: '#282e2f',

//       TitleBarBackground: 'rgb(40, 46, 47)',
//       TitleBarBorderBottomColor: '#222',
//       TitleBarColor: '#cccccc',
//       TitleBarColorInactive: 'rgba(204, 204, 204, 0.6)',
//     },
//   }) //?.
// }

const get = colorThemeId => {
  return '';
};
const set = (colorThemeId, data) => {
  //  noop
};

const GetColorThemeCssCachedNoop = {
  __proto__: null,
  get,
  set
};

// TODO make storage configurable via settings as localstorage or indexeddb
// also allow disabling caching via settings
// then measure which option could be fastest

const getCacheFn = config => {
  switch (config) {
    default:
      return GetColorThemeCssCachedNoop;
  }
};
const getColorThemeCssCached = async (colorThemeId, platform, getData) => {
  const config = '';
  const module = getCacheFn(config);
  const cachedData = module.get(colorThemeId);
  if (cachedData) {
    return cachedData;
  }
  const newData = await getData(colorThemeId, platform);
  module.set(colorThemeId, newData);
  return newData;
};

const readJson = url => {
  return invoke$2('FileSystem.readJson', url);
};

const join = (pathSeparator, ...parts) => {
  return parts.join(pathSeparator);
};

const getColorThemeUri = (extensions, colorThemeId) => {
  for (const extension of extensions) {
    if (!extension.colorThemes) {
      continue;
    }
    for (const colorTheme of extension.colorThemes) {
      if (colorTheme.id !== colorThemeId) {
        continue;
      }
      const absolutePath = join('/', extension.uri, colorTheme.path);
      return absolutePath;
    }
  }
  return '';
};

const getDynamicWebExtensions = () => {
  return state$1.webExtensions;
};

const getWebExtensionsUrl = assetDir => {
  return `${assetDir}/config/extensions.json`;
};

const getWebExtensions = async assetDir => {
  try {
    return await getJson$1(getWebExtensionsUrl(assetDir));
  } catch {
    return [];
  }
};

const getAllExtensions = async (assetDir, platform) => {
  if (typeof assetDir !== 'string') {
    assetDir = await invoke$2('Layout.getAssetDir');
  }
  if (!platform) {
    platform = await invoke$2('Layout.getPlatform');
  }
  string(assetDir);
  number(platform);
  const meta = getDynamicWebExtensions();
  if (platform === Web) {
    const webExtensions = await getWebExtensions(assetDir);
    return [...webExtensions, ...meta];
  }
  const local = await invoke$1('ExtensionManagement.getAllExtensions');
  return [...local, ...meta];
};

const getColorThemeJson$2 = async (colorThemeId, assetDir, platform) => {
  const extensions = await getAllExtensions(assetDir, platform);
  const colorThemeUri = getColorThemeUri(extensions, colorThemeId);
  if (!colorThemeUri) {
    return {};
  }
  const json = await readJson(colorThemeUri);
  return json;
};

const getColorThemeUrlWeb = (assetDir, colorThemeId) => {
  return `${assetDir}/extensions/builtin.theme-${colorThemeId}/color-theme.json`;
};
const getColorThemeJson$1 = (colorThemeId, assetDir) => {
  const url = getColorThemeUrlWeb(assetDir, colorThemeId);
  // TODO handle error ?
  return getJson$1(url);
};

const getColorThemeJson = (colorThemeId, platform, assetDir) => {
  if (platform === Web) {
    return getColorThemeJson$1(colorThemeId, assetDir);
  }
  return getColorThemeJson$2(colorThemeId, assetDir, platform);
};

const getColorThemeCssFromJson = async (colorThemeId, colorThemeJson) => {
  const colorThemeCss = createColorThemeFromJson(/* colorThemeId */colorThemeId, /* colorThemeJson */colorThemeJson);
  return colorThemeCss;
  // TODO generate color theme from jsonc
};
const getColorThemeCssNew = async (colorThemeId, platform, assetDir) => {
  const colorThemeJson = await getColorThemeJson(colorThemeId, platform, assetDir);
  const colorThemeCss = await getColorThemeCssFromJson(colorThemeId, colorThemeJson);
  return colorThemeCss;
};
const getColorThemeCss = (colorThemeId, platform) => {
  return getColorThemeCssCached(colorThemeId, platform, getColorThemeCssNew);
};

const getExtensionColorThemeNames = extension => {
  return extension.colorThemes || [];
};
const getColorThemeId = colorTheme => {
  return colorTheme.id;
};

// TODO should send names to renderer worker or names with ids?
const getColorThemeNamesFromExtensions = async extensions => {
  const colorThemes = extensions.flatMap(getExtensionColorThemeNames);
  const colorThemeNames = colorThemes.map(getColorThemeId);
  return colorThemeNames;
};

const getColorThemeNames = async (assetDir, platform) => {
  string(assetDir);
  number(platform);
  const extensions = await getAllExtensions(assetDir, platform);
  const colorThemeNames = getColorThemeNamesFromExtensions(extensions);
  return colorThemeNames;
};

const getExtension = async (id, assetDir, platform) => {
  const allExtensions = await getAllExtensions(assetDir, platform);
  for (const extension of allExtensions) {
    if (extension.id === id) {
      return extension;
    }
  }
  return undefined;
};

const getPortTuple = () => {
  const {
    port1,
    port2
  } = new MessageChannel();
  return {
    port1,
    port2
  };
};

const createWebViewIpc = async webView => {
  const {
    origin,
    uid
  } = webView;
  const {
    port1,
    port2
  } = getPortTuple();
  const rpcPromise = MessagePortRpcParent.create({
    commandMap: {},
    isMessagePortOpen: false,
    messagePort: port2
  });
  const portType = 'test';
  await invokeAndTransfer('WebView.setPort', uid, port1, origin, portType);
  // TODO maybe don't send a message port only to get object url?
  // TODO dispose rpc to avoid memory leak
  const rpc = await rpcPromise;
  return rpc;
};

const webViews = Object.create(null);
const getWebView = id => {
  return webViews[id];
};

// TODO if webViewId is provided,
// 1. read file as blob
// 2. send blob to webview
// 3. create objecturl in webview
// 4. send back objecturl to extension host worker
// 5. provide objectUrl to extension

const getRemoteUrlForWebView = async (uri, options = {}) => {
  // TODO webviews should be stored in iframe worker
  const webView = getWebView(options.webViewId);
  if (!webView) {
    throw new Error(`webview ${options.webViewId} not found`);
  }
  const [rpc, blob] = await Promise.all([createWebViewIpc(webView), invoke('FileSystem.getBlob', uri)]);
  const objectUrl = await rpc.invoke('createObjectUrl', blob);
  return objectUrl;
};

const getRpcInfo = rpcId => {
  const info = get$2(rpcId);
  if (!info) {
    throw new Error(`Rpc not found ${rpcId}`);
  }
  return info;
};

const emptyStatus = {
  activationEndTime: 0,
  activationEvent: '',
  activationStartTime: 0,
  activationTime: 0,
  id: '',
  importEndTime: 0,
  importStartTime: 0,
  importTime: 0,
  status: None
};
const getRuntimeStatus = extensionId => {
  return get$3(extensionId) || emptyStatus;
};

const commandMapRef = {};

const handleMessagePort = async port => {
  await PlainMessagePortRpc.create({
    commandMap: commandMapRef,
    messagePort: port
  });
};

const initializeExtensionHostWorker = async () => {
  const rpc = await TransferMessagePortRpcParent.create({
    commandMap: commandMapRef,
    async send(port) {
      await sendMessagePortToExtensionHostWorker(port, 0);
    }
  });
  set$6(rpc);
};

const initializeFileSystemWorker = async () => {
  const rpc = await TransferMessagePortRpcParent.create({
    commandMap: commandMapRef,
    async send(port) {
      await sendMessagePortToFileSystemWorker(port, 0);
    }
  });
  set$5(rpc);
};

const getRpc = async platform => {
  // TODO create connection to shared process
  if (platform === Remote) {
    const rpc = await WebSocketRpcParent2.create({
      commandMap: commandMapRef,
      type: 'shared-process'
    });
    return rpc;
  }
  if (platform === Electron) {
    const rpc = TransferMessagePortRpcParent.create({
      commandMap: commandMapRef,
      async send(port) {
        await sendMessagePortToSharedProcess(port);
      }
    });
    return rpc;
  }
  return undefined;
};
const initializeSharedProcess = async platform => {
  const rpc = await getRpc(platform);
  if (rpc) {
    set$3(rpc);
  }
};

const initialize = async platform => {
  update({
    platform
  });
  await Promise.all([initializeFileSystemWorker(), initializeSharedProcess(platform), initializeExtensionHostWorker()]);
};

const installExtension = async () => {
  // TODO
};

const getRemoteUrl = uri => {
  if (uri.startsWith('/')) {
    return `/remote${uri}`;
  }
  return `/remote/${uri}`;
};

const getLanguagesFromExtension = (extension, platform) => {
  // TODO what if extension is null? should not crash process, handle error gracefully
  // TODO what if extension languages is not of type array?
  // TODO what if language is null?
  if (!extension) {
    return [];
  }
  if (!extension.languages) {
    return [];
  }
  const extensionPath = extension.path;
  const getLanguageFromExtension = language => {
    if (language.tokenize) {
      if (typeof language.tokenize !== 'string') {
        console.warn(`[info] ${language.id}: language.tokenize must be of type string but was of type ${typeof language.tokenize}`);
        return {
          ...language,
          extensionPath,
          tokenize: ''
        };
      }
      const relativePath = `${extensionPath}/${language.tokenize}`;
      const absolutePath = platform === Web ? relativePath : getRemoteUrl(relativePath);
      return {
        ...language,
        extensionPath,
        tokenize: absolutePath
      };
    }
    return language;
  };
  return extension.languages.map(getLanguageFromExtension);
};

const getLanguages = async (platform, assetDir) => {
  try {
    number(platform);
    string(assetDir);
    const extensions = await getAllExtensions(assetDir, platform);
    const languages = extensions.flatMap(extension => getLanguagesFromExtension(extension, platform));
    return languages;
  } catch (error) {
    throw new VError(error, 'Failed to load languages');
  }
};

const uninstallExtension = async () => {
  // TODO
};

const commandMap = {
  'Extensions.activate2': activateExtension2,
  'Extensions.activate3': activateExtension3,
  'Extensions.addWebExtension': addWebExtension,
  'Extensions.createWebViewWorkerRpc': createWebViewWorkerRpc,
  'Extensions.createWebViewWorkerRpc2': createWebViewWorkerRpc2,
  'Extensions.disable': disableExtension,
  'Extensions.disable2': disableExtension2,
  'Extensions.enable': enableExtension,
  'Extensions.enable2': enableExtension2,
  'Extensions.getAllExtensions': getAllExtensions,
  'Extensions.getColorThemeCss': getColorThemeCss,
  'Extensions.getColorThemeCssFromJson': getColorThemeCssFromJson,
  'Extensions.getColorThemeJson': getColorThemeJson,
  'Extensions.getColorThemeNames': getColorThemeNames,
  'Extensions.getDynamicWebExtensions': getDynamicWebExtensions,
  'Extensions.getExtension': getExtension,
  'Extensions.getLanguages': getLanguages,
  'Extensions.getRemoteUrlForWebView': getRemoteUrlForWebView,
  'Extensions.getRpcInfo': getRpcInfo,
  'Extensions.getRuntimeStatus': getRuntimeStatus,
  'Extensions.handleMessagePort': handleMessagePort,
  'Extensions.importExtension': importExtension,
  'Extensions.initialize': initialize,
  'Extensions.install': installExtension,
  'Extensions.invalidateExtensionsCache': invalidateExtensionsCache,
  'Extensions.uninstall': uninstallExtension
};

const listen = async () => {
  Object.assign(commandMapRef, commandMap);
  const rpc = await WebWorkerRpcClient.create({
    commandMap: commandMap
  });
  set$4(rpc);
};

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

main();
