/* globals i18n */
import Component from 'ember-component';
import computed from 'ember-computed';
import injectService from 'ember-service/inject';
import run from 'ember-runloop';
import Mousetrap from 'mousetrap';
import observer from 'ember-metal/observer';
import $ from 'jquery';
import { getRichPresence } from 'web-client/utilities/presence-util';

export default Component.extend({
  classNames: ['conversation-windowz', 'js-conversation-window'],
  classNameBindings: ['hasUnread:has-unread', 'hasFocus:has-focus'],

  store: injectService(),
  badges: injectService(),
  conversations: injectService('twitch-conversations/conversations'),
  session: injectService(),
  tracking: injectService(),

  roomName: computed.alias('conversations.currentRoomName'),
  conversation: computed.alias('conversations.activeConversation'),
  whisperPlaceholder: i18n('Send a whisper (Press esc to close)'),
  messageToSend: '',
  showSettings: false,
  isLoadingMessages: false,
  thread: computed.alias('conversation.thread'),

  stuckToBottom: true,
  isUserScrolling: false,

  relativeIndex: 0,

  messages: computed.readOnly('thread.messages'),
  notices: computed.readOnly('thread.notices'),

  hasUnread: computed('thread.hasUnread', 'hasFocus', function() {
    return (this.get('thread.hasUnread') && !this.get('hasFocus'));
  }),

  didInsertElement() {
    this._super(...arguments);
    this.$('.js-whisper-content').on('scroll mousedown wheel DOMMouseScroll mousewheel', this._scrollHandler.bind(this));
    this.$('.js-whisper-container.scroll').TrackpadScrollEmulator({
      wrapContent: false,
      scrollbarHideStrategy: 'rightAndBottom'
    });
    this.mousetrap = Mousetrap(document.getElementById('#active-window')).bind('esc', () => {
      if (this.get('hasFocus')) {
        this.sendAction('closeConversation');
      }
      return false;
    });
    this._maybeFocusInput();
    if (this.get('hasFocus')) {
      this.markThreadRead();
    }
  },

  didReceiveAttrs() {
    this._super(...arguments);
    this._scrollToBottom();
    this._setDivider();
    this._maybeFetchMoreMessages();

    this.settingsOutsideClickHandler = this.settingsOutsideClickHandler.bind(this);
    let friendInfo = this.get('store').peekRecord('friends-list-user', this.get('otherUser.id'));
    let friendLocation;
    if (friendInfo && friendInfo.get('isFriend')) {
      let availability = friendInfo.get('availability');
      let activity = friendInfo.get('activities')[0];
      this.set('truncatedPresence', getRichPresence(availability, activity, true));
      if (activity.channel_login) {
        friendLocation = activity.channel_login;
      } else if (['broadcasting', 'streaming'].includes(activity.type)) {
        friendLocation = this.get('otherUser.username');
      }
    }
    this.set('isFriend', friendInfo ? friendInfo.get('isFriend') : false);
    this.set('friendLocation', friendLocation);
  },

  markThreadRead() {
    if (this.get('thread.isSaving')) {
      this.runTask('markThreadRead', 100);
    } else {
      this.debounceTask('_markReadHandler', 100);
    }
  },

  shouldShowSpamCTA: computed('thread.lastMarkedNotSpam', 'thread.spamLikelihood', function () {
    let spamCTADismissed = (this.get('thread.lastMarkedNotSpam') > 0);
    let likelihood = this.get('thread.spamLikelihood');

    return !spamCTADismissed && (likelihood === 'medium' || likelihood === 'high');
  }),


  _preserveFocus: false,

  mouseDown() {
    this.markThreadRead();
    this.set('_preserveFocus', true);
    //  We only prevent the closing of this box during this event propogation
    //  cycle
    this.runTask(() => {
      this.set('_preserveFocus', false);
    });
  },

  click(evt) {
    this.markThreadRead();
    this.set('isFocused', true);
    // Text can still be selected here after deselecting, so schedule for the next iteration.
    this.runTask(() => {
      if (window.getSelection().toString().trim() !== '') { return; }
      if ($(evt.target).is('.js-filter-input')) { return; }
      this._maybeFocusInput();
    });
  },

  headerBadges: computed('otherUser.badges.[]', function () {
    let otherUserBadges = this.get('otherUser.badges') || [];
    return this.get('badges').getBadgesData(otherUserBadges);
  }),

  otherUser: computed('thread.participants', function () {
    return this.get('thread.participants')
             .rejectBy('id', this.get('session.userData.id').toString())
             .get('firstObject');
  }),

  returnToChannelHeader: computed('roomName', function() {
    let translatedString = `\u21AA ${i18n('{{channelName}}\'s Chat')}`;
    return translatedString.replace('{{channelName}}', this.get('roomName'));
  }),

  _setDivider() {
    if (this.isDestroyed) { return; }
    if (this.get('thread.unreadCount') > 0) {
      this.set('messageIdBeforeDivider', this.get('thread.lastReadId'));
    } else {
      this.set('messageIdBeforeDivider', 0);
    }
  },

  _removeDivider() {
    this.set('messageIdBeforeDivider', 0);
  },



  hasFocus: computed('isFocused', function () {
    return this.get('isFocused');
  }),

  _maybeFocusInput() {
    // Focusing the text area in these cases can cause a
    // blank bar to appear over the collapsed window
    if (this.get('hasFocus')) {
      this.$('#active-window textarea').focus();
    }
  },

  settingsOutsideClickHandler(e) {
    // Auto dismiss the settings menu upon a click anywhere else except the icon
    // This method is bound to `this` in init()
    let $selector = this.$('.conversation-settings-menu-container'),
        $toggle = this.$('.conversation-settings-menu-toggle');

    if (!$selector.is(e.target) &&
    $selector.has(e.target).length === 0 &&
    !$toggle.is(e.target) &&
    $toggle.has(e.target).length === 0) {
      $selector.addClass('hidden');
      this.unbindSettingsOutsideClickHandler();
    }
  },

  unbindSettingsOutsideClickHandler() {
    $(document).off('click', this.settingsOutsideClickHandler);
  },

  actions: {
    scrollToBottom() {
      this._scrollToBottom();
    },
    removeDivider() {
      this._removeDivider();
    },

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

        $(document).on('click', this.settingsOutsideClickHandler);
      } else {
        this.$('.conversation-settings-menu-container').addClass('hidden');
      }
    },

    openInModal(template, context) {
      this.sendAction('openInModal', template, context);
    },

    closeModal() {
      this.sendAction('closeModal');
    },

    closeConversation() {
      this.sendAction('closeConversation');
    },

    track(event, data) {
      this.get('tracking').trackEvent({event, data});
    }
  },

  focusIn() {
    this.set('isFocused', true);
  },

  focusOut() {
    if (!this.get('_preserveFocus') && this.get('conversation')) {
      this.set('isFocused', false);
    }
  },

  didUpdateAttrs() {
    this._maybeFocusInput();
  },

  willDestroyElement(){
    this._super(...arguments);
    this.$('.js-whisper-content').off('scroll mousedown wheel DOMMouseScroll mousewheel');
    if (this.$('.js-whisper-container.scroll').data('plugin_TrackpadScrollEmulator')) {
      this.$('.js-whisper-container.scroll').TrackpadScrollEmulator('destroy');
    }
    this.mousetrap.unbind('esc');
  },

  willDestroy() {
    this._super(...arguments);
    this.unbindSettingsOutsideClickHandler();
  },

  _scrollHandler(e) {
    let scroller = this.$('.js-whisper-content');
    let STUCK_TO_BOTTOM_THRESHOLD = 2;
    if (this.isDestroyed) { return; }

    let scrollDistanceToBottom = scroller[0].scrollHeight - scroller[0].scrollTop - scroller[0].offsetHeight;
    let atBottom = scrollDistanceToBottom <= STUCK_TO_BOTTOM_THRESHOLD;

    // Differentiating user scroll vs JavaScript scroll: http://stackoverflow.com/questions/2834667
    // Concept copied from app/views/room.js
    if (e.which > 0 || e.type === 'mousedown' || e.type === 'mousewheel') {
      // We've got a user-initiated scroll event; if we're reaching the bottom,
      // stick to it.
      if (atBottom !== this.get('stuckToBottom')) {
        this.set('stuckToBottom', atBottom);
      }

      this.set('isUserScrolling', true);
      this.debounceTask('_maybeFetchMoreMessages', 100);
      this.debounceTask('_doneScrolling', 250);
    } else {
      // Or a code-initiated scroll event, in which case we want to scroll to
      // the bottom if we aren't already there
      if (this.get('stuckToBottom') && !atBottom && !this.get('isUserScrolling')) {
        this._scrollToBottom();
      }
    }
  },

  // Some browsers send scroll events which look like 'code-triggered' scroll
  // events to 'finalize' 'user-triggered' scroll events. This method will be
  // called via a very quick debounce, so we can ignore those events.
  _doneScrolling() {
    this.set('isUserScrolling', false);
  },

  // If the earliest message is in view, fetch more messages, else do nothing
  _maybeFetchMoreMessages() {
    // This only happens when starting a new conversation
    if (this.get('messages.length') === 0) {
      return false;
    }

    run.scheduleOnce('afterRender', this, function () {
      if (this.isDestroyed) { return; }
      let $container = this.$('.js-whisper-content')[0],
          $earliestLine = this.$('.js-whisper-content .conversation-chat-lines .conversation-chat-line')[0],
          targetRect = $earliestLine.getBoundingClientRect(),
          containerRect = $container.getBoundingClientRect();

      if (targetRect.bottom >= containerRect.top) {
        let thread = this.get('thread');

        if (!thread.hasEarlierMessages()) { return; }

        let numMessages = this.get('messages.length');
        this.set('isLoadingMessages', true);

        thread.loadEarlierMessages().then(() => {
          if (this.isDestroyed) { return; }

          this.set('isLoadingMessages', false);

          // FIXME: I don't think this scheduleOnce works because it's using an anonymous function
          run.scheduleOnce('afterRender', this, function () {
            if (this.isDestroyed) { return; }
            let formerScrollPosition = this.$('.conversation-chat-line').get(-numMessages).offsetTop;
            this.$('.js-whisper-content').scrollTop(formerScrollPosition);
          });
        });
      }
    });
  },

  // If we're stuck to the bottom, scroll down to it
  _maybeScrollToBottom() {
    if (this.get('stuckToBottom') && this.get('messages')) {
      this._scrollToBottom();
    }
  },

  _onMessageAdded: observer('messages.[]', 'notices.[]', function () {
    if (this.get('conversation.isDisplayed') && this.get('conversation.isFocused')) {
      this.markThreadRead();
    } else {
      run.once(this, this._setDivider);
    }
    run.once(this, this._maybeScrollToBottom);
  }),

  _scrollToBottom() {
    run.scheduleOnce('afterRender', this, this._scrollToBottomHandler);
  },

  _scrollToBottomHandler() {
    if (this.isDestroyed) { return; }
    this.$('.js-whisper-content').scrollTop(this.$('.js-whisper-content')[0].scrollHeight);
  },

  _markReadHandler() {
    this.get('conversations').markRead(this.get('thread'));
  }

});
