import QuickReactions from "./QuickReactions.vue";
import * as linkify from 'linkifyjs';
import linkifyHtml from 'linkify-html';
import utils from "../../plugins/utils"
import util from "../../plugins/utils";
import Hammer from "hammerjs";

linkify.options.defaults.className = "link";
linkify.options.defaults.target = { url: "_blank" };

export default {
  components: {
    QuickReactions,
  },
  props: {
    room: {
      type: Object,
      default: function () {
        return null;
      },
    },
    originalEvent: {
      type: Object,
      default: function () {
        return {};
      },
    },
    nextEvent: {
      type: Object,
      default: function () {
        return null;
      },
    },
    timelineSet: {
      type: Object,
      default: function () {
        return null;
      },
    },
    componentFn: {
      type: Function,
      default: function () {
        return () => {};
      },
    },
  },
  data() {
    return {
      event: {},
      thread: null,
      utils,
      mc: null,
      mcCustom: null
    };
  },
  beforeDestroy() {
    this.thread = null;
  },
  watch: {
    originalEvent: {
      immediate: true,
      handler(originalEvent, ignoredOldValue) {
        this.event = originalEvent;
        // Check not null and not {}
        if (originalEvent && originalEvent.isBeingDecrypted && originalEvent.isBeingDecrypted()) {
          this.originalEvent.getDecryptionPromise().then(() => {
            this.event = originalEvent;
          });
        }
      },
    },
    thread: {
      handler(newValue, oldValue) {
        if (oldValue) {
          oldValue.off("Relations.add", this.onAddRelation);
        }
        if (newValue) {
          newValue.on("Relations.add", this.onAddRelation);
        }
        this.processThread();
      },
      immediate: true,
    },
  },
  computed: {
    /**
     *
     * @returns true if event is non-null and contains data
     */
    validEvent() {
      return this.event && Object.keys(this.event).length !== 0;
    },

    /**
     * If this is a thread event, we return the root here, so all reactions will land on the root event.
     */
    eventForReactions() {
      if (this.event.parentThread) {
        return this.event.parentThread;
      }
      return this.event;
    },

    incoming() {
      return this.event && this.event.getSender() != this.$matrix.currentUserId;
    },

    /**
     * Don't show sender and time if the next event is within 2 minutes and also from us (= back to back messages)
     */
    showSenderAndTime() {
      if (!this.isPinned && this.nextEvent && this.nextEvent.getSender() == this.event.getSender()) {
        const ts1 = this.nextEvent.event.origin_server_ts;
        const ts2 = this.event.event.origin_server_ts;
        return ts1 - ts2 < 2 * 60 * 1000; // less than 2 minutes
      }
      return true;
    },

    inReplyToSender() {
      const originalEvent = this.validEvent && this.event.replyEvent;
      if (originalEvent) {
        const sender = this.eventSenderDisplayName(originalEvent);
        if (originalEvent.isThreadRoot || originalEvent.isMxThread) {
          return sender || this.$t("message.someone");
        } else {
          return this.$t("message.user_said", { user: sender || this.$t("message.someone") });
        }
      }
      return null;
    },

    inReplyToEvent() {
      return this.validEvent && this.event.replyEvent;
    },

    inReplyToText() {
      const relatesTo = this.event.getWireContent()["m.relates_to"];
      if (relatesTo && relatesTo["m.in_reply_to"]) {
        if (this.inReplyToEvent && (this.inReplyToEvent.isThreadRoot || this.inReplyToEvent.isMxThread)) {
          const children = this.timelineSet.relations
            .getAllChildEventsForEvent(this.inReplyToEvent.getId())
            .filter((e) => util.downloadableTypes().includes(e.getContent().msgtype));
          return this.$t("message.sent_media", { count: children.length });
        }
        const content = this.event.getContent();
        if ("body" in content) {
          const lines = content.body.split("\n").reverse() || [];
          while (lines.length && !lines[0].startsWith("> ")) lines.shift();
          // Reply fallback has a blank line after it, so remove it to prevent leading newline
          if (lines[0] === "") lines.shift();
          const text = lines[0] && lines[0].replace(/^> (<.*> )?/g, "");
          if (text) {
            return text;
          }
        }
        if (this.inReplyToEvent) {
          var c = this.inReplyToEvent.getContent();
          return c.body;
        }
        // We don't have the original text (at the moment at least)
        return this.$t("fallbacks.original_text");
      }
      return null;
    },

    messageText() {
      const relatesTo = this.event.getWireContent()["m.relates_to"];
      if (relatesTo && relatesTo["m.in_reply_to"]) {
        const content = this.event.getContent();
        if ("body" in content) {
          // Remove the new text and strip "> " from the old original text
          const lines = content.body.split("\n");
          while (lines.length && lines[0].startsWith("> ")) lines.shift();
          // Reply fallback has a blank line after it, so remove it to prevent leading newline
          if (lines[0] === "") lines.shift();
          return lines.join("\n");
        }
      }
      return this.event.getContent().body;
    },

    isPinned() {
      return this.event.parentThread ? this.event.parentThread.isPinned : this.event.isPinned;
    },

    /**
     * Classes to set for the message. Currently only for "messageIn"
     */

    messageClasses() {
      if (this.incoming) {
        return { messageIn: true, "from-admin": this.senderIsAdminOrModerator(this.event), "pinned": this.isPinned };
      } else {
        return { messageOut: true, "pinned": this.isPinned };
      }
    },

    userAvatar() {
      if (!this.$matrix.userAvatar) {
        return null;
      }
      return this.$matrix.matrixClient.mxcUrlToHttp(this.$matrix.userAvatar, 80, 80, "scale", true);
    },

    userAvatarLetter() {
      if (!this.$matrix.currentUser) {
        return null;
      }
      return (this.$matrix.currentUserDisplayName || this.$matrix.currentUserId.substring(1))
        .substring(0, 1)
        .toUpperCase();
    },
  },
  methods: {
    onAddRelation() {
      console.error("onAddRelation");
      this.processThread();
    },
    ownAvatarClicked() {
      this.$emit("own-avatar-clicked", { event: this.event });
    },

    otherAvatarClicked(avatarRef) {
      this.$emit("other-avatar-clicked", { event: this.event, anchor: avatarRef });
    },

    showContextMenu(buttonRef) {
      this.$emit("context-menu", { event: this.event, anchor: buttonRef });
    },

    /**
     * Get a display name given an event.
     */
    eventSenderDisplayName(event) {
      if (event.getSender() == this.$matrix.currentUserId) {
        return this.$t("message.you");
      }
      if (this.room) {
        const member = this.room.getMember(event.getSender());
        if (member) {
          return member.name;
        }
      }
      return event.getContent().displayname || event.getSender();
    },

    /**
     * In the case where the state_key points out a userId for an operation (e.g. membership events)
     * return the display name of the affected user.
     * @param event
     * @returns
     */
    eventStateKeyDisplayName(event) {
      if (event.getStateKey() == this.$matrix.currentUserId) {
        return this.$t("message.you");
      }
      if (this.room) {
        const member = this.room.getMember(event.getStateKey());
        if (member) {
          return member.name;
        }
      }
      return event.getStateKey();
    },

    messageEventAvatar(event) {
      if (this.room) {
        const member = this.room.getMember(event.getSender());
        if (member) {
          return member.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), 40, 40, "scale", true);
        }
      }
      return null;
    },

    /**
     * Return true if the event sender has a powel level > 0, e.g. is moderator or admin of some sort.
     */
    senderIsAdminOrModerator(event) {
      if (this.room) {
        const member = this.room.getMember(event.getSender());
        if (member) {
          return member.powerLevel > 0;
        }
      }
      return false;
    },

    redactedBySomeoneElse(event) {
      if (!event.isRedacted()) return false;
      const redactionEvent = event.getUnsigned().redacted_because;
      if (redactionEvent) {
        return redactionEvent.sender !== this.$matrix.currentUserId;
      }
      return false;
    },

    formatTimeAgo(time) {
      const date = new Date();
      date.setTime(time);
      var ti = Math.abs(new Date().getTime() - date.getTime());
      ti = ti / 1000; // Convert to seconds
      let s = "";
      if (ti < 60) {
        s = this.$t("global.time.recently");
      } else if (ti < 3600 && Math.round(ti / 60) < 60) {
        s = this.$tc("global.time.minutes", Math.round(ti / 60));
      } else if (ti < 86400 && Math.round(ti / 60 / 60) < 24) {
        s = this.$tc("global.time.hours", Math.round(ti / 60 / 60));
      } else {
        s = this.$tc("global.time.days", Math.round(ti / 60 / 60 / 24));
      }
      return this.toLocalNumbers(s);
    },

    /**
     * Possibly convert numerals to local representation (currently only for "bo" locale)
     * @param str String in which to convert numerals [0-9]
     * @returns converted string
     */
    toLocalNumbers(str) {
      if (this.$i18n.locale == "bo") {
        // Translate to tibetan numerals
        let result = "";
        for (let i = 0; i < str.length; i++) {
          let c = str.charCodeAt(i);
          if (c >= 48 && c <= 57) {
            result += String.fromCharCode(c + 0x0f20 - 48);
          } else {
            result += String.fromCharCode(c);
          }
        }
        return result;
      }
      return str;
    },

    linkify(text) {
      return linkifyHtml(text);
    },

    /**
     * Override this to handle updates to (the) message thread.
     */
    processThread() {},
    initMsgHammerJs(element) {
      this.mc = new Hammer(element);
      this.mcCustom = new Hammer.Manager(element);

      this.mcCustom.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));

      this.mcCustom.on("doubletap", (evt) => {
        var { top, left } = evt.target.getBoundingClientRect();
        var position = { top: `${top}px`, left: `${left}px`};
        this.$emit("addQuickHeartReaction", { position });
      });

      this.mc.on("press", () => {
        this.showContextMenu(this.$refs.opbutton);
      });

    }
  },
};
