<template>
  <b-modal
    id="inbox-modal"
    title
    :hide-footer="true"
    size="lg"
    @shown="onModalShown"
    @hide="onModalHide"
  >
    <div
      slot="modal-header"
      class="header-wrap container align-items-center"
    >
      <div class="row align-items-center">
        <div class="col-4">
          <div class="row align-items-center">
            <div class="col-2">
              <div
                class="svg-icon envelope"
                v-html="icons.messageIcon"
              ></div>
            </div>
            <div class="col-6">
              <h2
                v-once
                class="text-center"
              >
                {{ $t('messages') }}
              </h2>
            </div>
          </div>
        </div>
        <div class="col-4 offset-3">
          <toggle-switch
            class="float-right"
            :label="optTextSet.switchDescription"
            :checked="!user.inbox.optOut"
            :hover-text="optTextSet.popoverText"
            @change="toggleOpt()"
          />
        </div>
        <div class="col-1">
          <div class="close">
            <span
              class="svg-icon inline icon-10"
              aria-hidden="true"
              @click="close()"
              v-html="icons.svgClose"
            ></span>
          </div>
        </div>
      </div>
    </div>
    <div class="row">
      <div class="col-4 sidebar">
        <div class="search-section">
          <b-form-input
            v-model="search"
            :placeholder="$t('search')"
          />
        </div>
        <div
          v-if="filtersConversations.length === 0"
          class="empty-messages text-center"
        >
          <div
            class="svg-icon envelope"
            v-html="icons.messageIcon"
          ></div>
          <h4 v-once>
            {{ $t('emptyMessagesLine1') }}
          </h4>
          <p v-if="!user.flags.chatRevoked">
            {{ $t('emptyMessagesLine2') }}
          </p>
        </div>
        <div
          v-if="filtersConversations.length > 0"
          class="conversations"
        >
          <div
            v-for="conversation in filtersConversations"
            :key="conversation.key"
            class="conversation"
            :class="{active: selectedConversation.key === conversation.key}"
            @click="selectConversation(conversation.key)"
          >
            <div>
              <h3 :class="userLevelStyle(conversation)">
                {{ conversation.name }}
                <div
                  class="svg-icon"
                  v-html="tierIcon(conversation)"
                ></div>
              </h3>
            </div>
            <div class="time">
              <span
                v-if="conversation.username"
                class="mr-1"
              >@{{ conversation.username }} •</span>
              <span v-if="conversation.date">{{ conversation.date | timeAgo }}</span>
            </div>
            <div
              class="messagePreview"
            >
              {{ conversation.lastMessageText
                ? removeTags(parseMarkdown(conversation.lastMessageText)) : '' }}
            </div>
          </div>
        </div>
      </div>
      <div class="col-8 messages d-flex flex-column justify-content-between">
        <div
          v-if="!selectedConversation.key"
          class="empty-messages text-center"
        >
          <div
            class="svg-icon envelope"
            v-html="icons.messageIcon"
          ></div>
          <h4>{{ placeholderTexts.title }}</h4>
          <p v-html="placeholderTexts.description"></p>
        </div>
        <div
          v-if="selectedConversation && selectedConversationMessages.length === 0"
          class="empty-messages text-center"
        >
          <p>{{ $t('beginningOfConversation', {userName: selectedConversation.name}) }}</p>
        </div>
        <chat-messages
          v-if="selectedConversation && selectedConversationMessages.length > 0"
          ref="chatscroll"
          class="message-scroll"
          :chat="selectedConversationMessages"
          :inbox="true"
          :can-load-more="canLoadMore"
          :is-loading="messagesLoading"
          @message-removed="messageRemoved"
          @triggerLoad="infiniteScrollTrigger"
        />
        <div
          v-if="user.inbox.optOut && selectedConversation.key"
          class="pm-disabled-caption text-center"
        >
          <h4>{{ $t('PMDisabledCaptionTitle') }}</h4>
          <p>{{ $t('PMDisabledCaptionText') }}</p>
        </div>
        <div
          v-if="selectedConversation.key && !user.flags.chatRevoked"
          class="new-message-row"
        >
          <textarea
            v-model="newMessage"
            maxlength="3000"
            @keyup.ctrl.enter="sendPrivateMessage()"
          ></textarea>
          <button
            class="btn btn-secondary"
            @click="sendPrivateMessage()"
          >
            {{ $t('send') }}
          </button>
          <div class="row">
            <span class="ml-3">{{ currentLength }} / 3000</span>
          </div>
        </div>
      </div>
    </div>
  </b-modal>
</template>

<style lang="scss">
  #inbox-modal .modal-body {
    padding-top: 0px;
  }
</style>

<style lang="scss" scoped>
  @import '~@/assets/scss/colors.scss';

  .header-wrap {
    padding: 0.5em;

    h2 {
      margin: 0;
      line-height: 1;
    }
  }

  h3 {
    margin: 0rem;

    .svg-icon {
      width: 10px;
      display: inline-block;
      margin-left: .5em;
    }
  }

  .envelope {
    color: $gray-400 !important;
    margin: 0;
  }

  .sidebar {
    background-color: $gray-700;
    min-height: 540px;
    padding: 0;

    .search-section {
      padding: 1em;
      box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
    }
  }

  .messages {
    position: relative;
    padding-left: 0;
    padding-bottom: 6em;
    height: 540px;
  }

  .message-scroll {
    max-height: 500px;
    overflow-x: scroll;

    @media (min-width: 992px) {
      overflow-x: hidden;
      overflow-y: scroll;
    }
  }

  .to-form input {
    width: 60%;
    display: inline-block;
    margin-left: 1em;
  }

  .empty-messages {
    margin-top: 10em;
    color: $gray-400;
    padding: 1em;

    h4 {
      color: $gray-400;
      margin-top: 1em;
    }

    .envelope {
      width: 30px;
      margin: 0 auto;
    }
  }

  .pm-disabled-caption {

    padding-top: 1em;
    background-color: $gray-700;
    z-index: 2;

    h4, p {
      color: $gray-300;
    }

    h4 {
      margin-top: 0;
      margin-bottom: 0.4em;
    }

    p {
      font-size: 12px;
      margin-bottom: 0;
    }
  }

  .new-message-row {
    background-color: $gray-700;
    position: absolute;
    bottom: 0;
    height: 88px;
    width: 100%;
    padding: 1em;

    textarea {
      height: 80%;
      display: inline-block;
      vertical-align: bottom;
      width: 80%;
    }

    button {
      vertical-align: bottom;
      display: inline-block;
      box-shadow: none;
      margin-left: 1em;
    }
  }

  .conversations {
    max-height: 400px;
    overflow-x: hidden;
    overflow-y: scroll;
  }

  .conversation {
    padding: 1.5em;
    background: $white;
  }

  .conversation.active {
    border: 1px solid $purple-400;
  }

  .conversation:hover {
    cursor: pointer;
  }

  .time {
    font-size: 12px;
    color: $gray-200;
    margin-bottom: 0.5rem;
  }

  .messagePreview {
    display: block;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-word;
  }
</style>

<script>
import Vue from 'vue';
import moment from 'moment';
import filter from 'lodash/filter';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import habiticaMarkdown from 'habitica-markdown';
import axios from 'axios';
import { mapState } from '@/libs/store';
import styleHelper from '@/mixins/styleHelper';
import toggleSwitch from '@/components/ui/toggleSwitch';

import chatMessages from '../chat/chatMessages';
import messageIcon from '@/assets/svg/message.svg';
import svgClose from '@/assets/svg/close.svg';
import tier1 from '@/assets/svg/tier-1.svg';
import tier2 from '@/assets/svg/tier-2.svg';
import tier3 from '@/assets/svg/tier-3.svg';
import tier4 from '@/assets/svg/tier-4.svg';
import tier5 from '@/assets/svg/tier-5.svg';
import tier6 from '@/assets/svg/tier-6.svg';
import tier7 from '@/assets/svg/tier-7.svg';
import tier8 from '@/assets/svg/tier-mod.svg';
import tier9 from '@/assets/svg/tier-staff.svg';
import tierNPC from '@/assets/svg/tier-npc.svg';

export default {
  components: {
    chatMessages,
    toggleSwitch,
  },
  filters: {
    timeAgo (value) {
      return moment(new Date(value)).fromNow();
    },
  },
  mixins: [styleHelper],
  data () {
    return {
      icons: Object.freeze({
        messageIcon,
        svgClose,
        tier1,
        tier2,
        tier3,
        tier4,
        tier5,
        tier6,
        tier7,
        tier8,
        tier9,
        tierNPC,
      }),
      displayCreate: true,
      selectedConversation: {},
      search: '',
      newMessage: '',
      showPopover: false,
      messages: [],
      messagesByConversation: {}, // cache {uuid: []}
      loadedConversations: [],
      loaded: false,
      messagesLoading: false,
      initiatedConversation: null,
      updateConversionsCounter: 0,
    };
  },
  computed: {
    ...mapState({ user: 'user.data' }),
    canLoadMore () {
      return this.selectedConversation && this.selectedConversation.canLoadMore;
    },
    conversations () {
      const inboxGroup = groupBy(this.loadedConversations, 'uuid');

      // Add placeholder for new conversations
      if (this.initiatedConversation && this.initiatedConversation.uuid) {
        inboxGroup[this.initiatedConversation.uuid] = [{
          uuid: this.initiatedConversation.uuid,
          user: this.initiatedConversation.user,
          username: this.initiatedConversation.username,
          contributor: this.initiatedConversation.contributor,
          id: '',
          text: '',
          timestamp: new Date(),
        }];
      }
      // Create conversation objects
      const convos = [];
      for (const key of Object.keys(inboxGroup)) {
        const recentMessage = inboxGroup[key][0];

        const convoModel = {
          key: recentMessage.uuid,
          // Handles case where from user sent
          // the only message or the to user sent the only message
          name: recentMessage.user,
          username: !recentMessage.text ? recentMessage.username : recentMessage.toUserName,
          date: recentMessage.timestamp,
          lastMessageText: recentMessage.text,
          canLoadMore: true,
          page: 0,
        };

        convos.push(convoModel);
      }

      return convos;
    },
    // Separate from selectedConversation which
    // is not computed so messages don't update automatically
    selectedConversationMessages () {
      // Vue-subscribe to changes
      const subScribeToUpdate = this.messagesLoading || this.updateConversionsCounter > -1;

      const selectedConversationKey = this.selectedConversation.key;
      const selectedConversation = this.messagesByConversation[selectedConversationKey];
      this.messages = selectedConversation || []; // eslint-disable-line vue/no-side-effects-in-computed-properties, max-len

      const ordered = orderBy(this.messages, [m => m.timestamp], ['asc']);

      if (subScribeToUpdate) {
        return ordered;
      }

      return [];
    },
    filtersConversations () {
      // Vue-subscribe to changes
      const subScribeToUpdate = this.updateConversionsCounter > -1;

      const filtered = subScribeToUpdate && !this.search
        ? this.conversations
        : filter(
          this.conversations,
          conversation => conversation.name.toLowerCase().indexOf(this.search.toLowerCase()) !== -1,
        );

      const ordered = orderBy(filtered, [o => moment(o.date).toDate()], ['desc']);

      return ordered;
    },
    currentLength () {
      return this.newMessage.length;
    },
    placeholderTexts () {
      if (this.user.flags.chatRevoked) {
        return {
          title: this.$t('PMPlaceholderTitleRevoked'),
          description: this.$t('chatPrivilegesRevoked'),
        };
      }
      return {
        title: this.$t('PMPlaceholderTitle'),
        description: this.$t('PMPlaceholderDescription'),
      };
    },
    optTextSet () {
      if (!this.user.inbox.optOut) {
        return {
          switchDescription: this.$t('PMReceive'),
          popoverText: this.$t('PMEnabledOptPopoverText'),
        };
      }
      return {
        switchDescription: this.$t('PMReceive'),
        popoverText: this.$t('PMDisabledOptPopoverText'),
      };
    },
  },
  mounted () {
    this.$root.$on('habitica::new-inbox-message', data => {
      this.$root.$emit('bv::show::modal', 'inbox-modal');

      // Wait for messages to be loaded
      const unwatchLoaded = this.$watch('loaded', loaded => {
        if (!loaded) return;

        const conversation = this.conversations.find(convo => convo.key === data.userIdToMessage);
        if (loaded) setImmediate(() => unwatchLoaded());

        if (conversation) {
          this.selectConversation(data.userIdToMessage);
          return;
        }

        this.initiatedConversation = {
          uuid: data.userIdToMessage,
          user: data.displayName,
          username: data.username,
          backer: data.backer,
          contributor: data.contributor,
        };

        this.selectConversation(data.userIdToMessage);
      }, { immediate: true });
    });
  },
  destroyed () {
    this.$root.$off('habitica::new-inbox-message');
  },
  methods: {
    async onModalShown () {
      this.loaded = false;

      const conversationRes = await axios.get('/api/v4/inbox/conversations');
      this.loadedConversations = conversationRes.data.data;

      this.loaded = true;
    },
    onModalHide () {
      // reset everything
      this.loadedConversations = [];
      this.loaded = false;
      this.initiatedConversation = null;
      this.messagesByConversation = {};
      this.selectedConversation = {};
    },
    messageRemoved (message) {
      const messages = this.messagesByConversation[this.selectedConversation.key];

      const messageIndex = messages.findIndex(msg => msg.id === message.id);
      if (messageIndex !== -1) messages.splice(messageIndex, 1);
      if (this.selectedConversationMessages.length === 0) {
        this.initiatedConversation = {
          uuid: this.selectedConversation.key,
          user: this.selectedConversation.name,
          username: this.selectedConversation.username,
          backer: this.selectedConversation.backer,
          contributor: this.selectedConversation.contributor,
        };
      }
    },
    toggleClick () {
      this.displayCreate = !this.displayCreate;
    },
    toggleOpt () {
      this.$store.dispatch('user:togglePrivateMessagesOpt');
    },
    async selectConversation (key) {
      const convoFound = this.conversations.find(conversation => conversation.key === key);

      this.selectedConversation = convoFound || {};

      if (!this.messagesByConversation[this.selectedConversation.key]) {
        await this.loadMessages();
      }

      Vue.nextTick(() => {
        if (!this.$refs.chatscroll) return;
        const chatscroll = this.$refs.chatscroll.$el;
        chatscroll.scrollTop = chatscroll.scrollHeight;
      });
    },
    sendPrivateMessage () {
      if (!this.newMessage) return;

      const messages = this.messagesByConversation[this.selectedConversation.key];

      messages.push({
        sent: true,
        text: this.newMessage,
        timestamp: new Date(),
        toUser: this.selectedConversation.name,
        toUserName: this.selectedConversation.username,
        toUserContributor: this.selectedConversation.contributor,
        toUserBacker: this.selectedConversation.backer,
        toUUID: this.selectedConversation.uuid,

        id: '-1', // will be updated once the result is back
        likes: {},
        ownerId: this.user._id,
        uuid: this.user._id,
        fromUUID: this.user._id,
        user: this.user.profile.name,
        username: this.user.auth.local.username,
        contributor: this.user.contributor,
        backer: this.user.backer,
      });

      // Remove the placeholder message
      if (
        this.initiatedConversation
        && this.initiatedConversation.uuid === this.selectedConversation.key
      ) {
        this.loadedConversations.unshift(this.initiatedConversation);
        this.initiatedConversation = null;
      }

      this.selectedConversation.lastMessageText = this.newMessage;
      this.selectedConversation.date = new Date();

      Vue.nextTick(() => {
        if (!this.$refs.chatscroll) return;
        const chatscroll = this.$refs.chatscroll.$el;
        chatscroll.scrollTop = chatscroll.scrollHeight;
      });

      this.$store.dispatch('members:sendPrivateMessage', {
        toUserId: this.selectedConversation.key,
        message: this.newMessage,
      }).then(response => {
        const newMessage = response.data.data.message;
        const messageToReset = messages[messages.length - 1];
        messageToReset.id = newMessage.id; // just set the id, all other infos already set
        Object.assign(messages[messages.length - 1], messageToReset);
        this.updateConversionsCounter += 1;
      });

      this.newMessage = '';
    },
    close () {
      this.$root.$emit('bv::hide::modal', 'inbox-modal');
    },
    tierIcon (message) {
      const isNPC = Boolean(message.backer && message.backer.npc);
      if (isNPC) {
        return this.icons.tierNPC;
      }
      if (!message.contributor) return null;
      return this.icons[`tier${message.contributor.level}`];
    },
    removeTags (html) {
      const tmp = document.createElement('DIV');
      tmp.innerHTML = html;
      return tmp.textContent || tmp.innerText || '';
    },
    parseMarkdown (text) {
      if (!text) return null;
      return habiticaMarkdown.render(String(text));
    },
    infiniteScrollTrigger () {
      // show loading and wait until the loadMore debounced
      // or else it would trigger on every scrolling-pixel (while not loading)
      if (this.canLoadMore) {
        this.messagesLoading = true;
      }

      return this.loadMore();
    },
    loadMore () {
      this.selectedConversation.page += 1;
      return this.loadMessages();
    },
    async loadMessages () {
      this.messagesLoading = true;

      const requestUrl = `/api/v4/inbox/paged-messages?conversation=${this.selectedConversation.key}&page=${this.selectedConversation.page}`;
      const res = await axios.get(requestUrl);
      const loadedMessages = res.data.data;

      this.messagesByConversation[this.selectedConversation.key] = this.messagesByConversation[this.selectedConversation.key] || []; // eslint-disable-line max-len
      const loadedMessagesToAdd = loadedMessages
        .filter(m => this.messagesByConversation[this.selectedConversation.key].findIndex(mI => mI.id === m.id) === -1); // eslint-disable-line max-len
      this.messagesByConversation[this.selectedConversation.key].push(...loadedMessagesToAdd);

      // only show the load more Button if the max count was returned
      this.selectedConversation.canLoadMore = loadedMessages.length === 10;
      this.messagesLoading = false;
    },
  },
};
</script>
