import { Renderer2 } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

const CSS_CLASS_EXPANSION: string = `knk-track-knok-expansion`;
const CSS_CLASS_EXPANSION_HEADLINE: string = `${CSS_CLASS_EXPANSION}__headline`;
const CSS_CLASS_EXPANSION_CONTENT: string = `${CSS_CLASS_EXPANSION}__content`;
const CSS_CLASS_EXPANSION_CONTENT_EXPANDED: string = `${CSS_CLASS_EXPANSION_CONTENT}--expanded`;

interface IControlRoleToggleData {
  [content: string]: string | undefined;
}

export class ExpansionPanels {
  private renderer: Renderer2;
  private host: HTMLElement;
  private observers: MutationObserver[] = [];

  public static init(host: HTMLElement, renderer: Renderer2): ExpansionPanels {
    return new ExpansionPanels(host, renderer);
  }

  public static destroy(instance: ExpansionPanels): void {
    instance.destroy();
  }

  public constructor(host: HTMLElement, renderer: Renderer2) {
    this.renderer = renderer;
    this.host = host;
    this.run();
  }

  private monitor(content: HTMLElement, mutations: MutationRecord[], observer: MutationObserver): void {
    for (let mutation of mutations) {
      switch (mutation.type) {
        case 'childList':
          const timeout: any = setTimeout((): void => {
            if (this.isHTMLElement(content)) {
              if (content.classList.contains(CSS_CLASS_EXPANSION_CONTENT_EXPANDED) || content.style.maxHeight) {
                content.style.maxHeight = `${content.scrollHeight}px`;
              }
            }
            clearTimeout(timeout);
          }, 10);
          break;
        default:
      }
    }
  }

  private destroy(): void {
    if (Array.isArray(this.observers)) {
      this.observers.forEach((observer: MutationObserver | undefined): void => {
        if (observer) {
          observer.disconnect();
        }
      });
    }
  }

  private run(): void {
    const panels: HTMLElement[] = this.getPanels(this.host);
    panels.forEach((element: HTMLElement, index: number): void => {
      const header: HTMLElement = this.getHeader(element);
      const content: HTMLElement = this.getContent(element);
      const toggle: HTMLElement = this.getToggle(element);

      if (this.isHTMLElement(content)) {
        const observer: MutationObserver = new MutationObserver((mutations: MutationRecord[], observer: MutationObserver): void => {
          this.monitor(content, mutations, observer);
        });
        observer.observe(content, {
          attributeOldValue: false,
          attributes: false,
          characterData: false,
          characterDataOldValue: false,
          childList: true,
          subtree: true
        });
        this.observers.push(observer);

        if (content.classList.contains(CSS_CLASS_EXPANSION_CONTENT_EXPANDED)) {
          if (this.isHTMLElement(toggle)) {
            const {toggleOn}: { toggleOn: IControlRoleToggleData } = this.parseToggleData(toggle);
            toggle.innerHTML = toggleOn.content || toggle.innerHTML;
          }
          content.style.maxHeight = `${content.scrollHeight}px`;
        }
      }

      if (this.isHTMLElement(header)) {
        this.renderer.listen(header, 'click', (event: Event): boolean | void => {
          if (this.isHTMLElement(content)) {
            content.classList.toggle(CSS_CLASS_EXPANSION_CONTENT_EXPANDED);
            if (content.style.maxHeight) {
              if (this.isHTMLElement(toggle)) {
                const {toggleOff}: { toggleOff: IControlRoleToggleData } = this.parseToggleData(toggle);
                toggle.innerHTML = toggleOff.content || toggle.innerHTML;
              }
              content.style.maxHeight = null;
            } else {
              if (this.isHTMLElement(toggle)) {
                const {toggleOn}: { toggleOn: IControlRoleToggleData } = this.parseToggleData(toggle);
                toggle.innerHTML = toggleOn.content || toggle.innerHTML;
              }
              content.style.maxHeight = `${content.scrollHeight}px`;
            }
          }
        });
      }
    }, this);
  }

  private getPanels(element: HTMLElement): HTMLElement[] {
    const panels: NodeList = element.querySelectorAll(`[data-control='expansion-panel']`);
    return Object.assign([], panels) as HTMLElement[];
  }

  private getHeader(element: HTMLElement): HTMLElement {
    const header: HTMLElement = element.querySelector(`[data-control-role='header']`) as HTMLElement;
    return header;
  }

  private getContent(element: HTMLElement): HTMLElement {
    const content: HTMLElement = element.querySelector(`[data-control-role='content']`) as HTMLElement;
    return content;
  }

  private getToggle(element: HTMLElement): HTMLElement {
    const toggle: HTMLElement = element.querySelector(`[data-control-role='toggle']`) as HTMLElement;
    return toggle;
  }

  private parseToggleData(element: HTMLElement): { toggleOn: IControlRoleToggleData, toggleOff: IControlRoleToggleData } {
    const toggleOn: IControlRoleToggleData = Object.assign({content: undefined}, JSON.parse(element.dataset.toggleOn || null));
    const toggleOff: IControlRoleToggleData = Object.assign({content: undefined}, JSON.parse(element.dataset.toggleOff || null));
    const toggleData: { toggleOn: IControlRoleToggleData, toggleOff: IControlRoleToggleData } = {toggleOn, toggleOff};
    return toggleData;
  }

  private isHTMLElement(element: any): boolean {
    return (element !== null && element !== undefined) && element instanceof HTMLElement;
  }
}
