// The admin bar module implements Apostrophe's admin bar at the top of the screen. Any module
// can register a button (or more than one) for this bar by calling the `add` method of this
// module. Buttons can also be grouped into dropdown menus and restricted to those with
// particular permissions. [apostrophe-pieces](../apostrophe-pieces/index.html) automatically
// takes advantage of this module.
//
// On the browser side there are also conveniences to implement jQuery handlers for these
// menu items.
var _ = require('lodash');
module.exports = {
  alias: 'adminBar',
  // Push the assets and the browser call to create the browser-side singleton
  // that provides `apos.adminBar.link`. Most server-side initialization happens
  // in `self.modulesReady` rather than here.
  afterConstruct: function(self) {
    self.pushAssets();
  },
  construct: function(self, options) {
    self.items = [];
    self.groups = [];
    self.groupLabels = {};
    // Add an item to the admin bar.
    //
    // The name argument becomes the value of the `data-apos-admin-bar-item`
    // attribute of the admin bar item.
    //
    // `permission` should be a permission name such as `admin`
    // (the user must be a full admin) or `edit-apostrophe-event`
    // (the user can create events and edit their own). If
    // `permission` is null then being logged in is
    // good enough to see the item. (Securing your actual routes that
    // respond to these items is up to you.)
    //
    // Usually just one admin bar item per module makes sense, so it's
    // common to pass `self.__meta.name` (the module's name) as the name argument.
    //
    // For example, the pieces module does this:
    //
    // ```
    // self.apos.adminBar.add(self.__meta.name, self.pluralLabel, 'edit')
    // ```
    //
    // If you have an `events` module that subclasses pieces, then this
    // creates an admin bar item with a data-apos-admin-item="events" attribute.
    //
    // Or, if you wish to create an ordinary link, you can pass the `href` option
    // as part of the `options` object (fourth argument).
    //
    // You can use the `after` option to specify an admin bar item name
    // this item should appear immediately following.
    self.add = function(name, label, permission, options) {
      var index;
      var item = {
        name: name,
        label: label,
        permission: permission,
        options: options || {}
      };
      if (options && options.after) {
        index = _.findIndex(self.items, { name: options.after });
        if (index !== -1) {
          self.items.splice(index + 1, 0, item);
          return;
        }
      }
      self.items.push(item);
    };
    // Group several menu items together in the interface (currently
    // implemented as a dropdown menu). If `items` is an array of menu
    // item names, then the group's label is the same as the label of
    // the first item. If you wish the label to differ from the label
    // of the first item, instead pass an object with a `label` property
    // and an `items` property.
    self.group = function(items) {
      self.groups.push(items);
    };
    self.addHelpers({
      // Render the admin bar. If the user is not able to see any items,
      // nothing is rendered
      output: function() {
        // Find the subset of admin bar items this user is permitted to see
        var user = self.apos.templates.contextReq.user;
        if (!user) {
          return '';
        }
        var permissions = (user && user._permissions) || {};
        var items = _.filter(self.items, function(item) {
          if (!item.permission) {
            // Being logged in is good enough to see this
            return true;
          }
          return self.apos.permissions.can(self.apos.templates.contextReq, item.permission);
        });
        if (!items.length) {
          return '';
        }
        // Find the combined items and group them up into menus.
        // groupedItems becomes an array that mixes standalone
        // admin bar items with menus.
        groupedItems = [];
        var menu = false;
        _.each(items, function(item, i) {
          if (menu) {
            // We are already building up a grouped menu, but stop doing that
            // if this next item isn't part of it
            if (item.menuLeader === menu.leader.name) {
              menu.items.push(item);
              return;
            } else {
              menu = false;
            }
          }
          // Only build a menu if there are at least two items after filtering
          // for permissions
          if ((item.menuLeader === item.name) && (items[i + 1] && items[i + 1].menuLeader === item.name)) {
            menu = {
              menu: true,
              items: [ item ],
              leader: item,
              label: self.groupLabels[item.name] || item.label
            };
            groupedItems.push(menu);
          } else {
            groupedItems.push(item);
          }
        });
        return self.partial('adminBar', { items: groupedItems });
      }
    });
    // Like the assets module, we wait as long as humanly possible
    // to lock down the list of admin bar items, so that other modules
    // can bicker amongst themselves even during the `modulesReady` stage.
    // When we get to `afterInit`, we can no longer wait! Order and
    // group the admin bar items.
    self.afterInit = function() {
      self.orderItems();
      self.groupItems();
    };
    // Implement the `order` option. This insertion sort results
    // in putting everything otherwise unspecified at the end, as desired.
    // Items with the `last: true` option are moved to the end before the
    // explicit order is applied.
    //
    // Called by `afterInit`
    self.orderItems = function() {
      // Items with a preference to go last go last...
      var moving = [];
      while (true) {
        var moveIndex = _.findIndex(self.items, function(item) {
          return item.options.last;
        });
        if (moveIndex === -1) {
          break;
        }
        moving.push(self.items[moveIndex]);
        self.items.splice(moveIndex, 1);
      }
      self.items = self.items.concat(moving);
      // ... But then explicit order kicks in
      _.each(self.options.order || [], function(name) {
        var item = _.find(self.items, { name: name });
        if (item) {
          self.items = [ item ].concat(_.filter(self.items, function(item) {
            return item.name !== name;
          }));
        }
      });
    };
    // Marks items that have been grouped via the `groups` option — or via
    // `group` calls from modules, combined with the `addGroups` option —
    // with a `menuLeader` property and ensures that the items in a group
    // are consecutive in the order. We'll figure out the final menus at
    // render time so we can handle it properly if an individual
    // user only sees one of them, etc. Called by `afterInit`
    self.groupItems = function() {
      // Implement the groups and addGroups options. Mark the grouped items with a
      // `menuLeader` property.
      _.each(self.options.groups || self.groups.concat(self.options.addGroups || []), function(group) {
        var first = group[0];
        if (group.label) {
          self.groupLabels[group.items[0]] = group.label;
          group = group.items;
        }
        _.each(group, function(name, groupIndex) {
          var item = _.find(self.items, { name: name });
          if (item) {
            item.menuLeader = group[0];
          } else {
            return;
          }
          // Make sure the submenu items wind up following the leader
          // in self.items in the appropriate order
          if (name !== item.menuLeader) {
            var indexLeader = _.findIndex(self.items, { name: item.menuLeader });
            if (indexLeader === -1) {
              throw new Error('Admin bar grouping error: no match for ' + item.menuLeader + ' in menu item ' + item.name);
            }
            var indexMe = _.findIndex(self.items, { name: name });
            if (indexMe !== (indexLeader + groupIndex)) {
              // Swap ourselves into the right position following our leader
              if (indexLeader + groupIndex < indexMe) {
                indexMe++;
              }
              self.items.splice(indexLeader + groupIndex, 0, item);
              self.items.splice(indexMe, 1);
            }
          }
        });
      });
    };
    self.pushAssets = function() {
      self.pushAsset('script', 'user', { when: 'user' });
      options.browser = options.browser || {};
      self.apos.push.browserCall('user', 'apos.create(?, ?)', self.__meta.name, options.browser);
    };
  }
};
    // Browser side, you can call `apos.adminBar.link('item-name', function() { ...})`
    // to conveniently set up an event handler that fires when this button is clicked.