import { DOCUMENT } from "@angular/common";
import { HostListener, Inject, Injectable, Renderer2, RendererFactory2 } from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { marker } from "@biesbjerg/ngx-translate-extract-marker";
import { TranslateService } from "@ngx-translate/core";
import { RouterHandler } from "../services/router-handler.service";
import { GuideDirective } from "./guide.directive";

@Injectable({
  providedIn: "root",
})
export class GuideService {
  private activeGuides: GuideDirective[] = [];

  private guideHighlightDOMElement;
  private guideDisableFieldDOMElement;
  private guideDescDOMElement;
  private guideHighlightDOMElementBoundingClientRect: DOMRect;
  private guideDisableFieldDOMElementBoundingClientRect: DOMRect;
  private guideDescDOMElementBoundingClientRect: DOMRect;
  private guideTextDOMElement;

  /**
   * Check if the guide Input params are ready (ngOnInit called)
   */
  private readyGuides: number = 0;

  private highlightShadowSize: number = 40; // px

  /**
   * We will check the current site, if it is change, we reset the guide array
   */
  private currentPage: string;

  private renderer: Renderer2;

  private isGuideStarted: boolean = false;

  private ignoreThisPageGuides: boolean = false;

  /**
   * If somewhy you can not see the click target, you can leave the tutotial
   * if you click on the fog sometimes
   */
  private clickedOnDisableField: number = 0;
  private maxClickOnDisableFieldToEscape: number = 2;

  private _isGuideSystemOn: boolean = true;

  public turnGuideSystem(isActive: boolean){
    this._isGuideSystemOn = isActive;
    if (!isActive){
      localStorage.setItem('guides-off', "1");
      this.activeGuides = [];
      this.hideDOM();
    }
    else{
      localStorage.removeItem('guides-off');
    }
  }

  public isGuideSystemOn(): boolean{
    return this._isGuideSystemOn;
  }

  constructor(
    private routerHandler: RouterHandler,
    private translateService: TranslateService,
    rendererFactory: RendererFactory2,
    private matDialog: MatDialog,
    @Inject(DOCUMENT) private document: Document
  ) {
    if (localStorage.getItem('guides-off') == "1"){
      this._isGuideSystemOn = false;
    }

    this.renderer = rendererFactory.createRenderer(null, null);

    // create DOM for guides

    // pulsing click here bubble
    this.guideHighlightDOMElement = this.renderer.createElement("div");
    this.renderer.setAttribute(
      this.guideHighlightDOMElement,
      "id",
      "guide-highlight"
    );
    this.renderer.appendChild(
      this.document.body,
      this.guideHighlightDOMElement
    );

    // black fog
    this.guideDisableFieldDOMElement = this.renderer.createElement("div");
    this.renderer.setAttribute(
      this.guideDisableFieldDOMElement,
      "id",
      "guide-disable-field"
    );
    this.guideDisableFieldDOMElement.addEventListener('click', ()=>{
      this.clickedOnDisableField++;
      if (this.clickedOnDisableField == this.maxClickOnDisableFieldToEscape){
        this.activeGuides.splice(0, 1);
        this.callNextGuide();
        this.clickedOnDisableField = 0;
      }
    })

    this.renderer.appendChild(
      this.document.body,
      this.guideDisableFieldDOMElement
    );

    // description
    this.guideDescDOMElement = this.renderer.createElement("div");
    this.renderer.setAttribute(this.guideDescDOMElement, "id", "guide-desc");
    this.renderer.appendChild(this.document.body, this.guideDescDOMElement);

    // desc text
    this.guideTextDOMElement = this.renderer.createElement("p");
    this.renderer.appendChild(this.guideDescDOMElement, this.guideTextDOMElement);

    // skip btn
    let skipTextBtn = this.renderer.createElement("p");
    this.renderer.setAttribute(skipTextBtn, "id", "guide-skip");
    this.renderer.appendChild(this.guideDescDOMElement, skipTextBtn);

    let skipSpan = this.renderer.createElement("span");
    skipSpan.innerHTML = this.translateService.instant(marker("Skip guide"));
    this.renderer.appendChild(skipTextBtn, skipSpan)
    // skip all the guide what is loaded here
    skipSpan.addEventListener('click', ()=>{
      this.ignoreThisPageGuides = true;
      this.activeGuides.forEach((g)=>{
        g.markAsSeen();
      })
      this.activeGuides = [];
      this.callNextGuide();
    })

    let skipForEverSpan = this.renderer.createElement("span");
    skipForEverSpan.innerHTML = this.translateService.instant(marker("Turn off guide system"));
    this.renderer.appendChild(skipTextBtn, skipForEverSpan)
    // skip all the guide what is loaded here
    skipForEverSpan.addEventListener('click', ()=>{
      this.turnGuideSystem(false);
    })

    // calculate their dimensions
    this.guideHighlightDOMElementBoundingClientRect =
      this.guideHighlightDOMElement.getBoundingClientRect();

    this.guideDisableFieldDOMElementBoundingClientRect =
      this.guideDisableFieldDOMElement.getBoundingClientRect();

    this.guideDescDOMElementBoundingClientRect =
      this.guideDescDOMElement.getBoundingClientRect();

    // hide them
    this.hideDOM();

    // forward the click event to the host element
    this.guideHighlightDOMElement.addEventListener("click", () => {
      if (this.activeGuides.length > 0) {
        this.activeGuides[0].getElementNode().click();
      }
    });

    window.addEventListener('resize', ()=>{
      if (this.isGuideStarted && this.activeGuides.length > 0 && !this.showDOM(this.activeGuides[0])){
        this.activeGuides.splice(0, 1);
        this.callNextGuide();
      }
    })
  }

  /**
   * Only call this if the DOM is ready (ngOnInit)
   * This is only for directives
   * @param guide
   */
  public subscribeDirective(guide: GuideDirective) {
    if (!this.isGuideSystemOn()) return;

    let page = this.routerHandler.getRoute().url;

    if (this.currentPage != page) {
      // page changed, so reset the state
      this.currentPage = page;
      this.activeGuides = [];
      this.isGuideStarted = false;
      this.readyGuides = 0;
      this.ignoreThisPageGuides = false;
    }

    if (!this.ignoreThisPageGuides){
      this.activeGuides.push(guide);
    }
  }

  /**
   * Every guide should call this on ngOnInit, we need to load their Input params
   */
  public startGuide() {
    if (!this.isGuideSystemOn()) return;

    this.readyGuides++;

    if (!this.isGuideStarted && this.readyGuides == this.activeGuides.length) {
      this.isGuideStarted = true;
      this.sortGuides();

      this.matDialog.afterAllClosed.subscribe(()=>{
        this.callNextGuide();
      })
    }
  }

  private sortGuides() {
    this.activeGuides = this.activeGuides.sort((a, b) => {
      return a.getStep() - b.getStep();
    });
  }

  @HostListener('window:resize', ['$event'])
  resize(event: MouseEvent) {
    console.log('resize')
    this.showDOM(this.activeGuides[0]);
  }

  private callNextGuide() {
    if (!this.isGuideSystemOn()) return;

    if (this.activeGuides.length > 0) {
      this.clickedOnDisableField = 0;

      this.activeGuides[0].getElementNode().scrollIntoView({
        block: "center"
      });
      setTimeout(() => {
        if (this.activeGuides[0] && this.isElementVisible(this.activeGuides[0])) {
          if (this.showDOM(this.activeGuides[0])) {
            let currentGuide = this.activeGuides[0];
            currentGuide.activate().finally(() => {
              this.hideDOM();
              // wait for animations or events a little bit
              setTimeout(() => {
                // if we still have the guide (out of the window DOM refresh can remove it)
                if (this.activeGuides.indexOf(currentGuide) > -1){
                  this.activeGuides.splice(0, 1);
                  this.callNextGuide();
                }
              }, 200);
            });
          } else {
            console.warn("Guide textbox is too big for this screen", this.activeGuides[0].getElementNode());
            this.activeGuides.splice(0, 1);
            this.hideDOM();
            this.callNextGuide();
          }
        } else {
          console.warn(
            "Element for guide is out of the window",
            this.activeGuides[0]?.getElementNode()
          );
          this.activeGuides.splice(0, 1);
          this.callNextGuide();
        }
      }, 110);
    } else {
      console.log('guide done')
      this.hideDOM();
      this.isGuideStarted = false;
      this.readyGuides = 0;
      this.clickedOnDisableField = 0;
    }
  }

  private isElementVisible(el: GuideDirective): boolean {
    let { top, left, width, height } = el
      .getElementNode()
      .getBoundingClientRect();

    let style = window.getComputedStyle(el.getElementNode());

    return (
      style.display !== "none" &&
      style.opacity !== "0" &&
      // let some plus space for the flout numbers
      top >= -6 &&
      top + height <= window.innerHeight + 6 &&
      left >= -6 &&
      left + width <= window.innerWidth + 6
    );
  }

  private showDOM(el: GuideDirective): boolean {
    let { top, left, width, height } = el
      .getElementNode()
      .getBoundingClientRect();

    if (width == 0 || height == 0) return false;

    // this element size is dinamically change by the text, so calculate it
    this.guideTextDOMElement.innerHTML = el.getGuideText();
    this.guideDescDOMElement.style.display = "block";
    this.guideDescDOMElementBoundingClientRect =
      this.guideDescDOMElement.getBoundingClientRect();

    this.guideHighlightDOMElement.style.top =
      top +
      height / 2 -
      this.guideHighlightDOMElementBoundingClientRect.height / 2 +
      "px";
    this.guideHighlightDOMElement.style.left =
      left +
      width / 2 -
      this.guideHighlightDOMElementBoundingClientRect.width / 2 +
      "px";
    this.guideHighlightDOMElement.style.display = "block";

    this.guideDisableFieldDOMElement.style.top =
      top +
      height / 2 -
      this.guideDisableFieldDOMElementBoundingClientRect.height / 2 +
      "px";
    this.guideDisableFieldDOMElement.style.left =
      left +
      width / 2 -
      this.guideDisableFieldDOMElementBoundingClientRect.width / 2 +
      "px";
    this.guideDisableFieldDOMElement.style.display = "block";

    // showing the description field is a bit more complex
    // decide where it can fit
    // do not forget to add the pulsing range
    let descLeft =
      left + width / 2 - this.guideDescDOMElementBoundingClientRect.width / 2;

    let descTop =
      top + height / 2 - this.guideDescDOMElementBoundingClientRect.height / 2;

    // is there enough place on the right?
    let foundPos = false;

    let rightPos =
      left +
      width / 2 +
      this.guideHighlightDOMElementBoundingClientRect.width / 2 +
      this.highlightShadowSize;

    foundPos =
      rightPos + this.guideDescDOMElementBoundingClientRect.width <=
      window.innerWidth;
    if (foundPos) {
      descLeft = rightPos;
      descTop = Math.max(
        0,
        Math.min(
          descTop,
          window.innerHeight - this.guideDescDOMElementBoundingClientRect.height
        )
      );
    } else {
      // on its left?
      let leftPos =
        left +
        width / 2 -
        this.guideHighlightDOMElementBoundingClientRect.width / 2 -
        this.highlightShadowSize -
        this.guideDescDOMElementBoundingClientRect.width;

      foundPos = leftPos >= 0;

      if (foundPos) {
        descLeft = leftPos;
        descTop = Math.max(
          0,
          Math.min(
            descTop,
            window.innerHeight -
              this.guideDescDOMElementBoundingClientRect.height
          )
        );
      } else {
        // maybe the bottom section?
        let bottomPos =
          top +
          height / 2 +
          this.guideHighlightDOMElementBoundingClientRect.height / 2 +
          this.highlightShadowSize;

        foundPos =
          bottomPos + this.guideDescDOMElementBoundingClientRect.height <=
          window.innerHeight;

        if (foundPos) {
          descTop = bottomPos;
          descLeft = Math.max(
            0,
            Math.min(
              descLeft,
              window.innerWidth -
                this.guideDescDOMElementBoundingClientRect.width
            )
          );
        } else {
          // maybe the top section?
          let topPos =
            top +
            height / 2 -
            this.guideHighlightDOMElementBoundingClientRect.height / 2 -
            this.highlightShadowSize -
            this.guideDescDOMElementBoundingClientRect.height;

          foundPos = topPos >= 0;

          if (foundPos) {
            descTop = topPos;
            descLeft = Math.max(
              0,
              Math.min(
                descLeft,
                window.innerWidth -
                  this.guideDescDOMElementBoundingClientRect.width
              )
            );
          } else {
            return false;
          }
        }
      }
    }

    this.guideDescDOMElement.style.left = descLeft + "px";
    this.guideDescDOMElement.style.top = descTop + "px";

    return true;
  }

  private hideDOM() {
    [
      this.guideHighlightDOMElement,
      this.guideDisableFieldDOMElement,
      this.guideDescDOMElement,
    ].forEach((el) => {
      el.style.display = "none";
    });
  }

  public unsubscribe(guide: GuideDirective) {
    let pos = this.activeGuides.indexOf(guide);
    if (pos > -1) {
      this.activeGuides.splice(pos, 1);
    }
  }


}
