// site-chat.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0

'use strict';

const DTP_COMPONENT = { name: 'Site Chat', slug: 'site-chat' };
const dtp = window.dtp = window.dtp || { }; // jshint ignore:line

const EMOJI_EXPLOSION_DURATION = 8000;
const EMOJI_EXPLOSION_INTERVAL = 100;

import DtpLog from 'dtp/dtp-log.js';
import UIkit from 'uikit';
import SiteReactions from './site-reactions.js';
import * as picmo from 'picmo';

export default class SiteChat {

  constructor (app) {
    this.app = app;
    this.log = new DtpLog(DTP_COMPONENT);

    this.ui = {
      menu: document.querySelector('#chat-room-menu'),
      form: document.querySelector('#chat-input-form'),
      messageList: document.querySelector('#chat-message-list'),
      messages: [ ],
      messageMenu: document.querySelector('.chat-message-menu'),
      input: document.querySelector('#chat-input-text'),
      emojiPicker: document.querySelector('#site-emoji-picker'),
      isAtBottom: true,
      isModifying: false,
    };

    if (this.ui.messageList) {
      this.ui.messageList.addEventListener('scroll', this.onChatMessageListScroll.bind(this));
      this.updateTimestamps();
      setTimeout(( ) => {
        this.log.info('constructor', 'scrolling chat', { top: this.ui.messageList.scrollHeight });
        this.ui.messageList.scrollTo({ top: this.ui.messageList.scrollHeight, behavior: 'instant' });
      }, 100);
      this.ui.reactions = new SiteReactions();
      this.lastReaction = new Date();
    }

    if (this.ui.input) {
      this.ui.input.addEventListener('keydown', this.onChatInputKeyDown.bind(this));
    }

    if (this.ui.emojiPicker) {
      this.ui.picmo = picmo.createPicker({
        rootElement: this.ui.emojiPicker,
        theme: picmo.darkTheme,
      });
      this.ui.picmo.addEventListener('emoji:select', this.onEmojiSelected.bind(this));
    }

    this.lastReaction = new Date();

    if (window.localStorage) {
      this.mutedUsers = window.localStorage.mutedUsers ? JSON.parse(window.localStorage.mutedUsers) : [ ];
      this.filterChatView();
    }
  }

  async filterChatView ( ) {
    this.mutedUsers.forEach((block) => {
      document.querySelectorAll(`.chat-message[data-author-id="${block.userId}"]`).forEach((message) => {
        message.parentElement.removeChild(message);
      });
    });
  }

  async toggleChatInput (event) {
    event.preventDefault();
    event.stopPropagation();

    this.ui.input.toggleAttribute('hidden');
    if (this.ui.input.getAttribute('hidden')) {
      this.ui.input.focus();
    }

    return true;
  }

  async openChatInput ( ) {
    if (this.ui.input.hasAttribute('hidden')) {
      this.ui.input.removeAttribute('hidden');
    }
    return true;
  }

  async onChatInputKeyDown (event) {
    if (event.key === 'Enter' && !event.shiftKey) {
      return this.sendUserChat(event);
    }
  }

  async onChatMessageListScroll (/* event */) {
    const prevBottom = this.ui.isAtBottom;
    const scrollPos = this.ui.messageList.scrollTop + this.ui.messageList.clientHeight;

    this.ui.isAtBottom = scrollPos >= (this.ui.messageList.scrollHeight - 8);
    if (this.ui.isAtBottom !== prevBottom) {
      this.log.info('onChatMessageListScroll', 'at-bottom status change', { atBottom: this.ui.isAtBottom });
      if (this.ui.isAtBottom) {
        this.ui.messageMenu.classList.remove('chat-menu-visible');
      } else {
        this.ui.messageMenu.classList.add('chat-menu-visible');
      }
    }
  }

  async resumeChatScroll ( ) {
    this.ui.messageList.scrollTop = this.ui.messageList.scrollHeight;
  }

  async sendUserChat (event) {
    event.preventDefault();

    if (!dtp.room || !dtp.room._id) {
      UIkit.modal.alert('There is a problem with Chat. Please refresh the page.');
      return;
    }

    const roomId = dtp.room._id;
    const content = this.ui.input.value;
    this.ui.input.value = '';

    if (content.length === 0) {
      return true;
    }

    this.log.info('sendUserChat', 'sending chat message', { roomId, content });
    this.app.socket.emit('user-chat', {
      channelType: 'ChatRoom',
      channel: roomId,
      content,
    });

    // set focus back to chat input
    this.ui.input.focus();

    return true;
  }

  async sendReaction (event) {
    const NOW = new Date();
    if (NOW - this.lastReaction < 1000) {
      return;
    }
    this.lastReaction = NOW;

    const target = event.currentTarget || event.target;
    if (!target) {
      return;
    }

    const reaction = target.getAttribute('data-reaction');
    this.log.info('sendReaction', 'sending user reaction', { reaction });
    this.app.socket.emit('user-react', {
      subjectType: 'ChatRoom',
      subject: dtp.room._id,
      reaction,
    });
  }

  async appendUserChat (message) {
    const isAtBottom = this.ui.isAtBottom;
    if (this.mutedUsers.find((block) => block.userId === message.user._id)) {
      this.log.info('appendUserChat', 'message is from blocked user', {
        _id: message.user._id,
        username: message.user.username,
      });
      return; // sender is blocked by local user on this device
    }

    const fragment = document.createDocumentFragment();
    fragment.innerHTML = message.html;

    this.ui.isModifying = true;
    this.ui.messageList.insertAdjacentHTML('beforeend', message.html);
    this.trimMessages();
    this.updateTimestamps();

    if (isAtBottom) {
      /*
       * This is jank. I don't know why I had to add this jank, but it is jank.
       * The browser started emitting a scroll event *after* I issue this scroll
       * command to return to the bottom of the view. So, I have to issue the
       * scroll, let it fuck up, and issue the scroll again. I don't care why.
       */
      this.ui.messageList.scrollTo(0, this.ui.messageList.scrollHeight);
      setTimeout(( ) => {
        this.ui.isAtBottom = true;
        this.ui.messageList.scrollTo(0, this.ui.messageList.scrollHeight);
        this.ui.isModifying = false;
      }, 25);
    }
  }

  async appendSystemMessage (message) {
    this.log.debug('appendSystemMessage', 'received system message', { message });

    const systemMessage = document.createElement('div');
    systemMessage.setAttribute('data-message-type', message.type);
    systemMessage.classList.add('uk-margin-small');
    systemMessage.classList.add('chat-message');
    systemMessage.classList.add('system-message');

    const chatContent = document.createElement('div');
    chatContent.classList.add('chat-content');
    chatContent.classList.add('uk-text-break');
    chatContent.innerHTML = message.content;
    systemMessage.appendChild(chatContent);

    const chatTimestamp = document.createElement('div');
    chatTimestamp.classList.add('chat-timestamp');
    chatTimestamp.classList.add('uk-text-small');
    chatTimestamp.innerHTML = moment(message.created).format('hh:mm:ss a');
    systemMessage.appendChild(chatTimestamp);

    this.ui.messageList.appendChild(systemMessage);
    this.trimMessages();
    this.updateTimestamps();

    if (this.ui.isAtBottom) {
      this.ui.messageList.scrollTo(0, this.ui.messageList.scrollHeight);
    }
  }

  trimMessages ( ) {
    while (this.ui.messageList.childNodes.length > 50) {
      this.ui.messageList.removeChild(this.ui.messageList.childNodes.item(0));
    }
  }

  updateTimestamps ( ) {
    const timestamps = document.querySelectorAll('[data-dtp-timestamp]');
    timestamps.forEach((timestamp) => {
      const created = timestamp.getAttribute('data-dtp-timestamp');
      const format = timestamp.getAttribute('data-dtp-time-format');
      timestamp.textContent = moment(created).format(format || 'MMM DD, YYYY, [at] hh:mm:ss a');
    });
  }

  createEmojiReact (message) {
    this.ui.reactions.create(message.reaction);
  }

  triggerEmojiExplosion ( ) {
    const reactions = ['happy', 'angry', 'honk', 'clap', 'fire', 'laugh'];
    const stopHandler = this.stopEmojiExplosion.bind(this);

    if (this.emojiExplosionTimeout && this.emojiExplosionInterval) {
      clearTimeout(this.emojiExplosionTimeout);
      this.emojiExplosionTimeout = setTimeout(stopHandler, EMOJI_EXPLOSION_DURATION);
      return;
    }

    // spawn 10 emoji reacts per second until told to stop
    this.emojiExplosionInterval = setInterval(( ) => {
      // choose a random reaction from the list of available reactions and
      // spawn it.
      const reaction = reactions[Math.floor(Math.random() * reactions.length)];
      this.ui.reactions.create({ reaction });
    }, EMOJI_EXPLOSION_INTERVAL);

    // set a timeout to stop the explosion
    this.emojiExplosionTimeout = setTimeout(stopHandler, EMOJI_EXPLOSION_DURATION);
  }

  stopEmojiExplosion ( ) {
    if (!this.emojiExplosionTimeout || !this.emojiExplosionInterval) {
      return;
    }

    clearTimeout(this.emojiExplosionTimeout);
    delete this.emojiExplosionTimeout;

    clearInterval(this.emojiExplosionInterval);
    delete this.emojiExplosionInterval;
  }

  async showForm (event, roomId, formName) {
    try {
      UIkit.dropdown(this.ui.menu).hide(false);
      await this.app.showForm(event, `/chat/room/${roomId}/form/${formName}`);
    } catch (error) {
      UIkit.modal.alert(`Failed to display form: ${error.message}`);
    }
  }

  async toggleEmojiPicker (/* event */) {
    this.ui.emojiPicker.classList.toggle('picker-open');
  }

  async onEmojiSelected (event) {
    this.ui.emojiPicker.classList.remove('picker-open');
    return this.insertContentAtCursor(event.emoji);
  }

  async insertContentAtCursor (content) {
    this.ui.input.focus();

    if (document.selection) {
      let sel = document.selection.createRange();
      sel.text = content;
    } else if (this.ui.input.selectionStart || (this.ui.input.selectionStart === 0)) {
      let startPos = this.ui.input.selectionStart;
      let endPos = this.ui.input.selectionEnd;

      let oldLength = this.ui.input.value.length;
      this.ui.input.value =
        this.ui.input.value.substring(0, startPos) +
        content +
        this.ui.input.value.substring(endPos, this.ui.input.value.length);

      this.ui.input.selectionStart = startPos + (this.ui.input.value.length - oldLength);
      this.ui.input.selectionEnd = this.ui.input.selectionStart;
    } else {
      this.ui.input.value += content;
    }
  }

  async deleteInvite (event) {
    const target = event.currentTarget || event.target;
    const roomId = target.getAttribute('data-room-id');
    const inviteId = target.getAttribute('data-invite-id');
    try {
      const response = await fetch(`/chat/room/${roomId}/invite/${inviteId}`, { method: 'DELETE' });
      await this.app.processResponse(response);
    } catch (error) {
      console.log('delete canceled', error);
      return;
    }
  }

  async deleteChatRoom (event) {
    const target = event.currentTarget || event.target;
    const roomId = target.getAttribute('data-room-id');
    try {
      const response = await fetch(`/chat/room/${roomId}`, { method: 'DELETE' });
      await this.app.processResponse(response);
    } catch (error) {
      console.log('delete canceled', error);
      return;
    }
  }
}