(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (typeof module === "object" && module.exports) {
    module.exports = factory();
  } else {
    root.SimpleLightbox = factory();
  }
})(this, function () {
  function assign(target) {
    for (var i = 1; i < arguments.length; i++) {
      var obj = arguments[i];

      if (obj) {
        for (var key in obj) {
          obj.hasOwnProperty(key) && (target[key] = obj[key]);
        }
      }
    }

    return target;
  }

  function addClass(element, className) {
    if (element && className) {
      element.className += " " + className;
    }
  }

  function removeClass(element, className) {
    if (element && className) {
      element.className = element.className
        .replace(new RegExp("(\\s|^)" + className + "(\\s|$)"), " ")
        .trim();
    }
  }

  function parseHtml(html) {
    var div = document.createElement("div");
    div.innerHTML = html.trim();

    return div.childNodes[0];
  }

  function matches(el, selector) {
    return (el.matches || el.matchesSelector || el.msMatchesSelector).call(
      el,
      selector
    );
  }

  function getWindowHeight() {
    return "innerHeight" in window
      ? window.innerHeight
      : document.documentElement.offsetHeight;
  }

  function SimpleLightbox(options) {
    this.init.apply(this, arguments);
  }

  SimpleLightbox.defaults = {
    // add custom classes to lightbox elements
    elementClass: "",
    elementLoadingClass: "slbLoading",
    htmlClass: "slbActive",
    closeBtnClass: "",
    nextBtnClass: "",
    prevBtnClass: "",
    loadingTextClass: "",

    // customize / localize controls captions
    closeBtnCaption: "Close",
    nextBtnCaption: "Next",
    prevBtnCaption: "Previous",
    loadingCaption: "Loading...",

    bindToItems: true, // set click event handler to trigger lightbox on provided $items
    closeOnOverlayClick: true,
    closeOnEscapeKey: true,
    nextOnImageClick: true,
    showCaptions: true,

    captionAttribute: "title", // choose data source for library to glean image caption from
    urlAttribute: "href", // where to expect large image

    startAt: 0, // start gallery at custom index
    loadingTimeout: 100, // time after loading element will appear

    appendTarget: "body", // append elsewhere if needed

    beforeSetContent: null, // convenient hooks for extending library behavoiur
    beforeClose: null,
    afterClose: null,
    beforeDestroy: null,
    afterDestroy: null,
    videoRegex: new RegExp(/youtube.com|youtu.be|vimeo.com/), // regex which tests load url for iframe content
  };

  assign(SimpleLightbox.prototype, {
    init: function (options) {
      options = this.options = assign({}, SimpleLightbox.defaults, options);

      var self = this;
      var elements;

      if (options.$items) {
        elements = options.$items.get();
      }

      if (options.elements) {
        elements = [].slice.call(
          typeof options.elements === "string"
            ? document.querySelectorAll(options.elements)
            : options.elements
        );
      }

      this.eventRegistry = { lightbox: [], thumbnails: [] };
      this.items = [];
      this.captions = [];

      if (elements) {
        elements.forEach(function (element, index) {
          self.items.push(element.getAttribute(options.urlAttribute));

          //look for a caption in various spots
          var caption = element.getAttribute(options.captionAttribute);
          if (!caption) {
            //try to find the title attribute on the underlying image
            caption = element
              .querySelector("img")
              ?.getAttribute(options.captionAttribute);

            if (!caption) {
              //try to find an attached fig caption
              caption =
                element.parentElement?.querySelector("figcaption")?.textContent;
            }
          }
          self.captions.push(caption);

          if (options.bindToItems) {
            self.addEvent(
              element,
              "click",
              function (e) {
                e.preventDefault();
                self.showPosition(index);
              },
              "thumbnails"
            );
          }
        });
      }

      if (options.items) {
        this.items = options.items;
      }

      if (options.captions) {
        this.captions = options.captions;
      }
    },

    addEvent: function (element, eventName, callback, scope) {
      this.eventRegistry[scope || "lightbox"].push({
        element: element,
        eventName: eventName,
        callback: callback,
      });

      element.addEventListener(eventName, callback);

      return this;
    },

    removeEvents: function (scope) {
      this.eventRegistry[scope].forEach(function (item) {
        item.element.removeEventListener(item.eventName, item.callback);
      });

      this.eventRegistry[scope] = [];

      return this;
    },

    next: function () {
      return this.showPosition(this.currentPosition + 1);
    },

    prev: function () {
      return this.showPosition(this.currentPosition - 1);
    },

    normalizePosition: function (position) {
      if (position >= this.items.length) {
        position = 0;
      } else if (position < 0) {
        position = this.items.length - 1;
      }

      return position;
    },

    showPosition: function (position) {
      var newPosition = this.normalizePosition(position);

      if (typeof this.currentPosition !== "undefined") {
        this.direction = newPosition > this.currentPosition ? "next" : "prev";
      }

      this.currentPosition = newPosition;

      return this.setupLightboxHtml()
        .prepareItem(this.currentPosition, this.setContent)
        .show();
    },

    loading: function (on) {
      var self = this;
      var options = this.options;

      if (on) {
        this.loadingTimeout = setTimeout(function () {
          addClass(self.$el, options.elementLoadingClass);

          self.$content.innerHTML =
            '<p class="slbLoadingText ' +
            options.loadingTextClass +
            '">' +
            options.loadingCaption +
            "</p>";
          self.show();
        }, options.loadingTimeout);
      } else {
        removeClass(this.$el, options.elementLoadingClass);
        clearTimeout(this.loadingTimeout);
      }
    },
    getVideoURL: function (url) {
      var frm = "//_URL_";
      var converts = [
        {
          rx: /^(?:https?:)?\/\/(?:www\.)?vimeo\.com\/([^\?&"]+).*$/g,
          tmpl: frm.replace("_URL_", "player.vimeo.com/video/$1"),
        },
        {
          rx: /^.*(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=|embed\/|v\/|user\/.+\/)?([^\?&"]+).*$/g,
          tmpl: frm.replace("_URL_", "www.youtube.com/embed/$1"),
        },
        {
          rx: /^.*(?:https?:\/\/)?(?:www\.)?(?:youtube-nocookie\.com)\/(?:watch\?v=|embed\/|v\/|user\/.+\/)?([^\?&"]+).*$/g,
          tmpl: frm.replace("_URL_", "www.youtube-nocookie.com/embed/$1"),
        },
      ];
      for (var i = 0; i < converts.length; i++) {
        if (converts[i].rx.test(url)) {
          return url.replace(converts[i].rx, converts[i].tmpl);
        }
      }
      return url;
    },
    prepareItem: function (position, callback) {
      var self = this;
      var url = this.items[position];
      this.loading(true);
      if (this.options.videoRegex.test(url)) {
        var videoURL = this.getVideoURL(url);
        callback.call(
          self,
          parseHtml(
            '<div class="slbIframeCont"><iframe class="slbIframe" frameborder="0" allowfullscreen src="' +
              videoURL +
              '"></iframe></div>'
          )
        );
      } else {
        var $imageCont = parseHtml(
          '<div class="slbImageWrap"><img class="slbImage" src="' +
            url +
            '" /></div>'
        );

        this.$currentImage = $imageCont.querySelector(".slbImage");

        if (this.options.showCaptions && this.captions[position]) {
          $imageCont.appendChild(
            parseHtml(
              '<div class="slbCaption">' + this.captions[position] + "</div>"
            )
          );
        }

        this.loadImage(url, function () {
          self.setImageDimensions();

          callback.call(self, $imageCont);

          self.loadImage(
            self.items[self.normalizePosition(self.currentPosition + 1)]
          );
        });
      }

      return this;
    },

    loadImage: function (url, callback) {
      if (!this.options.videoRegex.test(url)) {
        var image = new Image();
        callback && (image.onload = callback);
        image.src = url;
      }
    },

    setupLightboxHtml: function () {
      var o = this.options;

      if (!this.$el) {
        this.$el = parseHtml(
          '<div class="slbElement ' +
            o.elementClass +
            '">' +
            '<div class="slbOverlay"></div>' +
            '<div class="slbWrapOuter">' +
            '<div class="slbWrap">' +
            '<div class="slbContentOuter">' +
            '<div class="slbContent"></div>' +
            '<button type="button" title="' +
            o.closeBtnCaption +
            '" class="slbCloseBtn ' +
            o.closeBtnClass +
            '">×</button>' +
            (this.items.length > 1
              ? '<div class="slbArrows">' +
                '<button type="button" title="' +
                o.prevBtnCaption +
                '" class="prev slbArrow' +
                o.prevBtnClass +
                '">' +
                o.prevBtnCaption +
                "</button>" +
                '<button type="button" title="' +
                o.nextBtnCaption +
                '" class="next slbArrow' +
                o.nextBtnClass +
                '">' +
                o.nextBtnCaption +
                "</button>" +
                "</div>"
              : "") +
            "</div>" +
            "</div>" +
            "</div>" +
            "</div>"
        );

        this.$content = this.$el.querySelector(".slbContent");
      }

      this.$content.innerHTML = "";

      return this;
    },

    show: function () {
      if (!this.modalInDom) {
        document.querySelector(this.options.appendTarget).appendChild(this.$el);
        addClass(document.documentElement, this.options.htmlClass);
        this.setupLightboxEvents();
        this.modalInDom = true;
      }

      return this;
    },

    setContent: function (content) {
      var $content = typeof content === "string" ? parseHtml(content) : content;
      this.loading(false);

      this.setupLightboxHtml();

      removeClass(this.$content, "slbDirectionNext");
      removeClass(this.$content, "slbDirectionPrev");

      if (this.direction) {
        addClass(
          this.$content,
          this.direction === "next" ? "slbDirectionNext" : "slbDirectionPrev"
        );
      }

      if (this.options.beforeSetContent) {
        this.options.beforeSetContent($content, this);
      }

      this.$content.appendChild($content);

      return this;
    },

    setImageDimensions: function () {
      if (this.$currentImage) {
        this.$currentImage.style.maxHeight = getWindowHeight() + "px";
      }
    },

    setupLightboxEvents: function () {
      var self = this;

      if (this.eventRegistry.lightbox.length) {
        return this;
      }

      this.addEvent(this.$el, "click", function (e) {
        var $target = e.target;

        if (
          matches($target, ".slbCloseBtn") ||
          (self.options.closeOnOverlayClick && matches($target, ".slbWrap"))
        ) {
          self.close();
        } else if (matches($target, ".slbArrow")) {
          matches($target, ".next") ? self.next() : self.prev();
        } else if (
          self.options.nextOnImageClick &&
          self.items.length > 1 &&
          matches($target, ".slbImage")
        ) {
          self.next();
        }
      })
        .addEvent(document, "keyup", function (e) {
          self.options.closeOnEscapeKey && e.keyCode === 27 && self.close();

          if (self.items.length > 1) {
            (e.keyCode === 39 || e.keyCode === 68) && self.next();
            (e.keyCode === 37 || e.keyCode === 65) && self.prev();
          }
        })
        .addEvent(window, "resize", function () {
          self.setImageDimensions();
        });

      return this;
    },

    close: function () {
      if (this.modalInDom) {
        this.runHook("beforeClose");
        this.removeEvents("lightbox");
        this.$el && this.$el.parentNode.removeChild(this.$el);
        removeClass(document.documentElement, this.options.htmlClass);
        this.modalInDom = false;
        this.runHook("afterClose");
      }

      this.direction = undefined;
      this.currentPosition = this.options.startAt;
    },

    destroy: function () {
      this.close();
      this.runHook("beforeDestroy");
      this.removeEvents("thumbnails");
      this.runHook("afterDestroy");
    },

    runHook: function (name) {
      this.options[name] && this.options[name](this);
    },
  });

  SimpleLightbox.open = function (options) {
    var instance = new SimpleLightbox(options);

    return options.content
      ? instance.setContent(options.content).show()
      : instance.showPosition(instance.options.startAt);
  };

  SimpleLightbox.registerAsJqueryPlugin = function ($) {
    $.fn.simpleLightbox = function (options) {
      var lightboxInstance;
      var $items = this;

      return this.each(function () {
        if (!$.data(this, "simpleLightbox")) {
          lightboxInstance =
            lightboxInstance ||
            new SimpleLightbox($.extend({}, options, { $items: $items }));
          $.data(this, "simpleLightbox", lightboxInstance);
        }
      });
    };

    $.SimpleLightbox = SimpleLightbox;
  };

  if (typeof window !== "undefined" && window.jQuery) {
    SimpleLightbox.registerAsJqueryPlugin(window.jQuery);
  }

  return SimpleLightbox;
});
