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

import { LisioReaderAdapter } from "../adapters/reader/lisio-reader-adapter";
import { Lisio } from "../lisio";
import { LisioBroadcastChannelController } from "./broadcastChannel/lisio-broadcast-channel-controller";

class LisioMutationObserverController {
  private _broadcastChannel: LisioBroadcastChannelController;
  private _canReapply = false;
  private _lisio: Lisio;
  private _mutationsQueue: MutationRecord[] = [];
  private _observer?: MutationObserver;
  private _timeout?: number;
  private _pendingConnect: boolean;
  private _pendingProcess: number;

  constructor(broadcastChannel: LisioBroadcastChannelController, lisio: Lisio) {
    this._broadcastChannel = broadcastChannel;
    this._lisio = lisio;
    this._pendingConnect = false;
    this._pendingProcess = 0;
  }

  public set canReapply(value: boolean) {
    this._canReapply = value;
  }

  public get observer() {
    return this._observer;
  }

  public modifyPendingProcess(value: number){
    this._pendingProcess += value;
    if(this._pendingConnect && this._pendingProcess <= 0){
      this._pendingProcess = 0;
      this.connectObserver();
    }
  }

  public checkObserverTagFilter(mutationList: MutationRecord[]) {
    const tagFilter = ["SCRIPT", "STYLE"];
    return (
      mutationList.findIndex((mutation) => {
        if (mutation.addedNodes.length === 1) {
          return this.checkTagNameTagFilter(mutation.addedNodes, tagFilter);
        } else if (mutation.removedNodes.length) {
          return this.checkTagNameTagFilter(mutation.removedNodes, tagFilter);
        } else {
          return 0;
        }
      }) == -1
    );
  }

  public checkTagNameTagFilter(nodes: NodeList, tagFilter: string[]) {
    return Array.from(nodes).findIndex(({ nodeName }) =>
      tagFilter.includes(nodeName),
    );
  }

  public connectObserver() {
    if(this._pendingProcess <= 0){
      this._pendingProcess = 0;
      if (this._observer != undefined) {
        this._observer.disconnect();
        this._canReapply = true;
        this._observer.takeRecords();
        this._observer.observe(document.body, {
          childList: true,
          subtree: true,
          attributes: false,
          characterData: true,
        });
      }
    }else{
      this._pendingConnect = true;
    }
  }

  public disconnectObserver() {
    this._canReapply = false;
    this._observer?.disconnect();
    if (this._timeout != undefined) {
      clearTimeout(this._timeout);
    }
  }

  public setObserver() {
    // ici mettre à true
    this._canReapply = false;
    this._mutationsQueue.splice(0, Infinity);
    this._observer = new MutationObserver((mutationList) => {
      this._observer?.takeRecords();
      if (this._canReapply && !this.checkObserverTagFilter(mutationList)) {
        for (const mutation of mutationList) {
          if (
            Array.from(mutation.addedNodes).find(
              (node) => node.textContent != "",
            ) != null ||
            mutation.removedNodes.length > 0
          ) {
            this._mutationsQueue.push(mutation);
          }
        }
        if (this._timeout == undefined) {
          clearTimeout(this._timeout);
        }
        this._timeout = setTimeout(() => {
          clearTimeout(this._timeout);
          this._timeout = undefined;
          this._observer?.disconnect();
          this._lisio.reset(false);
          this.connectObserver();

          const toSend = [];
          const toRemove = [];
          const readerAdapter = this._lisio.params.params.has(
            LisioBooleanParameterNames.READING_MODE,
          )
            ? (this._lisio.adapters.get(
                LisioBooleanParameterNames.READING_MODE,
              ) as LisioReaderAdapter | undefined)
            : undefined;
          for (const mutation of this._mutationsQueue) {
            if (mutation.type === "childList") {
              for (const addedNode of mutation.addedNodes) {
                if (
                  addedNode != undefined &&
                  addedNode.parentElement != undefined &&
                  addedNode.nodeType === 3
                ) {
                  const closestLisioIndex = (
                    addedNode instanceof HTMLElement
                      ? addedNode
                      : addedNode.parentElement
                  ).closest("[data-lisio-index]");
                  if (closestLisioIndex != undefined) {
                    const nodeIterator = document.createNodeIterator(
                      closestLisioIndex,
                      NodeFilter.SHOW_TEXT,
                      {
                        acceptNode(node) {
                          return node.textContent?.trim() === ""
                            ? NodeFilter.FILTER_REJECT
                            : NodeFilter.FILTER_ACCEPT;
                        },
                      },
                    );
                    const nodeIteratorAddedNode = document.createNodeIterator(
                      addedNode,
                      NodeFilter.SHOW_TEXT,
                      {
                        acceptNode(node) {
                          return node.textContent?.trim() === ""
                            ? NodeFilter.FILTER_REJECT
                            : NodeFilter.FILTER_ACCEPT;
                        },
                      },
                    );
                    if (
                      readerAdapter != undefined &&
                      closestLisioIndex instanceof HTMLElement &&
                      addedNode instanceof HTMLElement &&
                      closestLisioIndex.dataset.lisioClickable == undefined &&
                      (closestLisioIndex.tagName === "A" ||
                        closestLisioIndex.tagName === "BUTTON" ||
                        (closestLisioIndex.tagName === "INPUT" &&
                          closestLisioIndex.getAttribute("type") ===
                            "button") ||
                        addedNode.onclick != undefined)
                    ) {
                      closestLisioIndex.dataset.lisioClickable =
                        readerAdapter.dataLisioClickable.toString();
                      addedNode.dataset.lisioClickable =
                        readerAdapter.dataLisioClickable.toString();
                      readerAdapter.dataLisioClickable++;
                    }

                    let child;
                    while ((child = nodeIterator.nextNode())) {
                      const childAdded = nodeIteratorAddedNode.nextNode();
                      if (child.parentElement != undefined) {
                        for (const attribute of Array.from(
                          child.parentElement.attributes,
                        )) {
                          if (
                            attribute.name != "data-lisio-index" &&
                            attribute.name != "class" &&
                            attribute.name != "style"
                          ) {
                            child.parentElement.removeAttribute(attribute.name);
                          }
                        }
                        if (
                          readerAdapter != undefined &&
                          childAdded?.parentElement != undefined &&
                          child.parentElement.dataset.lisioClickable ==
                            undefined &&
                          (child.parentElement.tagName === "A" ||
                            child.parentElement.tagName === "BUTTON" ||
                            (child.parentElement.tagName === "INPUT" &&
                              child.parentElement.getAttribute("type") ===
                                "button") ||
                            child.parentElement.onclick != undefined)
                        ) {
                          child.parentElement.dataset.lisioClickable =
                            readerAdapter.dataLisioClickable.toString();
                          childAdded.parentElement.dataset.lisioClickable =
                            readerAdapter.dataLisioClickable.toString();
                          readerAdapter.dataLisioClickable++;
                        }
                      }
                    }

                    if (
                      readerAdapter != undefined &&
                      window.origin != "null" &&
                      closestLisioIndex instanceof HTMLElement
                    ) {
                      toSend.push({
                        name: closestLisioIndex.tagName,
                        index: Number.parseInt(
                          closestLisioIndex.dataset.lisioIndex || "-1",
                        ),
                        content: closestLisioIndex.innerHTML,
                        classes: closestLisioIndex.className,
                        position: readerAdapter.all.indexOf(closestLisioIndex),
                        alreadyExists: closestLisioIndex != addedNode,
                        clickable: Number.parseInt(
                          closestLisioIndex.dataset.lisioClickable || "-1",
                        ),
                      });
                    }
                  }
                }
              }

              if (location.origin != "null") {
                for (const removedNode of mutation.removedNodes) {
                  if (mutation.target instanceof Element) {
                    const closestLisioIndex =
                      mutation.target.closest("[data-lisio-index]");
                    if (
                      closestLisioIndex instanceof HTMLElement ||
                      closestLisioIndex instanceof SVGElement
                    ) {
                      const clonedNode = closestLisioIndex.cloneNode(true);
                      const nodeIterator = document.createNodeIterator(
                        clonedNode,
                        NodeFilter.SHOW_TEXT,
                        {
                          acceptNode(node) {
                            return node.textContent?.trim() === ""
                              ? NodeFilter.FILTER_REJECT
                              : NodeFilter.FILTER_ACCEPT;
                          },
                        },
                      );
                      let child;
                      while ((child = nodeIterator.nextNode())) {
                        if (child.parentElement instanceof Element) {
                          for (const attribute of Array.from(
                            child.parentElement.attributes,
                          )) {
                            if (
                              attribute.name != "data-lisio-index" &&
                              attribute.name != "class" &&
                              attribute.name != "style"
                            ) {
                              child.parentElement?.removeAttribute(
                                attribute.name,
                              );
                            }
                          }
                        }
                      }
                      if (readerAdapter != undefined) {
                        toRemove.push({
                          content: closestLisioIndex.innerHTML,
                          position:
                            readerAdapter.oldAll.indexOf(closestLisioIndex),
                          alreadyExists: closestLisioIndex != removedNode,
                        });
                      }
                    }
                  }
                }
              }
            } else if (
              mutation.type === "characterData" &&
              window.origin != "null" &&
              mutation.target.parentElement instanceof Element
            ) {
              const text =
                mutation.target.parentElement.closest("[data-lisio-index]");
              if (
                text instanceof HTMLElement &&
                text.dataset.lisioIndex != undefined &&
                mutation.target.textContent != undefined
              ) {
                const nodeIterator = document.createNodeIterator(
                  text,
                  NodeFilter.SHOW_TEXT,
                  {
                    acceptNode(node) {
                      return node.textContent?.trim() === ""
                        ? NodeFilter.FILTER_REJECT
                        : NodeFilter.FILTER_ACCEPT;
                    },
                  },
                );
                let iChild = 1;
                let child = nodeIterator.nextNode();
                while (child != null && child !== mutation.target) {
                  child = nodeIterator.nextNode();
                  iChild++;
                }
                this._broadcastChannel.sendModifiedTextMessage(
                  text.dataset.lisioIndex,
                  iChild,
                  mutation.target.textContent,
                );
              }
            }
          }
          if (readerAdapter != undefined) {
            if (toSend.length > 0) {
              this._broadcastChannel.sendNewNodesMessage(toSend);
            }

            if (toRemove.length > 0) {
              this._broadcastChannel.sendRemovedNodesMessage(toRemove);
            }
            this._mutationsQueue.splice(0, Infinity);
            toSend.splice(0, toSend.length);
            toRemove.splice(0, toSend.length);
          }
          this._lisio.apply(true);
        }, 500);
      }
    });
    this.connectObserver();
  }
}

export { LisioMutationObserverController };
