import { app } from "./app.js";
import { NjetComponent } from "./njet.js";
import { FileUploadGrid } from "./file-upload-grid.js";
class ChatInputComponent extends NjetComponent {
  autoCompletions = {
    "example 1": () => {
    },
    "example 2": () => {
    },
  }
  hiddenCompletions = {
    "/starsRender": () => {
      app.rpc.starsRender(this.channelUid, this.value.replace("/starsRender ", ""))
    }
  }
  users = []
  textarea = null
  _value = ""
  lastUpdateEvent = null
  expiryTimer = null;
  queuedMessage = null;
  lastMessagePromise = null;

  constructor() {
    super();
    this.lastUpdateEvent = new Date();
    this.textarea = document.createElement("textarea");
    this.value = this.getAttribute("value") || "";
  }

  get value() {
    return this._value;
  }

  set value(value) {
    this._value = value;
    this.textarea.value = this._value;
  }

  get allAutoCompletions() {
    return Object.assign({}, this.autoCompletions, this.hiddenCompletions)
  }

  resolveAutoComplete(input) {
    let value = null;

    for (const key of Object.keys(this.allAutoCompletions)) {
      if (key.startsWith(input.split(" ", 1)[0])) {
        if (value) {
          return null;
        }
        value = key;
      }
    }

    return value;
  }

  isActive() {
    return document.activeElement === this.textarea;
  }

  focus() {
    this.textarea.focus();
  }

  getAuthors() {
    return this.users.flatMap((user) => [user.username, user.nick])
  }

  extractMentions(text) {
    return Array.from(text.matchAll(/@([a-zA-Z0-9_-]+)/g), m => m[1]);
  }

  matchMentionsToAuthors(mentions, authors) {
    return mentions.map(mention => {
      let closestAuthor = null;
      let minDistance = Infinity;
      const lowerMention = mention.toLowerCase();

      authors.forEach(author => {
        const lowerAuthor = author.toLowerCase();
        let distance = this.levenshteinDistance(lowerMention, lowerAuthor);


        if (!this.isSubsequence(lowerMention, lowerAuthor)) {
          distance += 10
        }

        if (distance < minDistance) {
          minDistance = distance;
          closestAuthor = author;

        }

      });

      return { mention, closestAuthor, distance: minDistance };
    });
  }

  levenshteinDistance(a, b) {
    const matrix = [];

    // Initialize the first row and column
    for (let i = 0; i <= b.length; i++) {
      matrix[i] = [i];
    }
    for (let j = 0; j <= a.length; j++) {
      matrix[0][j] = j;
    }

    // Fill in the matrix
    for (let i = 1; i <= b.length; i++) {
      for (let j = 1; j <= a.length; j++) {
        if (b.charAt(i - 1) === a.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j] + 1,    // Deletion
            matrix[i][j - 1] + 1,    // Insertion
            matrix[i - 1][j - 1] + 1 // Substitution
          );
        }
      }
    }

    return matrix[b.length][a.length];
  }


  replaceMentionsWithAuthors(text) {
    const authors = this.getAuthors();
    const mentions = this.extractMentions(text);

    const matches = this.matchMentionsToAuthors(mentions, authors);
    let updatedText = text;
    matches.forEach(({ mention, closestAuthor }) => {
      const mentionRegex = new RegExp(`@${mention}`, 'g');
      updatedText = updatedText.replace(mentionRegex, `@${closestAuthor}`);
    });

    return updatedText;
  }


  async connectedCallback() {
    this.user = null
    app.rpc.getUser(null).then((user) => {
      this.user = user
    })

    this.liveType = this.getAttribute("live-type") === "true";
    this.liveTypeInterval =
      parseInt(this.getAttribute("live-type-interval")) || 6;
    this.channelUid = this.getAttribute("channel");

    app.rpc.getRecentUsers(this.channelUid).then(users => {
      this.users = users
    })
    this.messageUid = null;

    this.classList.add("chat-input");

    this.fileUploadGrid = new FileUploadGrid();
    this.fileUploadGrid.setAttribute("channel", this.channelUid);
    this.fileUploadGrid.style.display = "none";
    this.appendChild(this.fileUploadGrid);

    this.textarea.setAttribute("placeholder", "Type a message...");
    this.textarea.setAttribute("rows", "2");

    this.appendChild(this.textarea);

    this.uploadButton = document.createElement("upload-button");
    this.uploadButton.setAttribute("channel", this.channelUid);
    this.uploadButton.addEventListener("upload", (e) => {
      this.dispatchEvent(new CustomEvent("upload", e));
    });
    this.uploadButton.addEventListener("uploaded", (e) => {
      this.dispatchEvent(new CustomEvent("uploaded", e));
    });
    this.subscribe("file-uploading", (e) => {
        this.fileUploadGrid.style.display = "block";   

        this.uploadButton.style.display = "none";
        this.textarea.style.display = "none";
    })
    this.appendChild(this.uploadButton);
    this.textarea.addEventListener("blur", () => {
      this.updateFromInput("");
    });
    this.textarea.addEventListener("keyup", (e) => {
      if (e.key === "Enter" && !e.shiftKey) {

        const message = this.replaceMentionsWithAuthors(this.value);
        e.target.value = "";

        if (!message) {
          return;
        }
        let autoCompletionHandler = this.allAutoCompletions[this.value.split(" ", 1)[0]];
        if (autoCompletionHandler) {
          autoCompletionHandler();
          this.value = "";
          e.target.value = "";

          return;
        }

        this.finalizeMessage(this.messageUid)

        return;
      }

      this.updateFromInput(e.target.value);
    });

    this.textarea.addEventListener("keydown", (e) => {
      this.value = e.target.value;

      let autoCompletion = null;
      if (e.key === "Tab") {
        e.preventDefault();
        autoCompletion = this.resolveAutoComplete(this.value);
        if (autoCompletion) {
          e.target.value = autoCompletion;
          this.value = autoCompletion;
          return;
        }
      }

      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
      }

      if (e.repeat) {
        this.updateFromInput(e.target.value);
      }
    });

    this.addEventListener("upload", (e) => {
      this.focus();
    });
    this.addEventListener("uploaded", function (e) {
      let message = e.detail.files.reduce((message, file) => {
        return `${message}[${file.name}](/channel/attachment/${file.relative_url})`;
      }, '');
      app.rpc.sendMessage(this.channelUid, message, true);
    });
    setTimeout(() => {
      this.focus();
    }, 1000)
  }

  trackSecondsBetweenEvents(event1Time, event2Time) {
    const millisecondsDifference = event2Time.getTime() - event1Time.getTime();
    return millisecondsDifference / 1000;
  }

  isSubsequence(s, t) {
    let i = 0, j = 0;
    while (i < s.length && j < t.length) {
      if (s[i] === t[j]) {
        i++;
      }
      j++;
    }
    return i === s.length;
  }

  flagTyping() {
    if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) >= 1) {
      this.lastUpdateEvent = new Date();
      app.rpc.set_typing(this.channelUid, this.user.color).catch(() => {
      });
    }
  }

  finalizeMessage(messageUid) {
    if (!messageUid) {
      if (this.value.trim() === "") {
        return;
      }
      this.sendMessage(this.channelUid, this.replaceMentionsWithAuthors(this.value), !this.liveType);
    } else if (messageUid.startsWith("?")) {
      const lastQueuedMessage = this.queuedMessage;

      this.lastMessagePromise?.then((uid) => {
        const updatePromise = lastQueuedMessage ? app.rpc.updateMessageText(uid, lastQueuedMessage) : Promise.resolve();
        return updatePromise.finally(() => {
          return app.rpc.finalizeMessage(uid);
        })
      })
    } else {
      app.rpc.finalizeMessage(messageUid)
    }
    this.value = "";
    this.messageUid = null;
    this.queuedMessage = null;
    this.lastMessagePromise = null
  }

  updateFromInput(value) {
    if (this.expiryTimer) {
      clearTimeout(this.expiryTimer);
      this.expiryTimer = null;
    }

    this.value = value;

    this.flagTyping()

    if (this.liveType && value[0] !== "/") {
      this.expiryTimer = setTimeout(() => {
        this.finalizeMessage(this.messageUid)
      }, this.liveTypeInterval * 1000);


      const messageText = this.replaceMentionsWithAuthors(value);
      if (this.messageUid?.startsWith("?")) {
        this.queuedMessage = messageText;
      } else if (this.messageUid) {
        app.rpc.updateMessageText(this.messageUid, messageText).then((d) => {
          if (!d.success) {
            this.messageUid = null
            this.updateFromInput(value)
          }
        })
      } else {
        const placeHolderId = "?" + crypto.randomUUID();
        this.messageUid = placeHolderId;

        this.lastMessagePromise = this.sendMessage(this.channelUid, messageText, !this.liveType).then(async (uid) => {
          if (this.liveType && this.messageUid === placeHolderId) {
            if (this.queuedMessage && this.queuedMessage !== messageText) {
              await app.rpc.updateMessageText(uid, this.queuedMessage)
            }
            this.messageUid = uid;
          }

          return uid
        });
      }
    }
  }

  async sendMessage(channelUid, value, is_final) {
    if (!value.trim()) {
      return null;
    }
    return await app.rpc.sendMessage(channelUid, value, is_final);
  }
}

customElements.define("chat-input", ChatInputComponent);
