let Widget, Composition, Content, Skin;
import EmbedApi from './api';
import toast from 'pyrsmk-toast';

const send = message => {
  (window.opener || window.parent).postMessage(JSON.stringify(message), '*');
};

const messenger = {
  load: () => send({ t: 'u', d: window.name }),
  apiReady: () => send({ t: 'd', d: window.name }),
  notify: (ev, data) =>
    send({ t: 'ce', id: window.name, d: data, e: ev.type || ev }),
  replyPublicApiCall: (id, err, data) => send({ t: 'pr', id, err, d: data }),
};

const _newComposition = (widget, demo) => {
  const id = 'c-0';
  return {
    id,
    widget_id: widget.id,
    width: widget.width,
    height: widget.height,
    skin_id: widget.skins[0].id,
    content: !demo
      ? []
      : widget.content[0].content.map(function(c, i) {
          c.id = i;
          c.composition_id = id;
          c._isDemoContent = true;
          return c;
        }),
    metadata: {
      client: (window.location.search.match(/client_id=([^\&$]+)/) || [])[1],
    },
  };
};

// TODO relay mouse and keyboard events - maybe not?
export default class EmbedController {
  constructor(
    el,
    {
      loader,
      widget,
      user,
      composition,
      skin,
      js,
      css,
      data,
      demo = true,
      bootstrapped = false,
    }
  ) {
    // widget element
    this.el = el;

    // loader
    this.loader = loader;

    // read if it's a bootstrapped widget
    this.bootstrapped = bootstrapped;

    // read widget data
    if (!widget && !data) throw new Error('You must supply either `widget` or `data`');
    this.widget = widget;
    this.dataURL = data;
    this.user = user;
    this.composition = composition;
    this.skin = skin;
    this.loadDemo = demo;
    // widget js & css
    this.js = 'https://widgetic.com' + js;
    this.css = 'https://widgetic.com' + css;

    // start loading widget
    this.dataLoaded = Widgetic.Aye.defer();
    this.dataLoaded.promise.then(this._showBranding.bind(this));
    // init widget
    this._init();
    
    // init editor
    this._initEditorApi();
  }

  _init() {
    const wait = () => {};

    // Bootstrapped widgets(using spine - coffescript widgets)
    if (!this.bootstrapped) {
      toast(
        // load shims, css and libraries in paralel
        'https://files.widgetic.com/file/widgetic-uploads/assets/js/core-js.shim.min.js',
        'https://widgetic.com/bundles/blogvioapp/js/editor/core.vendor.js',
        'https://widgetic.com/bundles/blogvioapp/js/require.js',
        'https://widgetic.com/bundles/blogvioapp/css/embed.css',
        this.css,
        wait,
        // load spine next, depends on require.js
        'https://widgetic.com/bundles/blogvioapp/js/spine.js',
        wait,
        // load widgetic code, depends on spine
        'https://widgetic.com/bundles/blogvioapp/js/editor/core.js',
        this._loadModels.bind(this),
        this._loadData.bind(this),
        this.js,
        // load the widget, depends on hydrated models
        () =>
          this.dataLoaded.promise
            .then(this._loadWidget.bind(this))
            .fail(e => console.error(e))
      );
    }
    // Angular & Svelte widgets 
    else {
      this._loadModels();
      this._loadData();
      this._loadWidget();
    }
  }

  _loadModels() {
    Widget = window.require('core/models/widget');
    Composition = window.require('core/models/composition');
    Content = window.require('core/models/content');
    Skin = window.require('core/models/skin');
  }

  _loadData() {
    // if the data is local, proceed to hydrating the models
    if (!this.dataURL) return this._hydrateModels();

    Widgetic.uxhr(this.dataURL, null, {
      success: response => {
        let data;
        try {
          data = JSON.parse(response);
        } catch (e) {
          return console.error('Could not parse response', response);
        }
        this.skin = data.skin;
        this.composition = data.composition;
        this.widget = data.widget;
        this.user = data.user;
        this._hydrateModels();
      },
      error: error =>
        console.error('The widget data could not be loaded', error),
    });
  }

  _hydrateModels() {
    this.composition = this.composition || _newComposition(this.widget, this.loadDemo);
    // console.log("Embed created composition:", this.composition);
    
    // load composition
    Widget.refresh(this.widget);
    Composition.refresh(this.composition);

    // load skin
    Skin.refresh(this.skin || this.widget.skins);
    this._skin = Skin.first();
    
    // load content
    Content.refresh(this.composition.content);

    this.dataLoaded.resolve();

    // init embed api
    this._initApi();
  }

  _initApi() {
    const api = new EmbedApi();
    Widgetic.Event.on(window, 'message', api.receiver);

    api.skinSet.listen(this._onSkinSet.bind(this));
    api.contentCleared.listen(this._onContentCleared.bind(this));
    api.contentAdded.listen(this._onContentAdded.bind(this));
    api.publicApiCalled.listen(this._onPublicApiCalled.bind(this));

    messenger.apiReady();
  }

  _initEditorApi() {
    window.getPreview = () => ({
      widget: () => this.widgetController,
      window,
    });

    window.getData = () => ({
      widget: this.widget,
      composition: $.extend({}, this.composition, {
        widget_id: this.widget.id,
        width: (this.composition || this.widget).width,
        height: (this.composition || this.widget).height,
        content: this.composition.content,
        skin_id: this.composition.skin_id,
      }),
      skin: this.skin || this.widget.skins[0],
      user: this.user,
    });

    // used by weebly editor
    // TODO remove
    // @deprecated
    window.getCurrentData = () => ({ widget: this.widgetController });

    // used by weebly editor
    // TODO remove
    const access_token = (window.location.search.match(
      /access_token=([^\&$]+)/
    ) || [])[1];
    if (access_token) Widgetic.auth.token(access_token);
  }

  _loadWidget() {
    const uri = Widgetic.parse(window.location.href);

    const mode =
      uri.queryKey.edit_mode !== undefined
        ? Widgetic.UI.Composition.EDIT_MODE
        : Widgetic.UI.Composition.EMBED_MODE;

    // create widget metadata
    const metadata = {
      mode,
      embedOptions: {
        resize: uri.queryKey.resize,
        autoscale: uri.queryKey.autoscale,
        adaptive: uri.queryKey.adaptive,
      },
      widget: this.widget,
      composition: {
        id: this.composition.id,
      },
      referrer: document.referrer,
    };

    // get widget main component(coffee, angular, svelte 1, svelte 2, svelte 3)
    const _widget = window.require(this.widget.module);
    // console.log("Embed controller load:", _widget)

    // create an instance of the widget's main component with the skin and content
    this.widgetController = new _widget(
      { metadata },
      this._skin,
      Content.all()
    );
    // console.log("Embed controller load:", this.widgetController)

    // remove loader
    this.loader && this.loader.parentNode.removeChild(this.loader);

    // add the widget container
    // TODO replace jquery use with native DOM
    const widgetContainer = $(
      '<div class="widget-container" style="width:1px; min-width: 100%; height: 100%">'
    );
    $(this.el).append(widgetContainer);
    // TODO remove when all widgets are updated to svelte
    widgetContainer.append(this.widgetController.el);
    this.widgetController.added();

    // add messenger listeners for widget's public events
    this.widgetController.getPublicEvents &&
      this.widgetController.getPublicEvents().forEach(event => {
        this.widgetController.on(event, data => messenger.notify(event, data));
      });
    messenger.load();
  }

  _showBranding() {
    // read branding value
    let brandingValue = new URLSearchParams(window.location.search).get('branding')
    // console.log("read brandingValue:", brandingValue);
    if(!brandingValue) brandingValue = 'off'

    // check if user is premium
    let isPremium = (this.user && this.user.premium) ? this.user.premium : false;
    
    // establish branding
    if(!isPremium) brandingValue = "on";
    // console.log("final brandingValue:", this.user, isPremium, brandingValue);
    
    
    // show branding if user is free or flag is on
    if (brandingValue != "off") window.initBranding();
  }

  _onSkinSet(skin) {
    this._skin = skin;
    if (!this.widgetController) return;
    this.widgetController.setSkin(this._skin);
  }

  _onContentCleared() {
    if (!this.widgetController) return;
    this.widgetController.clearContent();
  }

  _onContentAdded(item) {
    if (!this.widgetController) return;
    this.widgetController.addContent(item);
  }

  _onPublicApiCalled(data) {
    if (!this.widgetController) throw new Error('The widget is not ready yet.');
    if (!this.widgetController.getPublicMethods)
      throw new Error('The widget does not support public api calls.');

    const publicMethods = this.widgetController.getPublicMethods();
    const widget = this.widgetController.widget;

    if (publicMethods.indexOf(data.method) === -1 || !(data.method in widget))
      throw new Error(
        'The widget does not support method `' + data.method + '`.'
      );

    try {
      const result = widget[data.method].apply(widget, data.args);
      messenger.replyPublicApiCall(data.id, null, result);
    } catch (e) {
      messenger.replyPublicApiCall(data.id, {
        message: e.message,
        stack: e.stack,
      });
    }
  }
}
