(function (factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) : factory({});

})((function (exports) {

'use strict';

let sync_promises = [], getHPainter, _openui5args = {},
    jsrp = null, geo = null; // old JSROOT.Painter and JSROOT.GEO handles

function _sync() {
   let arr;
   if (sync_promises) {
      arr = sync_promises;
      sync_promises = [];
   }

   return Promise.all(arr).then(() => {

      if (globalThis.JSROOT) {
         if (getHPainter) globalThis.JSROOT.hpainter = getHPainter();
         if (globalThis.JSROOT.batch_mode && globalThis.JSROOT.isBatchMode && !globalThis.JSROOT.isBatchMode())
            globalThis.JSROOT.setBatchMode(true);
      }

      return globalThis.JSROOT;
   });
}


function loadPainter() {
   if (jsrp)
      return Promise.resolve(jsrp);
   return Promise.all([import('../modules/d3.mjs'), import('../modules/draw.mjs'), import('../modules/base/colors.mjs'),
                       import('../modules/base/BasePainter.mjs'), import('../modules/base/ObjectPainter.mjs'),
                       import('../modules/base/TAttLineHandler.mjs'), import('../modules/gui/menu.mjs')]).then(res => {
      if (jsrp)
         return jsrp;
      globalThis.d3 = res[0]; // assign global d3
      jsrp = Object.assign({}, res[1], res[2], res[3], res[4], res[5], res[6]);
      globalThis.JSROOT.Painter = jsrp;
      globalThis.JSROOT.BasePainter = res[3].BasePainter;
      globalThis.JSROOT.ObjectPainter = res[4].ObjectPainter;
      return jsrp;
   });
}

exports.httpRequest = function(...args) {
   return _sync().then(() => import('../modules/core.mjs')).then(handle => handle.httpRequest(...args));
}

exports.loadScript = function(...args) {
   return _sync().then(() => import('../modules/core.mjs')).then(handle => handle.loadScript(...args));
}

exports.buildGUI = function(...args) {
   return _sync().then(() => import('../modules/gui.mjs')).then(handle => handle.buildGUI(...args));
}

exports.openFile = function(...args) {
   return _sync().then(() => import('../modules/io.mjs')).then(handle => handle.openFile(...args));
}

exports.parse = function(json) {
   if (typeof json == 'string')
      json = JSON.parse(json);

   if (json && typeof json == 'object')
      json.$parse_workaround = true;
   return json;
}

function test_parse_workaround(obj) {
   if (obj && (typeof obj == 'object') && obj.$parse_workaround) {
      delete obj.$parse_workaround;
      if (JSROOT?.parse)
         obj = JSROOT.parse(obj);
   }
   return obj;
}

exports.draw = function(dom, obj, opt) {
   return _sync().then(() => import('../modules/draw.mjs')).then(handle => handle.draw(dom, test_parse_workaround(obj), opt));
}

exports.redraw = function(dom, obj, opt) {
   return _sync().then(() => import('../modules/draw.mjs')).then(handle => handle.redraw(dom, test_parse_workaround(obj), opt));
}

let workaround_settings = {};

exports.settings = workaround_settings;

/** @summary Old v6 method to load JSROOT functionality
  * @desc
  * Following components can be specified
  *    - 'io'     TFile functionality
  *    - 'tree'   TTree support
  *    - 'painter' d3.js plus basic painting functions
  *    - 'geom'   TGeo support
  *    - 'math'   some methods from TMath class
  *    - 'hierarchy' hierarchy browser
  *    - 'openui5' OpenUI5 and related functionality
  * @param {Array|string} req - list of required components (as array or string separated by semicolon)
  * @return {Promise} with array of requirements (or single element) */

function v6_require(need) {
   if (!need)
      return Promise.resolve(null);

   if (typeof need == 'string') need = need.split(';');

   need.forEach((name, indx) => {
      if ((name.indexOf('load:') == 0) || (name.indexOf('user:') == 0))
         need[indx] = name.slice(5);
      else if (name == '2d')
         need[indx] = 'painter';
      else if ((name == 'jq2d') || (name == 'jq'))
         need[indx] = 'hierarchy';
      else if (name == 'v6')
         need[indx] = 'gpad';
      else if (name == 'v7')
         need[indx] = 'v7gpad';
   });

   let arr = [];

   need.forEach(name => {
      if (name == 'hist')
         arr.push(Promise.all([import('../modules/hist/TH1Painter.mjs'), import('../modules/hist/TH2Painter.mjs'), import('../modules/hist/THStackPainter.mjs')]).then(arr => {
            // copy hist painter objects into JSROOT
            Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]);
         }));
      else if (name == 'more')
         arr.push(import('../modules/draw/more.mjs'));
      else if (name == 'gpad')
         arr.push(loadPainter().then(() => Promise.all([import('../modules/gpad/TAxisPainter.mjs'), import('../modules/gpad/TPadPainter.mjs'), import('../modules/gpad/TCanvasPainter.mjs')])).then(arr => {
            // copy all classes
            Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]);
            if (jsrp) jsrp.ensureTCanvas = arr[2].ensureTCanvas;
            return globalThis.JSROOT;
         }));
      else if (name == 'v7gpad')
         arr.push(loadPainter().then(() => Promise.all([import('../modules/gpad/RAxisPainter.mjs'), import('../modules/gpad/RPadPainter.mjs'), import('../modules/gpad/RCanvasPainter.mjs')])).then(arr => {
            // copy all classes
            Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]);
            if (jsrp) jsrp.ensureRCanvas = arr[2].ensureRCanvas;
            arr[1].RPadPainter.prototype.drawObject = globalThis.JSROOT.draw;
            arr[2].RPadPainter.prototype.getObjectDrawSettings = globalThis.JSROOT.getDrawSettings;
            return globalThis.JSROOT;
         }));
      else if (name == 'io')
         arr.push(import('../modules/io.mjs'));
      else if (name == 'tree')
         arr.push(import('../modules/tree.mjs'));
      else if (name == 'geom')
         arr.push(geo ? Promise.resolve(geo) : loadPainter().then(() => Promise.all([import('../modules/geom/geobase.mjs'),
            import('../modules/geom/TGeoPainter.mjs'), import('../modules/base/base3d.mjs'), import('../modules/three.mjs'), import('../modules/three_addons.mjs')])).then(res => {

            if (geo)
               return geo;

            globalThis.JSROOT.GEO = geo = Object.assign({}, res[0], res[1]);
            globalThis.JSROOT.TGeoPainter = res[1].TGeoPainter;
            globalThis.JSROOT.Painter.createGeoPainter = res[1].createGeoPainter;
            globalThis.JSROOT.Painter.GeoDrawingControl = res[1].GeoDrawingControl;

            class myPoints extends res[2].PointsCreator {
               noPromise() { return true; }
            };
            globalThis.JSROOT.Painter.PointsCreator = myPoints;

            if (!globalThis.THREE)
               globalThis.THREE = Object.assign({}, res[3], res[4]);

            return geo;
         }));
      else if (name == 'math')
         arr.push(import('../modules/base/math.mjs'));
      else if (name == 'latex')
         arr.push(import('../modules/base/latex.mjs'));
      else if (name == 'painter')
         arr.push(loadPainter());
      else if (name == 'hierarchy')
         arr.push(Promise.all([import('../modules/gui/display.mjs'), import('../modules/gui/HierarchyPainter.mjs'), import('../modules/draw/TTree.mjs')]).then(arr => {
            Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]);
            getHPainter = arr[0].getHPainter;
            globalThis.JSROOT.hpainter = getHPainter();
            return globalThis.JSROOT;
         }));
      else if (name == 'openui5')
         arr.push(import('../modules/gui/utils.mjs').then(handle => handle.loadOpenui5(_openui5args)));
      else if (name == 'interactive')
         arr.push(import('../modules/gui/utils.mjs').then(handle => { return { addMoveHandler: handle.addMoveHandler }; }));
      else if (name.indexOf('.js') >= 0)
         arr.push(import('../modules/core.mjs').then(handle => handle.loadScript(name)));
   });

   // need notify calling function when require is completed
   let notify;
   sync_promises.push(new Promise(func => { notify = func; }));

   return new Promise(resolveFunc => {
      Promise.all(arr).then(res => {
         resolveFunc(res.length == 1 ? res[0] : res);
         if (notify) notify(true);
      });
   });
}


exports.require = v6_require;

exports.define = function(req, factoryFunc) {
   let pr = new Promise(resolveFunc => {
       v6_require(req).then(arr => {
         if (req.length < 2) factoryFunc(arr)
                        else factoryFunc(...arr);
         resolveFunc(true);
       });
   });

   sync_promises.push(pr); // will wait until other PRs are finished
}

// duplicate function here, used before loading any other functionality
exports.decodeUrl = function(url) {
   let res = {
      opts: {},
      has(opt) { return this.opts[opt] !== undefined; },
      get(opt,dflt) { return this.opts[opt] ?? dflt; }
   };

   if (!url || (typeof url !== 'string')) {
      if (typeof document === 'undefined')
         return res;
      url = document.URL;
   }
   res.url = url;

   let p1 = url.indexOf('?');
   if (p1 < 0)
      return res;
   url = decodeURI(url.slice(p1+1));

   while (url) {
      // try to correctly handle quotes in the URL
      let pos = 0, nq = 0, eq = -1, firstq = -1;
      while ((pos < url.length) && (nq || ((url[pos] !== '&') && (url[pos] !== '#')))) {
         switch (url[pos]) {
            case "'": if (nq >= 0) nq = (nq+1)%2; if (firstq < 0) firstq = pos; break;
            case '"': if (nq <= 0) nq = (nq-1)%2; if (firstq < 0) firstq = pos; break;
            case '=': if ((firstq < 0) && (eq < 0)) eq = pos; break;
         }
         pos++;
      }
      if ((eq < 0) && (firstq < 0)) {
         res.opts[url.slice(0,pos)] = '';
      } if (eq > 0) {
         let val = url.slice(eq+1, pos);
         if (((val[0] === "'") || (val[0] === '"')) && (val.at(0) === val.at(-1)))
            val = val.slice(1, val.length - 1);
         res.opts[url.slice(0,eq)] = val;
      }
      if ((pos >= url.length) || (url[pos] == '#'))
         break;
      url = url.slice(pos+1);
   }

   return res;
}


exports.connectWebWindow = function(arg) {
   if (typeof arg == 'function')
      arg = { callback: arg };
   else if (!arg || (typeof arg != 'object'))
      arg = {};

   _openui5args = arg;

   let d = exports.decodeUrl();

   if (d.has('headless') && d.get('key')) {
      let is_chrome = false;
      if ((typeof document !== 'undefined') && (typeof window !== 'undefined'))
         is_chrome = (!!window.chrome && !browser.isOpera) || (navigator.userAgent.indexOf('HeadlessChrome') >= 0);
      if (is_chrome) {
         let element = document.createElement('script');
         element.setAttribute('type', 'text/javascript');
         element.setAttribute('src', 'root_batch_holder.js?key=' + d.get('key'));
         document.head.appendChild(element);
         arg.ignore_chrome_batch_holder = true;
      }
   }

   return _sync().then(() => {
      let prereq = '';
      if (arg.prereq)
         prereq = arg.prereq;
      if (arg.prereq2)
         prereq += ';' + arg.prereq2;
      if (!prereq)
         return;

      return v6_require(prereq).then(() => {
         delete arg.prereq;
         delete arg.prereq2;

         if (arg.prereq_logdiv && document) {
            let elem = document.getElementById(arg.prereq_logdiv);
            if (elem) elem.innerHTML = '';
            delete arg.prereq_logdiv;
         }
      });
   }).then(() => import('../modules/webwindow.mjs')).then(h => {
      globalThis.JSROOT.WebWindowHandle = h.WebWindowHandle;
      return h.connectWebWindow(arg);
   });
}

// try to define global JSROOT
if ((typeof globalThis !== 'undefined') && !globalThis.JSROOT) {
   console.warn('Usage of JSRoot.core.js script is obsolete. Please swicth to ES6 modules. See https://github.com/root-project/jsroot/blob/master/docs/JSROOT.md#migration-v6---v7');

   globalThis.JSROOT = exports;
   globalThis.JSROOT.extend = Object.assign;
   globalThis.JSROOT._complete_loading = _sync;

   let pr = Promise.all([import('../modules/core.mjs'),
                         import('../modules/draw.mjs'),
                         import('../modules/gui/HierarchyPainter.mjs'),
                         import('../modules/gui/display.mjs')]).then(arr => {
      Object.assign(globalThis.JSROOT, arr[0], arr[1], arr[2]);
      Object.assign(globalThis.JSROOT.settings, workaround_settings);
      globalThis.JSROOT._ = arr[0].internals;
      getHPainter = arr[3].getHPainter;
      globalThis.JSROOT.hpainter = getHPainter();
   });

   sync_promises.push(pr);
}

}));
