/* globals Twitch */

import Component from 'ember-component';
import computed from 'ember-computed';
import injectService from 'ember-service/inject';
import observer from 'ember-metal/observer';
import run from 'ember-runloop';
import { assign } from 'ember-platform';
import ChannelModel from 'web-client/models/deprecated-channel';
import { isAscii } from 'web-client/helpers/format-display-name';
import { wrap } from 'web-client/utilities/promises';
import SendRouteActionMixin from 'web-client/mixins/send-route-action';
import { nativeBroadcastsExperiment } from 'web-client/utilities/chat-sticky';
import injectController from 'web-client/utilities/inject-controller';
import $ from 'jquery';

const CHAT_COMMANDS_FOR_USERNAMES = ['w', 'ban', 'unban', 'mod', 'unmod', 'host', 'ignore', 'unignore', 'timeout', 'untimeout'];
const TWITCH_BOT_USERNAME = 'automod';

export default Component.extend(SendRouteActionMixin, {
  api: injectService(),
  badges: injectService(),
  bitsRoom: injectService(),
  bitsPinnedCheers: injectService(),
  bitsRenderingConfig: injectService(), // TODO temporary support for BTTV
  chatSticky: injectService(),
  conversations: injectService('twitch-conversations/conversations'),
  globals: injectService(),
  hosts: injectService(),
  layout: injectService(),
  session: injectService(),
  settings: injectService('chatSettings'),
  tmi: injectService(),
  tracking: injectService(),

  applicationController: injectController('application'),

  showInteractions: false,

  newInteractionsExist: false,

  visibleInteractions: null,

  _hasTrackedScrollUp: false,

  showBLCNotification: false,

  showDisplayNameNotification: false,

  showEmoticonSelector: false,

  showModerationCard: false,

  showResetTopCheerConfirmation: false,

  showViewerList: false,

  hasFlashNotTimedOut: computed.not('room.flashTimedOut'),

  isBroadcasterLanguageChatEnabled: computed.alias('globals.broadcastLanguageChatEnabled'),

  isChatRoomLoading: computed.and('isRoomModelLoading', 'isFlashEnabled', 'hasFlashNotTimedOut'),

  isDarkMode: computed.or('settings.darkMode', 'layout.isTheatreMode'),

  isFlashEnabled: computed.not('isFlashDisabled'),

  isRoomModelLoading: computed.readOnly('room.isLoading'),

  loggedIn: computed.readOnly('session.isAuthenticated'),

  isBitsEnabled: computed.readOnly('bitsRoom.isEnabled'),

  isTagEnabledChannel: computed.readOnly('bitsRoom.isTagEnabledChannel'),

  // Hosting Notification
  isHosting: computed.notEmpty('room.channel.newChannel.hostModeTarget'),
  canShowHostingNotification: true,
  showHostingNotification: computed.and('canShowHostingNotification', 'isHosting'),

  // Resub Notification
  canShowResubNotification: true,
  availableChatNotificationTokens: computed.alias('room.roomProperties.available_chat_notification_tokens'),
  isChatNotificationTokenEnabled: computed.not('room.roomProperties.block_chat_notification_token'),
  isRoomPropertiesLoaded: computed.not('room.roomProperties.isLoading'),

  showResubNotification: computed.and('isRoomPropertiesLoaded', 'isChatNotificationTokenEnabled', 'resubNotification', 'canShowResubNotification'),

  resubNotification: computed('availableChatNotificationTokens', function () {
    let availableTokens = this.get('availableChatNotificationTokens') || [];

    return availableTokens.find((token) => {
      return token.token_kind === 'subscription' || token.token_kind === 'premium';
    });
  }),
  // End Resub Notification

  showARNotification: computed('chatSticky.altRoleStreams', function() {
    return Object.keys(this.get('chatSticky.altRoleStreams')).length > 1;
  }),

  showNBNotification: computed('chatSticky.nbStreams', function () {
    let streams = this.get('chatSticky.nbStreams');
    let sufficientStreams = Object.keys(streams).length > 1;
    let primaryStream = nativeBroadcastsExperiment.primary_stream;
    let primaryStreamIsLive = primaryStream in streams;
    return sufficientStreams && primaryStreamIsLive;
  }),

  showVPNotification: computed.notEmpty('chatSticky.viewingPartyHosts'),

  showTwitchBotModNotification: computed('room.roomProperties.twitchbot_rule_id', function () {
      let shouldShowNotification = (this.get('session.isAuthenticated') &&
        !Twitch.storage.get('twitchbot_notification_closed') &&
        this.get('room.isModerator') &&
        this.get('room.roomProperties.twitchbot_rule_id') > 0);

      if (shouldShowNotification) {
        this.get('tracking').trackEvent({
          event: 'twitchbot_notification_seen',
          services: ['spade'],
          data: this.get('roomTrackingData')
        });
      }
      return shouldShowNotification;
  }),

  showTwitchBotBroadcasterNotification: computed('room.roomProperties.twitchbot_rule_id', function () {
      let shouldShowNotification =  (this.get('session.isAuthenticated') &&
        !Twitch.storage.get('twitchbot_notification_closed') &&
        this.get('room.isBroadcaster') &&
        this.get('room.roomProperties.twitchbot_rule_id') === 0);

      if (shouldShowNotification) {
        this.get('tracking').trackEvent({
          event: 'twitchbot_notification_seen',
          services: ['spade'],
          data: this.get('roomTrackingData')
        });
      }
      return shouldShowNotification;
  }),

  isCheerMessage: computed.readOnly('bitsRoom.isCheerMessage'),

  isBitsTooltipShown: computed.not('bitsRoom.isTooltipHidden'),

  isBitsChannelEnabled: computed.not('bitsRoom.isRecipientIneligible'),

  hasStickyNotification: computed.or('showResubNotification',
                                     'showHostingNotification',
                                     'showBLCNotification',
                                     'showNBNotification',
                                     'showVPNotification',
                                     'showARNotification',
                                     'showDisplayNameNotification',
                                     'showTwitchBotModNotification',
                                     'showTwitchBotBroadcasterNotification'),

  showPinnedCheers: computed('hasStickyNotification', 'bitsPinnedCheers.isEnabled', 'isBitsChannelEnabled', function () {
    return !this.get('hasStickyNotification') && this.get('isBitsChannelEnabled') && this.get('bitsPinnedCheers.isEnabled');
  }),

  _initializeBits(channelModel) {
    let bitsRoomSvc = this.get('bitsRoom');

    bitsRoomSvc.setupService({
      channelId: channelModel._id,
      channelName: channelModel.id,
      channelDisplayName: channelModel.display_name,

      onSuccessfulCheer: () => {
        this.set('room.messageToSend', '');
        this.set('room.savedInput', '');
      },
      onFailedCheer: () => {
        this.set('room.savedInput', this.get('room.messageToSend'));
      },
      onAuthError: () => {
        this.sendRouteAction('handleNotLoggedIn', {
          mpSourceAction: 'chat-bits',
          params: { sudo_reason: 'bits' }
        });
      },
      onTooltipShow: () => {
        if (this.get('showEmoticonSelector')) {
          this.set('showEmoticonSelector', false);
        }
      },
      onAnimatedEmoteSelection: (animatedEmote) => {
        let currentMessage = this.get('room.messageToSend');
        let addSpace = currentMessage.length > 0 && currentMessage[currentMessage.length - 1] !== ' ';
        let appendedMessage = addSpace ? `${currentMessage} ${animatedEmote} ` : `${currentMessage}${animatedEmote} `;

        this.set('room.messageToSend', appendedMessage);

        // move focus back to chat textarea
        $('.js-chat-container .chat_text_input').focus();
      }
    });

    if (this.get('bitsRoom.isEnabled')) {
      this.addObserver('room.messageToSend', this, () => {
        bitsRoomSvc.updateMessageToSend(this.get('room.messageToSend') || '');
      });

      this.addObserver('showEmoticonSelector', this, () => {
        if (this.get('showEmoticonSelector')) {
          if (this.get('isCheerMessage')) {
            this.set('showEmoticonSelector', false);
          } else if (this.get('isBitsTooltipShown')) {
            bitsRoomSvc.dismissTooltip();
          }
        }
      });

      // TODO temporary support for BTTV
      this.get('bitsRenderingConfig').loadRenderConfig(channelModel.id);
    }
  },

  init() {
    this._super(...arguments);
    this._scrollToBottomRequested = false;
    this._chatMessagesTop = 0;
    this.set('visibleInteractions', {});

    this.get('tmi.tmiSession').then(session => {
      if (this.isDestroyed) { return; }

      this.set('tmiSession', session);
    });

    let room = this.get('room');
    if (room) {
      this.set('roomTrackingData', room.getTrackingData());
      /* Retrieve channel-specific badges */
      let channel = room.get('channel');
      if (channel) {
        channel.load().then(currentChannelModel => {
          if (this.isDestroyed) { return; }

          this.get('chatSticky').updateStickyWithRoom({
            name: this.get('room.id'),
            id: currentChannelModel._id
          });

          this.get('badges').updateChannelBadges(currentChannelModel._id, room.get('id'));
          this._initializeBits(currentChannelModel);
        });
      }
    } else {
      this.set('roomTrackingData', {}); // Sometimes the `room` gets set to undefined (see `blurRoom`)
    }
  },

  didInsertElement() {
    this._super(...arguments);
    this._scrollEvents = `scroll.${this.elementId} mousedown.${this.elementId} wheel.${this.elementId} DOMMouseScroll.${this.elementId} mousewheel.${this.elementId} keyup.${this.elementId}`;
    this._$chatMessagesScroller = this.$('.js-chat-messages .tse-scroll-content');
    this._initScroller();
    this._prepareOnChatMessageInsert();
    this._prepareStickyBottom();
    this._recordChatMessagesPosition();
  },

  willDestroyElement() {
    this._super(...arguments);
    if (this.$('.js-chat-messages').data('plugin_TrackpadScrollEmulator')) {
      this.$('.js-chat-messages').TrackpadScrollEmulator('destroy');
    }
    if (this._$chatMessagesScroller) {
      this._$chatMessagesScroller.off(this._scrollEvents);
    }

    this.get('bitsRoom').reset();
    this.get('bitsPinnedCheers').reset();
  },

  interactionsExist: computed.or(
    'room.roomProperties.sce_title_preset_text_1',
    'room.roomProperties.sce_title_preset_text_2',
    'room.roomProperties.sce_title_preset_text_3',
    'room.roomProperties.sce_title_preset_text_4',
    'room.roomProperties.sce_title_preset_text_5'
  ),

  updateNewInteractionsExist: observer(
    'room.roomProperties.sce_title_preset_text_1',
    'room.roomProperties.sce_title_preset_text_2',
    'room.roomProperties.sce_title_preset_text_3',
    'room.roomProperties.sce_title_preset_text_4',
    'room.roomProperties.sce_title_preset_text_5',
    function () {
      this.set('newInteractionsExist', true);
    }
  ),

  clearInteractions: observer('room.id', function () {
    this.set('showInteractions', false);
    this.set('newInteractionsExist', false);
  }),

  _isAlreadyBilingual: observer('room.restrictedChatLanguage', function () {
    // Track all instances of bilingual users bypassing BLC
    let shortLangCode = this._getShortLangCode(this.get('room.restrictedChatLanguage'));
    if (Twitch.storage.get(`is_bilingual_in_${shortLangCode}`)) {
      this.trackBilingualClick();
    }
  }),

  _restrictedLanguageChanged: observer('room.restrictedChatLanguage', function () {
    this.set('showBLCNotification', this._isBlockedByBroadcasterLanguageChat());
    this.set('showDisplayNameNotification', this._shouldshowDisplayNameNotification());
    if (this.get('showDisplayNameNotification')) {
      this.get('tracking').trackEvent({
        event: 'displayname_notification',
        data: {
          login: this.get('session.userData.login')
        }
      });
    }
  }),

  _roomIDChanged: observer('room.id', function () {
    this.set('showBLCNotification', this._isBlockedByBroadcasterLanguageChat());
  }),

  trackChatModeration(command, options) {
    this.get('tracking').trackEvent({
      services: ['spade'],
      event: 'client_chat_moderation',
      data: assign({
        command,
        msg_id: options.msg_id,
        target_user: options.user,
        source: options.tracking_source
      }, this.get('roomTrackingData'))
    });
  },

  trackBilingualClick() {
    let {isAuthenticated, userData} = this.get('session');
    if (isAuthenticated) {
      this.get('tracking').trackEvent({
        event: 'bilingual_click',
        data: {
          player: 'site',
          login: userData.login,
          device_id: Twitch.idsForMixpanel.getOrCreateUniqueId(),
          channel: this.get('room.channel.id'),
          game: this.get('room.channel.game'),
          language: Twitch.receivedLanguage,
          preferred_language: Twitch.preferredLanguage,
          BLC_language: this.get('room.restrictedChatLanguage')
        }
      });
    }
  },

  isChatCommandForUsername(command) {
    if (command[0] !== '/') {
      return false;
    }

    return CHAT_COMMANDS_FOR_USERNAMES.includes(command.substring(1)); // remove the `/`
  },

  actions: {
    sendMessage() {
      let messageToSend = this.get('room.messageToSend');
      let username;

      if (!this.get('session.isAuthenticated')) {
        this.sendRouteAction('handleNotLoggedIn', {mpSourceAction: 'chat'});
        this.set('room.savedInput', messageToSend);
        return;
      }

      let messageParts = messageToSend.split(' ');
      let command = messageParts[0];
      if (this.isChatCommandForUsername(command)) {
        username = messageParts[1];
        if (username[0] === '@') {
          username = username.substr(1);
        }
        if (!isAscii(username)) { // if username is a CJK displayname.
          let messages = this.get('room.messages');
          if (Array.isArray(messages)) {
            messages.find((message) => { // we use `find` here to short-circuit the search after the first message is found
              if (message.tags && message.tags['display-name'] === username) {
                messageParts[1] = message.from;
                username = message.from;
                messageToSend = messageParts.join(' '); // Preserve all parts of the message
                return true;
              }
              return false;
            });
          }
          this.set('showEmoticonSelector', false);
        }
        if (command === '/timeout') {
          this.trackChatModeration('timeout', {'user' : username, 'tracking_source' : 'chat_command'});
        } else if (command === '/ban') {
          this.trackChatModeration('ban', {'user' : username, 'tracking_source' : 'chat_command'});
        } else if (command === '/unban') {
          this.trackChatModeration('unban', {'user' : username, 'tracking_source' : 'chat_command'});
        }
        if (command !== '/ignore') {
          this.get('room').send(messageToSend);
        } else {
          this.set('userToIgnore', username);
          this.set('room.messageToSend', '');
          this.sendRouteAction('openInModal', 'shared/ignore-form-modal', this);
        }
      } else {
        if (command === '/unpin' || command === '/unpinrecent') {
          let userId = this.get('session.userData.id');
          if (this.get('bitsPinnedCheers').canDismissPinnedCheer(userId, this.get('room.isModeratorOrHigher'), 'recent')) {
            this.get('bitsPinnedCheers').dismissMessage(userId, 'recent');
          }
          this.set('room.messageToSend', '');
          return;
        } else if (command === '/reset') {
          let userId = this.get('session.userData.id');
          if (this.get('bitsPinnedCheers').canDismissPinnedCheer(userId, this.get('room.isModeratorOrHigher'), 'top')) {
            this.get('bitsPinnedCheers').dismissMessage(userId, 'top');
          }
          this.set('room.messageToSend', '');
          return;
        }

        if (!this.get('room.isWhisperMessage') && this._isBlockedByBroadcasterLanguageChat()) {
          this.set('showBLCNotification', true);
          return;
        }
        if (this.get('isBitsEnabled') && this.get('isCheerMessage')) {
          this.get('bitsRoom').sendMessage(messageToSend);
        } else {
          this.get('room').send(messageToSend);
        }
        this.set('showEmoticonSelector', false);
      }
    },

    toggleEmoticonSelector() {
      this.toggleProperty('showEmoticonSelector');
      this.get('tracking').trackEvent({
        event: this.get('showEmoticonSelector') ? 'emoticon-selector-open' : 'emoticon-selector-close',
        services: ['mixpanel'],
        data: { channel: this.get('model.id')}
      });
    },

    scrollToBottom() {
      this._scrollToBottom();
    },

    accommodatePinnedMessage(height) {
      let currentTop = this.$('.js-chat-messages').position().top;
      let desiredTop = this._chatMessagesTop + height;

      if (currentTop !== desiredTop) {
        this.$('.js-chat-messages').css('top', desiredTop);
        this._scrollToBottom();
      }
    },

    toggleViewerList() {
      this.toggleProperty('showViewerList');
      if (this.get('showViewerList')) {
        this.get('room.viewers').load();
      }
    },

    banUser(options) {
      this.get('room.tmiRoom').banUser(options.user);
      this.trackChatModeration('ban', options);
    },

    unbanUser(options) {
      this.get('room.tmiRoom').unbanUser(options.user);
      this.trackChatModeration('unban', options);
    },

    modUser(options) {
      this.get('room.tmiRoom').modUser(options.user);
    },

    timeoutUser(options) {
      this.get('room.tmiRoom').timeoutUser(options.user);
      this.trackChatModeration('timeout', options);
    },

    ignoreUser(options) {
      this.set('userToIgnore', options.user);
      this.sendRouteAction('openInModal', 'shared/ignore-form-modal', this);
    },

    ignoreUserWithReason(reason) {
      this.get('room.tmiRoom').ignoreUser(this.get('userToIgnore'), reason);
      this.sendRouteAction('closeModal');
      this.set('userToIgnore', '');
    },

    unignoreUser(options) {
      this.get('room.tmiRoom').unignoreUser(options.user);
    },

    showModOverlay(options) {
      if (options.sender.toLowerCase() === TWITCH_BOT_USERNAME) {
        return;
      }
      let user = ChannelModel.find({id: options.sender});
      user.load();
      this.set('showModerationCard', true);
      this.set('moderationCardInfo', {
        user,
        renderTop: options.top,
        renderLeft: options.left,
        isIgnored: this.get('tmiSession').isIgnored(options.sender),
        isChannelOwner: this.get('session.userData.login') === options.sender,
        channelHref: Twitch.uri.channel(options.sender),
        isModeratorOrHigher: this.get('room.isModeratorOrHigher')
      });
    },

    closeModerationCard() {
      this.set('showModerationCard', false);
    },

    showResetTopCheer(options) {
      this.set('showResetTopCheerConfirmation', true);
      this.set('resetTopCheerConfirmationInfo', {
        renderLeft: options.left,
        renderTop: options.top,
        username: options.from,
        cheerAmount: options.amount,
        channelName: this.get('room.id')
      });
    },

    hideResetTopCheer() {
      this.set('showResetTopCheerConfirmation', false);
    },

    moderationCardTracking(name) {
      if (name === 'chat_card_open') {
        let room = this.get('room.tmiRoom');
        wrap(() => room.list()).then((response) => {
          let chatterCount = response && response.data && response.data.chatter_count;
          if (chatterCount || chatterCount === 0) {
            return chatterCount;
          }
        }, () => {/* swallow errors */}).then((count) => {
          this.get('tracking').trackEvent({
            event: 'chat_card_open',
            data: { 'num_chatters': count }
          });
        });
      }
    },

    messageUser(login) {
      this.get('conversations').openConversation(login);
      this.set('showModerationCard', false);
    },

    acceptChatRules() {
      this.set('room.shouldDisplayChatRules', false);
      this.get('tracking').trackEvent({
        event: 'accept_chat_rules',
        services: ['spade'],
        data: assign({
          rules_type : this.get('inChatRulesExperiment')
        }, this.get('roomTrackingData'))
      });
    },

    clickSubscriber() {
      window.open(`/${this.get('room.id')}/subscribe?ref=in_chat_subscriber_link`, '_blank');
    },

    focusChatInputWindow() {
      this.get('room').showChatRules();
    },

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

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

    // Notification Actions
    closeHostingNotification() {
      this.set('canShowHostingNotification', false);
    },
    closeResubNotification() {
      this.set('canShowResubNotification', false);
    },

    postResubMessage(tokenId, message) {
      let availableTokens = this.get('availableChatNotificationTokens');

      if (!availableTokens || availableTokens.length === 0) { return; }

      let url = `/api/channels/${this.get('room.id')}/use_chat_notification_token`;
      let data = { token_id: tokenId, custom_message: message }; // eslint-disable-line camelcase

      return this.get('api').request('post', url, data).then(() => {
        if (this.isDestroyed) { return; }

        this.set('availableChatNotificationTokens', availableTokens.filter(token => token.token_id !== tokenId));
      });
    },

    displaynameNotificationClose() {
      this.set('showDisplayNameNotification', false);
      Twitch.storage.set('displayname_closed', true);
      this.get('tracking').trackEvent({
        event: 'displayname_close',
        data: {
          login: this.get('session.userData.login'),
          user_language: Twitch.receivedLanguage
        }
      });
    },

    arNotificationClose() {
      this.get('tracking').trackEvent({
        event: 'close_alt_role_sticky',
        data: {
          channel: this.get('room.id'),
          login: this.get('session.userData.login'),
          url: window.location.href,
          browser: this.get('globals.browserName')
        }
      });
      this.set('showARNotification', false);
    },

    blcNotificationClose() {
      this.set('showBLCNotification', false);
    },

    bilingualClick() {
      this.trackBilingualClick();
      let shortLangCode = this._getShortLangCode(this.get('room.restrictedChatLanguage'));
      Twitch.storage.set(`is_bilingual_in_${shortLangCode}`, true);
      this.set('showBLCNotification', false);
    },

    nbNotificationClose() {
      this.get('tracking').trackEvent({
        event: 'close_nb_sticky',
        data: {
          channel: this.get('room.id'),
          login: this.get('session.userData.login'),
          user_language: Twitch.receivedLanguage,
          url: window.location.href,
          browser: this.get('globals.browserName')
        }
      });
      this.set('showNBNotification', false);
    },

    vpNotificationClose() {
      this.get('tracking').trackEvent({
        event: 'close_viewer_party_sticky',
        data: {
          channel: this.get('room.id'),
          login: this.get('session.userData.login'),
          url: window.location.href,
          browser: this.get('globals.browserName')
        }
      });
      this.set('showVPNotification', false);
    },
    // End Notification Actions

    interact(button) {
      if (!this.get('session.isAuthenticated')) {
        this.sendRouteAction('handleNotLoggedIn', { mpSourceAction: 'chat' });
        return;
      }

      let action = this.get(`visibleInteractions.${button}`);

      this.get('room').send(`:act ${action}`, { mpCommandClicked: true });
      this.set('showInteractions', false);
    },

    toggleInteractions() {
      if (!this.get('showInteractions')) {
        this.set('newInteractionsExist', false);
        this.set('visibleInteractions', {
          sce_title_preset_text_1: this.get('room.roomProperties.sce_title_preset_text_1'),
          sce_title_preset_text_2: this.get('room.roomProperties.sce_title_preset_text_2'),
          sce_title_preset_text_3: this.get('room.roomProperties.sce_title_preset_text_3'),
          sce_title_preset_text_4: this.get('room.roomProperties.sce_title_preset_text_4'),
          sce_title_preset_text_5: this.get('room.roomProperties.sce_title_preset_text_5')
        });
      }

      this.toggleProperty('showInteractions');
    },

    twitchBotModNotificationClose(dismissalType) {
      this.set('showTwitchBotModNotification', false);
      Twitch.storage.set('twitchbot_notification_closed', true);
      this.get('tracking').trackEvent({
        event: 'twitchbot_notification_closed',
        services: ['spade'],
        data: assign({
          dismissal_type : dismissalType
        }, this.get('roomTrackingData'))
      });
      this.sendRouteAction('closeModal');
    },

    twitchBotBroadcasterNotificationClose(dismissalType) {
      this.set('showTwitchBotBroadcasterNotification', false);
      Twitch.storage.set('twitchbot_notification_closed', true);
      this.get('tracking').trackEvent({
        event: 'twitchbot_notification_closed',
        services: ['spade'],
        data: assign({
          dismissal_type : dismissalType
        }, this.get('roomTrackingData'))
      });
      this.sendRouteAction('closeModal');
    }
  },

  _initScroller() {
    this.$('.js-chat-messages').TrackpadScrollEmulator({
      wrapContent: false,
      scrollbarHideStrategy: 'rightAndBottom'
    });
  },

  _scrollToBottom() {
    this._scrollToBottomRequested = true;
    run.schedule('afterRender', () => {
      if (this.isDestroying || this.isDestroyed || !this._scrollToBottomRequested) {
        return;
      }
      this._scrollToBottomRequested = false;

      let scroller = this._$chatMessagesScroller;
      if (scroller && scroller.length) {
        scroller.scrollTop(scroller[0].scrollHeight);
        this._setStuckToBottom(true);
        if (!this._hasTrackedScrollUp) {
          // We want to track when the user scrolls up to see more historical messages.
          // To do that, we're going to keep track of which messages were visible the
          // last time a message arrived while we were stuck to the bottom.
          this._updateHistoricalMessageVisibilityData();
        }
      }
    });
  },

  _setStuckToBottom(stuckToBottom) {
    this.set('stuckToBottom', stuckToBottom);
    let room = this.get('room');
    if (room) {
      room.messageBufferSize = stuckToBottom ? 150 : 300;
    }
  },

  _prepareStickyBottom() {
    let STUCK_TO_BOTTOM_THRESHOLD = 10;
    this._setStuckToBottom(true);
    /** Differentiating user scroll vs JavaScript scroll: http://stackoverflow.com/questions/2834667 */
    this._$chatMessagesScroller.on(this._scrollEvents, (e) => {
      let scroller = this._$chatMessagesScroller;
      if (scroller && scroller[0] && (e.which > 0 || e.type === 'mousedown' || e.type === 'mousewheel')) {
        let scrollDistanceToBottom = scroller[0].scrollHeight - scroller[0].scrollTop - scroller[0].offsetHeight;
        run(() => {
          this._setStuckToBottom(scrollDistanceToBottom <= STUCK_TO_BOTTOM_THRESHOLD);
          this._trackHistoryScroll();
        });
      }
    });
  },

  _recordChatMessagesPosition() {
    this._chatMessagesTop = this.$('.js-chat-messages').position().top;
  },

  _prepareOnChatMessageInsert() {
    this.addObserver('room.messages', () => {
      if (!this.get('stuckToBottom')) {
        return;
      }

      this._scrollToBottom();
    });
  },

  _updateHistoricalMessageVisibilityData() {
    let messages = document.querySelectorAll(`#${this.elementId} .historical-message`);
    if (messages.length === 0) {
      return;
    }

    /* Build queues of messages to update so we don't touch DOM while
     * doing measurement */
    let hiddenMessages = [];
    let visibleMessages = [];

    /* Pre-calculate some things we will need for each message */
    let pageYOffset = window.pageYOffset;
    let clientTop = document.documentElement.clientTop;

    /* Loop the messages checking visibility */
    for (let i=0; i<messages.length; i++) {
      let el = messages[i];
      if (checkIsMessageVisible(pageYOffset, clientTop, el)) {
        hiddenMessages.push(el);
      } else {
        visibleMessages.push(el);
      }
    }

    /* Set hidden data. Use setAttribute since `dataSet` is not available
     * on IE10 */
    if (hiddenMessages.length) {
      for (let i=0; i<hiddenMessages.length; i++) {
        hiddenMessages[i].setAttribute('data-was-hidden', 'true');
      }
    }

    /* Set visible data. */
    if (visibleMessages.length) {
      for (let i=0; i<visibleMessages.length; i++) {
        visibleMessages[i].removeAttribute('data-was-hidden');
      }
    }
  },

  _trackHistoryScroll() {
    if (this._hasTrackedScrollUp) {
      return;
    }

    /* Pre-calculate some things we will need for each message */
    let pageYOffset = window.pageYOffset;
    let clientTop = document.documentElement.clientTop;

    let messages = this.$('.historical-message[data-was-hidden=true]');
    if (messages.length === 0) {
      return;
    }

    /*
     * Iterate over messages that were hidden. For each check to see if it
     * is now visible. If it is, then track an event and exit the loop.
     */
    messages.each((i, e) => {
      if (checkIsMessageVisible(pageYOffset, clientTop, e)) {
        // We've scrolled a previously-hidden historical message into view, let's track it
        this.get('tracking').trackEvent({
          event: 'scroll_up_to_history',
          data: {
            channel:      this.get('room.id'),
            device_id:    Twitch.idsForMixpanel.getOrCreateUniqueId(),
            login:        this.get('session.userData.login'),
            logged_in:    this.get('session.isAuthenticated'),
            time:         new Date().getTime(),
            num_messages: this.get('room.messages.length')
          }
        });
        this._hasTrackedScrollUp = true;

        return false; // Breaks out of the 'each' loop
      }
    });
  },

  _shouldshowDisplayNameNotification() {
    let whitelistedLanguages = ['ja', 'ko', 'zh', 'zh-cn', 'zh-tw', 'zh-hk'];

    return this.get('session.isAuthenticated') &&
      isAscii(this.get('session.userData.name')) &&
      !Twitch.storage.get('displayname_closed') &&
      whitelistedLanguages.includes(Twitch.receivedLanguage);
  },

  _isBlockedByBroadcasterLanguageChat() {
    let restrictedChatLanguage = this.get('room.restrictedChatLanguage');
    let restrictedChatLang = this._getShortLangCode(restrictedChatLanguage);

    if (!this.get('isBroadcasterLanguageChatEnabled') || !restrictedChatLang) {
      return false;
    }

    let isLoggedIn = Twitch.user.isLoggedIn();
    let isGroupRoom = this.get('room.isGroupRoom');
    let userLanguage = this._getShortLangCode(Twitch.receivedLanguage);
    let isSameLanguage = userLanguage === restrictedChatLang;
    let isModeratorOrHigher = this.get('room.isModeratorOrHigher');

    if (!isLoggedIn || isGroupRoom || isSameLanguage || isModeratorOrHigher) {
      return false;
    }

    return !Twitch.storage.get(`is_bilingual_in_${restrictedChatLang}`);
  },

  _showBroadcasterLanguageNotification() {
    this.set('showBLCNotification', true);
  },

  _getShortLangCode(langCode) {
    return langCode ? langCode.substr(0, 2) : '';
  }
});

function checkIsMessageVisible(pageYOffset, clientTop, el) {
  let rect = el.getBoundingClientRect();
  return !!((rect.top - pageYOffset - clientTop) < -(el.offsetHeight));
}
