import React, { useContext, createContext, useMemo, forwardRef, useState as useState$1, useRef, useEffect } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import NodeKey from 'react-node-key';
import ReactDOM from 'react-dom';

function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);

  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);

    if (enumerableOnly) {
      symbols = symbols.filter(function (sym) {
        return Object.getOwnPropertyDescriptor(object, sym).enumerable;
      });
    }

    keys.push.apply(keys, symbols);
  }

  return keys;
}

function _objectSpread2(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i] != null ? arguments[i] : {};

    if (i % 2) {
      ownKeys(Object(source), true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      });
    }
  }

  return target;
}

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }

  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function () {
    var self = this,
        args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);

      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }

      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }

      _next(undefined);
    });
  };
}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}

function _extends() {
  _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };

  return _extends.apply(this, arguments);
}

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

function _objectWithoutProperties(source, excluded) {
  if (source == null) return {};

  var target = _objectWithoutPropertiesLoose(source, excluded);

  var key, i;

  if (Object.getOwnPropertySymbols) {
    var sourceSymbolKeys = Object.getOwnPropertySymbols(source);

    for (i = 0; i < sourceSymbolKeys.length; i++) {
      key = sourceSymbolKeys[i];
      if (excluded.indexOf(key) >= 0) continue;
      if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
      target[key] = source[key];
    }
  }

  return target;
}

function _slicedToArray(arr, i) {
  return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}

function _arrayWithHoles(arr) {
  if (Array.isArray(arr)) return arr;
}

function _iterableToArrayLimit(arr, i) {
  var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];

  if (_i == null) return;
  var _arr = [];
  var _n = true;
  var _d = false;

  var _s, _e;

  try {
    for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
      _arr.push(_s.value);

      if (i && _arr.length === i) break;
    }
  } catch (err) {
    _d = true;
    _e = err;
  } finally {
    try {
      if (!_n && _i["return"] != null) _i["return"]();
    } finally {
      if (_d) throw _e;
    }
  }

  return _arr;
}

function _unsupportedIterableToArray(o, minLen) {
  if (!o) return;
  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  var n = Object.prototype.toString.call(o).slice(8, -1);
  if (n === "Object" && o.constructor) n = o.constructor.name;
  if (n === "Map" || n === "Set") return Array.from(o);
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}

function _arrayLikeToArray(arr, len) {
  if (len == null || len > arr.length) len = arr.length;

  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

  return arr2;
}

function _nonIterableRest() {
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}

// 值类型判断 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var isUndefined = function isUndefined(val) {
  return typeof val === 'undefined';
};
var isNull = function isNull(val) {
  return val === null;
};
var isFunction = function isFunction(val) {
  return typeof val === 'function';
};
var isString = function isString(val) {
  return typeof val === 'string';
};
var isExist = function isExist(val) {
  return !(isUndefined(val) || isNull(val));
};
var isNaN = function isNaN(val) {
  return isFunction(Number.isNaN) ? Number.isNaN(val) : val !== val;
}; // eslint-disable-line

var isNumber = function isNumber(val) {
  return typeof val === 'number' && !isNaN(val);
};
var isPromiseLike = function isPromiseLike(val) {
  return isExist(val) && isFunction(val.then);
}; // 值类型判断 -------------------------------------------------------------

var get = function get(obj) {
  var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  var defaultValue = arguments.length > 2 ? arguments[2] : undefined;

  try {
    if (isNumber(keys)) {
      keys = String(keys);
    }

    var result = (isString(keys) ? keys.split('.') : keys).reduce(function (res, key) {
      return res[key];
    }, obj);
    return isUndefined(result) ? defaultValue : result;
  } catch (e) {
    return defaultValue;
  }
};
var run = function run(obj) {
  var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  keys = isString(keys) ? keys.split('.') : keys;
  var func = get(obj, keys);
  var context = get(obj, keys.slice(0, -1));

  for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
    args[_key - 2] = arguments[_key];
  }

  return isFunction(func) ? func.call.apply(func, [context].concat(args)) : func;
};

var delay = function delay(time) {
  return new Promise(function (resolve) {
    return setTimeout(resolve, time);
  });
};
/**
 * [防抖]
 * @param {Function} func 执行函数
 * @param {Number} wait 多少毫秒后运行一次
 */

var debounce = function debounce(func) {
  var wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 16;
  var timeout;
  return function () {
    var _this = this;

    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    clearTimeout(timeout);
    timeout = setTimeout(function () {
      func.apply(_this, args);
    }, wait);
    return timeout;
  };
};
/**
 * [用来智能处理Promise类型返回值]
 * 当值生成过程为 promise 时，将得到 promise 类型返回值，按约定 resolve 最终值
 * 当过程不为 promise 时将直接得到值
 * @param {Function} executor 执行过程获取
 * @param {Function} valuer 值处理过程
 */

var promiseGuess = function promiseGuess(executor, valuer) {
  return function () {
    var _this2 = this;

    for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
      args[_key2] = arguments[_key2];
    }

    var value = executor.apply(this, args);
    return isPromiseLike(value) ? new Promise(function (resolve) {
      return value.then(function (value) {
        return resolve(valuer.call.apply(valuer, [_this2, null, value].concat(args)));
      })["catch"](function (err) {
        return resolve(valuer.call.apply(valuer, [_this2, err, undefined].concat(args)));
      });
    }) : valuer.call.apply(valuer, [this, null, value].concat(args));
  };
};

var _excluded = ["forwardRef", "__key"];
var _filehash = "nNBh";
var context = /*#__PURE__*/createContext();
var Provider = context.Provider;

function withSanti(Component) {
  function WrappedComponent(_ref) {
    var forwardRef = _ref.forwardRef,
        nodeKey = _ref.__key,
        props = _objectWithoutProperties(_ref, _excluded);

    var count = 0;
    var contextValue = useMemo(function () {
      return {
        nodeKey: nodeKey,
        getCountedKey: function getCountedKey() {
          return nodeKey ? "".concat(nodeKey, ":").concat(count++) : undefined;
        }
      };
    }, [nodeKey]);
    return /*#__PURE__*/React.createElement(Provider, {
      value: contextValue,
      _nk: "".concat(_filehash, "11")
    }, /*#__PURE__*/React.createElement(Component, _extends({}, props, {
      ref: forwardRef,
      _nk: "".concat(_filehash, "21")
    })));
  }

  var ForwardedRefHOC = /*#__PURE__*/forwardRef(function (props, ref) {
    return /*#__PURE__*/React.createElement(NodeKey, {
      _nk: "".concat(_filehash, "31")
    }, function (nodeKey) {
      return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
        forwardedRef: ref,
        __key: nodeKey,
        _nk: "".concat(_filehash, "41")
      }));
    });
  });
  return hoistStatics(ForwardedRefHOC, WrappedComponent);
}

function useNodeKey() {
  return useContext(context) || {
    nodeKey: undefined,
    getCountedKey: function getCountedKey() {
      return undefined;
    }
  };
}
function useSID() {
  var _useNodeKey = useNodeKey(),
      sid = _useNodeKey.nodeKey,
      getCountedSID = _useNodeKey.getCountedKey;

  return {
    sid: sid,
    getCountedSID: getCountedSID
  };
}

var KEY = '__SSRDATA__';

function inject() {
  var script = document.getElementById(KEY) || document.createElement('script');
  script.id = KEY;
  script.innerHTML = "window.".concat(KEY, "={};");
  document.head.appendChild(script);
}

function bootstrap() {
  if (window.__SSR__) {
    inject();
  } else {
    if (isExist(document.getElementById(KEY))) {
      window.__SSRED__ = true;
    }
  }

  var data = window[KEY] || {};

  function remove(key) {
    delete data[key];
  }

  function set(key, value) {
    if (!window.__SSR__) {
      return;
    }

    data[key] = value;
    document.getElementById(KEY).innerHTML = "window.".concat(KEY, "=").concat(JSON.stringify(data), ";");
  }

  function get(key) {
    var value = data[key];
    remove(key); // 初始值只在 html 就绪后使用一次，使用即销毁

    return value;
  }

  return {
    set: set,
    get: get,
    remove: remove
  };
}

var store = bootstrap();
var store$1 = {
  get: promiseGuess(function (key, builder) {
    if (!key && window.__SSR__) {
      // 若无 key 则 SSR 阶段不计算
      return undefined;
    }

    var value;

    if (!window.__SSR__) {
      value = store.get(key);
    }

    if (isUndefined(value)) {
      value = run(builder);
    }

    return value;
  }, function (err, value, key) {
    if (!key) {
      return [null, value];
    }

    if (err) {
      store.remove(key);
      return [err, undefined];
    }

    if (window.__SSR__) {
      store.set(key, value);
    }

    return [null, value];
  }),
  set: store.set
};

var warningMissingKey = debounce(function () {
  if (window.__SSR__) {
    return;
  }

  console.warn('You shouldn\'t use "santi.useState" outside a "withSanti"');
}, 32);

function useState(initialState, key) {
  var _useNodeKey = useNodeKey(),
      getCountedKey = _useNodeKey.getCountedKey;

  var nodeKey = key || run(getCountedKey);

  var _useReactState = useState$1(function () {
    if (!nodeKey) {
      warningMissingKey();
    }

    var _store$get = store$1.get(nodeKey, initialState),
        _store$get2 = _slicedToArray(_store$get, 2),
        err = _store$get2[0],
        value = _store$get2[1];

    if (err) {
      console.error(err);
    }

    return value;
  }),
      _useReactState2 = _slicedToArray(_useReactState, 2),
      state = _useReactState2[0],
      setState = _useReactState2[1];

  return [state, function (getNextState) {
    var nextState = run(getNextState, undefined, state);
    store$1.set(nodeKey, nextState);
    return setState(nextState);
  }, nodeKey];
}

var _excluded$1 = ["forwardedRef"];
var _filehash$1 = "8NS2";

var getInitialProps = function getInitialProps(fetch) {
  var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
  var key = arguments.length > 2 ? arguments[2] : undefined;
  return function (Component) {
    function WrappedComponent(_ref) {
      var forwardedRef = _ref.forwardedRef,
          props = _objectWithoutProperties(_ref, _excluded$1);

      var _useState = useState$1(false),
          _useState2 = _slicedToArray(_useState, 2),
          ready = _useState2[0],
          setReady = _useState2[1];

      var _useState3 = useState$1({}),
          _useState4 = _slicedToArray(_useState3, 2),
          ssrProps = _useState4[0],
          setSsrProps = _useState4[1];

      var mounted = useRef(true);

      var _useNodeKey = useNodeKey(),
          getCountedKey = _useNodeKey.getCountedKey;

      var nodeKey = key || run(getCountedKey);
      useEffect(function () {
        function init() {
          return _init.apply(this, arguments);
        }

        function _init() {
          _init = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
            var _yield$store$get, _yield$store$get2, err, ssrProps;

            return regeneratorRuntime.wrap(function _callee$(_context) {
              while (1) {
                switch (_context.prev = _context.next) {
                  case 0:
                    _context.next = 2;
                    return store$1.get(nodeKey, function () {
                      return run(fetch, undefined, props);
                    });

                  case 2:
                    _yield$store$get = _context.sent;
                    _yield$store$get2 = _slicedToArray(_yield$store$get, 2);
                    err = _yield$store$get2[0];
                    ssrProps = _yield$store$get2[1];

                    if (!err) {
                      _context.next = 9;
                      break;
                    }

                    console.error('[getInitialProps error]', err);
                    return _context.abrupt("return");

                  case 9:
                    if (mounted.current && ssrProps) {
                      setSsrProps(ssrProps);
                      setReady(true);
                    }

                  case 10:
                  case "end":
                    return _context.stop();
                }
              }
            }, _callee);
          }));
          return _init.apply(this, arguments);
        }

        init();
        return function () {
          mounted.current = false;
        };
      }, []);
      return ready ? /*#__PURE__*/React.createElement(Component, _extends({}, _objectSpread2(_objectSpread2({}, props), ssrProps), {
        ref: forwardedRef,
        _nk: "".concat(_filehash$1, "11")
      })) : run(fallback, undefined, props);
    }

    var ForwardedRefHOC = /*#__PURE__*/forwardRef(function (props, ref) {
      return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
        forwardedRef: ref,
        _nk: "".concat(_filehash$1, "21")
      }));
    });
    return withSanti(hoistStatics(ForwardedRefHOC, WrappedComponent));
  };
};

var ready = /*#__PURE__*/function () {
  var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var delayMillisecond,
        script,
        _args = arguments;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            delayMillisecond = _args.length > 0 && _args[0] !== undefined ? _args[0] : -1;
            _context.prev = 1;

            if (!(delayMillisecond && Number(delayMillisecond) >= 0)) {
              _context.next = 5;
              break;
            }

            _context.next = 5;
            return delay(delayMillisecond);

          case 5:
            if (window.__SSR__ && !window.__SSRREADY__) {
              script = document.createElement('script');
              script.innerHTML = 'window.__SSRREADY__=true';
              document.head.appendChild(script);
            } // ssr 阶段：通知 santi 可以采集页面内容
            // csr 阶段：通知 santi/render 已完成呈现


            document.dispatchEvent(new Event('ssr-ready'));

          case 7:
            _context.prev = 7;
            return _context.finish(7);

          case 9:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, null, [[1,, 7, 9]]);
  }));

  return function ready() {
    return _ref.apply(this, arguments);
  };
}();

var Container = withSanti(function (_ref) {
  var children = _ref.children;
  return children;
});

var _filehash$2 = "4mmK";
var ROOT_KEY = 'ssr-root';

function render(content, container, callback) {
  var element = /*#__PURE__*/React.createElement(Container, {
    _nk: "".concat(_filehash$2, "11")
  }, content); // 支持热加载
  // FIXME: Dev 阶段会导致渲染抖动

  if (module.hot) {
    return ReactDOM.render(element, container, callback);
  } // ssr 阶段将内容渲染至动态生成的 ssr-root 节点中


  if (window.__SSR__) {
    var ssrRoot = document.getElementById(ROOT_KEY);

    if (!ssrRoot) {
      ssrRoot = document.createElement('div');
      ssrRoot.style.cssText = 'width:100%;height:100%;';
      ssrRoot.id = ROOT_KEY;
      document.body.insertBefore(ssrRoot, container);
    }

    return ReactDOM.render(element, ssrRoot, callback);
  } // csr 阶段若为 ssr 渲染结果，则在 csr 完成后替换 ssr 结果
  // 注：不使用水合操作（ReactDOM.hydrate）因为可能造成节点错误问题


  if (window.__SSRED__) {
    var display = function display() {
      // 做一定延时，尽可能保证平滑呈现
      setTimeout(function () {
        var ssrRoot = document.getElementById(ROOT_KEY);

        if (ssrRoot) {
          try {
            ssrRoot.parentNode.removeChild(ssrRoot);
          } finally {// nothing
          }
        }

        container.style.display = '';
      }, 56);
    }; // 若为快照 ssr 则 csr 阶段将同样收到 ssr-ready 事件，在此事件后平滑呈现真实可用交互


    var _ssrRoot = document.getElementById(ROOT_KEY);

    var renderCallback = callback;
    container.style.display = 'none';
    container.innerHTML = _ssrRoot.innerHTML;

    if (window.__SSRREADY__) {
      var onReady = function onReady() {
        display();
        document.removeEventListener('ssr-ready', onReady);
      };

      document.addEventListener('ssr-ready', onReady);
    } else {
      // 若不为快照 ssr（一般为超时自动快照），则在 render 回调结束后呈现真实结果
      renderCallback = function renderCallback() {
        // FIXME: 超时快照可能造成呈现不平滑，具体表现为 SSR 切到真实内容过程中有轻微空屏现象，通俗为 “闪一下”，此问题待完美修正
        display();

        if (isFunction(callback)) {
          for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
          }

          callback.apply(this, args);
        }
      };
    }

    return ReactDOM.render(element, container, renderCallback);
  } // csr 若不为 ssr 渲染结果则正常渲染


  return ReactDOM.render(element, container, callback);
}

var _filehash$3 = "B/hE";

function Ready(_ref) {
  var children = _ref.children,
      delay = _ref.delay,
      _ref$onMount = _ref.onMount,
      onMount = _ref$onMount === void 0 ? false : _ref$onMount,
      _ref$when = _ref.when,
      when = _ref$when === void 0 ? false : _ref$when;
  useEffect(function () {
    if (onMount) {
      ready(delay);
    }
  }, []);
  useEffect(function () {
    if (when) {
      ready(delay);
    }
  }, [when]);
  return children;
}

function ReadyOnMount(_ref2) {
  var children = _ref2.children,
      delay = _ref2.delay;

  var _useState = useState$1(false),
      _useState2 = _slicedToArray(_useState, 2),
      ready = _useState2[0],
      setReady = _useState2[1];

  useEffect(function () {
    setReady(true);
  }, []);
  return /*#__PURE__*/React.createElement(Ready, {
    delay: delay,
    when: ready,
    _nk: "".concat(_filehash$3, "11")
  }, children);
}

Ready.OnMount = ReadyOnMount;

function NoSSR(_ref) {
  var children = _ref.children,
      _ref$fallback = _ref.fallback,
      fallback = _ref$fallback === void 0 ? null : _ref$fallback;
  return window.__SSR__ ? run(fallback) : children;
}

var index = {
  render: render,
  store: store$1,
  useState: useState,
  getInitialProps: getInitialProps,
  ready: ready,
  withSanti: withSanti,
  useNodeKey: useNodeKey,
  useSID: useSID,
  Ready: Ready,
  NoSSR: NoSSR
};

export default index;
export { NoSSR, Ready, getInitialProps, ready, render, store$1 as store, useNodeKey, useSID, useState, withSanti };
