/**
 * Supports render.html, a piece of the hydration fixture. See /hydration
 */

'use strict';

(function() {
  var Fixture = null;
  var output = document.getElementById('output');
  var status = document.getElementById('status');
  var hydrate = document.getElementById('hydrate');
  var reload = document.getElementById('reload');
  var renders = 0;
  var failed = false;

  var needsReactDOM = getBooleanQueryParam('needsReactDOM');
  var needsCreateElement = getBooleanQueryParam('needsCreateElement');

  function unmountComponent(node) {
    // ReactDOM was moved into a separate package in 0.14
    if (needsReactDOM) {
      ReactDOM.unmountComponentAtNode(node);
    } else if (React.unmountComponentAtNode) {
      React.unmountComponentAtNode(node);
    } else {
      // Unmounting for React 0.4 and lower
      React.unmountAndReleaseReactRootNode(node);
    }
  }

  function createElement(value) {
    // React.createElement replaced function invocation in 0.12
    if (needsCreateElement) {
      return React.createElement(value);
    } else {
      return value();
    }
  }

  function getQueryParam(key) {
    var pattern = new RegExp(key + '=([^&]+)(&|$)');
    var matches = window.location.search.match(pattern);

    if (matches) {
      return decodeURIComponent(matches[1]);
    }

    handleError(new Error('No key found for' + key));
  }

  function getBooleanQueryParam(key) {
    return getQueryParam(key) === 'true';
  }

  function setStatus(label) {
    status.innerHTML = label;
  }

  function prerender() {
    setStatus('Generating markup');

    return Promise.resolve()
      .then(function() {
        const element = createElement(Fixture);

        // Server rendering moved to a separate package along with ReactDOM
        // in 0.14.0
        if (needsReactDOM) {
          return ReactDOMServer.renderToString(element);
        }

        // React.renderComponentToString was renamed in 0.12
        if (React.renderToString) {
          return React.renderToString(element);
        }

        // React.renderComponentToString became synchronous in React 0.9.0
        if (React.renderComponentToString.length === 1) {
          return React.renderComponentToString(element);
        }

        // Finally, React 0.4 and lower emits markup in a callback
        return new Promise(function(resolve) {
          React.renderComponentToString(element, resolve);
        });
      })
      .then(function(string) {
        output.innerHTML = string;
        setStatus('Markup only (No React)');
      })
      .catch(handleError);
  }

  function render() {
    setStatus('Hydrating');

    var element = createElement(Fixture);

    // ReactDOM was split out into another package in 0.14
    if (needsReactDOM) {
      // Hydration changed to a separate method in React 16
      if (ReactDOM.hydrate) {
        ReactDOM.hydrate(element, output);
      } else {
        ReactDOM.render(element, output);
      }
    } else if (React.render) {
      // React.renderComponent was renamed in 0.12
      React.render(element, output);
    } else {
      React.renderComponent(element, output);
    }

    setStatus(renders > 0 ? 'Re-rendered (' + renders + 'x)' : 'Hydrated');
    renders += 1;
    hydrate.innerHTML = 'Re-render';
  }

  function handleError(error) {
    console.log(error);
    failed = true;
    setStatus('Javascript Error');
    output.innerHTML = error;
  }

  function loadScript(src) {
    return new Promise(function(resolve, reject) {
      var script = document.createElement('script');
      script.async = true;
      script.src = src;

      script.onload = resolve;
      script.onerror = function(error) {
        reject(new Error('Unable to load ' + src));
      };

      document.body.appendChild(script);
    });
  }

  function injectFixture(src) {
    Fixture = new Function(src + '\nreturn Fixture;')();

    if (typeof Fixture === 'undefined') {
      setStatus('Failed');
      output.innerHTML = 'Please name your root component "Fixture"';
    } else {
      prerender().then(function() {
        if (getBooleanQueryParam('hydrate')) {
          render();
        }
      });
    }
  }

  function reloadFixture(code) {
    renders = 0;
    unmountComponent(output);
    injectFixture(code);
  }

  window.onerror = handleError;

  reload.onclick = function() {
    window.location.reload();
  };

  hydrate.onclick = render;

  loadScript(getQueryParam('reactPath'))
    .then(function() {
      if (needsReactDOM) {
        return Promise.all([
          loadScript(getQueryParam('reactDOMPath')),
          loadScript(getQueryParam('reactDOMServerPath')),
        ]);
      }
    })
    .then(function() {
      if (failed) {
        return;
      }

      window.addEventListener('message', function(event) {
        var data = JSON.parse(event.data);

        switch (data.type) {
          case 'code':
            reloadFixture(data.payload);
            break;
          default:
            throw new Error(
              'Renderer Error: Unrecognized message "' + data.type + '"'
            );
        }
      });

      window.parent.postMessage(JSON.stringify({type: 'ready'}), '*');
    })
    .catch(handleError);
})();
