import template from './ws-content-selector.tmpl.html';

ContentSelectorDirective.$inject = ['$sce', '$filter'];

export default function ContentSelectorDirective($sce, $filter) {
  return {
    restrict: 'E',
    template,
    require: '?ngModel',
    priority: 2, // necessary to override required validator
    scope: {
      options: '<',
      type: '@',
      rowSize: '@',
      selected: '=?', // TODO: Deprecate this and use ngModel exclusively
      stacked: '<',
      neutral: '<',
      expandable: '<',
      ngModel: '=?',
    },
    link($scope, element, attrs, ngModelCtrl) {
      const validTypes = ['single', 'multi'];

      if (!$scope.type) {
        $scope.type = 'single';
      }

      $scope.optionMetaData = [];

      if (attrs.ngModel && attrs.selected) {
        throw new Error('[WsContentSelector] The "selected" attribute is deprecated, please use "ng-model"');
      }

      if (validTypes.indexOf($scope.type) === -1) {
        throw new Error('invalid type set on wsContentSelector');
      }

      if (!$scope.selected && _isMultiSelect()) {
        $scope.selected = [];
      }

      if (!$scope.rowSize) {
        $scope.rowSize = 4;
      }

      if (!$scope.stacked) {
        $scope.stacked = false;
      } else if ($scope.stacked === true) {
        $scope.rowSize = 1;
      }

      if (!$scope.neutral) {
        $scope.neutral = false;
      }

      $scope.colSize = Math.floor(12 / $scope.rowSize);

      $scope.$watchCollection('options', _rebuildInternalRepresentations);

      $scope.select = (selectedOption) => {
        if (selectedOption.disabled) {
          return;
        }

        if (_isMultiSelect()) {
          const index = $scope.selected.indexOf(selectedOption);

          if (index === -1) {
            $scope.selected.push(selectedOption);
          } else {
            $scope.selected.splice(index, 1);
          }
        } else {
          $scope.selected = selectedOption;
        }
      };

      $scope.isSelected = (option) => {
        if (_isMultiSelect()) {
          return $scope.selected.indexOf(option) !== -1;
        }

        return $scope.selected === option;
      };

      $scope.showMoreInfo = ($event, option) => {
        $event.stopPropagation();
        const metaData = $scope.getMetaData(option);
        metaData.moreInfo = !metaData.moreInfo;
      };

      $scope.mouseEnter = (option) => {
        $scope.getMetaData(option).hoveredOptions = {
          'border-left': `2px solid ${option.color ? option.color : '#fab131'}`,
        };
      };

      $scope.mouseLeave = (option) => {
        $scope.getMetaData(option).hoveredOptions = {};
      };

      $scope.getBufferRow = row => new Array($scope.rowSize - row.length);

      $scope.getOptionStyle = (option) => {
        if ($scope.neutral) {
          return '';
        }
        if ($scope.isSelected(option) && option.color) {
          return {
            'background-color': option.color,
            color: option.color,
          };
        } else if (!$scope.isSelected(option)) {
          return $scope.getMetaData(option).hoveredOptions;
        }

        return '';
      };

      $scope.getMetaData = (option) => {
        const idx = $scope.options.indexOf(option);
        return $scope.optionMetaData[idx];
      };

      if (ngModelCtrl) {
        _initializeNgModelCtrl();
      }

      function _isMultiSelect() {
        return $scope.type === 'multi';
      }

      function _rebuildInternalRepresentations() {
        $scope.optionMetaData = _buildOptionMetaData();
        $scope.chunkedOptions = _buildChunkedOptions();
      }

      function _buildOptionMetaData() {
        return $scope.options.map(option => ({
          trustedBody: $sce.trustAsHtml(option.body),
          trustedBlurb: $sce.trustAsHtml(option.blurb),
          trustedValueBlurb: $sce.trustAsHtml(option.valueBlurb),
        }));
      }

      function _buildChunkedOptions() {
        return $filter('chunk')($scope.options, $scope.rowSize);
      }

      function _initializeNgModelCtrl() {
        /*
        "selected" is the internal representation of the model,
         when it changes we need to update ngModel as well so that
         the change is reflected externally
        */
        if (_isMultiSelect()) {
          $scope.$watchCollection('selected', _syncNgModel);
        } else {
          $scope.$watch('selected', _syncNgModel);
        }

        // If ngModel is changed externally we need to update our
        // "selected" internal representation as well
        $scope.$watch('ngModel', () => {
          if (angular.isUndefined($scope.ngModel)) return;
          $scope.selected = $scope.ngModel;
        });

        // validators
        if (angular.isDefined(attrs.required)) {
          ngModelCtrl.$validators.required = _validateRequired;
        }
      }

      function _validateRequired(modelValue) {
        if (angular.isUndefined(modelValue) || modelValue === null) {
          return false;
        } else if (_isMultiSelect()) {
          return modelValue.length > 0;
        }

        return true;
      }

      /*
       If this is a multi-input, we need to pass an entirely new
       array to $setViewValue in order for the validators to be
       triggered :(
      */
      function _syncNgModel() {
        const modelValue = _isMultiSelect() ?
          [...$scope.selected] :
          $scope.selected;

        ngModelCtrl.$setViewValue(modelValue);
      }
    },
  };
}
