import 'babel-polyfill'; // fixme

import React from 'react';
import ReactDOM from 'react-dom';
import { isFunction } from 'lodash';
// import { Sagas } from 'react-redux-saga'; // fixme -- need to fork this and make compatible with react16
import { Provider } from 'react-redux';
import { object, arrayOf } from 'prop-types';

import { modules } from 'veritone-redux-common';
const { user: userModule, config: configModule } = modules;

import appConfig from '../../config.json';
import configureStore from '../redux/configureStore';

export default class VeritoneApp {
  _store = configureStore();
  _containerEl = null;
  _token = null;
  _refs = {};

  constructor(widgets) {
    this._widgets = widgets;

    this._store.dispatch(configModule.setConfig(appConfig));
  }

  // auth can follow two paths:
  // 1. internal apps, make /current-user api call with appInstance.login().
  //    this can happen automatically.
  // 2. oauth apps, popup window flow. must be initiated by user action (clicking a button).
  //    oauth apps must add an OAuthLoginButtonWidget and do not need to call login()
  login({ token } = {}) {
    // new VeritoneApp(...widgets)
    //   .login() // try to use an existing cookie
    // or
    //   .login({ token }); // use provided token in header

    // todo: handle promise result
    // make sure it rejects on bad auth
    if (token) {
      this._token = token;
      return this._store
        .dispatch(userModule.fetchUser({ token }))
        .then(this._handleLoginResponse);
    } else {
      return this._store
        .dispatch(userModule.fetchUser())
        .then(this._handleLoginResponse);
    }
  }

  _handleLoginResponse(action) {
    return action.error ? Promise.reject(action) : action;
  }

  mount() {
    const existingApp = document.getElementById('veritone-react-app');
    if (existingApp) {
      // todo: should this be an error?
      return console.warn(
        'The DOM element from a VeritoneApp instance already exists on this page. ' +
          'Destroy it before mounting a new one.'
      );
    }

    this._containerEl = document.createElement('div');
    this._containerEl.setAttribute('id', 'veritone-react-app');
    document.body.appendChild(this._containerEl);

    this._widgets.forEach(w => w.init());
    this._renderReactApp();

    return this;
  }

  destroy() {
    if (this._containerEl) {
      ReactDOM.unmountComponentAtNode(this._containerEl);
      try {
        document.body.removeChild(this._containerEl);
      } catch (e) {
        // ignore
      }
    }
  }

  getWidget(id) {
    return this._refs[id];
  }

  setWidgetRef = (id, ref) => {
    // try to get at the base component for @connected widgets.
    // fixme: generic solution (hoisting specified instance methods?)
    // https://github.com/elado/hoist-non-react-methods
    if (!ref) {
      // protect against errors when destroying the app
      return;
    }

    this._refs[id] = isFunction(ref.getWrappedInstance)
      ? ref.getWrappedInstance()
      : ref;
  };

  _renderReactApp() {
    ReactDOM.render(
      <Provider store={this._store}>
        <div>
          {this._widgets.map(w =>
            ReactDOM.createPortal(
              <w.Component
                {...w.props}
                // bind is OK because this isn't a component -- only renders
                // when mount() is called.
                // eslint-disable-next-line
                ref={this.setWidgetRef.bind(this, w.id)}
              />,
              w.el
            )
          )}
        </div>
      </Provider>,
      this._containerEl
    );
  }
}

// either
// const app = new VeritoneApp([new ModalWidget({ widgetId: 'something' })]);
// app.getWidget('something').open();
//
// // or
// const myWidget = new ModalWidget();
// const app = new VeritoneApp([myWidget]);
// myWidget.open();
// // but what if its in multiple apps? refs only apply to an app,
// // because thats where the widget's rendered instance lives.
//
// // so instead
// const myWidget = new ModalWidget();
// const app = new VeritoneApp([
//   myWidget,
//   new OtherWidget({ widgetId: 'something' })
// ]);
// // widget sets its own GUID when instantiated, or one can be specified.
// app.getWidget(myWidget.instanceId).open();
// app.getWidget('something').open();

@@ -2640,16 +2640,443 @@
 f) =%3E %7B%0A
+    if (!ref) %7B%0A      // protect against errors when destroying the app%0A      return;%0A    %7D%0A%0A%0A    if (isFunction(ref.getWrappedInstance) && !ref.wrappedInstance) %7B%0A      return console.warn(%0A        %60Warning: widget with id %22$%7Bid%7D%22 looks like it's wrapped with a%0A         @connect decorator, but the withRef option is not set to true.%0A         %7B withRef: true %7D should be set as the fourth argument to @connect%60%0A      )%0A    %7D%0A%0A
     // t
@@ -3261,101 +3261,8 @@
 ods%0A
-    if (!ref) %7B%0A      // protect against errors when destroying the app%0A      return;%0A    %7D%0A%0A
     

add helpful warning if withRef isn't supplied on @connected widgets