// Global javascript (loaded on all pages in Pattern Lab and Drupal)
// Should be used sparingly because javascript files can be used in components.

// JavaScript should be made compatible with libraries other than jQuery by
// wrapping it with an "anonymous closure". See:
// - https://drupal.org/node/1446420
// - http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

/**
 * Rocketship UI JS
 *
 * Contains: triggers for functions.
 * Functions themselves are split off and grouped below each behavior.
 */
(function (Drupal, once, window, document) {
  "use strict";
  // Set namespace for frontend UI javascript.
  if (typeof window.rocketshipUI === 'undefined') {
    window.rocketshipUI = {};
  }
  const self = window.rocketshipUI;

  ///////////////////////////////////////////////////////////////////////
  // Triggers
  ///////////////////////////////////////////////////////////////////////
  Drupal.behaviors.rocketshipUIBase = {
    attach: (context) => {
      const tabs = context.querySelector('.tabs__nav');
      const teasers = context.querySelectorAll('.fully-clickable');
      // Add a class to body to indicate the page has edit tabs
      // so we can set it fixed if needed.
      if (tabs) {
        document.body.classList.add('has-tabs');
      }
      // Smooth scrolling to anchors
      // True if fixed header, false for regular header.
      self.scrollToAnchor(0);
      const skipLink = document.querySelector('a[href="#main-content"]');
      const content = document.querySelector('#main-content');
      if (skipLink && content) {
        // When you click on the skiplink, force focus on the target.
        once('skip-link', [skipLink]).forEach((linkElement) => {
          linkElement.addEventListener('click', (e) => {
            content.focus();
            e.preventDefault();
          });
        });
      }
      // Make cards clickable: view teasers.
      if (teasers.length) {
        self.cardLink(teasers);
      }
    }
  };

  ///////////////////////////////////////////////////////////////////////
  // Functions
  ///////////////////////////////////////////////////////////////////////

  /**
   * Make a card clickable, if there is a content link in it to reference.
   * If no selector is given, it will try to go for a 'dedicated' link
   * - a 'read more' link
   * - or a button (if there is only 1).
   * Variables:
   * - elements: NodeList referencing an element.
   * - linkSelector: string (optional).
   */
  self.cardLink = (elements, linkSelector, fallbackSelector) => {
    var newTab = false;
    // For each of our elements.
    once('cardlink', elements).forEach((element) => {
      var el = element,
          link, hrefProp, targetProp, down, up;
      // Is a selector was passed to the function.
      // Use it to find the dedicated link to go to.
      if (typeof linkSelector !== 'undefined' && linkSelector.length) {
        link = el.querySelector(linkSelector);
      }
      // If selector not found, look for fallback.
      if (typeof link === 'undefined' || !link) {
        link = el.querySelector(fallbackSelector);
      }
      // If fallback selector found, look for other to use.
      if (typeof link === 'undefined' || !link) {
        link = self.getCardLink(el).link;
      }
      // If we have a link, go ahead.
      if (typeof link !== 'undefined' && link !== null && link) {
        hrefProp = link.getAttribute('href');
        targetProp = link.getAttribute('target');
        // Set cursor on the card.
        el.style.cursor = 'pointer';
        // If there is a dedicated link in the item
        // Make the div clickable
        // (ignore if a child link is clicked, so be sure to check target!).
        once('cardlink-mousedown', [el]).forEach((element) => {
          element.addEventListener('mousedown', (e) => {
            down = +new Date();
            if (e.ctrlKey || e.metaKey) {
              newTab = true;
            }
          });
        });
        once('cardlink-mouseup', [el]).forEach((element) => {
          element.addEventListener('mouseup', (e) => {
            up = +new Date();
            // Only fire if really clicked on element.
            if ((up - down) < 250) {
              var myEl = element;
              // If the target of the click is the el...
              // ... or a descendant of the el
              if (!myEl.is(e.target) || !myEl.contains(e.target)) {
                // If it's a link tag,
                if (e.target.matches('a, a *')) {
                  // do nothing
                  // Else, trigger the dedicated link
                } else {
                  // If new tab key is pressed
                  // Open in new tab.
                  if (newTab) {
                    window.open(hrefProp, '_blank');
                  } else {
                    // If target defined, open in target.
                    if (typeof targetProp !== 'undefined') {
                      // Fire a click event on the original link
                      window.open(hrefProp, targetProp);
                    } else {
                      window.location.href = hrefProp;
                    }
                  }
                }
              }
            }
            // Reset the flags that determine opening the link in a new browser tab.
            newTab = false;
          });
        });
      }
    });
  };

  /**
   * Get the card link.
   *
   * @param {HTMLElement} el The element.
   *
   * @return {object} The link object.
   */
  self.getCardLink = (el) => {
    let link;
    // 'read more'.
    link = el.querySelector('.field--name-node-link a');
    if (link) {
      return {
        link: link
      };
    }
    // singular button
    link = el.querySelector('.field--buttons a');
    if (link && link.length === 1) {
      return {
        link: link
      };
    }
    // other link field
    link = el.querySelector('[class*="field--name-field-link-"] a:last-of-type');
    if (link) {
      return {
        link: link
      };
    }
    return {
      link: null
    };
  };

  /**
   * Scroll to anchor in page.
   *
   * @param {number} offset Number to offset when scrolling to destination.
   */
  self.scrollToAnchor = (offset) => {
    let animatedScrolling = false;
    let exceptions = null;

    if (drupalSettings?.theme_settings?.scroll_to) {
      animatedScrolling = true;
    }

    if (drupalSettings?.theme_settings?.scroll_to_exceptions?.length) {
      exceptions = drupalSettings.theme_settings.scroll_to_exceptions;
    }
    // Only do animated scrolling if set in theme settings.
    if (animatedScrolling) {
      // On page load, check if hash in the url matches with an anchor on page
      // And scroll to it.
      var newOffset,
          tabs = document.querySelector('.tabs'),
          tabsHeight = 0,
          adminHeight = 0,
          header = document.querySelector('.sticky-top'); // Use whatever wrapper you need.
      // Check for fixed elements on top of site
      // To compensate offset of the anchor when scrolling.
      if (document.body.classList.contains('toolbar-fixed')) {
        adminHeight = document.querySelector('#toolbar-bar').offsetHeight;
        if (tabs) {
          tabsHeight = tabs.offsetHeight;
        }
      }
      // Only add offset if fixed header and/or value passed in function.
      if (typeof offset === 'undefined') offset = 0;
      // Function to calculate the offset, in case of fixed header or not.
      var calcOffset = () => {
        if (header && header.style.position === 'fixed') {
          offset = header.offsetHeight + offset + adminHeight + tabsHeight; // Compensate for fixed header height.
          // Compare offset to height of header to see if it's smaller
          // Must at least use the header height as offset or we'll get a gap.
          newOffset = header.offsetHeight;
          if (newOffset > offset) offset = newOffset;
        }
        else {
          // Calculate offset for a normal page (no fixed header).
          offset = 15 + offset + adminHeight + tabsHeight; // Compensate with bit of space above the element with the anchor.
        }
        // Need negative value for the scroll calculations.
        offset = -(offset);
        return offset;
      };
      // Get hash from page url (includes the hash sign).
      var urlHash = window.location.hash;
      if (urlHash && document.querySelector(urlHash)) {
        // Possible anchor links that refer to that hash.
        const anchorLinks = document.querySelectorAll(`a[href$="${ urlHash }"]`);

        // Trigger the scrollTo, only if NO anchorLink selector on the page
        // OR anchorLink selector is not part of exceptions.

        // 1) No anchor link
        if (anchorLinks.length < 1) {
          // Recalculate offset if fixed header.
          offset = calcOffset();

          // El, offset, speed, callback
          var scrollParams = {
            el: document.querySelector(urlHash),
            offset: offset,
            speed: 500
          };

          self.scrollTo(scrollParams);
        }

        // 2) Check all of anchorLinks if there are any + if not an exception
        once('scrollable-anchors-active', anchorLinks).forEach((linkElement) => {
          var myLink = linkElement;

          // Set an active class & scrollTo (if not part of the exclusion list).
          if (exceptions === null || !exceptions.includes(myLink)) {
            // Recalculate offset if fixed header.
            offset = calcOffset();

            // El, offset, speed, callback
            var scrollParams = {
              el: document.querySelector(urlHash),
              offset: offset,
              speed: 500
            };

            self.scrollTo(scrollParams);

            myLink.classList.add('js-active-anchor');
          }
        });
      }

      // When clicking an anchor, animate scroll to that anchor and add to history
      // If that anchor is not excluded.
      var anchorLinks = document.querySelectorAll('a[href*="#"]:not(a[href="#"])');

      once('scrollable-anchors', anchorLinks).forEach((linkElement) => {
        var anchorLink = linkElement;

        if (exceptions === null || !exceptions.includes(anchorLink)) {
          // Remove active classes on anchor links.
          anchorLink.classList.remove('active');
          anchorLink.classList.remove('active-trail');
        }

        // Click anchor link and animate scroll to it.
        once('scrollable-anchor-click', [anchorLink]).forEach((element) => {
          element.addEventListener('click', (e) => {
            // If there are links to ignore for smooth scroll
            // Or anchorLink is not an exception
            if (exceptions === null || !exceptions.includes(anchorLink)) {
              var path = element.href,
                  id, hash, target;

              if (typeof e.target.href !== 'undefined') {
                id = e.target.href.split('#')[1];
              }

              else if (typeof e.currentTarget.href !== 'undefined') {
                id = e.currentTarget.href.split('#')[1];
              }

              hash = `#${ id }`;
              target = document.querySelector(hash);

              // Recalculate offset if fixed header.
              offset = calcOffset();

              if (target) {
                // Set an active class.
                anchorLink.classList.add('js-active-anchor');

                var scrollParamsForTarget = {
                  el: target,
                  offset: offset,
                  speed: 500,
                  callback: () => {
                    // Change url without refresh.
                    document.location.hash = hash;
                  }
                };

                self.scrollTo(scrollParamsForTarget);

                // If supported by the browser we can also update the URL.
                if (window.history && window.history.pushState) {
                  history.pushState("", document.title, hash);
                }
                // If no anchor with id matching href
                // Check for anchor with a name matching the href.
              }
              else {
                var newTarget = document.querySelector('a[name="' + id + '"]');

                if (newTarget) {
                  // Set an active class.
                  anchorLink.classList.add('js-active-anchor');

                  var scrollParamsForNewTarget = {
                    el: newTarget,
                    offset: offset,
                    speed: 500,
                    callback: () => {
                      // Change url without refresh.
                      document.location.hash = hash;
                    }
                  };

                  self.scrollTo(scrollParamsForNewTarget);
                }
              }

              // The url, minus stuff after hash or parameters.
              var currentUrl = window.location.href.split(/[?#]/)[0];
              // The path, minus stuff after hash or parameters.
              var pathBase = path.split(/[?#]/)[0];

              // If the URLs (stripped of hashes and parameters) don't match up, it's
              // a link to a different page.
              // Don't scroll to anchor on same page on click.
              if (currentUrl.replace(/\/$/, "") !== pathBase.replace(/\/$/, "")) {
                return true;
              }
              // We're on the same page and don't need a page reload.
              e.preventDefault();
            }
          });
        });
      });

      // When going back/forward in history
      // Make sure the events associated with the anchor animations are fired
      // If that anchor is not excluded.
      window.onpopstate = () => {
        once('scrollable-anchors-popstate', anchorLinks).forEach((linkElement) => {
          const anchorLink = linkElement;

          if (exceptions === null || !exceptions.includes(anchorLink)) {
            const path = linkElement.href;
            // Get hash from page url.
            const urlHash = window.location.hash;
            // Get hash & ID from href.
            const linkHash = path.substring(path.indexOf("#"));

            // Recalculate offset if fixed header.
            offset = calcOffset();

            if (document.querySelector(linkHash)) {
              // If hash of current path matches hash of the url
              // Scroll to it.
              if (urlHash === linkHash) {
                if (exceptions === null || !exceptions.includes(anchorLink)) {
                  anchorLink.classList.add('js-active-anchor');

                  const scrollParams = {
                    el: document.querySelector(linkHash),
                    offset: offset,
                    speed: 500
                  };

                  self.scrollTo(scrollParams);
                }
              }
            }
          }
        });
      };
    }
  };

})(Drupal, once,  window, document);
