class NoticyWord{

  #range;

  #letterArray;

  constructor(wordState){

    this.#range = wordState.map( el => el.index );

    this.#letterArray = wordState.map( el => el.letter )
  }

  getRange(){
    return this.#range;
  }

  getStr(){
    return this.#letterArray.join("").slice(1);
  }
}

class SelectByKey{

  static KEY_UP = "ArrowUp";
  static KEY_DOWN = "ArrowDown";
  static HOVER_CLASS = "notify__hover-item";
  static KEY_ENTER = "Enter";
  static KEY_ESC = "Escape";
  
  #container;

  #selectedIndex = -1;

  #selectedItem;

  setContainer(divH){
    this.#container = divH;

    return this;
  }

  hover(key){

    let lastIndex = this.#container.childNodes().length - 2;

    this.clear();
    
    if(_e(key, SelectByKey.KEY_DOWN)){

      if(this.#selectedIndex <= lastIndex) {
        this.#selectedIndex++;
      } else {
        this.#selectedIndex = 0;
      }

    } else {
      if(this.#selectedIndex >= 1) {
        this.#selectedIndex--;
      } else {
        this.#selectedIndex = lastIndex + 1;
      }
    }

    utils.each(this.#container.childNodes(), (el, index) => {
      let item = h.from(el);

      if(_e(index, this.#selectedIndex)) this.#selectedItem = item.cl(SelectByKey.HOVER_CLASS).scrollIntoView();
    })

  }

  clear(){
    this.#container.eachOfClass(SelectByKey.HOVER_CLASS, (item) => {
      if(item){
        h.from(item).rcl(SelectByKey.HOVER_CLASS);
      }
    })
  }

  getSelectedItemIndex(el){
    if(this.#selectedItem){
      return this.#selectedItem.getData("indx");
    } else return null;
  }
 
  setSelectedIndex(val){
    this.#selectedIndex = val;
  }
}

class NotifyByTagname{

  static BASE_PATH = "/api/v2/flaw/hr/root/fetch/data";

  #state = [];

  #cursorPos = 0;

  #searchData;

  #input;

  #modal = h.div("notify__modal");

  #value;

  #pos;

  #selectByKey;

  #currentNoticyWord;

  #path;

  constructor(input, path = NotifyByTagname.BASE_PATH){
    this.#input = input;

    this.#path = path;

    this.#selectByKey = new SelectByKey(this);
  }

  keyboardEvent(){
    this.#input.onKeydown((event) => {
      if(_e(event.key, SelectByKey.KEY_ESC) && this.#modal.isInPage()){
        
        this.#modal.remove();
      
        return;
      }

      if(_e(event.key, SelectByKey.KEY_DOWN) || _e(event.key, SelectByKey.KEY_UP)){

        if(this.#modal.isInPage()){
          event.preventDefault();
          this.#selectByKey.setContainer(this.#modal.first()).hover(event.key);

          return;
        }
      }

      if(_e(event.key, SelectByKey.KEY_ENTER) && this.#modal.isInPage()){
        
        event.preventDefault()

        if(this.#selectByKey.getSelectedItemIndex()){
          this.itemClick(this.#searchData[this.#selectByKey.getSelectedItemIndex()]);

          this.#selectByKey.setSelectedIndex(-1);
        }
        
        return
      }

      this.basicEvent();
    })
  }

  init(){
    this.inputEvent();
    this.clickEvent();
    this.keyboardEvent();

    return this;
  }

  clickEvent(){
    this.#input.click(() => {

      this.basicEvent();
    })
  }

  search(str){

    let limit = 20;

    if(str.length > 2) limit = 40;

    let payLoad = {
      "page": 0,
      "limit": limit,
      "query": [
        {
        "field": "name",
        "value": str,
        "searchName": "name",
        "dataType": "object_chooser",
        "type": '%...%'
      }]
    }

    let path = Environment.dataApi + this.#path;
    
    Executor.runPostWithPayload(path, (data) => {

      this.#searchData = data.content;
      this.drawModal(data.content, str);
    
    }, payLoad, (e) => console.log(e))
  }

  drawModal(data){
      this.#modal.text(null);

      this.#modal.appendToBody().width(this.#pos.width).style().left(this.#pos.left).top(this.#pos.top - 200);

      let wrapper = h.div('notify__wrapper').appendTo(this.#modal);
      
      h.div("close-date-chooser").text(null).appendTo(this.#modal).click(() => this.#modal.remove());

      if(!data.length){
        wrapper.add(h.div("notify__empty").text("global.no_data"))
      }

      utils.each(data, (el, i) => {
        let item = h.div("notify__item").setData("indx", i).appendTo(wrapper);
        let left = h.div("notify__left").cl("flex-center").appendTo(item); 

        item.add(h.div("notify__name").text(el.name, false)).click((e, ev) => {
          if(e){
            this.itemClick(el, ev)
          }
        });

        if(el.icon) h.div("notify__icon").add(h.img(el.icon).width(22)).prependTo(left);
    })
  }

  itemClick(item){
    if(item){
      let letterArr = [...this.#value];
      this.remove();
  
      let letterRange = this.#currentNoticyWord.getRange();
      
      let beforeNoticy = letterArr.slice(0, letterRange[0]);
  
      let noticy = [..."@" + item.id];
  
      let afterNoticy = letterArr.slice(utils.last(letterRange) + 1, letterArr.length);
  
      let concatString = [].concat(beforeNoticy, [..."@" + item.id], afterNoticy);
  
      this.#value = concatString.join("");
  
      let cursorIndex = beforeNoticy.length + noticy.length;
  
      this.#input.text(this.#value, false);
  
      utils.zeroTimeOut(() => {
      
        this.#input.focus();

        dom.setCursorIndex(this.#input, cursorIndex);

        dom.setSelectionRange(this.#input, cursorIndex , cursorIndex);

        this.#modal.remove();
      }
      );
  
    }
    
  }

  findWord(){
    utils.zeroTimeOut(() => {
      let cursorAtNoticy = this.#state.filter((el) => {
        let is = false;

        utils.each(el.getRange(), (item) => {
          if (item == this.#cursorPos - 1) is = true;
        })

        return is;
      })[0]

      if(utils.notNull(cursorAtNoticy)){
        this.#currentNoticyWord = cursorAtNoticy;
        this.search(cursorAtNoticy.getStr());

      } else this.remove();
    })
  }

  basicEvent(){
    utils.zeroTimeOut(() => {

      this.position();

      this.#value = this.#input.val();

      if(!this.#value) this.#modal.remove();

      if(this.#value) {

        this.buildState(this.#value);
        
        this.#cursorPos = dom.getCursorIndex(this.#input);

        this.findWord();
      }

    })
  }

  inputEvent(){
    this.#input.inputFunc(() => {

      this.basicEvent();

    })
  }

  buildState(str){
    this.#state = [];
    let arr = str.split("");

    utils.each(arr, (letter, i) => {
      if(this.hasWhiteSpace(arr[i - 1]) ||
        !arr[i - 1] || arr[i - 1].charCodeAt(0) == 10 ||
        utils.last(arr).charCodeAt(0) == 10){

        if(_e(letter , "@")){
          this.#state.push(new NoticyWord(this.getWord(arr, i)))
        }
      }
    })

  }

  getWord(arr, indexFrom, word = []){

    if(arr[indexFrom] && !this.hasWhiteSpace(arr[indexFrom])){

      if(_e(arr[indexFrom], "\n")) {
        return word;
      }

      if(_e(arr[indexFrom], ".") || _e(arr[indexFrom], ":") || _e(arr[indexFrom], ",")) {
        return word;
      }

      word.push({
        letter: arr[indexFrom ],
        index: indexFrom
      });
      return this.getWord(arr, indexFrom + 1, word);
    } else return word;
    
  }

  remove(){
    this.#modal.remove();
    this.#selectByKey.setSelectedIndex(-1);
  }

  hasWhiteSpace(s) {
    if(!s) return false;
    return s.indexOf(' ') >= 0;
  }

  position(){
    this.#pos = {
      left: this.#input.getRect().left,
      top: this.#input.getRect().top,
      width: this.#input.getRect().width
    }
  }
}


class TextWithNotice{

  static NOTICE_CLASS = "comment__notify";
  static COMMENT_SPAN_CLASS = "comment__span";
  static WRAPPER_CLASS = "text-with-notice__wrapper";
  static PERSON = "person-"
  static GET_PERSON_PATH = "/api/v2/flaw/hr/root/fetch/form?id=";
  
  #text;

  #hasNotice = false;

  #stringToArray = [];

  constructor(text){
    this.#text = text;

    this.checkText();
  }

  buildTextDivH(where = null){
    
    let textWrapper = h.div(TextWithNotice.WRAPPER_CLASS);

    if(where) textWrapper = where.text(null);

    if(this.#hasNotice){

      this.transformStr();

      this.changeTextContentInNotice();

      utils.each(this.#stringToArray, elem => {
        textWrapper.add(elem.spanH) 
      })

    } else {
      textWrapper.add(h.span('null').text(this.#text, false));
    }

    return textWrapper;
  }

  changeTextContentInNotice(){

    this.#stringToArray = this.#stringToArray.map((el) => {
      if(el.isCanBeNoticy){

        let dataFromCache = cache.get(TextWithNotice.PERSON + el.id);
        
        if(dataFromCache) {

          this.buildNoticeSpanH(el.spanH, JSON.parse(dataFromCache));

        } else {

          this.getOnePerson(el.id, (data) => {

            this.buildNoticeSpanH(el.spanH, data.value);
  
            cache.add(TextWithNotice.PERSON + el.id, JSON.stringify(data.value));
  
          }, (err) => {
            return el;
          })
        }
        return el;

      } else {

        return el
      }
    })
  }

  buildNoticeSpanH(parentSpanH,data){

    parentSpanH.text(null);

    if(data.icon) h.img(data.icon).appendTo(parentSpanH);

    h.span(TextWithNotice.NOTICE_CLASS).text(data.name + " " +"(" + data.email + ")", false).appendTo(parentSpanH);
  }

  transformStr(){
    this.#stringToArray = this.#text.split("@").join(" @").split("<").join(" <").split(" ").map((word) => {
    if(_e(word[0], "@")){
      return {
        isCanBeNoticy: true,
        text: word,
        id: word.slice(1),
        spanH: h.span(TextWithNotice.NOTICE_CLASS).text(" " + word + " ", false).click(() => 
          h.magicView(word.slice(1), "flaw/hr", false)
        )
      }
    } else {
      return {
        isCanBeNoticy: false,
        text: word,
        spanH: h.span(TextWithNotice.COMMENT_SPAN_CLASS).text(word + " ", false)
      }
    }
    })

  }

  checkText(){
    this.#hasNotice =  this.#text.includes("@");
  }

  getOnePerson(id, success, error = null) {
    Executor.runGet(Environment.dataApi + TextWithNotice.GET_PERSON_PATH + id,
    success, false, Executor.HEADERS, error);
  }

}