import LisioTextTreeWalker from "../walkers/lisio-text-tree-walker";
import LisioAdapter from "./lisio-adapter";

interface LisioNavigableElement {
  array: HTMLElement[];
  currentIndex: number;
}

interface LisioNavigableElements {
  form: LisioNavigableElement;
  img: LisioNavigableElement;
  list: LisioNavigableElement;
  list_item: LisioNavigableElement;
  title: LisioNavigableElement;
}

/**
 * Class representing a keyboard navigation adapter extending an adapter.\
 * It aims to represents the font style functionality of Lisio.\
 * A keyboard navigation adapter is basically a functionality of Lisio which will change the keyboard navigation of texts in the main page.\
 */
class LisioKeyboardNavigationAdapter extends LisioAdapter<boolean> {
  private _keyDownHandlerBind = this.keyDownHandler.bind(this);
  private _navigableElements: LisioNavigableElements = {
    title: {
      array: [],
      currentIndex: 0,
    },
    list: {
      array: [],
      currentIndex: 0,
    },
    list_item: {
      array: [],
      currentIndex: 0,
    },
    form: {
      array: [],
      currentIndex: 0,
    },
    img: {
      array: [],
      currentIndex: 0,
    },
  };

  /**
   * Public method implementing abstract method adapt of Adapter
   * @param {LisioTextTreeWalker} walker - A walker to explore the DOM
   * @param {boolean} value - Value of the functionality
   * @returns Returns nothing
   * @source
   */
  public adapt(_: LisioTextTreeWalker, value: boolean): void {
    this.adaptFunction(document.documentElement, value);
    if (value) {
      const navigableElements = Array.from(
        document.body.querySelectorAll<HTMLElement>(
          "h1,h2,h3,h4,h5,h6,ul,ol,form,li,img,svg",
        ),
      );
      for (const navigableElement of navigableElements) {
        if (/h[1-6]/.test(navigableElement.localName)) {
          this._navigableElements.title.array.push(navigableElement);
        } else if (["ul", "ol"].includes(navigableElement.localName)) {
          this._navigableElements.list.array.push(navigableElement);
        } else if (navigableElement.localName === "li") {
          this._navigableElements.list_item.array.push(navigableElement);
        } else if (navigableElement.localName === "form") {
          this._navigableElements.form.array.push(navigableElement);
        } else if (
          navigableElement.localName === "img" ||
          navigableElement.localName === "svg"
        ) {
          this._navigableElements.img.array.push(navigableElement);
        }
      }
    }
  }

  protected adaptFunction(element: HTMLElement, value: boolean): void {
    if (value) {
      element.addEventListener("keydown", this._keyDownHandlerBind);
    } else {
      element.removeEventListener("keydown", this._keyDownHandlerBind);
    }
  }

  private isActuallyVisible(element: HTMLElement): boolean {
    if (!document.documentElement.contains(element)) return false;
    const style = getComputedStyle(element);
    if (
      style.display === "none" ||
      style.visibility === "hidden" ||
      style.opacity === "0" ||
      element.hidden
    ) {
      return false;
    }
    const rect = element.getBoundingClientRect();
    if (rect.width === 0 || rect.height === 0) {
      return false;
    }
    if (element.parentElement) {
      return this.isActuallyVisible(element.parentElement);
    }
    return true;
  }

  private keyDownHandler(event: KeyboardEvent) {
    const target = event.target as HTMLElement | undefined;
    if (
      event.key.toLowerCase() === "t" &&
      target?.localName != "input" &&
      target?.localName != "textarea"
    ) {
      this.navigateTo(
        this._navigableElements.title,
        this._navigableElements.title.currentIndex,
        true,
      );
    } else if (
      event.key.toLowerCase() === "l" &&
      target?.localName != "input" &&
      target?.localName != "textarea"
    ) {
      this.navigateTo(
        this._navigableElements.list,
        this._navigableElements.list.currentIndex,
        true,
      );
    } else if (
      event.key.toLowerCase() === "e" &&
      target?.localName != "input" &&
      target?.localName != "textarea"
    ) {
      this.navigateTo(
        this._navigableElements.list_item,
        this._navigableElements.list_item.currentIndex,
        true,
      );
    } else if (
      event.key.toLowerCase() === "f" &&
      target?.localName != "input" &&
      target?.localName != "textarea"
    ) {
      this.navigateTo(
        this._navigableElements.form,
        this._navigableElements.form.currentIndex,
        true,
      );
    } else if (
      event.key.toLowerCase() === "i" &&
      target?.localName != "input" &&
      target?.localName != "textarea"
    ) {
      this.navigateTo(
        this._navigableElements.img,
        this._navigableElements.img.currentIndex,
        true,
      );
    }
  }

  private navigateTo(
    navigableElement: LisioNavigableElement,
    startingIndex: number,
    isStart: boolean,
  ) {
    if (
      startingIndex !== navigableElement.currentIndex ||
      (startingIndex === navigableElement.currentIndex && isStart)
    ) {
      if (
        !this.isActuallyVisible(
          navigableElement.array[navigableElement.currentIndex],
        )
      ) {
        navigableElement.currentIndex++;
        if (navigableElement.currentIndex == navigableElement.array.length) {
          navigableElement.currentIndex = 0;
        }
        this.navigateTo(navigableElement, startingIndex, false);
      } else {
        if (
          navigableElement.array[navigableElement.currentIndex].tabIndex === -1
        ) {
          navigableElement.array[navigableElement.currentIndex].setAttribute(
            "tabindex",
            "-1",
          );
        }
        navigableElement.array[navigableElement.currentIndex].focus();
        navigableElement.array[navigableElement.currentIndex].scrollIntoView({
          behavior: "smooth",
          block: "center",
          inline: "nearest",
        });
        navigableElement.currentIndex++;
        if (navigableElement.currentIndex == navigableElement.array.length) {
          navigableElement.currentIndex = 0;
        }
      }
    }
  }
}

export default LisioKeyboardNavigationAdapter;
