import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Api } from './services/api';
import { Widget } from './components/Widget';
import { IData, ISettings, IDebug, Sources, Config, MinContentEvent } from './types';
import { Debug } from './debug';
import { Helpers } from './services/helpers';
import { DEFAULT_CONFIG, initial, StateSetter, StoreProvider, StoreState } from './services/store';
import { WidgetEvents } from './services/events';

class AdalongWidget extends WidgetEvents {
  /**
   * Layout of the widget: mosaic, carousel or wall
   * This information is known once the widget has been correctly loaded
   */
  public layout?: ISettings['type'];
  public loaded: boolean = false;
  public storeState: { getState: () => StoreState; stateSetter: StateSetter } = { getState: () => initial, stateSetter: () => {} };
  private _id = Helpers.randomId();
  private token?: string;
  private startDate = new Date();
  private config: Config;
  /* React Portals variables */
  private postViewerPortalId?: string;
  private portalDiv?: HTMLDivElement;

  /**
   * @param token Widget token
   */
  constructor(token?: string, config?: Config) {
    super();
    this.config = {
      ...DEFAULT_CONFIG,
      ...config,
    };
    if (this.config.id !== undefined) {
      this._id = this.config.id;
    }
    this.token = token;
    this.createAdalongAPI();
    this.emitWidgetCreated();
    this.createPostViewerPortal();
    Debug.try(() => {
      this.addFonts();
    });
  }

  /**
   * Get the readonly widget identifier
   */
  public get id() {
    return this._id;
  }

  public getSlideState(): { canSlideLeft: boolean; canSlideRight: boolean } {
    return {
      canSlideLeft: this.storeState.getState().canSlideLeft,
      canSlideRight: this.storeState.getState().canSlideRight,
    };
  }

  public changePost(dir: 'left' | 'right') {
    dispatchEvent(new CustomEvent('adalongWidget_changePost', { detail: dir }));
  }

  public setSlider(dir: 'left' | 'right') {
    dispatchEvent(new CustomEvent('adalongWidget_slide', { detail: dir }));
  }
  /**
   * Creates a div in the dom for the widget modal (react portals)
   */
  private createPostViewerPortal() {
    this.postViewerPortalId = `adalong-postViewer-${this._id}`;
    this.portalDiv = document.createElement('div');
    this.portalDiv.id = this.postViewerPortalId;
    document.body.appendChild(this.portalDiv);
  }

  public async load(element: string | HTMLElement, settings?: Partial<ISettings>) {
    if (!this.token) {
      throw new Error('No token provided');
    }
    if (this.destroyed) {
      // avoid reloaded an old destroyed instance
      return;
    }
    const token: string = this.token;
    await Debug.try(async () => {
      const rootElement = this.getRootElement(element);
      const sources = this.getSources(rootElement);
      const data: IData | null = await this.loadWidgetContent(token, sources, settings).catch((err) => {
        Debug.error(err);
        return null;
      });
      if (!data) {
        return;
      }
      this.layout = data.settings.type;

      ReactDOM.render(
        <StoreProvider
          state={{
            exportState: (getState: () => StoreState, setter: StateSetter) => {
              this.storeState.getState = getState;
              this.storeState.stateSetter = setter;
            },
            data: data ? { ...data } : undefined,
            root: rootElement,
            forceMobile: rootElement.getAttribute('data-forcemobile') === 'true',
            isMobile: Helpers.isMobile(rootElement.clientWidth),
            triggerEvent: this.triggerEvent.bind(this),
            config: this.config,
          }}
        >
          <Widget root={rootElement} startDate={this.startDate} postViewerPortalId={this.postViewerPortalId} />
        </StoreProvider>,
        rootElement
      );

      this.loaded = true;

      this.observeWidgetRemoval(rootElement);
    });
  }

  public setDebug(debug: IDebug) {
    if (!debug) {
      return;
    }
    Debug.try(() => {
      const { level, apiUrl } = debug;
      if (level) {
        Debug.setLevel(level);
      }
      if (apiUrl) {
        Api.apiUrl = apiUrl;
      }
    });
  }

  /**
   * Clean widget after removal
   */
  protected destroy(): void {
    // destroy events
    super.destroy();
    // remove react portal postviewer div
    this.portalDiv && document.body.removeChild(this.portalDiv);
    const index = window.adalongWidgetAPI.widgets.indexOf(this);
    if (index > -1) {
      // remove widget from api
      window.adalongWidgetAPI.widgets.splice(index, 1);
    }
    this.destroyed = true;
  }

  // /**
  //  * Add observer to destroy this widget instance when the element's is removed
  //  * It will wait for the page to be fully loaded
  //  */
  private observeWidgetRemoval(element: string | HTMLElement): void {
    const htmlElement: HTMLElement | null = typeof element === 'string' ? document.querySelector(element) : element;

    const observe = () => {
      window.removeEventListener('load', observe);

      const observer = new MutationObserver(() => {
        // directly check if the element is still in the dom
        const stillExists = document.body.contains(htmlElement);
        if (!stillExists) {
          observer.disconnect();
          this.destroy();
        }
      });
      observer.observe(document.body, { subtree: true, childList: true });
    };

    observe();
  }

  private async loadWidgetContent(token: string, sources?: Sources, settings?: Partial<ISettings>): Promise<IData | null> {
    if (!token) {
      throw new Error('No settings or token provided');
    }
    const defaultSettings: ISettings = await Api.getSettings(token);
    const finalSettings: ISettings = {
      ...defaultSettings,
      ...settings,
    };
    const content = await Api.getContent(token, sources, finalSettings);

    if (!content) {
      throw new Error('No content returned');
    }
    content.medias = content.medias.map((media, i) => ({ ...media, postIndex: i }));
    if (finalSettings.max_media) {
      content.medias = content.medias.slice(0, finalSettings.max_media);
    }
    const minContent: MinContentEvent = {
      required: finalSettings.min_media_to_display,
      current: content.medias.length,
    };
    if (minContent.required && minContent.current < minContent.required) {
      if (this.hasSubscribed('minContentNotReached')) {
        this.triggerEvent('minContentNotReached', minContent);
      } else {
        console.debug('Minimum content not reached', minContent);
      }
      return null;
    } else {
      this.triggerEvent('minContentReached', minContent);
    }
    return { settings: finalSettings, content };
  }

  private getRootElement(element: string | HTMLElement): HTMLElement {
    if (!element) {
      throw new Error('A valid css selector or a dom element must be provided');
    } else if (typeof element === 'string') {
      const e = document.querySelector<HTMLElement>(element);
      if (!e) {
        throw new Error(`Root '${element}' element not found`);
      }
      return e;
    }

    return element;
  }

  private addFonts() {
    const uid = 'adalong-fonts';
    const fonts = 'Montserrat+Subrayada|Montserrat:400,500,700|Be+Vietnam+Pro:300';
    if (!document.getElementById(uid)) {
      let link = document.createElement('link');
      link.id = uid;
      link.rel = 'stylesheet';
      link.href = `https://fonts.googleapis.com/css?family=${fonts}&display=swap`;
      document.head.appendChild(link);
    }
  }

  /**
   * Return sources set as attributes in the given dom element
   */
  private getSources(element: HTMLElement): Sources | undefined {
    try {
      return {
        productIds: element.dataset.products
          ? Helpers.isValidJson(element.dataset.products)
            ? JSON.parse(element.dataset.products)
            : element.dataset.products.split(',')
          : undefined,

        collectionIds: element.dataset.collections
          ? Helpers.isValidJson(element.dataset.collections)
            ? JSON.parse(element.dataset.collections)
            : element.dataset.collections.split(',')
          : undefined,
      };
    } catch (e) {
      console.error(e);
      return {};
    }
  }

  /**
   * Emit a custom event 'adalongWidgetCreated' when a widget has been created
   */
  private emitWidgetCreated() {
    const event = new CustomEvent('adalongWidgetCreated', {
      detail: {
        widget: this,
      },
    });
    document.dispatchEvent(event);
  }

  /**
   * Create the api to manipulate the widget(s)
   */
  private createAdalongAPI(): void {
    window.adalongWidgetAPI ??= { widgets: [] };
    window.adalongWidgetAPI.widgets.push(this);
  }
}

export = AdalongWidget;
