import { app } from "./app.js";
import { NjetComponent,eventBus } 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 ", ""))
    },
    "/leet": () => {
      this.value = this.textToLeet(this.value);
        this._leetSpeak = !this._leetSpeak;
    },
    "/l33t": () => {
      this._leetSpeakAdvanced = !this._leetSpeakAdvanced;
    }
  };

  users = [];
  textarea = null;
  _value = "";
  lastUpdateEvent = null;
  queuedMessage = null;
  lastMessagePromise = null;
  _leetSpeak = false;
  _leetSpeakAdvanced = false;
  constructor() {
    super();
    this.lastUpdateEvent = new Date();
    this.textarea = document.createElement("textarea");
      this.textarea.classList.add("chat-input-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;
  }
  textToLeet(text) {
  // L33t speak character mapping
  const leetMap = {
    'a': '4',
    'A': '4',
    'e': '3',
    'E': '3',
    'i': '1',
    'I': '1',
    'o': '0',
    'O': '0',
    's': '5',
    'S': '5',
    't': '7',
    'T': '7',
    'l': '1',
    'L': '1',
    'g': '9',
    'G': '9',
    'b': '6',
    'B': '6',
    'z': '2',
    'Z': '2'
  };
  
  // Convert text to l33t speak
  return text.split('').map(char => {
    return leetMap[char] || char;
  }).join('');
}


// Advanced version with random character selection
textToLeetAdvanced(text) {
  const leetMap = {
    'a': ['4', '@', '/\\'],
    'A': ['4', '@', '/\\'],
    'e': ['3', '€'],
    'E': ['3', '€'],
    'i': ['1', '!', '|'],
    'I': ['1', '!', '|'],
    'o': ['0', '()'],
    'O': ['0', '()'],
    's': ['5', '$'],
    'S': ['5', '$'],
    't': ['7', '+'],
    'T': ['7', '+'],
    'l': ['1', '|'],
    'L': ['1', '|'],
    'g': ['9', '6'],
    'G': ['9', '6'],
    'b': ['6', '|3'],
    'B': ['6', '|3'],
    'z': ['2'],
    'Z': ['2'],
    'h': ['#', '|-|'],
    'H': ['#', '|-|'],
    'n': ['|\\|'],
    'N': ['|\\|'],
    'm': ['|\\/|'],
    'M': ['|\\/|'],
    'w': ['\\/\\/'],
    'W': ['\\/\\/'],
    'v': ['\\/', 'V'],
    'V': ['\\/', 'V'],
    'u': ['|_|'],
    'U': ['|_|'],
    'r': ['|2'],
    'R': ['|2'],
    'f': ['|='],
    'F': ['|='],
    'd': ['|)'],
    'D': ['|)'],
    'c': ['(', '['],
    'C': ['(', '['],
    'k': ['|<'],
    'K': ['|<'],
    'p': ['|>'],
    'P': ['|>'],
    'x': ['><'],
    'X': ['><'],
    'y': ['`/'],
    'Y': ['`/']
  };
  
  return text.split('').map(char => {
    const options = leetMap[char];
    if (options) {
      return options[Math.floor(Math.random() * options.length)];
    }
    return char;
  }).join('');
}


  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.ttsButton = document.createElement("stt-button");
   
    this.snekSpeaker = document.createElement("snek-speaker");
    this.appendChild(this.snekSpeaker);

    this.ttsButton.addEventListener("click", (e) => {
       this.snekSpeaker.enable() 
    });

    this.appendChild(this.ttsButton);
    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.uploadButton.addEventListener("click", (e) => {
      e.preventDefault();
      this.fileUploadGrid.openFileDialog();
    });
    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.value, true).then(
          this.updateFromInput("")
      )
    });

    this.subscribe("file-uploads-done", (data)=>{
      this.textarea.style.display = "block";
      this.uploadButton.style.display = "block";
      this.fileUploadGrid.style.display = "none";
      let msg =data.reduce((message, file) => {
        return `${message}[${file.filename || file.name || file.remoteFile}](/channel/attachment/${file.remoteFile})`;
      }, '');
      app.rpc.sendMessage(this.channelUid, msg, true);
    });

    
    this.textarea.addEventListener("change",(e)=>{
        this.value = this.textarea.value;
      this.updateFromInput(e.target.value);
    })
    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) {
    let value = this.value;
    value =  this.replaceMentionsWithAuthors(value)
    if(this._leetSpeak){
        value = this.textToLeet(value);
    }else if(this._leetSpeakAdvanced){
        value = this.textToLeetAdvanced(value);
    }
    app.rpc.sendMessage(this.channelUid, value , true);
    this.value = "";
    this.messageUid = null;
    this.queuedMessage = null;
    this.lastMessagePromise = null;
  }
  


  updateFromInput(value, isFinal = false) {

    this.value = value;

    this.flagTyping();

    if (this.liveType && value[0] !== "/") {
      const messageText = this.replaceMentionsWithAuthors(value);
      this.messageUid = this.sendMessage(this.channelUid, messageText, !this.liveType || isFinal);
      return this.messageUid;
    }
  }

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

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

