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 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';
      }
      return 'object';
    case 'boolean':
      return 'boolean';
    default:
      return 'unknown';
  }
};
const object = value => {
  const type = getType(value);
  if (type !== 'object') {
    throw new AssertionError('expected value to be of type object');
  }
};

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$2 = 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$2(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$2(stderr);
  const messageIndex = lines.findIndex(isModuleNotFoundMessage);
  const message = lines[messageIndex];
  return {
    message,
    code: ERR_MODULE_NOT_FOUND
  };
};
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 {
    message: `Incompatible native node module: ${message}`,
    code: E_INCOMPATIBLE_NATIVE_MODULE
  };
};
const getModuleSyntaxError = () => {
  return {
    message: `ES Modules are not supported in electron`,
    code: E_MODULES_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$2(stderr);
  const {
    actualMessage,
    rest
  } = getDetails(lines);
  return {
    message: actualMessage,
    code: '',
    stack: rest
  };
};
class IpcError extends VError {
  // @ts-ignore
  constructor(betterMessage, stdout = '', stderr = '') {
    if (stdout || stderr) {
      // @ts-ignore
      const {
        message,
        code,
        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$8 = ({
  port
}) => {
  return port;
};
const signal$9 = port => {
  port.postMessage(readyMessage);
};
class IpcChildWithMessagePort 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() {
    // ignore
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
    this._rawIpc.start();
  }
}
const wrap$g = port => {
  return new IpcChildWithMessagePort(port);
};
const IpcChildWithMessagePort$1 = {
  __proto__: null,
  listen: listen$8,
  signal: signal$9,
  wrap: wrap$g
};
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 {
    resolve,
    promise
  } = 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({
      jsonrpc: '2.0',
      id: firstMessage.id,
      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 {
    resolve,
    promise
  } = 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({
        type,
        event
      });
    };
    addListener(eventEmitter, event, listener);
    listenerMap[event] = listener;
  }
  return promise;
};
const Message$1 = 3;
const create$5$1 = async ({
  messagePort,
  isMessagePortOpen
}) => {
  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 {
    type,
    event
  } = 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 Two = '2.0';
const create$4 = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};
const callbacks = Object.create(null);
const set$1 = (id, fn) => {
  callbacks[id] = fn;
};
const get$1 = id => {
  return callbacks[id];
};
const remove = id => {
  delete callbacks[id];
};
let id = 0;
const create$3$1 = () => {
  return ++id;
};
const registerPromise = () => {
  const id = create$3$1();
  const {
    resolve,
    promise
  } = Promise.withResolvers();
  set$1(id, resolve);
  return {
    id,
    promise
  };
};
const create$2 = (method, params) => {
  const {
    id,
    promise
  } = registerPromise();
  const message = {
    jsonrpc: Two,
    method,
    params,
    id
  };
  return {
    message,
    promise
  };
};
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$1 = lines => {
  return lines.split(NewLine$1);
};
const getCurrentStack = () => {
  const currentStack = joinLines(splitLines$1(new Error().stack || '').slice(2));
  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 = (...args) => {
  console.warn(...args);
};
const resolve = (id, response) => {
  const fn = get$1(id);
  if (!fn) {
    console.log(response);
    warn(`callback ${id} may already be disposed`);
    return;
  }
  fn(response);
  remove(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 = (id, error) => {
  return {
    jsonrpc: Two,
    id,
    error
  };
};
const getErrorResponse = (id, error, preparePrettyError, logError) => {
  const prettyError = preparePrettyError(error);
  logError(error, prettyError);
  const errorProperty = getErrorProperty(error, prettyError);
  return create$1(id, errorProperty);
};
const create$5 = (message, result) => {
  return {
    jsonrpc: Two,
    id: message.id,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$5(message, resultProperty);
};
const getErrorResponseSimple = (id, error) => {
  return {
    jsonrpc: Two,
    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');
};
const invokeHelper = async (ipc, method, params, useSendAndTransfer) => {
  const {
    message,
    promise
  } = create$2(method, params);
  if (useSendAndTransfer && ipc.sendAndTransfer) {
    ipc.sendAndTransfer(message);
  } else {
    ipc.send(message);
  }
  const responseMessage = await promise;
  return unwrapJsonRpcResult(responseMessage);
};
const send = (transport, method, ...params) => {
  const message = create$4(method, params);
  transport.send(message);
};
const invoke = (ipc, method, ...params) => {
  return invokeHelper(ipc, method, params, false);
};
const invokeAndTransfer = (ipc, method, ...params) => {
  return invokeHelper(ipc, method, params, true);
};

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 Error(`command not found ${command}`);
  }
  return fn(...args);
};

const createRpc = ipc => {
  const rpc = {
    // @ts-ignore
    ipc,
    /**
     * @deprecated
     */
    send(method, ...params) {
      send(ipc, method, ...params);
    },
    invoke(method, ...params) {
      return invoke(ipc, method, ...params);
    },
    invokeAndTransfer(method, ...params) {
      return invokeAndTransfer(ipc, method, ...params);
    },
    async dispose() {
      await ipc?.dispose();
    }
  };
  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, 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$a = async ({
  commandMap,
  messagePort
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const ipc = await listen$1(IpcChildWithMessagePort$1, {
    port: messagePort
  });
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  return rpc;
};
const MessagePortRpcClient = {
  __proto__: null,
  create: create$a
};
const create$3 = async ({
  commandMap,
  messagePort
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const rawIpc = await IpcParentWithMessagePort$1.create({
    messagePort,
    isMessagePortOpen: true
  });
  const ipc = IpcParentWithMessagePort$1.wrap(rawIpc);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  messagePort.start();
  return rpc;
};
const PlainMessagePortRpc = {
  __proto__: null,
  create: create$3
};
const create = 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
};

const GetTokensViewport = 'GetTokensViewport.getTokensViewport';
const HandleMessagePort = 'HandleMessagePort.handleMessagePort';
const HandleMessagePort2 = 'HandleMessagePort.handleMessagePort2';
const LoadTokenizer = 'Tokenizer.load';
const SetLines = 'TextDocument.setLines';
const TokenizeCodeBlock = 'Tokenizer.tokenizeCodeBlock';
const TokenizeIncremental = 'TokenizeIncremental.tokenizeIncremental';

const deepCopy = value => {
  return structuredClone(value);
};

const getInitialLineState = initialLineState => {
  return deepCopy(initialLineState);
};

const state = {
  warned: []
};
const flattenTokensArray = tokens => {
  const flattened = [];
  for (const token of tokens) {
    object(token);
    flattened.push(token.type, token.length);
  }
  return flattened;
};
const warnDeprecatedArrayReturn = (languageId, fn) => {
  if (state.warned.includes(fn)) {
    return;
  }
  state.warned.push(fn);
  console.warn(`tokenizers without hasArrayReturn=false are deprecated (language ${languageId})`);
};
/**
 *
 * @param {(line:string, lineState)=>any} tokenizeLine
 * @param {string} line
 * @param {any} lineStateAtStart
 * @param {boolean} hasArrayReturn
 * @returns
 */
const safeTokenizeLine = (languageId, tokenizeLine, line, lineStateAtStart, hasArrayReturn) => {
  try {
    const lineState = tokenizeLine(line, lineStateAtStart);
    if (!lineState || !lineState.tokens || !lineState.state) {
      throw new Error('invalid tokenization result');
    }
    if (!hasArrayReturn) {
      warnDeprecatedArrayReturn(languageId, tokenizeLine);
      // workaround for old tokenizers
      lineState.tokens = flattenTokensArray(lineState.tokens);
    }
    return lineState;
  } catch (error) {
    console.error(error);
    return {
      tokens: [/* type */0, /* length */line.length],
      lineState: lineStateAtStart
    };
  }
};

const lineMap = Object.create(null);
const lineCaches = Object.create(null);
const invalidStartIndices = Object.create(null);
const languageIds = Object.create(null);
const setLines = (editorId, lines) => {
  lineMap[editorId] = lines;
  lineCaches[editorId] = [];
  invalidStartIndices[editorId] = 0;
};
const getLines = editorId => {
  return lineMap[editorId];
};
const getLineCache = editorId => {
  return lineCaches[editorId];
};
const getInvalidStartIndex = editorId => {
  return invalidStartIndices[editorId];
};
const resetInvalidStartIndex = languageId => {
  for (const [key, value] of Object.entries(languageIds)) {
    if (value === languageId) {
      invalidStartIndices[key] = 0;
    }
  }
};
const setInvalidStartIndex = (editorId, index) => {
  invalidStartIndices[editorId] = index;
};
const setLanguageId = (editorId, languageId) => {
  languageIds[editorId] = languageId;
};

/**
 * @enum number
 */
const State = {
  TopLevelContent: 1
};

/**
 * @enum number
 */
const TokenType = {
  Text: 1
};
const TokenMap = {
  [TokenType.Text]: 'Text'
};
const initialLineState = {
  state: State.TopLevelContent
};
const hasArrayReturn = true;
const tokenizeLine = (line, lineState) => {
  return {
    tokens: [TokenType.Text, line.length],
    state: lineState.state
  };
};

const TokenizePlainText = {
  __proto__: null,
  State,
  TokenMap,
  TokenType,
  hasArrayReturn,
  initialLineState,
  tokenizeLine
};

const tokenizers = Object.create(null);
const set = (id, value) => {
  tokenizers[id] = value;
  resetInvalidStartIndex(id);
};
const get = id => {
  return tokenizers[id] || TokenizePlainText;
};

// TODO loadTokenizer should be invoked from renderer worker
const loadTokenizer = async (languageId, tokenizePath) => {
  if (!tokenizePath) {
    return;
  }
  try {
    // TODO check that tokenizer is valid
    // 1. tokenizeLine should be of type function
    // 2. getTokenClass should be of type function
    const tokenizer = await import(tokenizePath);
    if (typeof tokenizer.tokenizeLine !== 'function') {
      console.warn(`tokenizer.tokenizeLine should be a function in "${tokenizePath}"`);
      return;
    }
    if (!tokenizer.TokenMap || typeof tokenizer.TokenMap !== 'object' || Array.isArray(tokenizer.TokenMap)) {
      console.warn(`tokenizer.TokenMap should be an object in "${tokenizePath}"`);
      return;
    }
    set(languageId, tokenizer);
    return tokenizer.TokenMap;
  } catch (error) {
    // TODO better error handling
    console.error(error);
    return;
  }
};
const getTokenizer = languageId => {
  if (get(languageId)) {
    return get(languageId);
  }
  return TokenizePlainText;
};

const getTokensViewportEmbedded = (langageId, lines, lineCache, linesWithEmbed) => {
  const tokenizersToLoad = [];
  const embeddedResults = [];
  let topContext;
  for (const index of linesWithEmbed) {
    const result = lineCache[index + 1];
    const line = lines[index];
    if (result.embeddedLanguage) {
      const {
        embeddedLanguage,
        embeddedLanguageStart,
        embeddedLanguageEnd
      } = result;
      const embeddedTokenizer = getTokenizer(embeddedLanguage);
      if (embeddedLanguageStart !== line.length && embeddedTokenizer && embeddedTokenizer !== TokenizePlainText) {
        const isFull = embeddedLanguageStart === 0 && embeddedLanguageEnd === line.length;
        const partialLine = line.slice(embeddedLanguageStart, embeddedLanguageEnd);
        const embedResult = safeTokenizeLine(langageId, embeddedTokenizer.tokenizeLine, partialLine, topContext || getInitialLineState(embeddedTokenizer.initialLineState), embeddedTokenizer.hasArrayReturn);
        topContext = embedResult;
        result.embeddedResultIndex = embeddedResults.length;
        embeddedResults.push({
          result: embedResult,
          TokenMap: embeddedTokenizer.TokenMap,
          isFull
        });
      } else if (line.length === 0) {
        const embedResult = {
          tokens: []
        };
        result.embeddedResultIndex = embeddedResults.length;
        embeddedResults.push({
          result: embedResult,
          isFull: true,
          TokenMap: []
        });
      } else {
        tokenizersToLoad.push(embeddedLanguage);
        embeddedResults.push({
          result: {},
          isFull: false,
          TokenMap: []
        });
        topContext = undefined;
      }
    } else {
      topContext = undefined;
    }
  }
  return {
    tokenizersToLoad,
    embeddedResults
  };
};
const getTokenizeEndIndex = (invalidStartIndex, endLineIndex, tokenizeStartIndex) => {
  return invalidStartIndex < endLineIndex ? endLineIndex : tokenizeStartIndex;
};

// TODO only send changed lines to renderer process instead of all lines in viewport
const getTokensViewport = (editor, startLineIndex, endLineIndex, hasLinesToSend, id, linesToSend) => {
  if (hasLinesToSend) {
    setLines(id, linesToSend);
  }
  const {
    languageId
  } = editor;
  setLanguageId(id, languageId);
  const lines = getLines(id);
  const lineCache = getLineCache(id);
  const invalidStartIndex = getInvalidStartIndex(id);
  const tokenizer = get(languageId);
  const {
    hasArrayReturn,
    tokenizeLine,
    initialLineState
  } = tokenizer;
  const tokenizeStartIndex = invalidStartIndex;
  const tokenizeEndIndex = getTokenizeEndIndex(invalidStartIndex, endLineIndex, tokenizeStartIndex);
  const tokenizersToLoad = [];
  const embeddedResults = [];
  const linesWithEmbed = [];
  for (let i = tokenizeStartIndex; i < tokenizeEndIndex; i++) {
    const lineState = i === 0 ? getInitialLineState(initialLineState) : lineCache[i];
    const line = lines[i];
    const result = safeTokenizeLine(languageId, tokenizeLine, line, lineState, hasArrayReturn);
    // TODO if lineCacheEnd matches the one before, skip tokenizing lines after
    lineCache[i + 1] = result;
    if (result.embeddedLanguage) {
      result.embeddedResultIndex = linesWithEmbed.length;
      linesWithEmbed.push(i);
    }
  }
  const visibleLines = lineCache.slice(startLineIndex + 1, endLineIndex + 1);
  if (linesWithEmbed.length > 0) {
    const {
      tokenizersToLoad,
      embeddedResults
    } = getTokensViewportEmbedded(languageId, lines, lineCache, linesWithEmbed);
    // TODO support lineCache with embedded content
    setInvalidStartIndex(id, 0);
    return {
      tokens: visibleLines,
      tokenizersToLoad,
      embeddedResults
    };
  }
  setInvalidStartIndex(id, Math.max(invalidStartIndex, tokenizeEndIndex));
  return {
    tokens: visibleLines,
    tokenizersToLoad,
    embeddedResults
  };
};

const getLineInfo = (line, tokens, TokenMap) => {
  const tokensLength = tokens.length;
  let end = 0;
  let start = 0;
  const lineInfo = [];
  for (let i = 0; i < tokensLength; i += 2) {
    const tokenType = tokens[i];
    const tokenLength = tokens[i + 1];
    end += tokenLength;
    const text = line.slice(start, end);
    const className = `Token ${TokenMap[tokenType] || 'Unknown'}`;
    const normalizedText = text;
    lineInfo.push(normalizedText, className);
    start = end;
  }
  return lineInfo;
};

const getLineInfos = (lines, tokenizer, languageId) => {
  const lineInfos = [];
  const {
    tokenizeLine,
    initialLineState,
    hasArrayReturn,
    TokenMap
  } = tokenizer;
  let currentLineState = getInitialLineState(initialLineState);
  for (const line of lines) {
    const result = safeTokenizeLine(languageId, tokenizeLine, line, currentLineState, hasArrayReturn);
    const {
      tokens
    } = result;
    const lineInfo = getLineInfo(line, tokens, TokenMap);
    lineInfos.push(lineInfo);
    currentLineState = result;
  }
  return lineInfos;
};

const NewLine = '\n';

const splitLines = lines => {
  if (!lines) {
    return [];
  }
  return lines.split(NewLine);
};

const tokenizeCodeBlock = async (codeBlock, languageId, tokenizerPath) => {
  await loadTokenizer(languageId, tokenizerPath);
  const tokenizer = getTokenizer(languageId);
  const lines = splitLines(codeBlock);
  const lineInfos = getLineInfos(lines, tokenizer, languageId);
  return lineInfos;
};

const tokenizeIncremental = (id, languageId, oldLine, newLine, rowIndex, minLineY) => {
  const lineCache = getLineCache(id);
  const tokenizer = get(languageId);
  const initialLineState = lineCache[rowIndex] || getInitialLineState(tokenizer.initialLineState);
  const {
    tokens: oldTokens
  } = safeTokenizeLine(languageId, tokenizer.tokenizeLine, oldLine, initialLineState, tokenizer.hasArrayReturn);
  // @ts-ignore
  const {
    tokens: newTokens} = safeTokenizeLine(languageId, tokenizer.tokenizeLine, newLine, initialLineState, tokenizer.hasArrayReturn);
  if (newTokens.length !== oldTokens.length) {
    return undefined;
  }
  const incrementalEdits = [];
  let offset = 0;
  const relativeRowIndex = rowIndex - minLineY;
  for (let i = 0; i < oldTokens.length; i += 2) {
    const oldTokenType = oldTokens[i];
    const oldTokenLength = oldTokens[i + 1];
    const newTokenType = newTokens[i];
    const newTokenLength = newTokens[i + 1];
    if (oldTokenType === newTokenType && oldTokenLength !== newTokenLength && oldTokenLength > 0) {
      const columnTokenIndex = i / 2;
      incrementalEdits.push({
        rowIndex: relativeRowIndex,
        columnIndex: columnTokenIndex,
        text: newLine.slice(offset, offset + newTokenLength)
      });
    }
    offset += newTokenLength;
  }
  if (incrementalEdits.length === 1) {
    return incrementalEdits;
  }
  return undefined;
};

const commandMap$1 = {
  [GetTokensViewport]: getTokensViewport,
  [LoadTokenizer]: loadTokenizer,
  [SetLines]: setLines,
  [TokenizeCodeBlock]: tokenizeCodeBlock,
  [TokenizeIncremental]: tokenizeIncremental
};

const handleMessagePort = async port => {
  await MessagePortRpcClient.create({
    commandMap: commandMap$1,
    messagePort: port
  });
};
const handleMessagePort2 = async port => {
  await PlainMessagePortRpc.create({
    commandMap: commandMap$1,
    messagePort: port
  });
};

const commandMap = {
  ...commandMap$1,
  [HandleMessagePort]: handleMessagePort,
  [HandleMessagePort2]: handleMessagePort2
};

const listen = async () => {
  await WebWorkerRpcClient.create({
    commandMap: commandMap
  });
};

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

main();
