import { LisioBooleanParameterNames } from "@lisio/lisio-profils";

import { LisioMutationObserverController } from "../../controllers/lisio-mutation-observer-controller";
import { LisioParamsController } from "../../controllers/lisio-params-controller";
import LisioMessageReceivedName from "../../enums/lisio-message-received-name";
import LisioMessageManager from "../../managers/lisio-message-manager";
import LisioTextTreeWalker from "../../walkers/lisio-text-tree-walker";
import LisioAdapter from "../lisio-adapter";
import LisioListAdapter from "../lisio-list-adapter";

interface TranslatorObject {
  tags: HTMLElement[];
  text?: string;
}

/**
 * Class representing a deepl translation adapter extending an adapter.\
 * It aims to represents the font style functionality of Lisio.\
 * A deepl translation adapter is basically a functionality of Lisio which will change the deepl translation of texts in the main page.\
 */
class LisioDeeplTranslationAdapter extends LisioAdapter<string> {
  private _entries: Map<Element, IntersectionObserverEntry> = new Map<
    Element,
    IntersectionObserverEntry
  >();
  private _intersect?: IntersectionObserver;
  private _listAdapter?: LisioListAdapter;
  private _mutationController: LisioMutationObserverController;
  private _originLang?: string;
  private _paramsController: LisioParamsController;
  private _sanitizedTexts: TranslatorObject[] = [];
  private _translatorAPIBoundedResponseHandlers: Map<
    string,
    (
      texts: [
        {
          translatedText: string;
          translateLangDTO: {
            languageISO: string;
            languageDirection: string;
          };
          originTextDTO: {
            originText: string;
          };
        },
      ],
    ) => void
  > = new Map<
    string,
    (
      texts: [
        {
          translatedText: string;
          translateLangDTO: {
            languageISO: string;
            languageDirection: string;
          };
          originTextDTO: {
            originText: string;
          };
        },
      ],
    ) => void
  >();
  private _translatorAPITimeout = -1;

  constructor(
    mutationController: LisioMutationObserverController,
    paramsController: LisioParamsController,
  ) {
    super();
    this._mutationController = mutationController;
    this._paramsController = paramsController;
  }

  public set listAdapter(value: LisioListAdapter) {
    this._listAdapter = value;
  }

  /**
   * Public method implementing abstract method adapt of Adapter
   * @param {LisioTextTreeWalker} walker - A walker to explore the DOM
   * @param {string} value - Value of the functionality
   * @returns Returns nothing
   * @source
   */
  public adapt(walker: LisioTextTreeWalker, value: string): void {
    value = value.replace("_", "-");
    if (this._originLang != undefined) {
      this._originLang = document.documentElement.lang
        .toUpperCase()
        .replace("_", "-");
    }
    if (value != "default" && value != this._originLang) {
      if (!this._originLang || /^FR/.test(this._originLang)) {
        this._originLang = "FR";
      }
      this._intersect?.disconnect();
      this._intersect = new IntersectionObserver(
        this.translatorAPIIntersectionCallback.bind(this, value),
        {
          rootMargin: "0px",
          threshold: 1.0,
        },
      );
      if (this._paramsController.params.has(LisioBooleanParameterNames.LIST)) {
        this._listAdapter?.prepareForDeeplTrans(value);
      }
      this.observeTags(walker, value);
    } else if (
      value == "default" ||
      (this._originLang != undefined && value === this._originLang)
    ) {
      this._sanitizedTexts = [];
      if (this._translatorAPITimeout) {
        clearTimeout(this._translatorAPITimeout);
      }
      if (this._intersect != undefined) {
        this._intersect.disconnect();
        this._intersect = undefined;
      }
      const tagsToTranslate = document.querySelectorAll(
        "[lisio-original-text], [dir]:not([lisio-original-text])",
      );
      for (const tagToTranslate of tagsToTranslate) {
        if (tagToTranslate.hasAttribute("lisio-original-text")) {
          tagToTranslate.innerHTML =
            tagToTranslate.getAttribute("lisio-original-text") || "";
        }
        tagToTranslate.removeAttribute("lisio-already-trans-in");
        tagToTranslate.removeAttribute("lang");
        tagToTranslate.removeAttribute("xml:lang");
        tagToTranslate.removeAttribute("dir");
      }
      this._entries = new Map();
      LisioMessageManager.current.detachHandler(
        LisioMessageReceivedName.TRANSLATOR_API_TRANSLATE_RESPONSE,
      );
    }
  }

  public observeTags(walker: LisioTextTreeWalker, value: string) {
    const tags = Array.from(walker.tags.values()).filter(
      (tag) =>
        tag.hasAttribute("data-lisio-index") && !tag.hasAttribute("translate"),
    );
    const sanitizedTags = new Set(
      tags.concat(
        Array.from(
          document.querySelectorAll(
            'input[type="submit"], input[type="reset"]',
          ),
        ),
      ),
    );
    for (const sanitizedTag of sanitizedTags) {
      const alreadyTradLang = sanitizedTag.getAttribute(
        "lisio-already-trans-in",
      );
      const innerHTML = alreadyTradLang
        ? sanitizedTag.getAttribute("lisio-original-text")
        : sanitizedTag instanceof HTMLInputElement
          ? sanitizedTag.value.trim()
          : sanitizedTag.innerHTML.trim().replace(/\s{2,}/g, " ");
      if (innerHTML != undefined && /[a-zA-Z]+/.test(innerHTML)) {
        if (alreadyTradLang != value) {
          const find = this._sanitizedTexts.find(
            (obj) => obj.text === innerHTML,
          );
          if (find && find.tags.some(tag=>tag.dataset.lisioDataIndex !== sanitizedTag.dataset.lisioDataIndex)) {
            find.tags.push(sanitizedTag);
          } else {
            this._sanitizedTexts.push({
              text: innerHTML,
              tags: [sanitizedTag],
            });
          }
          if (sanitizedTag.localName === "option") {
            let parent: Element | undefined = sanitizedTag.parentElement as
              | Element
              | undefined;
            while (parent?.localName != "select") {
              parent = parent?.parentElement as Element | undefined;
            }
            this._intersect?.unobserve(parent);
            this._intersect?.observe(parent);
          } else {
            this._intersect?.observe(sanitizedTag);
          }
        }
      }
    }

    LisioMessageManager.current.attachHandler(
      LisioMessageReceivedName.TRANSLATOR_API_TRANSLATE_RESPONSE,
      this.translatorAPIResponseHandler.bind(this),
    );
  }

  protected adaptFunction(element: HTMLElement, value: string): void {
    if (element instanceof HTMLInputElement) {
      element.value = value;
    } else {
      element.innerHTML = value;
    }
  }

  private translatorAPIIntersectionCallback(
    this: LisioDeeplTranslationAdapter,
    lang: string,
    entries: IntersectionObserverEntry[],
  ) {
    if (this._translatorAPITimeout) {
      clearTimeout(this._translatorAPITimeout);
    }
    for (const entry of entries) {
      if (Math.round(entry.intersectionRatio) == 1) {
        this._entries.set(entry.target, entry);
      } else if (Math.round(entry.intersectionRatio) == 0) {
        this._entries.delete(entry.target);
      }
    }
    if (this._entries.size) {
      this._translatorAPITimeout = setTimeout(() => {
        let textsToSend: TranslatorObject[] = [];
        for (const entry of this._entries.values()) {
          const hasInput = entry.target.querySelector("input") != undefined;
          if(!hasInput || (hasInput && Array.from(entry.target.childNodes).filter(node => node.nodeType === 3 && node.nodeValue?.trim() != "").length === 0)){
            const allLisioTags =
            Array.from(entry.target.querySelectorAll<HTMLElement>("[data-lisio-index]"));
            //this.intersect.unobserve(entry.target);
            const tags: HTMLElement[] = [];
            if (allLisioTags.length == 0 && entry.target instanceof HTMLElement && !tags.some((tag)=>tag.dataset.lisioIndex === (entry.target as HTMLElement).dataset.lisioIndex)) {
              tags.push(entry.target);
            } else {
              for (const lisioTag of allLisioTags) {
                if(!tags.some((tag)=>tag.dataset.lisioIndex === lisioTag.dataset.lisioIndex)){
                  tags.push(lisioTag);
                }
              }
            }

            //const all = [];
            for (const tag of tags) {
              this._intersect?.unobserve(tag);
              const already = textsToSend.find((text) => text.tags.includes(tag));
              const toSend = this._sanitizedTexts.find((text) =>
                text.tags.includes(tag),
              );
              if (!already && toSend != undefined) {
                //all.push(this.sanitizedTexts.find(text => text.tags.includes(tag)));
                textsToSend.push(toSend);
              }
            }
          }
          //textsToSend = this.textsToSend.concat(all);
        }
        //this.textsToSend = this.textsToSend.filter(textToSend => textToSend);
        textsToSend = textsToSend.filter((textToSend) => textToSend);
        this._entries.clear();
        if(textsToSend.length > 0){
          const keyHandler = (+new Date()).toString(36).slice(-8);
          LisioMessageManager.current.sendTranslatorAPITranslate(
            keyHandler,
            this._originLang == undefined ? "fr" : this._originLang,
            lang,
            textsToSend.map((t) => t.text).filter((text) => text != undefined),
          );
          this._translatorAPIBoundedResponseHandlers.set(
            keyHandler,
            (
              texts: [
                {
                  translatedText: string;
                  translateLangDTO: {
                    languageISO: string;
                    languageDirection: string;
                  };
                  originTextDTO: {
                    originText: string;
                  };
                },
              ],
            ) => {
              this.translatorAPIResponse(textsToSend, texts);
            },
          );
        }
      }, 250);
    }
  }

  private translatorAPIResponse(
    originTexts: TranslatorObject[],
    texts: [
      {
        translatedText: string;
        translateLangDTO: {
          languageISO: string;
          languageDirection: string;
        };
        originTextDTO: {
          originText: string;
        };
      },
    ],
  ) {
    const textObjs = texts;
    if (originTexts.length != textObjs.length) {
      throw new Error("not same size");
    }
    this._mutationController.disconnectObserver();
    for (const [index, textObj] of textObjs.entries()) {
      for (const tag of originTexts[index].tags) {
        this.adaptFunction(tag, textObj.translatedText);
        tag.setAttribute(
          "lisio-already-trans-in",
          textObj.translateLangDTO.languageISO,
        );
        if (!tag.getAttribute("lisio-original-text")) {
          tag.setAttribute(
            "lisio-original-text",
            textObj.originTextDTO.originText,
          );
        }
        tag.setAttribute("lang", textObj.translateLangDTO.languageISO);
        tag.setAttribute("xml:lang", textObj.translateLangDTO.languageISO);
        tag.setAttribute("dir", textObj.translateLangDTO.languageDirection);
        const closestUl = tag.closest("ul:not([dir])");
        if (closestUl != undefined) {
          closestUl.setAttribute(
            "dir",
            textObj.translateLangDTO.languageDirection,
          );
        }
      }
    }
  }

  private translatorAPIResponseHandler(
    this: LisioDeeplTranslationAdapter,
    datas: string,
  ) {
    const {
      keyHandler,
      texts,
    }: {
      keyHandler: string;
      texts: [
        {
          translatedText: string;
          translateLangDTO: {
            languageISO: string;
            languageDirection: string;
          };
          originTextDTO: {
            originText: string;
          };
        },
      ];
    } = JSON.parse(datas);
    const handler = this._translatorAPIBoundedResponseHandlers.get(keyHandler);
    if (handler != undefined) {
      try {
        handler(texts);
      } catch (err) {
        console.log(err);
      } finally {
        this._translatorAPIBoundedResponseHandlers.delete(keyHandler);
        if (this._translatorAPIBoundedResponseHandlers.size === 0) {
          this._mutationController.connectObserver();
        }
      }
    }
  }
}

export default LisioDeeplTranslationAdapter;
