System.register(["reactive-jsx", "src/client/script-manager.js", "../files.js"], function (_export, _context) {
  "use strict";

  var _addSourceLocation, scriptManager, files;

  return {
    setters: [function (_reactiveJsx) {
      _addSourceLocation = _reactiveJsx.addSourceLocation;
    }, function (_srcClientScriptManagerJs) {
      scriptManager = _srcClientScriptManagerJs.default;
    }, function (_filesJs) {
      files = _filesJs.default;
    }],
    execute: function () {
      const __SystemJSRewritingHack = {};
      _recorder_._src_client_morphic_component_loader_js = _recorder_._src_client_morphic_component_loader_js || {};
      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "register", {
        get() {
          return register;
        },

        set(thisIsVererySecretVariableName) {
          _export("register", register = thisIsVererySecretVariableName);

          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "scriptManager", {
        get() {
          return scriptManager;
        },

        set(thisIsVererySecretVariableName) {
          scriptManager = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "files", {
        get() {
          return files;
        },

        set(thisIsVererySecretVariableName) {
          files = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });


      // store promises of loaded and currently loading templates
      if (!self.lively4loadingPromises) {
        // WARNING: only used by loadUnresolved and not loadByName
        self.lively4loadingPromises = {}; // just to be on the save side....
      }
      var loadingPromises = self.lively4loadingPromises;

      _export("loadingPromises", loadingPromises);

      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "loadingPromises", {
        get() {
          return loadingPromises;
        },

        set(thisIsVererySecretVariableName) {
          _export("loadingPromises", loadingPromises = thisIsVererySecretVariableName);

          return true;
        },

        enumerable: true,
        configurable: true
      });
      var _loggingEnabled = false;
      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "_loggingEnabled", {
        get() {
          return _loggingEnabled;
        },

        set(thisIsVererySecretVariableName) {
          _loggingEnabled = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      var _log = function (...args) {
        if (_loggingEnabled) {
          console.log("ComponentLoader ", ...args);
        }
      };

      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "_log", {
        get() {
          return _log;
        },

        set(thisIsVererySecretVariableName) {
          _log = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      var _timeEnabled = false;
      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "_timeEnabled", {
        get() {
          return _timeEnabled;
        },

        set(thisIsVererySecretVariableName) {
          _timeEnabled = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      var _timeLog = function (name, msg, ...args) {
        if (_timeEnabled) {
          console.log("[component] " + name + " " + Math.round(performance.now() - ComponentLoader.templateFirstLoadTimes[name]) + "ms " + msg, ...args);
        }
      };

      // for compatibility
      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "_timeLog", {
        get() {
          return _timeLog;
        },

        set(thisIsVererySecretVariableName) {
          _timeLog = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      function register(componentName, template, prototype) {
        return ComponentLoader.register(componentName, template, prototype);
      }

      /* #FutureWork should interactive state change of "(module) global" state be preserved while reloading / developing modules
          ComponentLoader.foo = 3
          ComponentLoader.foo
      
      #Discussion
      
      pro) expected in Smalltalk-like developent and live-programmning experience
      contra) gap between development-time and runtime (those manualy changes could make something work that without it won't...)
      
      synthese) if modules and classes are also objects that can have run-time-specific state they should be migrated the same as objects. 
      
      */

      _export("register", register);

      class ComponentLoader {

        static load() {
          this._def("templates", {});
          this._def("prototypes", {});
          this._def("proxies", {});
          this._def("templatePaths");
          this._def("templatePathsCache", {});
          this._def("templatePathsCacheTime", {});
          this._def("templateFirstLoadTimes", {});
        }

        static globalObject() {
          window.lively4components = window.lively4components || {};
          return window.lively4components;
        }

        static _def(name, init) {
          Object.defineProperty(this, name, {
            get() {
              // Since, we want to keep state accross module reloading.... #PreserveState
              var obj = this.globalObject();
              if (!obj[name]) obj[name] = init;
              return obj[name];
            },
            set(value) {
              var obj = this.globalObject();
              if (!obj[name]) obj[name] = value;
            },
            enumerable: true,
            configurable: true
          });
        }

        static updatePrototype(aClass, moduleName) {
          var componentName = moduleName.replace(/.*\//, "").replace(/\.js$/, "");
          if (componentName && this.prototypes[componentName]) {
            this.prototypes[componentName] = aClass;
            this.proxies[componentName].__proto__ = aClass;
            this.proxies[componentName].prototype.__proto__ = aClass.prototype;
          }
        }

        static async onCreatedCallback(object, componentName) {
          _log('onCreatedCallback ' + componentName

          // attach lively4scripts from the shadow root to this
          );if (this.shadowRoot) scriptManager.attachScriptsFromShadowDOM(object);

          // attach lively4script from the instance
          scriptManager.findLively4Script(object, false);

          if (ComponentLoader.prototypes[componentName].createdCallback) {
            ComponentLoader.prototypes[componentName].createdCallback.call(object);
          }

          // load any unknown elements, which this component might introduce
          _log('START onCreatedCallback loadUnresolved ' + componentName);

          this._livelyLoading = Promise.resolve();
          this._livelyLoadingDep = ComponentLoader.loadUnresolved(object, true, "onCreated " + componentName, false).then(args => {
            _log('FINISHED onCreatedCallback loadUnresolved ' + componentName

            // lively.fillTemplateStyles(object.shadowRoot, "source: " + componentName).then(() => {
            // call the initialize script, if it exists

            );if (typeof object.initialize === "function") {
              object.initialize();
            }
            _log("dispatch created " + componentName);
            _log("Identitity: " + (window.LastRegistered === object)

            // })
            );if (this.templateFirstLoadTimes[componentName]) {
              _log('Component first load time: ' + ((performance.now() - this.templateFirstLoadTimes[componentName]) / 1000).toFixed(3) + "s " + componentName + " ");
              this.templateFirstLoadTimes[componentName] = null;
            }
            _log("[component loader] fire created " + componentName);
            object._lively4created = Date.now();
            object.dispatchEvent(new Event("created")); // when we wait on other unresolved components, we can run into cyclic dependencies.... #Cyclic
          }).catch(e => {
            console.error(e);
            return e;
          });
          this._livelyLoadingDep;
        }

        static async onAttachedCallback(object, componentName) {

          if (this._livelyLoading) {
            await this._livelyLoading; // should we provicde this robustness here? Or should these be more pure metal...
          }

          _log("onAttachedCallback " + componentName);

          if (object.attachedCallback && ComponentLoader.proxies[componentName].attachedCallback != object.attachedCallback) {
            object.attachedCallback.call(object);
          } else if (ComponentLoader.prototypes[componentName].attachedCallback) {
            ComponentLoader.prototypes[componentName].attachedCallback.call(object);
          }
        }

        static async onDetachedCallback(object, componentName) {

          if (this._livelyLoading) {
            await this._livelyLoading;
          }

          if (object.detachedCallback && ComponentLoader.proxies[componentName].detachedCallback != object.detachedCallback) {
            object.detachedCallback.call(object);
          } else if (ComponentLoader.prototypes[componentName].detachedCallback) {
            ComponentLoader.prototypes[componentName].detachedCallback.call(object);
          }
        }

        static applyTemplate(element, componentName) {
          var template = this.templates[componentName];
          if (template) {
            if (!element.shadowRoot) {
              element.attachShadow({ mode: 'open' });
            }

            var fragment = template.cloneNode(true);
            fragment.childNodes.forEach(ea => {
              var clone = document.importNode(ea, true
              // #OriginTracking: attach meta infos here
              );element.shadowRoot.appendChild(clone);
            });
          }
        }

        // this function registers a custom element,
        // it is called from the bootstap code in the component templates
        static async register(componentName, template, aClass) {
          _log("[component loader] register " + componentName);
          var proxy;

          // For reflection and debugging
          this.templates[componentName] = template;
          this.prototypes[componentName] = aClass;

          if (template) {
            _log("[component loader] register fillTemplateStyles: " + componentName);
            await lively.fillTemplateStyles(template, "source: " + componentName);
          }

          if (!this.proxies[componentName]) {
            proxy = class extends HTMLElement {
              static get name() {
                return componentName;
              }

              get _lively4version() {
                return 2;
              }

              constructor() {
                _log("[component loader] Proxy Constructor " + componentName);

                super(); // always call super() first in the constructor.

                ComponentLoader.applyTemplate(this, componentName);
                ComponentLoader.onCreatedCallback(this, componentName);
              }

              connectedCallback(args) {
                _log('connectedCallback ' + componentName

                // return super.connectedCallback(...args)
                // super seams to bind early?
                );ComponentLoader.onAttachedCallback(this, componentName);
                if (this.constructor.__proto__.prototype.connectedCallback) {
                  return this.constructor.__proto__.prototype.connectedCallback.apply(this, args);
                }
              }
              disconnectedCallback(...args) {
                _log('diconnectedCallback ' + componentName

                // return super.disconnectedCallback(...args)
                );ComponentLoader.onDetachedCallback(this, componentName);
                if (this.constructor.__proto__.prototype.disconnectedCallback) {
                  return this.constructor.__proto__.prototype.disconnectedCallback.apply(this, args);
                }
              }

              adoptedCallback(...args) {
                _log('adoptedCallback ' + componentName
                // return super.adoptedCallback(...args)
                );if (this.constructor.__proto__.prototype.adoptedCallback) {
                  return this.constructor.__proto__.prototype.adoptedCallback.apply(this, args);
                }
              }
            };
            // set the prototype of the proxy the first time
            // #Idea: use "extemds aClass" ?
            //       proxy.__proto__ = aClass
            //       proxy.prototype.__proto__ = aClass.prototype

            _log("[component loader] define component: " + componentName);
            window.customElements.define(componentName, proxy); // #WebComponent #Magic
            this.proxies[componentName] = proxy;
          } else {
            proxy = this.proxies[componentName];
          }

          // change the prototype of the proxy
          proxy.__proto__ = aClass;
          proxy.prototype.__proto__ = aClass.prototype;
        }

        // this function loads all unregistered elements, starts looking in lookupRoot,
        // if lookupRoot is not set, it looks in the whole document.body,
        // if deep is set, it also looks into shadow roots
        static loadUnresolved(lookupRoot, deep, debuggingHint, withChildren = false, withyourself = false) {
          lookupRoot = lookupRoot || document.body;

          var selector = ":not(:defined)";
          var unresolved = [];

          // check if lookupRoot is unresolved

          // loot at me
          if (withyourself && lookupRoot.parentElement) {
            var unresolvedSiblingsAndMe = Array.from(lookupRoot.parentElement.querySelectorAll(selector));
            if (unresolvedSiblingsAndMe.includes(lookupRoot)) {
              unresolved.push(lookupRoot);
            }
          }
          // find all unresolved elements looking downwards from lookupRoot

          // look at my children? 
          if (withChildren) {
            unresolved = unresolved.concat(Array.from(lookupRoot.querySelectorAll(selector)));
          }

          // look into the shadow?
          if (deep) {
            var deepUnresolved = findUnresolvedDeep(lookupRoot);
            unresolved = unresolved.concat(deepUnresolved);
          }

          function findUnresolvedDeep(root) {
            var shadow = root.shadowRoot;
            if (!shadow) {
              return [];
            }

            var result = Array.from(shadow.querySelectorAll(selector));

            Array.from(shadow.children).forEach(child => {
              result = result.concat(findUnresolvedDeep(child));
            });

            return result;
          }

          // helper set to filter for unique tags
          var unique = new Set();

          var __debugOpenPromisedComponents = new Set();

          var promises = unresolved.filter(el => {
            // filter for unique tag names
            if (!el.nodeName || el.nodeName.toLowerCase() == "undefined") return false;
            var name = el.nodeName.toLowerCase();
            return !unique.has(name) && unique.add(name);
          }).map(el => {
            var name = el.nodeName.toLowerCase();
            if (loadingPromises[name]) {
              // the loading was already triggered
              return loadingPromises[name];
            }

            __debugOpenPromisedComponents.add(name
            // create a promise that resolves once el is completely created
            );var createdPromise = new Promise((resolve, reject) => {
              if (el._lively4created) {
                return resolve({ target: el });
              }
              el.addEventListener("created", evt => {
                evt.stopPropagation();
                __debugOpenPromisedComponents.delete(name);
                resolve(evt);
              });
            });

            // trigger loading the template of the unresolved element
            loadingPromises[name] = createdPromise;

            loadingPromises[name].name = "[Loaded " + name + " " + Date.now() + "]";

            this.loadByName(name).then(didInsertTag => {
              if (!didInsertTag) {
                console.error("Component Loader", `Template ${name} could not be loaded.`, 3, null, "yellow");
                delete loadingPromises[name];
                return null;
              }
            });

            return createdPromise;
          }).filter(promise => promise != null);

          _log("findUnresolvedDeep components: ", promises

          // return a promise that resolves once all unresolved elements from the unresolved-array
          // are completely created
          );return new Promise((resolve, reject) => {

            // fuck promises!!!! I hate them. There is one promise pending.... but just does not fail. It just hangs around doing nothing! #Jens
            promises.forEach(p => {
              p.then(r => {
                p.finished = true;
              }, er => console.log("ERROR in promise: " + p.name));
            });
            window.setTimeout(function () {
              var unfinished = false;
              var unfinishedPromise;
              promises.forEach(p => {
                if (!p.finished) {
                  unfinishedPromise = p;
                  unfinished = true;
                }
              });
              if (unfinished) {
                resolve("timeout" // "(if) the fuel gauge breaks, call maintenance. If they are not there in 20 minutes, fuck it."
                );console.warn("Timout due to unresolved promises, while loading " + unfinishedPromise.name + " context: " + debuggingHint, " unresolved: " + Array.from(__debugOpenPromisedComponents).join(", "));
              }
            }, 20 * 1000);

            Promise.all(promises).then(result => resolve(), err => {
              _log("ERROR loading component ", err);
            });
          });
        }

        static resetTemplatePathCache() {
          this.templatePathsCache = undefined;
          this.templatePathsCacheTime = undefined;
        }

        static async getTemplatePathContent(path) {

          let cacheInvalidationTime = 60 * 5 * 1000;
          let cached = this.templatePathsCache[path];
          let time = this.templatePathsCacheTime[path];
          if (cached && Date.now() - time < cacheInvalidationTime) return cached;

          let resultPromise = fetch(path, { method: 'OPTIONS' }).then(resp => {
            if (resp.status !== 200) return undefined;
            return resp.json();
          });
          this.templatePathsCacheTime[path] = Date.now();
          this.templatePathsCache[path] = new Promise(async (resolve, reject) => {
            let result = await resultPromise;
            if (result) {
              resolve({ contents: result.contents });
              return cached;
            }
          });
          return resultPromise;
        }

        static getTemplatePaths() {
          if (!this.templatePaths) {
            this.templatePaths = [lively4url + '/templates/', lively4url + '/src/components/', lively4url + '/src/components/widgets/', lively4url + '/src/components/tools/', lively4url + '/src/components/halo/', lively4url + '/src/components/demo/', lively4url + '/src/components/draft/', lively4url + '/src/components/d3/', lively4url + '/src/client/vivide/components/', lively4url + '/src/client/reactive/components/rewritten/', lively4url + '/src/client/reactive/components/basic/', lively4url + '/src/client/triples/components/', lively4url + '/src/client/pen-editor/components/', lively4url + '/src/babylonian-programming-editor/', lively4url + '/src/babylonian-programming-editor/demos/canvas/', lively4url + '/src/babylonian-programming-editor/demos/todo/', lively4url + '/src/client/reactive/components/rewritten/conduit/src/components/', lively4url + '/src/client/reactive/components/rewritten/conduit/rpComponents/']; // default
          }
          return this.templatePaths;
        }

        static addTemplatePath(path) {
          if (!lively.files.isURL(path)) {
            path = lively.location.href.replace(/[^/]*$/, path);
          }
          var all = this.getTemplatePaths();
          if (!all.includes(path)) {
            all.push(path);
          }
        }

        static async searchTemplateFilename(filename) {

          var templatePaths = this.getTemplatePaths();
          let templateDir = undefined;

          // #IDEA, using HTTP HEAD could be faster, but is not always implemented... as ource OPTIONS is neigher
          // this method avoids the 404 in the console.log

          // the OPTIONS request seems to break karma... waits to long..
          if (!window.__karma__) {
            for (templateDir of templatePaths) {
              try {
                var stats = await this.getTemplatePathContent(templateDir);
                var found = stats.contents.find(ea => ea.name == filename);
              } catch (e) {
                _log("searchTemplateFilename: could not get stats of  " + filename + " ERROR: ", e);
                found = null;
              }
              if (found) {
                return templateDir + filename;
              }
            }
          } else {
            // so the server did not understand OPTIONS, so lets ask for the files directly
            if (!found) {
              for (templateDir of templatePaths) {
                found = await fetch(templateDir + filename, { method: 'GET' } // #TODO use HEAD, after implementing it in lively4-server
                ).then(resp => resp.status == 200);
                if (found) {
                  return templateDir + filename;
                }
              }
            }
          }
          return undefined;
        }

        // #TODO use loadingPromises here... #Issue, as we used it in livley.js directly, we loaded lively-window in parralel... 
        static async loadByName(name) {
          _log("[component loader] loadByName " + name);

          this.templateFirstLoadTimes[name] = performance.now();
          var modUrl = await this.searchTemplateFilename(name + '.js');
          if (!modUrl) {
            throw new Error("Could not find template for " + name);
          }
          _timeLog(name, " found module filename"

          // #OriginTracking: get source code information here
          );var templateURL = await this.searchTemplateFilename(name + '.html');
          _timeLog(name, " found template filename"

          // Check  if the template will be loadable (this would e.g. fail if we were offline without cache)
          // We have to check this before inserting the link tag because otherwise we will have
          // the link tag even though the template was not properly loaded
          );try {
            if (files.exists(modUrl)) {
              _timeLog(name, "module exists");
              var mod = await System.import(modUrl);
              _timeLog(name, "module loaded");
              var aClass = mod.default;

              if (templateURL) {
                if (files.exists(templateURL)) {
                  _timeLog(name, "template exits");
                  var templateSource = await fetch(templateURL).then(r => r.text());
                  _timeLog(name, "template loaded");
                  var div = _addSourceLocation(document.createElement("div"), {
                    file: 'https://lively-kernel.org/lively4/lively4-RP19-4/src/client/morphic/component-loader.js',
                    end: {
                      column: 51,
                      line: 525
                    },
                    start: {
                      column: 22,
                      line: 525
                    }
                  });
                  div.innerHTML = templateSource;
                  var template = div.querySelector("template");
                  template.remove();
                }
              }
              this.register(name, template && template.content, aClass);
              _timeLog(name, "registered");
              return true;
            } else {
              return false;
            }
          } catch (error) {
            return false;
          }
        }

        // #TODO refactor this to use lively.create(), because this is not enough... 
        static createComponent(tagString) {
          var comp = _addSourceLocation(document.createElement(tagString), {
            file: 'https://lively-kernel.org/lively4/lively4-RP19-4/src/client/morphic/component-loader.js',
            end: {
              column: 48,
              line: 544
            },
            start: {
              column: 15,
              line: 544
            }
          });
          return comp;
        }

        static openIn(parent, component, beginning) {
          var created = false;
          var compPromise = new Promise(resolve => {
            if (component._lively4created) return resolve(component);

            component.addEventListener("created", e => {
              if (e.composedPath()[0] !== component) {
                _log("[components] ingnore and stop created event from child " + e.composedPath[0].tagName);
                return;
              }
              if (created) {
                // #Just check... we had this issue before
                throw new Error("[compontents] created called twice for " + component);
              } else {
                created = true;
                e.stopPropagation();
                resolve(e.target);
              }
            });
          });

          if (beginning) {
            parent.insertBefore(component, parent.firstChild);
          } else {
            parent.appendChild(component);
          }
          this.loadUnresolved(component, true, "openIn " + component, true, true);
          return compPromise;
        }

        static openInBody(component) {
          return this.openIn(document.body, component, true);
        }

        static async openInWindow(component, pos) {
          // this will call the window's createdCallback before
          // we append the child, if the window template is already
          // loaded
          var w = this.createComponent("lively-window");
          if (pos) {
            lively.setPosition(w, pos);
          }
          w.style.opacity = 0.2;
          w.appendChild(component);

          this.openInBody(w);

          if (!component.localName.match(/-/)) {
            return w; // standard elments... which are no components
          }

          // therefore, we need to call loadUnresolved again after
          // adding the child, so that it finds it and resolves it,
          // if it is currently unresolved
          var windowPromise = new Promise(resolve => {
            this.loadUnresolved(document.body, true, "openInWindow " + component).then(() => {
              w.style.opacity = 1.0;
              this.ensureWindowTitle(component, w);

              resolve(w);
            });
          });

          return windowPromise;
        }

        static ensureWindowTitle(component, w) {
          if (component.windowTitle) {
            w.setAttribute('title', '' + component.windowTitle);
          }
        }

        static reloadComponent(source) {
          var template = lively.html.parseHTML(source).find(ea => ea.localName == "template");
          if (!template) return;
          var name = template.id;
          if (!name) return;
          var templateClone = document.importNode(template.content, true);
          ComponentLoader.templates[name] = templateClone;

          return lively.fillTemplateStyles(templateClone, "source: " + name).then(() => name);
        }

      }

      _export("default", ComponentLoader);

      Object.defineProperty(_recorder_._src_client_morphic_component_loader_js, "ComponentLoader", {
        get() {
          return ComponentLoader;
        },

        set(thisIsVererySecretVariableName) {
          _export("default", ComponentLoader = thisIsVererySecretVariableName);

          return true;
        },

        enumerable: true,
        configurable: true
      });
      ComponentLoader.load();

      ComponentLoader.templatePathsCache = null;
    }
  };
});