//= require voltron-ext
//= require voltron-core

Voltron.addModule('Dispatch', function(){
  var _events = {};
  var _chains = {};
  var _callbacks = {};

  return {
    addEventWatcher: function(event){
      var args = Array.prototype.slice.call(arguments, 1).flatten().compact();
      _events[event] = args;
      $.each(args, function(index, evt){
        if(['element', 'event', 'data'].includes(evt.toLowerCase())){
          Voltron.debug('error', 'Provided event watcher argument %o is a reserved observer param and will be overridden when the event is dispatched. Consider changing the name of the argument in your call to addEventWatcher for %o', evt, event);
        }
      });
      Voltron.debug('info', 'Added event watcher for %o', event);
      return this;
    },

    addEventChain: function(when, then){
      when = when.toLowerCase();
      then = $.map([then].flatten(), function(item){
        return item.toLowerCase();
      });
      if(!_chains[when]) _chains[when] = [];
      $.each(then, function(index, t){
        if(!_chains[when].includes(t)){
          Voltron.debug('info', 'Added event chain. When an event name matching %o is fired, %o will also be triggered.', when + '*', t);
          _chains[when].push(t);
        }
      });
      return this;
    },

    addCallbackChain: function(when, callback){
      when = when.toLowerCase();
      if(!_callbacks[when]) _callbacks[when] = [];
      if(typeof callback == 'string' || typeof callback == 'function'){
        _callbacks[when].push([callback, Array.prototype.slice.call(arguments, 2)]);
        if(typeof callback == 'string'){
          Voltron.debug('info', 'Added callback chain. When an event name matching %o is fired, %o will also be triggered.', when + '*', callback);
        }else if(typeof callback == 'function'){
          Voltron.debug('info', 'Added callback chain. When an event name matching %o is fired, the provided callback function will also be triggered.', when + '*');
        }
      }else{
        Voltron.debug('warn', 'Callback chain for %o will not be added, the defined callback argument is not a string referencing a module method nor function.', when + '*');
      }
    },

    listen: function(){
      if(!$('body').data('_dispatch')){
        $('body').data('_dispatch', true);
        $('body').on(this.getEvents(), '[data-dispatch]', Voltron.getModule('Dispatch').trigger);
        Voltron.debug('info', 'Voltron dispatcher listening.');
      }
      return this;
    },

    getEvents: function(){
      return $.map(_events, function(val,key){
        return key;
      }).join(' ');
    },

    getHash: function(keys, vals){
      return keys.length === vals.length ? keys.reduce(function(obj, key, index){
        obj[key] = vals[index];
        return obj;
      }, {}) : {};
    },

    getArgumentHash: function(event, args){
      if(_events[event]){
        return this.getHash(_events[event], args);
      }
      return {};
    },

    chain: function(event, params, chainable){
      if(chainable){
        $.each(_chains, function(chain, then){
          if(event.startsWith(chain)){
            $.each(then, function(index, t){
              Voltron.dispatch(t, params, false);
            });
          }
        });

        $.each(_callbacks, function(callback, callbacks){
          if(event.startsWith(callback)){
            $.each(callbacks, function(index, cb){
              if(typeof cb[0] == 'string'){
                cb[1].unshift(cb[0]);
                Voltron.apply(Voltron, cb[1]);
              }else if(typeof cb[0] == 'function'){
                cb[0].apply(Voltron, cb[1]);
              }
            });
          }
        });
      }
    },

    trigger: function(){
      if(!args) args = {};
      var event = arguments[0];
      var args = Array.prototype.slice.call(arguments, 1);
      args = Voltron('Dispatch/getArgumentHash', event.type, args);
      var params = $.extend(args, { element: this, event: event, data: $(this).data() });

      var events = params.data.dispatch.split(/\s+/);

      if(events.includes(event.type)){
        if(params.data.event){
          Voltron.dispatch([event.type, params.data.event].join(':').toLowerCase(), params);
        }else if(this.id){
          Voltron.dispatch([event.type, this.id].join(':').toLowerCase(), params);
        }else{
          Voltron.dispatch([event.type, this.tagName].join(':').toLowerCase(), params);
        }
      }
    }
  };
}, true);

@@ -3018,24 +3018,200 @@
 chainable)%7B%0A
+      // chainable helps prevent recursion, since the event 'click' could trigger something%0A      // beginning with the same word, i.e. - 'clickable', it would loop endlessly.%0A
       if(cha
@@ -3436,27 +3436,33 @@
         %7D);%0A
-%0A
   
+    %7D%0A%0A
       $.each
@@ -3501,26 +3501,24 @@
 callbacks)%7B%0A
-  
         if(e
@@ -3551,26 +3551,24 @@
 %7B%0A          
-  
 $.each(callb
@@ -3602,26 +3602,24 @@
             
-  
 if(typeof cb
@@ -3650,18 +3650,16 @@
         
- 
- 
 cb%5B1%5D.un
@@ -3668,26 +3668,24 @@
 ift(cb%5B0%5D);%0A
-  
             
@@ -3725,26 +3725,24 @@
             
- 
- 
 %7Delse if(typ
@@ -3781,18 +3781,16 @@
         
-  
 cb%5B0%5D.ap
@@ -3802,34 +3802,32 @@
 oltron, cb%5B1%5D);%0A
-  
             %7D%0A  
@@ -3826,34 +3826,32 @@
     %7D%0A          
-  
 %7D);%0A          %7D%0A
@@ -3842,34 +3842,32 @@
 %7D);%0A        
-  
 %7D%0A      
   %7D);%0A      
@@ -3854,30 +3854,20 @@
 %7D%0A      
- 
- 
 %7D);%0A
-      %7D%0A
     %7D,%0A%0A

Allow callback chains to run outside of chainable scope