"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Child = void 0;
var coreWorker = _interopRequireWildcard(require("./core-worker"));
function _assert() {
  const data = _interopRequireDefault(require("assert"));
  _assert = function () {
    return data;
  };
  return data;
}
function _nullthrows() {
  const data = _interopRequireDefault(require("nullthrows"));
  _nullthrows = function () {
    return data;
  };
  return data;
}
function _logger() {
  const data = _interopRequireWildcard(require("@parcel/logger"));
  _logger = function () {
    return data;
  };
  return data;
}
function _diagnostic() {
  const data = _interopRequireWildcard(require("@parcel/diagnostic"));
  _diagnostic = function () {
    return data;
  };
  return data;
}
function _core() {
  const data = require("@parcel/core");
  _core = function () {
    return data;
  };
  return data;
}
var _bus = _interopRequireDefault(require("./bus"));
function _profiler() {
  const data = require("@parcel/profiler");
  _profiler = function () {
    return data;
  };
  return data;
}
var _Handle2 = _interopRequireDefault(require("./Handle"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
// The import of './Handle' should really be imported eagerly (with @babel/plugin-transform-modules-commonjs's lazy mode).
const Handle = _Handle2.default;
class Child {
  callQueue = [];
  maxConcurrentCalls = 10;
  responseId = 0;
  responseQueue = new Map();
  handles = new Map();
  sharedReferences = new Map();
  sharedReferencesByValue = new Map();
  constructor(ChildBackend) {
    this.child = new ChildBackend(m => {
      this.messageListener(m);
    }, () => this.handleEnd());

    // Monitior all logging events inside this child process and forward to
    // the main process via the bus.
    this.loggerDisposable = _logger().default.onLog(event => {
      _bus.default.emit('logEvent', event);
    });
    // .. and do the same for trace events
    this.tracerDisposable = _profiler().tracer.onTrace(event => {
      _bus.default.emit('traceEvent', event);
    });
  }
  workerApi = {
    callMaster: (request, awaitResponse = true) => this.addCall(request, awaitResponse),
    createReverseHandle: fn => this.createReverseHandle(fn),
    runHandle: (handle, args) => this.workerApi.callMaster({
      handle: handle.id,
      args
    }, true),
    getSharedReference: ref => this.sharedReferences.get(ref),
    resolveSharedReference: value => this.sharedReferencesByValue.get(value)
  };
  messageListener(message) {
    if (message.type === 'response') {
      return this.handleResponse(message);
    } else if (message.type === 'request') {
      return this.handleRequest(message);
    }
  }
  send(data) {
    this.child.send(data);
  }
  async childInit(module, childId) {
    // $FlowFixMe
    if (process.browser) {
      if (module === '@parcel/core/src/worker.js') {
        this.module = coreWorker;
      } else {
        throw new Error('No dynamic require possible: ' + module);
      }
    } else {
      // $FlowFixMe this must be dynamic
      this.module = require(module);
    }
    this.childId = childId;
    if (this.module.childInit != null) {
      await this.module.childInit();
    }
  }
  async handleRequest(data) {
    let {
      idx,
      method,
      args,
      handle: handleId
    } = data;
    let child = (0, _nullthrows().default)(data.child);
    const responseFromContent = content => ({
      idx,
      child,
      type: 'response',
      contentType: 'data',
      content
    });
    const errorResponseFromError = e => ({
      idx,
      child,
      type: 'response',
      contentType: 'error',
      content: (0, _diagnostic().anyToDiagnostic)(e)
    });
    let result;
    if (handleId != null) {
      try {
        var _this$handles$get;
        let fn = (0, _nullthrows().default)((_this$handles$get = this.handles.get(handleId)) === null || _this$handles$get === void 0 ? void 0 : _this$handles$get.fn);
        result = responseFromContent(fn(...args));
      } catch (e) {
        result = errorResponseFromError(e);
      }
    } else if (method === 'childInit') {
      try {
        let [moduleName, childOptions] = args;
        if (childOptions.shouldPatchConsole) {
          (0, _logger().patchConsole)();
        } else {
          (0, _logger().unpatchConsole)();
        }
        if (childOptions.shouldTrace) {
          _profiler().tracer.enable();
        }
        result = responseFromContent(await this.childInit(moduleName, child));
      } catch (e) {
        result = errorResponseFromError(e);
      }
    } else if (method === 'startProfile') {
      this.profiler = new (_profiler().SamplingProfiler)();
      try {
        result = responseFromContent(await this.profiler.startProfiling());
      } catch (e) {
        result = errorResponseFromError(e);
      }
    } else if (method === 'endProfile') {
      try {
        let res = this.profiler ? await this.profiler.stopProfiling() : null;
        result = responseFromContent(res);
      } catch (e) {
        result = errorResponseFromError(e);
      }
    } else if (method === 'takeHeapSnapshot') {
      try {
        let v8 = require('v8');
        result = responseFromContent(v8.writeHeapSnapshot('heap-' + args[0] + '-' + (this.childId ? 'worker' + this.childId : 'main') + '.heapsnapshot'));
      } catch (e) {
        result = errorResponseFromError(e);
      }
    } else if (method === 'createSharedReference') {
      let [ref, _value] = args;
      let value = _value instanceof ArrayBuffer ?
      // In the case the value is pre-serialized as a buffer,
      // deserialize it.
      (0, _core().deserialize)(Buffer.from(_value)) : _value;
      this.sharedReferences.set(ref, value);
      this.sharedReferencesByValue.set(value, ref);
      result = responseFromContent(null);
    } else if (method === 'deleteSharedReference') {
      let ref = args[0];
      let value = this.sharedReferences.get(ref);
      this.sharedReferencesByValue.delete(value);
      this.sharedReferences.delete(ref);
      result = responseFromContent(null);
    } else {
      try {
        result = responseFromContent(
        // $FlowFixMe
        await this.module[method](this.workerApi, ...args));
      } catch (e) {
        result = errorResponseFromError(e);
      }
    }
    try {
      this.send(result);
    } catch (e) {
      result = this.send(errorResponseFromError(e));
    }
  }
  handleResponse(data) {
    let idx = (0, _nullthrows().default)(data.idx);
    let contentType = data.contentType;
    let content = data.content;
    let call = (0, _nullthrows().default)(this.responseQueue.get(idx));
    if (contentType === 'error') {
      (0, _assert().default)(typeof content !== 'string');
      call.reject(new (_diagnostic().default)({
        diagnostic: content
      }));
    } else {
      call.resolve(content);
    }
    this.responseQueue.delete(idx);

    // Process the next call
    this.processQueue();
  }

  // Keep in mind to make sure responses to these calls are JSON.Stringify safe
  addCall(request, awaitResponse = true) {
    // $FlowFixMe
    let call = {
      ...request,
      type: 'request',
      child: this.childId,
      // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
      awaitResponse,
      resolve: () => {},
      reject: () => {}
    };
    let promise;
    if (awaitResponse) {
      promise = new Promise((resolve, reject) => {
        call.resolve = resolve;
        call.reject = reject;
      });
    }
    this.callQueue.push(call);
    this.processQueue();
    return promise ?? Promise.resolve();
  }
  sendRequest(call) {
    let idx;
    if (call.awaitResponse) {
      idx = this.responseId++;
      this.responseQueue.set(idx, call);
    }
    this.send({
      idx,
      child: call.child,
      type: call.type,
      location: call.location,
      handle: call.handle,
      method: call.method,
      args: call.args,
      awaitResponse: call.awaitResponse
    });
  }
  processQueue() {
    if (!this.callQueue.length) {
      return;
    }
    if (this.responseQueue.size < this.maxConcurrentCalls) {
      this.sendRequest(this.callQueue.shift());
    }
  }
  handleEnd() {
    this.loggerDisposable.dispose();
    this.tracerDisposable.dispose();
  }
  createReverseHandle(fn) {
    let handle = new Handle({
      fn,
      childId: this.childId
    });
    this.handles.set(handle.id, handle);
    return handle;
  }
}
exports.Child = Child;