(function () {
  'use strict';

  const _document = document;

  //------------------------------------------------------------------------------
  // document
  //------------------------------------------------------------------------------
  // create element
  const createElement = (...args) => _document.createElement(...args);

  //------------------------------------------------------------------------------
  // node, parentNode functions
  //------------------------------------------------------------------------------

  // append children nodes
  const append = (parent, ...children) => parent.append(...children);
  // setAttribute
  const setAttribute = (node, attribute, value) => node.setAttribute(attribute, value);

  //------------------------------------------------------------------------------
  // EventTarget
  //------------------------------------------------------------------------------
  // addEventListener
  const addEventListener = (target, type, fn, options) => target.addEventListener(type, fn, options);
  // dispatchEvent
  const dispatchEvent = (target, ev) => target.dispatchEvent(ev);

  const _Object$1 = Object;

  const _Object_create$1 = _Object$1.create;

  const _Object = Object;

  const _Object_hasOwn = _Object.hasOwn;

  const _Symbol = Symbol;

  const _Symbol_for = _Symbol.for;

  // Library stuff
  const S_Instance = `Instance`;
  const PREFIX = S_Instance + `.`;

  // symbols
  const SYMBOL_EVENT_HANDLERS = _Symbol_for(PREFIX + `eventHandlers`);

  // import Instance from '../Instance.mjs';
  // import Collection from '../Collection.mjs';

  // const Instance_prototype = Instance.prototype;
  // const Collection_prototype = Collection.prototype;

  /**
   * @function dispatch
   * @desc dispatch an event to the event listeners for them to handle
   *
   * @param {Object} [evt] An event object with additional data/properties to be
   * sent to the event handlers.
   * @param {Object} [instance=this] The instance on which to find event handlers.
   *
   * @returns {undefined} nothing
   */
  const dispatch = function (
    evt,
    instance = this,

    // variables
    objs = [instance],
    obj,
    handlersByEvent,
    // handler,
    i,
  ) {

    // NOTE: We loop through the list and check to see if the object is an
    // instance of any of the Classes (index 0) in the list, and if so, add it's 
    // corresponding .prototype (index 1) to the list...this allows for an 
    // "opt-in" approach.  If you want dispatch to fire on specific Class's 
    // .prototype, you have to add it into the list.
    // This is for a couple of reasons: 
    // - Performance...this way we aren't calling Object.getPrototypeOf which is
    // performance intensive.
    // - Non-wasted CPU...We don't loop over anything that we know we don't need
    // to handle...
    // - Simplicity...API becomes very simple.
    // for (obj of dispatch._) {
    //   (instance instanceof obj[0]) &&
    //   (objs.push(obj[1]));
    //   // (Array_prototype_push_call(objs, obj[1]));
    // }
    if (instance.__dispatch__) {
      // objs = objs.concat(instance.__dispatch__);
      // NOTE: This is fastest right now...
      for (obj of instance.__dispatch__) {
        // NOTE: For "beforeInstance" and "instance" (and possibly others?) the 
        // instance is Instance_prototype which is ALSO in __dispatch__ causing
        // the handler to be run twice. :( So, I'm solving this so that the 
        // handler only runs once.
        obj !== instance && 
        objs.push(obj);
      }
    }


    // loop through each object and handle the events
    for (obj of objs) {

      // make sure there are event handlers
      if (
        // this object has that own property
        _Object_hasOwn(obj, SYMBOL_EVENT_HANDLERS) && 
        // if we have event handlers for that name
        (handlersByEvent = obj[SYMBOL_EVENT_HANDLERS]?.[evt.type])
      ) {
        // NOTE: Removing "slice" makes this function quicker...
        // Counting backwards also seems to eliminate the 
        // possibility of event handlers being added or removed and will 
        // not have any possibility of missing a handler in the list.
        for (i = handlersByEvent.length - 1; i >=0; i--) {
          handlersByEvent[i](evt);
        }
      }
      
    }

    // Legacy implementation
    // // TODO: support "event.target"
    // // TODO: support "event.stopPropagation()" => will prevent the next prototype
    // // in the__proto__ chain from being called...would immediately return
    // // TOOD: support "event.stopImmediatePropagation()" => will prevent the 
    // // next inner handler from being called...would immediately return

    // // loop until we hit Object.prototype
    // do {
      
    //   // reference to the handlers
    //   if (
    //     // this object has that own property
    //     _Object_hasOwn(instance, SYMBOL_EVENT_HANDLERS) && 
    //     // if we have event handlers for that name
    //     (handlersByEvent = instance[SYMBOL_EVENT_HANDLERS]?.[evt.type])
    //   ) {

    //     // loop through and call all event handlers
    //     // NOTE: We make a copy of the event handlers so that if one of these 
    //     // event handlers adds or removes an event handler in this list as a 
    //     // result of dispatching an event, it will not cause any errors.
    //     // for (handler of handlersByEvent.slice()) {
    //     //   handler(evt);
    //     // }
    //     // NOTE: Removing "slice" makes this function quicker...
    //     // Counting backwards also seems to eliminate the 
    //     // possibility of event handlers being added or removed and will 
    //     // not have any possibility of missing a handler in the list.
    //     for (i = handlersByEvent.length - 1; i >=0; i--) {
    //       handlersByEvent[i](evt);
    //     }
    //   }

    // } while (
    //   // instance is not instance.prototype
    //   // NOTE: This would already be handled below, but this is meant as a 
    //   // micro-optimization because Object.getPrototypeOf is a performance-
    //   // intensive method...this line tries to avoid calling Object.getPrototypeOf
    //   // if we can...the problem is that it introduces a cicular dependency
    //   // instance !== Instance_prototype && 
    //   // instance !== Collection_prototype &&
    //   // traverse up the __proto__ chain
    //   (instance = Object_getPrototypeOf(instance)) && 
    //   // and it isn't Object.prototype
    //   instance !== Object_prototype
    // );

    return evt;
  };

  /**
   * @function off
   * @desc Remove an event listener for an event
   *
   * @param {string} name The event name to stop listening to.
   * @param {Function} fn A callback function to call when the event happens.
   * @param {Object} [instance=this] The instance to stop listening to.
   *
   * @returns {undefined} nothing
   */
  const off = function (
    name, 
    fn, 
    // default to this
    instance = this,

    // variables
    // handlers,
    handlersByEvent,
    index
  ) {
    
    // reference to the handlers
    if (
      // this object has that own property
      _Object_hasOwn(instance, SYMBOL_EVENT_HANDLERS) && 
      // and they are set
      // (handlers = instance[SYMBOL_EVENT_HANDLERS]) &&
      // and it has that name
      // (handlersByEvent = handlers[name]) &&
      (handlersByEvent = instance[SYMBOL_EVENT_HANDLERS]?.[name]) &&
      // and that callback is included
      (
        // NOTE: index could be 0 which is falsy...that would exit from this
        // condition
        (index = handlersByEvent.indexOf(fn)),
        (index !== -1)
      )
    ) {
      // remove it
      handlersByEvent.splice(
        index,
        1
      );
    }

  };

  const _null = null;

  const _Object_create = _Object.create;

  const _Object_defineProperty = _Object.defineProperty;

  /**
   * @function on
   * @desc Add an event listener for an event
   *
   * @param {string} name The event name to listen to.
   * @param {Function} fn A callback function to call when the event happens.
   * @param {Object} [instance=this] The instance to listening to.
   *
   * @returns {undefined} nothing
   */
  const on = function (
    name, 
    fn,
    instance = this,

    // variables
    handlers,
    handlersByEvent,
  ) {

    // default to an object
    // instance[SYMBOL_EVENT_HANDLERS] = instance[SYMBOL_EVENT_HANDLERS] || {};
    // NOTE: We have to define this as an own property or we could be adding event
    // handlers to the __proto__ of this instance.
    if (!_Object_hasOwn(instance, SYMBOL_EVENT_HANDLERS)) {
      _Object_defineProperty(instance, SYMBOL_EVENT_HANDLERS, {
        // make sure these values are false
        // S_enumerable
        // S_configurable
        // S_writable
        value: _Object_create(_null),
      });
    }

    // reference to the handlers
    handlers = instance[SYMBOL_EVENT_HANDLERS];

    // default to an array
    handlersByEvent = handlers[name] = handlers[name] || [];

    // add it if it isn't already there
    handlersByEvent.push(fn);
    // Array_prototype_push_call(handlersByEvent, fn);
    
    // NOTE: We used to do this, but I realized that it's probably likely that
    // this never actually happens...it's a preventative measure that I don't
    // think I should have to handle...
    // if (!handlersByEvent.includes(fn)) {
    //   handlersByEvent.push(fn);
    // }

  };

  /**
   * @function EventTarget
   * @desc The EventTarget interface.
   * 
   * @param {Object} value This can be either the instance or the 
   * initial properties of an instance.
   *
   * @returns {Object} an EventTarget object.
   */
  const EventTarget = function (instance) {
    // assign all of the event target methods if EventTarget is being called 
    // as a function rather than being used as a class...
    /**
     * @function addEventListener
     * @alias on
     * @desc The addEventListener method.
     * 
     * @param {String} name The event name.
     * @param {Function} fn The function callback. 
     *
     * @returns {undefined} nothing
     */
    instance.on = 
    instance.addEventListener = on;

    /**
     * @function removeEventListener
     * @alias off
     * @desc The removeEventListener method.
     * 
     * @param {String} name The event name.
     * @param {Function} fn The function callback. 
     *
     * @returns {undefined} nothing
     */
    instance.off = 
    instance.removeEventListener = off;

    /**
     * @function dispatchEvent
     * @alias dispatch
     * @alias trigger
     * @desc The dispatchEvent method
     * 
     * @param {String} name The event name.
     * @param {Object} evt The event object. 
     *
     * @returns {undefined} nothing
     */
    instance.dispatchEvent = 
    instance.dispatch = 
    instance.trigger = dispatch;
  };

  // add the methods to the prototype
  EventTarget(EventTarget.prototype);

  const _setTimeout = setTimeout;

  const _window = window;

  const S_statechange = `statechange`;

  // PopStateEvent
  //------------------------------------------------------------------------------
  const PopStateEvent = _window.PopStateEvent;
  // StateChangeEvent
  class StateChangeEvent extends PopStateEvent {
    // get [SYMBOL_toStringTag]() {
    //   return 'StateChangeEvent';
    // }
  }
  // fire our custom event when something happens
  addEventListener(_window, `popstate`,
    (ev) => _setTimeout(
      () => dispatchEvent(_window, new StateChangeEvent(S_statechange, ev)),
      0
    )
  );

  const _Array = Array;

  const _Array_isArray = _Array.isArray;

  const _Array_prototype = _Array.prototype;

  const _Function = Function;

  const _Function_prototype = _Function.prototype;

  const _Function_prototype_call = _Function_prototype.call;

  _Function_prototype_call.bind(_Array_prototype.forEach);

  _Function_prototype_call.bind(_Array_prototype.join);

  _Function_prototype_call.bind(_Array_prototype.map);

  const _Object_prototype = _Object$1.prototype;

  _Function_prototype_call.bind(_Object_prototype.hasOwnProperty);

  const S_function = `function`;

  // a collection of dom functions used for building my app

  // symbol for linked child objects
  const assignScope = (node, parent, scope) => {
    
    // if it is not the root
    if (parent) {

      // get the parent scope
      let parentScope = parent._;

      // if we want a new scope
      if (scope) {

        // get a reference to the parent scope on this scope (a.k.a the _proto_)
        scope._ = parentScope;

        // change to property descriptors
        for (let property in scope) {
          scope[property] = {
            // configurable: true, // don't want this reconfigured
            enumerable: true,
            writable: true,
            value: scope[property],
          };
        }
        // Object_setPrototypeOf(scope, parentScope);
        // This is faster:
        scope = _Object_create$1(parentScope, scope);

      // if we don't care about a new scope, simply reference my parent's scope
      } else {
        scope = parentScope;
      }

    // if it is the root
    } else {
      scope = scope || {};
    }

    // assign the scope to the node/element
    node._ = scope;
  };

  // createNS
  const createNS = (namespace, name, parent, attributes, children, scope) => {
    let
      options,
      element,
      attribute;

    // extra options to createElement
    if (attributes?.is) {
      options = {
        is: attributes.is
      };
      delete attributes.is;
    }

    // create the element
    {
      element = createElement(name, options);
    }

    // set all of the attributes/properties
    if (attributes) {
      for (attribute in attributes) {

        // if it is a property, set it using dot notation, etc.
        if (
          (attribute in element)
        ) {
          element[attribute] = attributes[attribute];

        // otherwise set it as an attribute
        } else {
          // properties of things like SVG are able to be set only through
          // setAttribute
          // setAttributeNS(element, namespace, attribute, attributes[attribute]);
          setAttribute(element, attribute, attributes[attribute]);
        }

      }
    }

    // assign scope
    assignScope(element, parent, scope);

    // if it is a function
    if (
      children &&
      typeof children === S_function
    ) {
      // call the function passing the parent for the children to access
      children = children(element);
    }

    // if children array, append them
    if (_Array_isArray(children)) {
      // append the children
      append(element, ...children);
    }

    return element;
  };
  // create
  const create = (...args) => (
    createNS(0, ...args)
  );

  // create a section element
  const app = create(`section`, null, {}, (el) => [
    // create an h1 element as a child of the section element
    // - whose parent is the section we just created (indicated by the second parameter)
    create(`h1`, el, {}, () => [
      // create a text node as a child of the h1
      // - with a data value of 'Hello World!'
      'Hello World!'
    ])
  ]);

  // append the element to the body
  document.body.append(app);

})();
