import lisioChangeChannelHandler from "../../controllers/broadcastChannel/handlers/lisio-change-channel-handler";
import lisioClickChannelHandler from "../../controllers/broadcastChannel/handlers/lisio-click-channel-handler";
import lisioClosingChannelHandler from "../../controllers/broadcastChannel/handlers/lisio-closing-channel-handler";
import lisioScrollChannelHandler from "../../controllers/broadcastChannel/handlers/lisio-scroll-channel-handler";
import { LisioBroadcastChannelController } from "../../controllers/broadcastChannel/lisio-broadcast-channel-controller";
import LisioBroadcastChannelMessagesNames from "../../enums/lisio-broadcast-channel-message-names";
import { LisioConfig } from "../../lisio-init";
import LisioMessageManager from "../../managers/lisio-message-manager";
import LisioImgToBlob from "../../misc/lisio-img-to-blob";
import { isTouchDeviceLisio } from "../../utils";
import LisioTextTreeWalker from "../../walkers/lisio-text-tree-walker";
import LisioAdapter from "../lisio-adapter";
import ExploreCalendarHelper from "./helpers/explore-calendar-helper";
import ExploreFormHelper from "./helpers/explore-form-helper";
import ExploreOtherHelper from "./helpers/explore-other-helper";
import ExploreTableHelper from "./helpers/explore-table-helper";

interface ReaderObject {
  alt?: string | undefined;
  blank?: string;
  change?: number;
  childNodes?: ReaderObject[];
  classes?: string;
  clickable?: number;
  cols?: string | undefined;
  content?: string;
  cursor?: string;
  days?: ReaderObject[];
  daysNumbers?: ReaderObject[];
  display?: string;
  duration?: number;
  events?: CalendarEvents;
  for?: string | undefined;
  hasCaption?: boolean;
  height?: string;
  href?: string;
  iCalendar?: number;
  id?: string;
  index?: string;
  insert?: string;
  isHidden?: boolean;
  isInSlider?: boolean;
  isShadowRootCalendar?: boolean;
  loading?: string | undefined;
  maybeACalendar?: boolean;
  maybeAnEvent?: boolean;
  month?: ReaderObject[];
  name?: string;
  nextButtonClickable?: string;
  numberEvent?: number;
  objectFit?: string;
  objectPosition?: string;
  parent?: number;
  placeholder?: string | undefined;
  position: number;
  prevButtonClickable?: string;
  rows?: string | undefined;
  selectedOptionContent?: string;
  src?: string;
  startingDate?: number;
  startingMonth?: number;
  startingYear?: number;
  type?: string;
  value?: string;
  width?: string;
  year?: ReaderObject[];
}

/**
 * Class representing a reader adapter extending an adapter.\
 * It aims to represents the font style functionality of Lisio.\
 * A reader adapter is basically a functionality of Lisio which will change the reader of texts in the main page.\
 */
class LisioReaderAdapter extends LisioAdapter<boolean> {
  private _all: (HTMLElement | SVGElement)[] = [];
  private _askTimeout = -1;
  private _broadcastChannel: LisioBroadcastChannelController;
  private _dataLisioChange = 0;
  private _dataLisioClickable = 0;
  private _exploreCalendarHelper: ExploreCalendarHelper;
  private _exploreFormHelper: ExploreFormHelper;
  private _exploreOtherHelper: ExploreOtherHelper;
  private _exploreTableHelper: ExploreTableHelper;
  private _lisioConfig: LisioConfig;
  private _oldAll: (HTMLElement | SVGElement)[] = [];
  private _stylesToAdd = new Map<string, string>();
  private _toSend: ReaderObject[] = [];

  /**
   *
   * @param {LisioBroadcastChannelController} broadcastChannel - A broadcast channel controler to communicate with reader
   * @param styleSheet
   */
  constructor(
    broadcastChannel: LisioBroadcastChannelController,
    lisioConfig: LisioConfig,
  ) {
    super();
    this._broadcastChannel = broadcastChannel;
    this._broadcastChannel.attachHandler(
      LisioBroadcastChannelMessagesNames.BCMS_READY,
      this.lisioReadyChannelHandler.bind(this),
    );
    this._broadcastChannel.attachHandler(
      LisioBroadcastChannelMessagesNames.BCMS_ON_CHANGE,
      lisioChangeChannelHandler.bind(this),
    );
    this._broadcastChannel.attachHandler(
      LisioBroadcastChannelMessagesNames.BCMS_ON_CLICK,
      lisioClickChannelHandler.bind(this),
    );
    this._broadcastChannel.attachHandler(
      LisioBroadcastChannelMessagesNames.BCMS_SCROLL_TO,
      lisioScrollChannelHandler.bind(this),
    );
    this._broadcastChannel.attachHandler(
      LisioBroadcastChannelMessagesNames.BCMS_NAV_RESPONSE,
      lisioClosingChannelHandler.bind(this),
    );
    this._exploreCalendarHelper = new ExploreCalendarHelper(this);
    this._exploreTableHelper = new ExploreTableHelper(
      this._exploreCalendarHelper,
    );
    this._exploreFormHelper = new ExploreFormHelper(
      this._exploreCalendarHelper,
    );
    this._exploreOtherHelper = new ExploreOtherHelper(
      this._exploreCalendarHelper,
    );
    this._lisioConfig = lisioConfig;
  }

  public get all() {
    return this._all;
  }

  public get broadcastChannel() {
    return this._broadcastChannel;
  }

  public get dataLisioChange() {
    return this._dataLisioChange;
  }

  public set dataLisioChange(value: number) {
    this._dataLisioChange = value;
  }

  public get dataLisioClickable() {
    return this._dataLisioClickable;
  }

  public set dataLisioClickable(value: number) {
    this._dataLisioClickable = value;
  }

  public get oldAll() {
    return this._oldAll;
  }

  public get stylesToAdd() {
    return this._stylesToAdd;
  }

  public get toSend() {
    return this._toSend;
  }

  /**
   * 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(_: LisioTextTreeWalker, value: boolean): void {
    if (value) {
      window.localStorage.setItem("lisio_reading_mode", "1");

      this._broadcastChannel.sendAnyoneThereMessage({
        value: window.location.hostname,
      });
      this._askTimeout = setTimeout(() => {
        console.trace("open reading mode");
        if (__BUILD_TARGET__ === "extension") {
          LisioMessageManager.current.sendOpenSidePanel();
        } else {
          const loadingImg = document.createElement("img");
          loadingImg.id = "lisio-loading";
          loadingImg.src = `${import.meta.env.VITE_LISIO_DOMAIN}`;
          const reading_page = window.open(
            "",
            "_blank",
            `height=${screen.height},width=${
              screen.width * 0.98
            },scrollbars=yes,status=yes`,
          );
          if (reading_page != undefined) {
            // reading_page.document.body.append(loadingImg);
            const style = document.createElement("style");
            style.textContent += `h1, h2, h3 {color: ${this._lisioConfig.userSettings.colorPrimary}}`;
            reading_page.document.body.append(style);
            const script2 = document.createElement("script");
            script2.textContent = `
            window.lisioAccesskey = "${window.lisioAccesskey}";
            const lisioConfig = JSON.parse(\`${JSON.stringify(
              this._lisioConfig,
            )}\`)`;
            reading_page.document.body.append(script2);
            const script = document.createElement("script");
            script.src = `${import.meta.env.VITE_LISIO_DOMAIN}/solution/dist-site/readingMode.js`;
            script.type = "module";
            reading_page.document.body.append(script);
            const script1 = document.createElement("script");
            script1.src = `${import.meta.env.VITE_LISIO_DOMAIN}/solution/dist-site/lisioinit.js`;
            script1.type = "module";
            reading_page.document.body.append(script1);
          } else {
            console.log("can't open popup");
          }
        }
      }, 1000);
    } else {
      window.localStorage.removeItem("lisio_reading_mode");
      this._broadcastChannel.sendCloseMessage();
    }
  }

  public handleImage(media: HTMLImageElement): ReaderObject {
    const styles = window.getComputedStyle(media);
    const display = styles.display;
    const objectFit = styles.objectFit;
    const objectPosition = styles.objectPosition;
    const visibility = styles.visibility;
    const opacity = styles.opacity;
    const indexOfMedia = this._all.indexOf(media);
    let lisioClickable: number | undefined;

    if (styles.cursor === "pointer" || media.onclick != undefined) {
      lisioClickable = this._dataLisioClickable;
      media.dataset.lisioClickable = this._dataLisioClickable.toString();
      this._dataLisioClickable++;
    }

    media.dataset.lisioPosition = indexOfMedia.toString();
    const datas: ReaderObject = {
      src: media.currentSrc === "" ? media.src : media.currentSrc,
      width: styles.width.slice(0, styles.width.length - 2),
      height: styles.height.slice(0, styles.height.length - 2),
      alt: media.getAttribute("alt") as string | undefined,
      objectFit,
      objectPosition,
      cursor: styles.cursor,
      display,
      position: indexOfMedia,
      insert: indexOfMedia === 0 ? "before" : "after",
      clickable: lisioClickable,
      isInSlider: false,
      isHidden:
        media.offsetParent == undefined ||
        visibility === "hidden" ||
        parseInt(opacity) === 0,
    };

    return datas;
  }

  public handleStyles(
    tag: HTMLElement | SVGElement,
    tagInHTML: HTMLElement | SVGElement,
  ) {
    let fontWeight = tag.style.fontWeight;
    let fontStyle = tag.style.fontStyle;
    let display = tag.style.display;
    let opacity = tag.style.opacity;
    let visibility = tag.style.visibility;
    let cursor = tag.style.cursor;
    let textTransform = tag.style.textTransform;
    let pointerEvents = tag.style.pointerEvents;
    if (
      fontWeight === "" ||
      fontStyle == "" ||
      display == "" ||
      opacity == "" ||
      visibility == "" ||
      cursor == "" ||
      textTransform == "" ||
      pointerEvents == ""
    ) {
      const computedStyles = window.getComputedStyle(tagInHTML);
      fontWeight = computedStyles.fontWeight;
      fontStyle = computedStyles.fontStyle;
      display = computedStyles.display;
      opacity = computedStyles.opacity;
      visibility = computedStyles.visibility;
      cursor = computedStyles.cursor;
      textTransform = computedStyles.textTransform;
      pointerEvents = computedStyles.pointerEvents;
    }

    if (tag instanceof HTMLElement) {
      tag.className = "";
    }
    try {
      /* empty */
    } catch (error) {
      console.log(tag, error);
    }
    if (fontWeight != "") {
      const className = `fw-${fontWeight}`;
      tag.classList.add(className);
      if (!this._stylesToAdd.has(className)) {
        this._stylesToAdd.set(className, `font-weight : ${fontWeight}`);
      }
    }
    if (fontStyle != "") {
      const className = `fs-${fontStyle}`;
      tag.classList.add(className);
      if (!this._stylesToAdd.has(className)) {
        this._stylesToAdd.set(className, `font-style : ${fontStyle}`);
      }
    }
    if (cursor != "") {
      const cursorName = this.simpleHash(cursor);
      const className = `cr-${cursorName}`;
      tag.classList.add(className);
      if (!this._stylesToAdd.has(className)) {
        this._stylesToAdd.set(className, `cursor : ${cursor}`);
      }
    }

    if (pointerEvents != "") {
      const className = `pe-${pointerEvents}`;
      tag.classList.add(className);
      if (!this._stylesToAdd.has(className)) {
        this._stylesToAdd.set(className, `pointer-events : ${pointerEvents}`);
      }
    }

    if (cursor === "pointer" && pointerEvents != "none") {
      tag.dataset.lisioClickable = this._dataLisioClickable.toString();
      tagInHTML.dataset.lisioClickable = this._dataLisioClickable.toString();
      this._dataLisioClickable++;
    }

    if (textTransform !== "none") {
      const className = `tt-${textTransform}`;
      tag.classList.add(className);
      if (!this._stylesToAdd.has(className)) {
        this._stylesToAdd.set(className, `text-transform : ${textTransform}`);
      }
    }
    return tagInHTML instanceof HTMLElement
      ? tagInHTML.offsetParent == undefined ||
          visibility === "hidden" ||
          parseFloat(opacity) === 0
      : visibility === "hidden" || parseFloat(opacity) === 0;
  }

  public handleVideo(video: HTMLVideoElement): string {
    video.pause();
    const source = video.querySelector("source");
    return source?.src || video.src;
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  public async lisioReadyChannelHandler(_: string) {
    const mediaConverter = new LisioImgToBlob();

    clearTimeout(this._askTimeout);
    const allBackgroundImageTags = this.fillAll();
    for (const tag of this._all) {
      if (tag.closest<HTMLElement>("table") != undefined) {
        await this._exploreTableHelper.exploreTable(this, tag, mediaConverter);
      } else if (tag.closest<HTMLElement>("form") != undefined) {
        await this._exploreFormHelper.exploreForm(this, tag, mediaConverter);
      } else {
        await this._exploreOtherHelper.exploreOther(
          this,
          tag,
          mediaConverter,
          allBackgroundImageTags,
        );
      }
    }
    for (let i = 0; i < this._exploreTableHelper.tables.length; i++) {
      if (this._exploreTableHelper.tables[i].childNodes?.length === 0) {
        const iT = this._toSend.indexOf(this._exploreTableHelper.tables[i]);
        if (iT > -1) {
          this._toSend.splice(iT, 1);
          this._exploreTableHelper.tables.pop();
          i--;
        }
      }
    }
    console.log(this._toSend, mediaConverter.base64s, this._stylesToAdd);

    this._broadcastChannel.sendInitHTMLMessage({
      back: document.referrer ? new URL(document.referrer).origin : "",
      isMobile: isTouchDeviceLisio(),
      texts: this._toSend,
      styles: Object.fromEntries(this._stylesToAdd.entries()),
      domain: window.location.hostname,
      base64s: Object.fromEntries(mediaConverter.base64s),
      calendarLang: this._exploreCalendarHelper.calendarLang as "fr" | "en",
    });
  }

  public reset() {
    this._all.splice(0, Infinity);
    this._oldAll.splice(0, Infinity);
  }

  public simpleHash(string: string): string {
    let hash = 0;
    for (let i = 0; i < string.length; i++) {
      const char = string.charCodeAt(i);
      hash = (hash << 5) - hash + char;
    }
    return (hash >>> 0).toString(36).padStart(7, "0");
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
  protected adaptFunction(_element: HTMLElement, _value: boolean): void {}

  private fillAll() {
    const { extraSelectors, allBackgroundImageTags } = this.getSelectors();
    const excludedTags = [
      "input",
      "select",
      "textarea",
      'button[type="submit"]',
      'button[type="reset"]',
      "label",
      "meter",
      "form",
    ];
    const excludedTagsSelector = excludedTags.join(", ");
    const filteredTags = Array.from(
      new Set(
        Array.from(
          document.querySelectorAll<HTMLElement | SVGElement>(
            `[data-lisio-index]:not(svg *, video, option), img:not([lisio-no-gather]):not(#lisio-label *):not(#lisio-disable-band *), svg:not(#lisio-label *):not(#lisio-disable-band *), video, input:not([type="hidden"]), select, textarea, button[type="submit"], button[type="reset"], label, meter, form, table, thead, tr, th, td, tbody, embed[type="application/pdf"], object[type="application/pdf"], iframe${
              extraSelectors.length > 0 ? `, ${extraSelectors}` : ""
            }`,
          ),
        ),
      ),
    ).filter((node) => {
      const isInForm =
        node.closest<HTMLElement>("form") != undefined &&
        node.closest<HTMLElement>("table") == undefined;
      const isExcludedTag = excludedTags.includes(node.localName);
      const hasChildExcludedTag =
        node.querySelector(excludedTagsSelector) != undefined;

      if (
        (isInForm && !isExcludedTag) ||
        ((node.closest<HTMLElement>("form") != undefined ||
          node.closest<HTMLElement>("table") != undefined) &&
          Array.from(node.children).filter((elt) => elt.localName === "select")
            .length > 0)
      ) {
        return !hasChildExcludedTag;
      }

      return true;
    }); //new Set(this.tags.filter(tag=>tag.getAttribute("data-lisio-index") != undefined && tag.closest<HTMLElement>("table") == undefined && tag.closest<HTMLElement>("form") == undefined));
    const easypickerI = filteredTags.findIndex((elt) =>
      elt.classList.contains("easepick-wrapper"),
    );
    if (easypickerI > -1 && filteredTags[easypickerI].shadowRoot != undefined) {
      this._exploreCalendarHelper.shadowRootCalendar =
        filteredTags[easypickerI].shadowRoot;
      const allNodes = filteredTags[easypickerI].shadowRoot.querySelectorAll<
        HTMLElement | SVGElement
      >(".month-name, .previous-button, .next-button, .dayname, .day");
      filteredTags.splice(easypickerI, 1, ...allNodes);
    }
    this._all.splice(0, Infinity);
    this._all.push(...filteredTags);
    return allBackgroundImageTags;
  }

  private getSelectors() {
    const selectorsWithBackgroundImage = new Map<string, string>();
    selectorsWithBackgroundImage.set(
      '[style*="background"]:not(#lisio-label, #lisio-label *, [style*="background: none;"])',
      "",
    );
    for (const stylesSheet of document.styleSheets) {
      if (
        stylesSheet.href == undefined ||
        !stylesSheet.href.includes("lisio")
      ) {
        try {
          for (const cssRule of stylesSheet.cssRules) {
            if (cssRule instanceof CSSStyleRule) {
              const backgroundImageSrc =
                cssRule.style?.getPropertyValue("background-image");
              if (backgroundImageSrc?.includes("url(")) {
                selectorsWithBackgroundImage.set(
                  cssRule.selectorText,
                  backgroundImageSrc.slice(4, backgroundImageSrc.length - 1),
                );
              }
            }
          }
        } catch (error) {
          console.log(error, stylesSheet);
        }
      }
    }
    const extraSelectorsArray = Array.from(selectorsWithBackgroundImage.keys());
    extraSelectorsArray.push(".easepick-wrapper");
    const extraSelectors = extraSelectorsArray.join(",");
    const allBackgroundImageTags = Array.from(
      document.querySelectorAll<HTMLElement | SVGElement>(extraSelectors),
    );
    return { extraSelectors, allBackgroundImageTags };
  }
}

type CalendarEvents = Record<
  number,
  Record<number, Record<number, Record<number, ReaderObject[]>>>
>;

export { LisioReaderAdapter };

export type { ReaderObject, CalendarEvents };
