/**
 * Builds a nested accordion widget.
 *
 * Invoke on an HTML list element with the jQuery plugin pattern.
 * - For example, $('.navbar-menu').drupalNavbarMenu();
 */

(function ($, Drupal) {

"use strict";

/**
 * Store the open menu tray.
 */

  $.fn.drupalNavbarMenu = function (options) {

    // Merge options onto defaults.
    var settings = $.extend({}, {
      twisties: true,
      activeTrail: true,
      listLevels: true,
      //A function that returns the list (li) element.
      findItem: null,
      // A function that returns the element which should be treated as the
      // "link" for a menu item.
      findItemElement: null,
      // A function that returns the sub-menu element of a menu item.
      findItemSubMenu: null
    }, options);

    var ui = {
      'handleOpen': Drupal.t('Extend'),
      'handleClose': Drupal.t('Collapse')
    };
    /**
     * Handle clicks from the disclosure button on an item with sub-items.
     *
     * @param {Object} event
     *   A jQuery Event object.
     */
    function toggleClickHandler (event) {
      var $toggle = $(event.target).filter('.navbar-handle');
      if ($toggle.length) {
        var $item = $toggle.closest('li');
        // Toggle the list item.
        toggleList($item);
        // Close open sibling menus.
        var $openItems = $item.siblings().filter('.open');
        toggleList($openItems, false);
      }
    }
    /**
     * Toggle the open/close state of a list is a menu.
     *
     * @param {jQuery} $item
     *   The li item to be toggled.
     *
     * @param {Boolean} switcher
     *   A flag that forces toggleClass to add or a remove a class, rather than
     *   simply toggling its presence.
     */
    function toggleList ($item, switcher) {
      var $toggle = $item.children('.navbar-box').children('.navbar-handle');
      switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open');
      // Toggle the item open state.
      $item.toggleClass('open', switcher);
      // Twist the toggle.
      $toggle.toggleClass('open', switcher);
      // Adjust the toggle text.
      $toggle
        .find('.action')
        // Expand Structure, Collapse Structure
        .text((switcher) ?  ui.handleClose : ui.handleOpen);
    }
    /**
     * Wrap menu links is standardized markup.
     *
     * Items with sub-elements have a list toggle attached to them. Menu item
     * links and the corresponding list toggle are wrapped with in a div
     * classed with .navbar-box. The .navbar-box div provides a positioning
     * context for the item list toggle twisty.
     *
     * @param {jQuery} $menu
     *   The root of the menu to be initialized.
     */
    function processMenuLinks ($items, settings, $menu) {
      // Initialize items and their links.
      $items
        .once('navbar-menu-item')
        .each(function (index, element) {
          var $item = $(element);
          var $handle = settings.findItemElement && settings.findItemElement($item, $menu) || $item.children('a, span');
          if ($handle.length) {
            $handle
              // Add a handle to each list item if it has a menu.
              .addClass('navbar-menu-item')
              .wrap('<div class="navbar-box">');
          }
          // Store a reference to the box wrapping element so that it needn't be
          // found again through DOM searching.
          $item.data({'box': $handle.parent().get(0)});
        });
      // Twisties allow for expand/collapse of nested menu items.
      if (settings.twisties) {
        var options = {
          'class': 'navbar-icon navbar-handle',
          'action': ui.handleOpen,
          'text': ''
        };
        $items
          .each(function (index, element) {
            var $item = $(element);
            // The following code involves a lot of DOM traversing. Exit early
            // if possible, but don't mark the menu as processed just yet, so
            // $.once() can't be called here.
            if ($item.hasClass('navbar-menu-twisties-processed')) {
              return;
            }
            var $menu = settings.findItemSubMenu && settings.findItemSubMenu($item, $menu) || $item.children('ul');
            if ($menu.length) {
              // Get the item 'link' element.
              var $box = $($item.data('box'));
              if ($box.length) {
                var $twistyItem = $item.once('navbar-menu-twisties');
                if ($twistyItem.length) {
                  options.text = Drupal.t('@label', {'@label': $box.text()});
                  $item.addClass('navbar-twisty');
                  $box.append(Drupal.theme('navbarMenuItemToggle', options));
                }
              }
            }
          });
      }
    }
    /**
     * Adds a level class to each list based on its depth in the menu.
     *
     * This function is called recursively on each sub level of lists elements
     * until the depth of the menu is exhausted.
     *
     * @param {jQuery} $lists
     *   A jQuery object of ul elements.
     *
     * @param {Integer} level
     *   The current level number to be assigned to the list elements.
     */
    function markListLevels ($lists, level, settings, $menu) {
      level = (!level) ? 1 : level;
      var $items = settings.findItem && settings.findItem($lists, $menu) || $lists.children('li');
      $items.addClass('navbar-level-' + level);
        // Retrieve child menus.
      var $lists = settings.findItemSubMenu && settings.findItemSubMenu($items, $menu) || $items.children('ul');
      if ($lists.length) {
        markListLevels($lists, level + 1, settings, $menu);
      }
    }
    /**
     * On page load, open the active menu item.
     *
     * Marks the trail of the active link in the menu back to the root of the
     * menu with .active-trail.
     *
     * @param {jQuery} $menu
     *   The root of the menu.
     */
    function openActiveItem ($menu) {
      var pathname = location.pathname;
      // Get all menu items that start with the location path.
      var $pathItem = $menu.find('a[href^="' + pathname + '"]');

      // Clear any existing menu trails.
      $menu.find('.navbar-active, .navbar-active-trail').removeClass('navbar-active navbar-active-trail');
      /**
       * Adds the navbar-active class active menu item.
       *
       * In addition a navbar-active-trail class is added to all parent menu
       * list items.
       *
       * @param jQuery $pathItem
       *   A jQuery object of the active menu item.
       */
      function markItemTrail ($pathItem) {
        $pathItem.addClass('navbar-active');
        var $activeTrail = $pathItem.parentsUntil('.navbar-root', 'li').addClass('navbar-active-trail');
        toggleList($activeTrail, true);
      }

      if ($pathItem.length) {
        // If the path yields more than one match, try to narrow the items down
        // by the params and hash.
        if ($pathItem.length > 1) {
          for (var i = 0, aspects = ['', location.search, location.hash, (location.search + location.hash)], len = aspects.length; i < len; i++) {
            $pathItem = $menu.find('a[href="' + pathname + aspects[i] + '"]');
            if ($pathItem.length === 1) {
              break;
            }
          }
        }
        if ($pathItem.length === 1) {
          markItemTrail($pathItem);
        }
      }
    }
    // Return the jQuery object.
    return this.each(function (selector) {
      var $menu = $(this);
      var rootNotProcessed = $menu.once('navbar-menu');
      if (rootNotProcessed.length) {
        $menu
          .addClass('navbar-menu-root navbar-root')
          .on('click.navbar', toggleClickHandler);
      }
      // Process components of the menu.
      processMenuLinks($menu.find('li'), settings, $menu);
      // Add a menu level class to each menu item.
      if (settings.listLevels) {
        markListLevels($menu, 1, settings, $menu);
      }
      // Restore previous and active states.
      if (settings.activeTrail) {
        openActiveItem($menu);
      }
    });
  };

  /**
   * A toggle is an interactive element often bound to a click handler.
   *
   * @return {String}
   *   A string representing a DOM fragment.
   */
  Drupal.theme.navbarMenuItemToggle = function (options) {
    return '<button class="' + options['class'] + '"><span class="action">' + options.action + '</span><span class="label">' + options.text + '</span></button>';
  };

}(jQuery, Drupal));
