"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const errors = require("../errors.js");
const event = require("../event.js");
const lifecycle = require("../lifecycle.js");
const platform = require("../platform.js");
const types = require("../types.js");
const strings = require("../strings.js");
const INITIALIZE = "$initialize";
let webWorkerWarningLogged = false;
function logOnceWebWorkerWarning(err) {
  if (!platform.isWeb) {
    return;
  }
  if (!webWorkerWarningLogged) {
    webWorkerWarningLogged = true;
    console.warn("Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq");
  }
  console.warn(err.message);
}
class RequestMessage {
  constructor(vsWorker, req, method, args) {
    this.vsWorker = vsWorker;
    this.req = req;
    this.method = method;
    this.args = args;
    this.type = 0;
  }
}
class ReplyMessage {
  constructor(vsWorker, seq, res, err) {
    this.vsWorker = vsWorker;
    this.seq = seq;
    this.res = res;
    this.err = err;
    this.type = 1;
  }
}
class SubscribeEventMessage {
  constructor(vsWorker, req, eventName, arg) {
    this.vsWorker = vsWorker;
    this.req = req;
    this.eventName = eventName;
    this.arg = arg;
    this.type = 2;
  }
}
class EventMessage {
  constructor(vsWorker, req, event2) {
    this.vsWorker = vsWorker;
    this.req = req;
    this.event = event2;
    this.type = 3;
  }
}
class UnsubscribeEventMessage {
  constructor(vsWorker, req) {
    this.vsWorker = vsWorker;
    this.req = req;
    this.type = 4;
  }
}
class SimpleWorkerProtocol {
  constructor(handler) {
    this._workerId = -1;
    this._handler = handler;
    this._lastSentReq = 0;
    this._pendingReplies = /* @__PURE__ */ Object.create(null);
    this._pendingEmitters = /* @__PURE__ */ new Map();
    this._pendingEvents = /* @__PURE__ */ new Map();
  }
  setWorkerId(workerId) {
    this._workerId = workerId;
  }
  sendMessage(method, args) {
    const req = String(++this._lastSentReq);
    return new Promise((resolve, reject) => {
      this._pendingReplies[req] = {
        resolve,
        reject
      };
      this._send(new RequestMessage(this._workerId, req, method, args));
    });
  }
  listen(eventName, arg) {
    let req = null;
    const emitter = new event.Emitter({
      onFirstListenerAdd: () => {
        req = String(++this._lastSentReq);
        this._pendingEmitters.set(req, emitter);
        this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg));
      },
      onLastListenerRemove: () => {
        this._pendingEmitters.delete(req);
        this._send(new UnsubscribeEventMessage(this._workerId, req));
        req = null;
      }
    });
    return emitter.event;
  }
  handleMessage(message) {
    if (!message || !message.vsWorker) {
      return;
    }
    if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
      return;
    }
    this._handleMessage(message);
  }
  _handleMessage(msg) {
    switch (msg.type) {
      case 1:
        return this._handleReplyMessage(msg);
      case 0:
        return this._handleRequestMessage(msg);
      case 2:
        return this._handleSubscribeEventMessage(msg);
      case 3:
        return this._handleEventMessage(msg);
      case 4:
        return this._handleUnsubscribeEventMessage(msg);
    }
  }
  _handleReplyMessage(replyMessage) {
    if (!this._pendingReplies[replyMessage.seq]) {
      console.warn("Got reply to unknown seq");
      return;
    }
    let reply = this._pendingReplies[replyMessage.seq];
    delete this._pendingReplies[replyMessage.seq];
    if (replyMessage.err) {
      let err = replyMessage.err;
      if (replyMessage.err.$isError) {
        err = new Error();
        err.name = replyMessage.err.name;
        err.message = replyMessage.err.message;
        err.stack = replyMessage.err.stack;
      }
      reply.reject(err);
      return;
    }
    reply.resolve(replyMessage.res);
  }
  _handleRequestMessage(requestMessage) {
    let req = requestMessage.req;
    let result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
    result.then((r) => {
      this._send(new ReplyMessage(this._workerId, req, r, void 0));
    }, (e) => {
      if (e.detail instanceof Error) {
        e.detail = errors.transformErrorForSerialization(e.detail);
      }
      this._send(new ReplyMessage(this._workerId, req, void 0, errors.transformErrorForSerialization(e)));
    });
  }
  _handleSubscribeEventMessage(msg) {
    const req = msg.req;
    const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event2) => {
      this._send(new EventMessage(this._workerId, req, event2));
    });
    this._pendingEvents.set(req, disposable);
  }
  _handleEventMessage(msg) {
    if (!this._pendingEmitters.has(msg.req)) {
      console.warn("Got event for unknown req");
      return;
    }
    this._pendingEmitters.get(msg.req).fire(msg.event);
  }
  _handleUnsubscribeEventMessage(msg) {
    if (!this._pendingEvents.has(msg.req)) {
      console.warn("Got unsubscribe for unknown req");
      return;
    }
    this._pendingEvents.get(msg.req).dispose();
    this._pendingEvents.delete(msg.req);
  }
  _send(msg) {
    let transfer = [];
    if (msg.type === 0) {
      for (let i = 0; i < msg.args.length; i++) {
        if (msg.args[i] instanceof ArrayBuffer) {
          transfer.push(msg.args[i]);
        }
      }
    } else if (msg.type === 1) {
      if (msg.res instanceof ArrayBuffer) {
        transfer.push(msg.res);
      }
    }
    this._handler.sendMessage(msg, transfer);
  }
}
class SimpleWorkerClient extends lifecycle.Disposable {
  constructor(workerFactory, moduleId, host) {
    super();
    let lazyProxyReject = null;
    this._worker = this._register(workerFactory.create("vs/base/common/worker/simpleWorker", (msg) => {
      this._protocol.handleMessage(msg);
    }, (err) => {
      if (lazyProxyReject) {
        lazyProxyReject(err);
      }
    }));
    this._protocol = new SimpleWorkerProtocol({
      sendMessage: (msg, transfer) => {
        this._worker.postMessage(msg, transfer);
      },
      handleMessage: (method, args) => {
        if (typeof host[method] !== "function") {
          return Promise.reject(new Error("Missing method " + method + " on main thread host."));
        }
        try {
          return Promise.resolve(host[method].apply(host, args));
        } catch (e) {
          return Promise.reject(e);
        }
      },
      handleEvent: (eventName, arg) => {
        if (propertyIsDynamicEvent(eventName)) {
          const event2 = host[eventName].call(host, arg);
          if (typeof event2 !== "function") {
            throw new Error(`Missing dynamic event ${eventName} on main thread host.`);
          }
          return event2;
        }
        if (propertyIsEvent(eventName)) {
          const event2 = host[eventName];
          if (typeof event2 !== "function") {
            throw new Error(`Missing event ${eventName} on main thread host.`);
          }
          return event2;
        }
        throw new Error(`Malformed event name ${eventName}`);
      }
    });
    this._protocol.setWorkerId(this._worker.getId());
    let loaderConfiguration = null;
    if (typeof platform.globals.require !== "undefined" && typeof platform.globals.require.getConfig === "function") {
      loaderConfiguration = platform.globals.require.getConfig();
    } else if (typeof platform.globals.requirejs !== "undefined") {
      loaderConfiguration = platform.globals.requirejs.s.contexts._.config;
    }
    const hostMethods = types.getAllMethodNames(host);
    this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
      this._worker.getId(),
      JSON.parse(JSON.stringify(loaderConfiguration)),
      moduleId,
      hostMethods
    ]);
    const proxyMethodRequest = (method, args) => {
      return this._request(method, args);
    };
    const proxyListen = (eventName, arg) => {
      return this._protocol.listen(eventName, arg);
    };
    this._lazyProxy = new Promise((resolve, reject) => {
      lazyProxyReject = reject;
      this._onModuleLoaded.then((availableMethods) => {
        resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen));
      }, (e) => {
        reject(e);
        this._onError("Worker failed to load " + moduleId, e);
      });
    });
  }
  getProxyObject() {
    return this._lazyProxy;
  }
  _request(method, args) {
    return new Promise((resolve, reject) => {
      this._onModuleLoaded.then(() => {
        this._protocol.sendMessage(method, args).then(resolve, reject);
      }, reject);
    });
  }
  _onError(message, error) {
    console.error(message);
    console.info(error);
  }
}
function propertyIsEvent(name) {
  return name[0] === "o" && name[1] === "n" && strings.isUpperAsciiLetter(name.charCodeAt(2));
}
function propertyIsDynamicEvent(name) {
  return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9));
}
function createProxyObject(methodNames, invoke, proxyListen) {
  const createProxyMethod = (method) => {
    return function() {
      const args = Array.prototype.slice.call(arguments, 0);
      return invoke(method, args);
    };
  };
  const createProxyDynamicEvent = (eventName) => {
    return function(arg) {
      return proxyListen(eventName, arg);
    };
  };
  let result = {};
  for (const methodName of methodNames) {
    if (propertyIsDynamicEvent(methodName)) {
      result[methodName] = createProxyDynamicEvent(methodName);
      continue;
    }
    if (propertyIsEvent(methodName)) {
      result[methodName] = proxyListen(methodName, void 0);
      continue;
    }
    result[methodName] = createProxyMethod(methodName);
  }
  return result;
}
exports.SimpleWorkerClient = SimpleWorkerClient;
exports.logOnceWebWorkerWarning = logOnceWebWorkerWarning;
