(function () {
  'use strict';

  Drupal.behaviors.ik_modals = {
    settings: {
      sessionModalConverted: 'Drupal.ik_modals.converted.',
      sessionModalDismissed: 'Drupal.ik_modals.dismissed.',
      sessionModalSeen: 'Drupal.ik_modals.last_seen.',
      storageLastVisit: 'Drupal.ik_modals.last_visit',
    },

    /**
     * Event Callback when modal is hidden.
     *
     * @param {Event} e 
     * @param {object} module - reference to Drupal.behaviors.ik_modals
     */
    onModalHidden: function(e, module) {
      const modal = e.target.closest('.ik-modal')
      const modalId = modal.getAttribute('id')
  
      // Remove keydown listener
      e.target.removeEventListener('keydown', function(e) {
        module.handleKeydowns(e, module)
      })

      // Reset the expiration on modal hide.
      module.setModalCookie(module.settings.sessionModalDismissed + modalId)

      // Dispatch custom event on hide.
      if (module.settings.useBoostrap === false) {
        modal.dispatchEvent(
          new CustomEvent('modal.hidden', {
            modal: modal,
            settings: module.settings
          })
        )
      }
    },

    /**
     * Event Callback when modal is shown.
     *
     * @param {Event} e 
     * @param {object} module - reference to Drupal.behaviors.ik_modals
     */
    onModalShown: function(e, module) {
      // Add keydown listener
      e.target.addEventListener('keydown', function(e) {
        module.handleKeydowns(e, module)
      })

      // Dispatch custom event on shown.
      if (module.settings.useBoostrap === false) {
        modal.dispatchEvent(
          new CustomEvent('modal.shown', {
            modal: modal,
            settings: module.settings
          })
        )
      }
    },

    /**
     * Merges our settings (this.settings) and Drupal's settings. (settings.modals)
     */
    getSettings: function(settings) {
      return {
        ...this.settings,
        ...settings
      }
    },

    /**
     * Adds additional checks for trapping focus inside modal when it's open.
     * @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/#kbd_label
     * 
     * @param {Event} e
     * @param {object} module - reference to Drupal.behaviors.ik_modals
     */
    handleKeydowns: function(e, module) {
      const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
      const modal = e.target.closest('.ik-modal')
      const firstFocusableElement = modal.querySelectorAll(focusableElements)[0] // get first element to be focused inside modal
      const focusableContent = modal.querySelectorAll(focusableElements)
      const lastFocusableElement = focusableContent[focusableContent.length - 1] // get last element to be focused inside modal
      let isTabPressed = e.key === 'Tab' || e.keyCode === 9
      let isEscapeKey = e.key === 'Escape' || e.keyCode === 27

      if (isEscapeKey) {
        module.hideModal(modal, module)
        return
      }

      if (!isTabPressed) {
        return
      }
    
      if (e.shiftKey) { // if shift key pressed for shift + tab combination
        if (document.activeElement === firstFocusableElement) {
          lastFocusableElement.focus() // add focus for the last focusable element
          e.preventDefault()
        }
      } else { // if tab key is pressed
        if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
          firstFocusableElement.focus() // add focus for the first focusable element
          e.preventDefault()
        }
      }
    },

    /**
     * Handles internal elements in modal
     *
     * @param {Element} modal the modal DOM element
     * @param {string} modalId the modal ID attribute
     */
    handleModalInternals: function(modal, modalId) {
      const module = this
      const hiddenEvent = module.settings.useBoostrap === true ? 'hide.bs.modal' : 'modal.hidden'
      const shownEvent = module.settings.useBoostrap === true ? 'shown.bs.modal' : 'modal.shown'

      // If user clicks on a link, assume it's a conversion.
      // Set a different expiration for a conversion.
      modal.querySelectorAll('a').forEach(function (ele) {
        ele.addEventListener('click', function (e) {
          e.preventDefault()
          module.setModalCookie(module.settings.sessionModalConverted + modalId);

          if (!ele.hasAttribute('target')) {
              e.preventDefault()
              window.location.replace(ele.getAttribute('href'))
           }
        })
      })

      // If user submits a form, count it as a conversion.
      // Stop the event, add our conversion cookie and then submit.
      modal.querySelectorAll('form').forEach(function (ele) {
        ele.addEventListener('submit', function (e) {
          e.preventDefault()
          module.setModalCookie(module.settings.sessionModalConverted + modalId)
          ele.submit()
        })
      })

      modal.addEventListener(shownEvent, function(e) {
        module.onModalShown(e, module)
      })

      modal.addEventListener(hiddenEvent, function(e) {
        module.onModalHidden(e, module)
      })
    },

    /**
     * Programatically closes the modal.
     * 
     * @param {Element} modal the modal DOM element
     * @param {object} module - reference to Drupal.behaviors.ik_modals
     */
    hideModal: function(modal, module) {
      const useBoostrap = module.settings.useBoostrap
      const closeCallback = Drupal.behaviors[module.settings.closeCallback]

      if (useBoostrap === false && typeof closeCallback === 'function') {
        closeCallback(modal)
        module.onModalHidden({
          target: modal
        }, module)
      } else if (useBoostrap === true) {
        new bootstrap.Modal(modal).hide()
      }
    },

    /**
     *  Method that checks if the modal should be shown at all.
     *
     * @param {string} modalId the id attribute of a modal DOM element.
     * @param {object} modalSettings the settings for the modal.
     * @param {bool} modalShown if the modal has already been shown.
     *
     * @return {bool} if the modal is active and should be shown.
     */
    isModalAvailable: function(modalId, modalSettings, modalShown) {
      const path = document.location.pathname
      const origin = document.location.origin
      const date = moment().format('YYYY-MM-DD')
      // What we have stored in cookies. Saves the last-seen date of the modal.
      const lastSeen = localStorage.getItem(this.settings.sessionModalSeen + modalId)
      // Last converted
      const lastConverted = localStorage.getItem(this.settings.sessionModalConverted + modalId)
      // Last Dismissed
      const lastDismissed = localStorage.getItem(this.settings.sessionModalDismissed + modalId)
      // Users last visit to site.
      const lastVisit = localStorage.getItem(this.settings.storageLastVisit)
      // Users location info.
      const userSettings = this.settings.user

      // Helps determine if the criteria has already failed a check.
      let failedPrevCheck = false;
      let isActive = false
      let visitExpired
      let showAgain;
      let matched;
      let regex;
      let modalDebug = [];
      let {
        debugName,
        showAgainConvert,
        showAgainDismiss,
        showDateStart,
        showDateEnd,
        showIfReferred,
        showLocationsCountries,
        showLocationsState,
        showOnPages,
        showOverride,
        userVisitedLast
      } = modalSettings

      // If we have <= 0 set in backend, make it equal null
      if (parseInt(showAgainConvert) < 0) {
        showAgainConvert = null
      }
      if (parseInt(showAgainDismiss) < 0) {
        showAgainDismiss = null
      }
      if (parseInt(userVisitedLast) < 0) {
        userVisitedLast = null
      }

      // Check last visit setting.
      // Only show users the modal if it's been x days since last visit.
      // Additional check below if it's set and not been past yet.
      if (lastVisit !== null && userVisitedLast !== null) {
        visitExpired = moment(lastVisit).add(userVisitedLast, 'days')

        if (lastVisit && moment().isAfter(visitExpired, 'minute')) {
          isActive = true
          modalDebug.push(isActive + ' - user visited less than ' + userVisitedLast + ' days ago')
        }
      }

      // Check Page Settings.
      // If no page settings, active on all pages.
      if (showOnPages.length === 0) {
        isActive = true
        modalDebug.push(isActive + ' - because 0 showOnPages value')
      }
      else if (showOnPages.length > 0) {

        // If there are pages, activate them on the specific paths.
        matched = false
        for (var i = 0; i < showOnPages.length; i++) {
          if (showOnPages[i].includes('*')) {
            regex = this.wildcardToRegExp(showOnPages[i])

            if (path.match(regex)) {
              isActive = true
              matched = true
              modalDebug.push(isActive + ' - because ' + path + ' matched regex.')
            }
          }
          else if (showOnPages[i] === path || showOnPages[i].replace(origin, '') === path) {
            isActive = true
            matched = true
            modalDebug.push(isActive + ' - because ' + path + ' matched')
          }
          else if (path === '/' && (showOnPages[i] === '/home' || showOnPages[i] === '<front>')) {
            isActive = true
            matched = true
            modalDebug.push(isActive + ' - because ' + path + ' matched')
          }
        }

        if (matched === false) {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because there were no showOnPages paths matched ' + path + '.')
        }
      }

      // Check Location Settings: Country.
      if (showLocationsCountries.length > 0 && failedPrevCheck === false) {
        if (userSettings && userSettings.country_code && showLocationsCountries.indexOf(userSettings.country_code) > -1) {
          isActive = true
          modalDebug.push(isActive + ' - because country matched: ' + userSettings.country_code)
        }
        else {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because country did not match: ' + userSettings.country_code)
        }
      }

      // Check Location Settings: State.
      if (showLocationsState.length > 0 && failedPrevCheck === false) {
        if (userSettings && userSettings.region_code && showLocationsState.indexOf(userSettings.region_code) > -1) {
          isActive = true
          modalDebug.push(isActive + ' - because state matched: ' + userSettings.region_code)
        }
        else {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because state did not match: ' + userSettings.region_code)
        }
      }

      // Check Referral Settings. (if previous check hasn't failed)
      if (showIfReferred.length > 0 && failedPrevCheck === false) {
        let parsed = document.referrer ? document.referrer.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/) : null;
        let previousURL = parsed && {
          href: document.referrer,
          protocol: parsed[1],
          origin: parsed[2],
          hostname: parsed[3],
          port: parsed[4],
          pathname: parsed[5],
          search: parsed[6],
          hash: parsed[7]
        }
        matched = false

        if (previousURL) {
          for (var i = 0; i < showIfReferred.length; i++) {
            if (showIfReferred[i].includes('*')) {
              regex = this.utils.wildcardToRegExp(showOnPages[i])

              if (previousURL.pathname.match(regex)) {
                isActive = true
                matched = true
                modalDebug.push(isActive + ' - because matched referrer url: ' + previousURL.pathname)
              }
            }
            else if (previousURL.pathname === '/' && (showIfReferred[i] === '/home' || showIfReferred[i] === '<front>')) {
              isActive = true
              matched = true
              modalDebug.push(isActive + ' - because matched referrer url: ' + previousURL.pathname)
            }
            else if (showIfReferred[i].replace(origin, '') === previousURL.pathname && previousURL.origin === origin) {
              isActive = true
              matched = true
              modalDebug.push(isActive + ' - because matched referrer url: ' + previousURL.pathname)
            }
            else if (showIfReferred[i] === document.referrer) {
              isActive = true
              matched = true
              modalDebug.push(isActive + ' - because matched referrer url: ' + document.referrer)
            }
            else if (showIfReferred[i].indexOf('http') > -1) {
              let referrerParsed = showIfReferred[i].match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
              let referrer = referrerParsed && {
                href: showIfReferred[i],
                protocol: referrerParsed[1],
                origin: referrerParsed[2],
                hostname: referrerParsed[3],
                port: referrerParsed[4],
                pathname: referrerParsed[5],
                search: referrerParsed[6],
                hash: referrerParsed[7]
              }
              let referHost = referrer.hostname.replace('www.', '')
              let previousHost = previousURL.hostname.replace('www.', '')

              // If it's twitter's shortening service.
              if (referHost === 'twitter.com' && previousHost === 't.co') {
                isActive = true
                matched = true
                modalDebug.push(isActive + ' - because matched referrer url: ' + previousHost)
              }

              if (referHost === previousHost && (previousURL.pathname === referrer.pathname) || referrer.pathname === '/') {
                isActive = true
                matched = true
                modalDebug.push(isActive + ' - because matched referrer url: ' + previousURL.pathname)
              }
            }
          }
        }

        // If no matches, we need to make inactive.
        if (matched === false) {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because matched there were no matching referrer urls')
        }
      }

      // Check Date Settings.
      // Only effect if isActive is true.
      // We'll disable if dates are set and are outside today.
      if (isActive === true && showDateStart) {
        if (!showDateEnd) {
          showDateEnd = showDateStart
        }

        if (showDateStart > date) {
          isActive = false
        }

        if (showDateEnd < date) {
          isActive = false
          failedPrevCheck = true;
          modalDebug.push(isActive + ' - because show end date has past')
        }
      }

      // Check what cookies exist.
      // Check generic lastSeen
      if (isActive === true && lastSeen) {
        if (showAgainDismiss) {
          showAgain = moment(lastSeen).add(showAgainDismiss, 'days').format('YYYY-MM-DD hh:mm:ss')

          if (moment().isBefore(showAgain, 'minute')) {
            isActive = false
            failedPrevCheck = true
            modalDebug.push(isActive + ' - because user last saw modal less than ' + showAgainDismiss + ' days')
          }
        }
        else {
          isActive = false;
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because user already saw this modal and has no show again settings')
        }
      }

      // Check lastDismissed
      if (isActive === true && lastDismissed) {
        if (showAgainDismiss) {
          showAgain = moment(lastDismissed).add(showAgainDismiss, 'days').format('YYYY-MM-DD hh:mm:ss')

          if (moment().isBefore(showAgain, 'minute')) {
            isActive = false
            failedPrevCheck = true
            modalDebug.push(isActive + ' - because user last saw modal less than ' + showAgainDismiss + ' days')
          }
        }
        else {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because user already saw this modal and has no show again settings')
        }
      }

      // Check lastDismissed
      if (isActive === true && lastConverted) {
        if (showAgainConvert) {
          showAgain = moment(lastConverted).add(showAgainConvert, 'days').format('YYYY-MM-DD hh:mm:ss')

          if (moment().isBefore(showAgain, 'minute')) {
            isActive = false
            failedPrevCheck = true
            modalDebug.push(isActive + ' - because user last interacted with modal less than ' + showAgainConvert + ' days')
          }
        }
        else {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because user already saw this modal and has no show again after interacts settings')
        }
      }

      // Double check if the last visit should make active false.
      if (isActive === true && userVisitedLast !== null && lastVisit !== null) {
        visitExpired = moment().subtract(userVisitedLast, 'days').format('YYYY-MM-DD hh:mm:ss')

        if (lastVisit && moment(lastVisit).isBefore(visitExpired)) {
          isActive = false
          failedPrevCheck = true
          modalDebug.push(isActive + ' - because user last visted the website less than ' + userVisitedLast + ' days ago')
        }
      }

      if (isActive === false && showOverride && showOverride === true) {
        isActive = true
        modalDebug.push(isActive + ' - because showOverride is set to true')
      }

      // Check if we've already shown a modal.
      if (isActive === true && modalShown) {
        isActive = false
        failedPrevCheck = true
        modalDebug.push(isActive + ' - because another modal is already being shown on the page. Will show next time.')
      }

      if (this.settings && this.settings.debug === true) {
        console.log('******** MODAL DEBUG id: ' + modalId + ' ********')
        console.log('modal name: ' + debugName)
        console.log('modal was shown:')

        for (i = 0; i < modalDebug.length; i++) {
          console.log('  - ' + modalDebug[i])
        }

        console.log('modal settings:', modalSettings)
      }

      return isActive
    },

    /**
     * Sets the modal cookie.
     *
     * @param {string} key Storage key of the cookie.
     */
    setModalCookie: function(key) {
      var time = moment().format('YYYY-MM-DD hh:mm:ss')
      localStorage.setItem(key, time)
    },

     /**
     * Sets a timeout when to show the modal.
     *
     * @param {Element} modal the modal DOM element
     * @param {string} modalId the modal ID attribute
     * @param {integer} timeout time to show the modal (in milliseconds)
     *
     */
    showModalTimeout: function (modal, modalId, timeout) {
      const useBoostrap = this.settings.useBoostrap
      const openCallback = Drupal.behaviors[this.settings.openCallback]
      this.setModalCookie(this.settings.sessionModalSeen + modalId);

      if (useBoostrap === false && typeof openCallback === 'function') {
        setTimeout(function () {
          openCallback(modal)
        }, timeout)
        
        var event = new CustomEvent('modal.opened', { modal });
        modal.dispatchEvent(event)
      } else if (useBoostrap === true) {
        var modalObject = new bootstrap.Modal(modal)

        setTimeout(function () {
          modalObject.show()
        }, timeout)
      }
    },

    /**
     * Creates a RegExp from the given string, converting asterisks to .* expressions,
     * and escaping all other characters.
     *
     * @param {string} s string with wildcard.
     *
     * @return {object} returns regular expression object.
     */
    wildcardToRegExp: function (s) {
      return new RegExp('^' + s.split(/\*+/).map((s) => {
        return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
      }).join('.*') + '$')
    },


    attach: function (context, settings) {
      this.settings = this.getSettings({
        ...settings.modals,
        user: settings.user
      })
      const module = this;
      const modalElements = document.querySelectorAll('.ik-modal')
      let modalShown = false

      modalElements.forEach(modal => {
        const modalId = modal.getAttribute('id')
        const modalSettings = module.settings[modalId]
        const timeout = modalSettings.showDelay ? (modalSettings.showDelay * 1000) : 3000

        let isActive = module.isModalAvailable(modalId, modalSettings, modalShown)

        module.handleModalInternals(modal, modalId, modalSettings.showAgainConvert, modalSettings.showAgainDismiss)

        // only target our modal content type
        if (modalId.indexOf('modal--') > -1) {
          if (isActive && modalShown === false) {
            modalShown = true
            module.showModalTimeout(modal, modalId, timeout)
          }
        }
      })

      localStorage.setItem(module.settings.storageLastVisit, moment().format('YYYY-MM-DD hh:mm:ss'))
    }
  };

  
})(Drupal);
