text: angular.module('ui.bootstrap.tabs', [])
  
  .controller('UibTabsetController', ['$scope', function ($scope) {
    var ctrl = this,
      oldIndex;
    ctrl.tabs = [];
  
    ctrl.select = function(index) {
      if (!destroyed) {
        var previousIndex = findTabIndex(oldIndex);
        var previousSelected = ctrl.tabs[previousIndex];
        if (previousSelected) {
          previousSelected.tab.onDeselect();
          previousSelected.tab.active = false;
        }
  
        var selected = ctrl.tabs[index];
        if (selected) {
          selected.tab.onSelect();
          selected.tab.active = true;
          ctrl.active = selected.index;
          oldIndex = selected.index;
        } else if (!selected && angular.isNumber(oldIndex)) {
          ctrl.active = null;
          oldIndex = null;
        }
      }
    };
  
    ctrl.addTab = function addTab(tab) {
      ctrl.tabs.push({
        tab: tab,
        index: tab.index
      });
      ctrl.tabs.sort(function(t1, t2) {
        if (t1.index > t2.index) {
          return 1;
        }
  
        if (t1.index < t2.index) {
          return -1;
        }
  
        return 0;
      });
  
      if (tab.index === ctrl.active || !angular.isNumber(ctrl.active) && ctrl.tabs.length === 1) {
        var newActiveIndex = findTabIndex(tab.index);
        ctrl.select(newActiveIndex);
      }
    };
  
    ctrl.removeTab = function removeTab(tab) {
      var index = findTabIndex(tab.index);
  
      if (tab.index === ctrl.active) {
        var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
          index - 1 : index + 1 % ctrl.tabs.length;
        ctrl.select(newActiveTabIndex);
      }
  
      ctrl.tabs.splice(index, 1);
    };
  
    $scope.$watch('tabset.active', function(val) {
      if (angular.isNumber(val) && val !== oldIndex) {
        ctrl.select(findTabIndex(val));
      }
    });
  
    var destroyed;
    $scope.$on('$destroy', function() {
      destroyed = true;
    });
  
    function findTabIndex(index) {
      for (var i = 0; i < ctrl.tabs.length; i++) {
        if (ctrl.tabs[i].index === index) {
          return i;
        }
      }
    }
  }])
  
  .directive('uibTabset', function() {
    return {
      transclude: true,
      replace: true,
      scope: {},
      bindToController: {
        active: '=?',
        type: '@'
      },
      controller: 'UibTabsetController',
      controllerAs: 'tabset',
      templateUrl: function(element, attrs) {
        return attrs.templateUrl || 'uib/template/tabs/tabset.html';
      },
      link: function(scope, element, attrs) {
        scope.vertical = angular.isDefined(attrs.vertical) ?
          scope.$parent.$eval(attrs.vertical) : false;
        scope.justified = angular.isDefined(attrs.justified) ?
          scope.$parent.$eval(attrs.justified) : false;
        if (angular.isUndefined(attrs.active)) {
          scope.active = 0;
        }
      }
    };
  })
  
  .directive('uibTab', ['$parse', function($parse) {
    return {
      require: '^uibTabset',
      replace: true,
      templateUrl: function(element, attrs) {
        return attrs.templateUrl || 'uib/template/tabs/tab.html';
      },
      transclude: true,
      scope: {
        heading: '@',
        index: '=?',
        classes: '@?',
        onSelect: '&select', //This callback is called in contentHeadingTransclude
                            //once it inserts the tab's content into the dom
        onDeselect: '&deselect'
      },
      controller: function() {
        //Empty controller so other directives can require being 'under' a tab
      },
      controllerAs: 'tab',
      link: function(scope, elm, attrs, tabsetCtrl, transclude) {
        scope.disabled = false;
        if (attrs.disable) {
          scope.$parent.$watch($parse(attrs.disable), function(value) {
            scope.disabled = !! value;
          });
        }
  
        if (angular.isUndefined(attrs.index)) {
          if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
            scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
          } else {
            scope.index = 0;
          }
        }
  
        if (angular.isUndefined(attrs.classes)) {
          scope.classes = '';
        }
  
        scope.select = function() {
          if (!scope.disabled) {
            var index;
            for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
              if (tabsetCtrl.tabs[i].tab === scope) {
                index = i;
                break;
              }
            }
  
            tabsetCtrl.select(index);
          }
        };
  
        tabsetCtrl.addTab(scope);
        scope.$on('$destroy', function() {
          tabsetCtrl.removeTab(scope);
        });
  
        //We need to transclude later, once the content container is ready.
        //when this link happens, we're inside a tab heading.
        scope.$transcludeFn = transclude;
      }
    };
  }])
  
  .directive('uibTabHeadingTransclude', function() {
    return {
      restrict: 'A',
      require: '^uibTab',
      link: function(scope, elm) {
        scope.$watch('headingElement', function updateHeadingElement(heading) {
          if (heading) {
            elm.html('');
            elm.append(heading);
          }
        });
      }
    };
  })
  
  .directive('uibTabContentTransclude', function() {
    return {
      restrict: 'A',
      require: '^uibTabset',
      link: function(scope, elm, attrs) {
        var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
  
        //Now our tab is ready to be transcluded: both the tab heading area
        //and the tab content area are loaded.  Transclude 'em both.
        tab.$transcludeFn(tab.$parent, function(contents) {
          angular.forEach(contents, function(node) {
            if (isTabHeading(node)) {
              //Let tabHeadingTransclude know.
              tab.headingElement = node;
            } else {
              elm.append(node);
            }
          });
        });
      }
    };
  
    function isTabHeading(node) {
      return node.tagName && (
        node.hasAttribute('uib-tab-heading') ||
        node.hasAttribute('data-uib-tab-heading') ||
        node.hasAttribute('x-uib-tab-heading') ||
        node.tagName.toLowerCase() === 'uib-tab-heading' ||
        node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
        node.tagName.toLowerCase() === 'x-uib-tab-heading'
      );
    }
  });
  