import Component from 'ember-component';
import computed from 'ember-computed';
import injectService from 'ember-service/inject';
import run from 'ember-runloop';
import $ from 'jquery';
import on from 'ember-evented/on';
import observer from 'ember-metal/observer';
import { A as emberA } from 'ember-array/utils';
import { KEY_CODES, keyboardListener } from 'web-client/utilities/keyboard';
import Mousetrap from 'mousetrap';

export default Component.extend({
  classNameBindings: [':conversations-list-container', 'hasConversations::no-conversations', 'isListDisplayed:list-displayed'],
  store: injectService(),
  convService: injectService('twitch-conversations/conversations'),
  conversations: computed(function() { return emberA(); }),
  listedConversations: computed('conversations.@each.thread.{messages,isArchived}', function () {
    let conversations = this.get('conversations');
    let listedConversations = conversations.filter(conversation => {
      let messageCount = conversation.get('thread.messages.length');
      let isArchived = conversation.get('thread.isArchived');

      return messageCount > 0 && !isArchived;
    });

    return listedConversations;
  }),

  isListDisplayed: false,
  searchString: '',
  focusedItemIndex: -1,

  hasConversations: computed.notEmpty('conversations'),

  _onDestroy: on('willDestroyElement', function() {
    $(document).off(`click.${this.elementId}-settings`);
    $(document).off(`click.${this.elementId}-list`);
    this.$('.js-conversations-list-inner').off('scroll');

    Mousetrap.unbind('alt+w');
  }),

  keyPress(e) {
    let targetClasses = e.target.classList;

    if ((e.keyCode === KEY_CODES.enter || e.keyCode === KEY_CODES.space) && targetClasses.contains("conversations-list-icon")) {
      this.send("toggleConversationsList");
      e.preventDefault();
    }
  },

  _handleNavigation(e) {
    let target = e.target;
    // The keyboard event handler binds globally, so we manually ignore the
    // event if the target isn't inside the whisper list.
    if (!$.contains(this.element, target)) {
      return;
    }

    let navigationDirection;
    if (e.keyCode === KEY_CODES.down) {
      navigationDirection = 1;
    } else if (e.keyCode === KEY_CODES.up) {
      navigationDirection = -1;
    } else {
      return;
    }

    e.preventDefault();

    let maxFocusable;
    // If we have an active search, base the index math off of all displayed
    // list items, rather than just the conversations we're aware of
    if (this.get('searchString') === '') {
      maxFocusable = this.get('conversations').length;
    } else {
      maxFocusable = this.$(".conversations-list-item").length;
    }

    let currentSelection = this.get('focusedItemIndex');
    let newSelection = currentSelection + navigationDirection;

    if (newSelection < -1) {
      // -1 is essentially "search box", handle "up" the same as "down"
      newSelection = 0;
    } else if (newSelection === -1) {
      // If we're moving "up" from the top of the list, just select the search box
      this.$(".conversations-list-search-bar input").focus();
    } else if (newSelection >= maxFocusable && maxFocusable > 0) {
      // If you're trying to scroll off the bottom of the list, just re-select it
      // do this by "unsetting" the current focus index so it gets re-set later
      newSelection = currentSelection;
      this.set('focusedItemIndex', -1);
    }

    // Otherwise we're somewhere in the middle, so just continue normally.
    this.set('focusedItemIndex', newSelection);
  },

  _outsideClickListHandler(e) {
    if (this.isDestroyed) { return; }
    let $list = this.$('.js-conversations-list'),
        $toggle = this.$('.js-conversations-list-bottom-bar');
    if (!$list.is(e.target) &&
        $list.has(e.target).length === 0 &&
        !$toggle.is(e.target) &&
        $toggle.has(e.target).length === 0) {
      this.set('isListDisplayed', false);
      $(document).off(`click.${this.elementID}-list`);
    }
  },

  _outsideClickSettingsHandler(e) {
    if (this.isDestroyed) { return; }
    let $list = this.$('.js-conversation-settings-menu-container'),
        $listicon = this.$('.js-conversations-list-settings-toggle');
    if (!$list.is(e.target) &&
        $list.has(e.target).length === 0 &&
        !$listicon.is(e.target) &&
        $listicon.has(e.target).length === 0) {
      this.$('.js-conversation-settings-menu-container').addClass('hidden');
      $(document).off(`click.${this.elementId}-settings`);
    }
  },

  _resetNavigationState: observer('searchString', function() {
    this.set('focusedItemIndex', -1);
  }),

  actions: {
    hideList() {
      this.set('isListDisplayed', false);
    },

    clearSearch() {
      this.set('searchString', '');
    },

    toggleConversationsList() {
      if (this.get('isListDisplayed')) {
        this.set('isListDisplayed', false);
      } else {
        this.set('isListDisplayed', true);
        this.set('focusedItemIndex', -1);

        // Auto dismiss the conversations list upon a click anywhere else except the icon

        $(document).on(`click.${this.elementId}-list`, this._outsideClickListHandler.bind(this));

        // FIXME: I don't think this scheduleOnce works because it uses an anonymous function
        run.scheduleOnce('afterRender', this, function () {
          this.$('.conversations-list-search-bar input').focus();
        });
      }
    },

    hideSettings() {
      this.$('.js-conversation-settings-menu-container').addClass('hidden');
    },

    toggleListSettings() {
      if (this.$('.js-conversation-settings-menu-container').hasClass('hidden')) {
        this.$('.js-conversation-settings-menu-container').removeClass('hidden');

        // Auto dismiss the conversations list upon a click anywhere else except the icon

        this.clickSettingsHandler = $(document).on(`click.${this.elementId}-settings`, this._outsideClickSettingsHandler.bind(this));
      } else {
        this.$('.js-conversation-settings-menu-container').addClass('hidden');
      }
    }
  },

  _setupListeners: on('didInsertElement', function () {
    this.$('.js-conversations-list-inner').on('scroll', this._scrollHandler.bind(this));

    Mousetrap.bindGlobal('alt+w', () => {
      run(this, 'send', 'toggleConversationsList');
      return false;
    });

    // Ember ignores arrow key events events in input fields, so we need to use
    // the global handler instead.
    // This component might go away too, so we need to store a reference to the
    // handler so we can remove it later. It binds to the body so we can't just
    // unbind all handlers somewhere. [2] is the last element in the return
    // value: the actual function.
    let listenerHandle = keyboardListener({
      up: this._handleNavigation,
      down: this._handleNavigation
    });
    this.addEventListener(...listenerHandle);
  }),

  _scrollHandler() {
    this.debounceTask('_maybeFetchMoreThreads', 100);
  },

  _isLastItemVisible() {
    let $container = this.$('.js-conversations-list-inner')[0],
        $lastThread = this.$('.js-conversations-list-inner .conversations-list-item').last()[0],
        targetRect = $lastThread.getBoundingClientRect(),
        containerRect = $container.getBoundingClientRect();

    if (targetRect.top <= containerRect.bottom) {
      return true;
    }
  },

  _maybeFetchMoreThreads() {
    if (this._isLastItemVisible()) {
      this.get('convService').fetchMoreThreads();
    }
  }
});
