'use strict';

/* exported createWorker */
function createWorker({url, lifeTime = 300}) {
  let worker;
  let id;
  let timer;
  const pendingResponse = new Map();
  return new Proxy({}, {
    get(target, prop) {
      return (...args) => {
        if (!worker) init();
        return invoke(prop, args);
      };
    },
  });

  function init() {
    id = 0;
    worker = new Worker('/js/worker-util.js?' + new URLSearchParams({url}));
    worker.onmessage = onMessage;
  }

  function uninit() {
    worker.onmessage = null;
    worker.terminate();
    worker = null;
  }

  function onMessage({data: {id, data, error}}) {
    pendingResponse.get(id)[error ? 'reject' : 'resolve'](data);
    pendingResponse.delete(id);
    if (!pendingResponse.size && lifeTime >= 0) {
      timer = setTimeout(uninit, lifeTime * 1000);
    }
  }

  function invoke(action, args) {
    return new Promise((resolve, reject) => {
      pendingResponse.set(id, {resolve, reject});
      clearTimeout(timer);
      worker.postMessage({id, action, args});
      id++;
    });
  }
}

/* exported createWorkerApi */
function createWorkerApi(methods) {
  self.onmessage = async ({data: {id, action, args}}) => {
    let data, error;
    try {
      data = await methods[action](...args);
    } catch (err) {
      error = true;
      data = cloneError(err);
    }
    self.postMessage({id, data, error});
  };
}

function cloneError(err) {
  return Object.assign({
    name: err.name,
    stack: err.stack,
    message: err.message,
    lineNumber: err.lineNumber,
    columnNumber: err.columnNumber,
    fileName: err.fileName,
  }, err);
}

if (self.WorkerGlobalScope) {

  const loadedUrls = [];

  self.require = urls => {
    const toLoad = (Array.isArray(urls) ? urls : [urls])
      .map(u => u.endsWith('.js') ? u : u + '.js')
      .filter(u => !loadedUrls.includes(u));
    if (toLoad) {
      loadedUrls.push(...toLoad);
      importScripts(...toLoad);
    }
  };

  const url = new URLSearchParams(location.search).get('url');
  if (url) require(url);
}