import CSSProperties from "../enums/lisio-css-properties";
import { appendToHead } from "../utils";

class LisioStyleSheetController {
  private static _current: LisioStyleSheetController;

  private _alreadyInsertedSelectors = new Map<string, number>();
  private _stylesheet: CSSStyleSheet;

  private constructor() {
    const style = document.createElement("style");
    style.id = "lisio-func-styles";
    appendToHead(style);
    let rightStylesheet;
    for (const stylesheet of document.styleSheets) {
      if (
        stylesheet.ownerNode instanceof Element &&
        stylesheet.ownerNode.id === style.id
      ) {
        rightStylesheet = stylesheet;
      }
    }
    if (rightStylesheet == undefined && style.sheet != undefined) {
      this._stylesheet = style.sheet;
    } else if(rightStylesheet != undefined) {
      this._stylesheet = rightStylesheet;
    }
    else{
      throw new Error("No stylesheet");
    }
  }

  public static get current() {
    if (!this._current) {
      this._current = new LisioStyleSheetController();
    }
    return this._current;
  }

  public insertOrReplaceRule(
    properties: Map<string, string>,
    selector: string,
    oldSelector?: string,
  ) {
    if (oldSelector != undefined && oldSelector != "") {
      this.removeRule(oldSelector);
    }
    this.insertRule(selector, properties);
  }

  public insertRawRule(id: string, rawCSS: string) {
    if (!this._alreadyInsertedSelectors.has(id)) {
      for (const [key, value] of this._alreadyInsertedSelectors.entries()) {
        this._alreadyInsertedSelectors.set(key, value + 1);
      }
      this._alreadyInsertedSelectors.set(id, 0);
      this._stylesheet.insertRule(rawCSS);
    }
  }

  public insertRule(selector: string, properties: Map<string, string>) {
    if (!this._alreadyInsertedSelectors.has(selector)) {
      const cssProperties: string = Array.from(properties.entries()).reduce(
        (previousValue, [propertyName, propertyValue]) =>
          `${previousValue} ${propertyName}: ${propertyValue} !important;`,
        "",
      );
      for (const [key, value] of this._alreadyInsertedSelectors.entries()) {
        this._alreadyInsertedSelectors.set(key, value + 1);
      }
      const cssRule = `${selector}{${cssProperties}}`;
      this._alreadyInsertedSelectors.set(selector, 0);
      this._stylesheet.insertRule(cssRule);
    }
  }

  public removeRule(selector: string) {
    const ruleIndex: number | undefined =
      this._alreadyInsertedSelectors.get(selector);
    if (ruleIndex != undefined) {
      this._stylesheet.deleteRule(ruleIndex);
      this._alreadyInsertedSelectors.delete(selector);
      for (const [key, value] of this._alreadyInsertedSelectors.entries()) {
        if (value > ruleIndex) {
          this._alreadyInsertedSelectors.set(key, value - 1);
        }
      }
    }
  }

  public replaceImportantInStyleAttribute(
    propertiesToCheck: CSSProperties[],
    elementToCheck: HTMLElement,
  ) {
    for (const property of propertiesToCheck) {
      const styleAttr: string | null = elementToCheck.getAttribute("style");
      if (styleAttr != null) {
        const reg = RegExp(`(${property}\\s?:)(.*?)(!important)(;?)`, "g").exec(
          styleAttr,
        );
        if (reg != null) {
          elementToCheck.setAttribute(
            "style",
            styleAttr.replace(reg[0], `${reg[1]}${reg[2]}${reg[4]}`),
          );
        }
      }
    }
  }

  public reset() {
    for (const ruleId of this._alreadyInsertedSelectors.values()) {
      this._stylesheet.deleteRule(ruleId);
    }
    this._alreadyInsertedSelectors.clear();
  }
}

export default LisioStyleSheetController;
