import angular from 'angular';
import _ from 'lodash';
import coreModule from '../core_module';

export class ValueSelectDropdownCtrl {
  dropdownVisible: any;
  highlightIndex: any;
  linkText: any;
  oldVariableText: any;
  options: any;
  search: any;
  selectedTags: any;
  selectedValues: any;
  tags: any;
  variable: any;

  hide: any;
  onUpdated: any;

  /** @ngInject */
  constructor(private $q) {}

  show() {
    this.oldVariableText = this.variable.current.text;
    this.highlightIndex = -1;

    this.options = this.variable.options;
    this.selectedValues = _.filter(this.options, { selected: true });

    this.tags = _.map(this.variable.tags, value => {
      let tag = { text: value, selected: false };
      _.each(this.variable.current.tags, tagObj => {
        if (tagObj.text === value) {
          tag = tagObj;
        }
      });
      return tag;
    });

    this.search = {
      query: '',
      options: this.options.slice(0, Math.min(this.options.length, 1000)),
    };

    this.dropdownVisible = true;
  }

  updateLinkText() {
    let current = this.variable.current;

    if (current.tags && current.tags.length) {
      // filer out values that are in selected tags
      let selectedAndNotInTag = _.filter(this.variable.options, option => {
        if (!option.selected) {
          return false;
        }
        for (let i = 0; i < current.tags.length; i++) {
          let tag = current.tags[i];
          if (_.indexOf(tag.values, option.value) !== -1) {
            return false;
          }
        }
        return true;
      });

      // convert values to text
      let currentTexts = _.map(selectedAndNotInTag, 'text');

      // join texts
      this.linkText = currentTexts.join(' + ');
      if (this.linkText.length > 0) {
        this.linkText += ' + ';
      }
    } else {
      this.linkText = this.variable.current.text;
    }
  }

  clearSelections() {
    _.each(this.options, option => {
      option.selected = false;
    });

    this.selectionsChanged(false);
  }

  selectTag(tag) {
    tag.selected = !tag.selected;
    let tagValuesPromise;
    if (!tag.values) {
      tagValuesPromise = this.variable.getValuesForTag(tag.text);
    } else {
      tagValuesPromise = this.$q.when(tag.values);
    }

    tagValuesPromise.then(values => {
      tag.values = values;
      tag.valuesText = values.join(' + ');
      _.each(this.options, option => {
        if (_.indexOf(tag.values, option.value) !== -1) {
          option.selected = tag.selected;
        }
      });

      this.selectionsChanged(false);
    });
  }

  keyDown(evt) {
    if (evt.keyCode === 27) {
      this.hide();
    }
    if (evt.keyCode === 40) {
      this.moveHighlight(1);
    }
    if (evt.keyCode === 38) {
      this.moveHighlight(-1);
    }
    if (evt.keyCode === 13) {
      if (this.search.options.length === 0) {
        this.commitChanges();
      } else {
        this.selectValue(this.search.options[this.highlightIndex], {}, true, false);
      }
    }
    if (evt.keyCode === 32) {
      this.selectValue(this.search.options[this.highlightIndex], {}, false, false);
    }
  }

  moveHighlight(direction) {
    this.highlightIndex = (this.highlightIndex + direction) % this.search.options.length;
  }

  selectValue(option, event, commitChange, excludeOthers) {
    if (!option) {
      return;
    }

    option.selected = this.variable.multi ? !option.selected : true;

    commitChange = commitChange || false;
    excludeOthers = excludeOthers || false;

    let setAllExceptCurrentTo = newValue => {
      _.each(this.options, other => {
        if (option !== other) {
          other.selected = newValue;
        }
      });
    };

    // commit action (enter key), should not deselect it
    if (commitChange) {
      option.selected = true;
    }

    if (option.text === 'All' || excludeOthers) {
      setAllExceptCurrentTo(false);
      commitChange = true;
    } else if (!this.variable.multi) {
      setAllExceptCurrentTo(false);
      commitChange = true;
    } else if (event.ctrlKey || event.metaKey || event.shiftKey) {
      commitChange = true;
      setAllExceptCurrentTo(false);
    }

    this.selectionsChanged(commitChange);
  }

  selectionsChanged(commitChange) {
    this.selectedValues = _.filter(this.options, { selected: true });

    if (this.selectedValues.length > 1) {
      if (this.selectedValues[0].text === 'All') {
        this.selectedValues[0].selected = false;
        this.selectedValues = this.selectedValues.slice(1, this.selectedValues.length);
      }
    }

    // validate selected tags
    _.each(this.tags, tag => {
      if (tag.selected) {
        _.each(tag.values, value => {
          if (!_.find(this.selectedValues, { value: value })) {
            tag.selected = false;
          }
        });
      }
    });

    this.selectedTags = _.filter(this.tags, { selected: true });
    this.variable.current.value = _.map(this.selectedValues, 'value');
    this.variable.current.text = _.map(this.selectedValues, 'text').join(' + ');
    this.variable.current.tags = this.selectedTags;

    if (!this.variable.multi) {
      this.variable.current.value = this.selectedValues[0].value;
    }

    if (commitChange) {
      this.commitChanges();
    }
  }

  commitChanges() {
    // if we have a search query and no options use that
    if (this.search.options.length === 0 && this.search.query.length > 0) {
      this.variable.current = { text: this.search.query, value: this.search.query };
    } else if (this.selectedValues.length === 0) {
      // make sure one option is selected
      this.options[0].selected = true;
      this.selectionsChanged(false);
    }

    this.dropdownVisible = false;
    this.updateLinkText();

    if (this.variable.current.text !== this.oldVariableText) {
      this.onUpdated();
    }
  }

  queryChanged() {
    this.highlightIndex = -1;
    this.search.options = _.filter(this.options, option => {
      return option.text.toLowerCase().indexOf(this.search.query.toLowerCase()) !== -1;
    });

    this.search.options = this.search.options.slice(0, Math.min(this.search.options.length, 1000));
  }

  init() {
    this.selectedTags = this.variable.current.tags || [];
    this.updateLinkText();
  }
}

/** @ngInject */
export function valueSelectDropdown($compile, $window, $timeout, $rootScope) {
  return {
    scope: { variable: '=', onUpdated: '&' },
    templateUrl: 'public/app/partials/valueSelectDropdown.html',
    controller: 'ValueSelectDropdownCtrl',
    controllerAs: 'vm',
    bindToController: true,
    link: function(scope, elem) {
      let bodyEl = angular.element($window.document.body);
      let linkEl = elem.find('.variable-value-link');
      let inputEl = elem.find('input');

      function openDropdown() {
        inputEl.css('width', Math.max(linkEl.width(), 80) + 'px');

        inputEl.show();
        linkEl.hide();

        inputEl.focus();
        $timeout(
          function() {
            bodyEl.on('click', bodyOnClick);
          },
          0,
          false
        );
      }

      function switchToLink() {
        inputEl.hide();
        linkEl.show();
        bodyEl.off('click', bodyOnClick);
      }

      function bodyOnClick(e) {
        if (elem.has(e.target).length === 0) {
          scope.$apply(function() {
            scope.vm.commitChanges();
          });
        }
      }

      scope.$watch('vm.dropdownVisible', newValue => {
        if (newValue) {
          openDropdown();
        } else {
          switchToLink();
        }
      });

      let cleanUp = $rootScope.$on('template-variable-value-updated', () => {
        scope.vm.updateLinkText();
      });

      scope.$on('$destroy', () => {
        cleanUp();
      });

      scope.vm.init();
    },
  };
}

coreModule.controller('ValueSelectDropdownCtrl', ValueSelectDropdownCtrl);
coreModule.directive('valueSelectDropdown', valueSelectDropdown);
