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';
// eslint-disable-next-line import/no-cycle
import { GuideDirective } from './guide.directive';
import { AppStorage } from 'src/app/shared/app-storage';

@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) {
      AppStorage.setItem('guides-off', '1');
      this.activeGuides = [];
      this.hideDOM();
    } else {
      AppStorage.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 (AppStorage.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;
      let cbCopy = [...this.activeGuides];
      cbCopy.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);
    }
  }
}
