"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.FactoryMeta = exports.FactoryChunk = exports.Factory = void 0;

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));

var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));

var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));

var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));

var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));

var _tsFns = require("ts-fns");

var _meta = require("./meta.js");

var _excluded = ["$entries", "$create", "$adapt"],
    _excluded2 = ["default", "validators", "type", "create", "save", "map", "setter", "getter"];

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

var FactoryMeta = /*#__PURE__*/function (_SceneMeta) {
  (0, _inherits2.default)(FactoryMeta, _SceneMeta);

  var _super = _createSuper(FactoryMeta);

  function FactoryMeta(options) {
    var _this;

    (0, _classCallCheck2.default)(this, FactoryMeta);
    var $entries = options.$entries,
        $create = options.$create,
        $adapt = options.$adapt,
        opts = (0, _objectWithoutProperties2.default)(options, _excluded);
    _this = _super.call(this, opts);
    (0, _tsFns.define)((0, _assertThisInitialized2.default)(_this), '$entries', function () {
      return $entries;
    });
    (0, _tsFns.define)((0, _assertThisInitialized2.default)(_this), '$create', function () {
      return $create;
    });
    (0, _tsFns.define)((0, _assertThisInitialized2.default)(_this), '$adapt', function () {
      return $adapt;
    });
    return _this;
  }

  (0, _createClass2.default)(FactoryMeta, [{
    key: "extend",
    value: function extend() {
      var attrs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      var $entries = this.$entries,
          $create = this.$create,
          $adapt = this.$adapt;
      return (0, _get2.default)((0, _getPrototypeOf2.default)(FactoryMeta.prototype), "extend", this).call(this, _objectSpread(_objectSpread({}, attrs), {}, {
        $entries: $entries,
        $create: $create,
        $adapt: $adapt
      }));
    }
  }]);
  return FactoryMeta;
}(_meta.SceneMeta);

exports.FactoryMeta = FactoryMeta;
var FactoryChunk = /*#__PURE__*/(0, _createClass2.default)(function FactoryChunk(options) {
  (0, _classCallCheck2.default)(this, FactoryChunk);
  Object.assign(this, options);
});
exports.FactoryChunk = FactoryChunk;

var Factory = /*#__PURE__*/function () {
  // entries should be a Model constructor or an array of Model constructors
  function Factory(entries, attrs) {
    (0, _classCallCheck2.default)(this, Factory);
    this.entries = entries;
    this.attrs = attrs;
    this.meta = null;
    this.init(entries);
  }
  /**
   * @param {Model|Model[]} Entries
   */


  (0, _createClass2.default)(Factory, [{
    key: "init",
    value: function init(Entries) {
      var _this2 = this;

      var factory = this;
      var isList = (0, _tsFns.isArray)(Entries);

      var _ref = this.attrs || {},
          _default = _ref.default,
          _ref$validators = _ref.validators,
          _validators = _ref$validators === void 0 ? [] : _ref$validators,
          _type = _ref.type,
          _create = _ref.create,
          _save = _ref.save,
          _map = _ref.map,
          _setter = _ref.setter,
          _getter = _ref.getter,
          attrs = (0, _objectWithoutProperties2.default)(_ref, _excluded2);

      var setupLinkage = function setupLinkage(child, parent, key) {
        var scenes = parent.$$scenes; // do only once

        if (factory.transport) {
          factory.transport(child, parent, scenes);
        } // --------


        if (!factory.linkage) {
          return;
        }

        var register = function register() {
          var deps = parent.collect(function () {
            factory.linkage(child, parent, scenes);
          });

          var fn = function fn() {
            factory.linkage(child, parent, scenes); // -------
            // check whether the child is in parent model, if not, remove watchers

            if (!deps) {
              return;
            }

            var unwatch = function unwatch() {
              deps.forEach(function (dep) {
                parent.unwatch(dep, fn);
              });
            };

            var sub = parent[key];

            if (isList) {
              if (!sub.includes(child)) {
                unwatch();
              }
            } else {
              if (sub !== child) {
                unwatch();
              }
            }
          };

          deps.forEach(function (dep) {
            parent.watch(dep, fn); // did not watch deeply
          });
          parent.off('init', register);
        }; // when setup transport, the parent is not built ready,
        // we should must wait it ready to collect deps


        if (!parent.$ready) {
          parent.on('init', register);
        } else {
          register();
        }
      };

      var ThisFactoryMeta = /*#__PURE__*/function (_FactoryMeta) {
        (0, _inherits2.default)(ThisFactoryMeta, _FactoryMeta);

        var _super2 = _createSuper(ThisFactoryMeta);

        function ThisFactoryMeta() {
          (0, _classCallCheck2.default)(this, ThisFactoryMeta);
          return _super2.apply(this, arguments);
        }

        (0, _createClass2.default)(ThisFactoryMeta, [{
          key: "defineScenes",
          value: function defineScenes() {
            if ((0, _tsFns.isFunction)(factory.scenes)) {
              return factory.scenes();
            }

            return {};
          }
        }]);
        return ThisFactoryMeta;
      }(FactoryMeta);

      var $adapt = factory.adapt.bind(factory);

      var createInstance = function createInstance(value, key, parent) {
        var scenes = parent.$$scenes;
        var editof = parent.$$editof;
        var FoundModel = factory.select(Entries, value, parent, key);

        if (!FoundModel) {
          throw new Error('[TySheMo]: Factory.select Model not found!');
        }

        var LocalModel = scenes ? FoundModel.Scene(scenes) : FoundModel;
        var ChoosedModel = editof ? /*#__PURE__*/function (_LocalModel$Edit) {
          (0, _inherits2.default)(_class, _LocalModel$Edit);

          var _super3 = _createSuper(_class);

          function _class() {
            (0, _classCallCheck2.default)(this, _class);
            return _super3.apply(this, arguments);
          }

          return (0, _createClass2.default)(_class);
        }(LocalModel.Edit()) : /*#__PURE__*/function (_LocalModel) {
          (0, _inherits2.default)(_class2, _LocalModel);

          var _super4 = _createSuper(_class2);

          function _class2() {
            (0, _classCallCheck2.default)(this, _class2);
            return _super4.apply(this, arguments);
          }

          return (0, _createClass2.default)(_class2);
        }(LocalModel);

        if (factory.override) {
          ChoosedModel.prototype._takeOverrideMetas = function () {
            return factory.override(this, parent, scenes);
          };
        }

        if (factory.adapt(Entries, value, key, parent)) {
          // TODO how to rewrite scenes?
          if (scenes && scenes.length && !(0, _tsFns.isEqual)(scenes, value.$$scenes) || editof) {
            value = value.Chunk().toJSON();
          } else {
            value._setParent([parent, key]);

            return value;
          }
        }

        if ((0, _tsFns.isObject)(value)) {
          return factory.instance(ChoosedModel, value, {
            key: key,
            parent: parent,
            scenes: scenes
          });
        }

        return factory.instance(ChoosedModel, {}, {
          key: key,
          parent: parent,
          scenes: scenes
        });
      };

      if (isList) {
        var filter = function filter(items, key, parent) {
          var nexts = items.filter(function (item) {
            if (_this2.adapt(Entries, item, key, parent)) {
              return true;
            }

            if ((0, _tsFns.isObject)(item)) {
              return true;
            }

            return false;
          });
          return nexts;
        };

        var gen = function gen(items, key, parent) {
          var nexts = filter(items, key, parent);
          var values = nexts.map(function (next) {
            var child = createInstance(next, key, parent);
            setupLinkage(child, parent, key);
            return child;
          });
          var outs = values.filter(function (item) {
            return item;
          });
          return outs;
        };

        var attributes = _objectSpread(_objectSpread({}, attrs), {}, {
          default: factory.default(function (key) {
            var items = (0, _tsFns.isFunction)(_default) ? _default.call(this, key) : _default;
            var values = (0, _tsFns.isArray)(items) ? items : [];
            return gen(values, key, this);
          }),
          type: factory.type(Entries),
          validators: factory.validators(_validators),
          create: factory.create(function (value, key, json) {
            var coming = _create ? _create.call(this, value, key, json) : value;
            return gen((0, _tsFns.isArray)(coming) ? coming : [], key, this);
          }),
          save: factory.save(_save || function (ms) {
            return ms.map(function (m) {
              return m.Chunk().toJSON();
            });
          }),
          map: factory.map(_map || function (ms) {
            return ms.map(function (m) {
              return m.Chunk().toData();
            });
          }),
          setter: factory.setter(function (value, key) {
            var coming = _setter ? _setter.call(this, value, key) : value;
            return gen((0, _tsFns.isArray)(coming) ? coming : [], key, this);
          }),
          $entries: Entries,
          $create: gen,
          $adapt: $adapt
        });

        this.meta = new ThisFactoryMeta(attributes);
      } else {
        var _gen = function _gen(value, key, parent) {
          var child = createInstance(value, key, parent);
          setupLinkage(child, parent, key);
          return child;
        };

        var _attributes = _objectSpread(_objectSpread({}, attrs), {}, {
          default: factory.default(function (key) {
            var value = (0, _tsFns.isFunction)(_default) ? _default.call(this, key) : _default;
            return _gen(value, key, this);
          }),
          type: factory.type(Entries),
          validators: factory.validators(_validators),
          create: factory.create(function (value, key, json) {
            var coming = _create ? _create.call(this, value, key, json) : value;
            return _gen(coming, key, this);
          }),
          save: factory.save(_save || function (m) {
            return m.Chunk().toJSON();
          }),
          map: factory.map(_map || function (m) {
            return m.Chunk().toData();
          }),
          setter: factory.setter(function (value, key) {
            var coming = _setter ? _setter.call(this, value, key) : value;
            return _gen(coming, key, this);
          }),
          $entries: Entries,
          $create: _gen,
          $adapt: $adapt
        });

        this.meta = new ThisFactoryMeta(_attributes);
      }
    }
    /**
     * choose which Model to use before instance
     * @param {*} Entries
     * @param {*} data
     * @param {*} key
     * @param {*} parent
     * @returns
     */

  }, {
    key: "select",
    value: function select(Entries) {
      return (0, _tsFns.isArray)(Entries) ? Entries[0] : Entries;
    }
    /**
     * modify or change Model instance
     * @param {*} model
     * @returns
     */

  }, {
    key: "instance",
    value: function instance(ChoosedModel, data, options) {
      return new ChoosedModel(data, options);
    }
    /**
     * detect whether a data is adapt to Entries
     * @param {*} Entries
     * @param {*} data
     * @returns {boolean}
     */

  }, {
    key: "adapt",
    value: function adapt(Entries, data) {
      return (0, _tsFns.isArray)(Entries) ? Entries.some(function (Entry) {
        return (0, _tsFns.isInstanceOf)(data, Entry);
      }) : (0, _tsFns.isInstanceOf)(data, Entries);
    } // transport
    // linkage
    // override

  }, {
    key: "default",
    value: function _default(fn) {
      return fn;
    }
  }, {
    key: "type",
    value: function type(_type2) {
      return _type2;
    }
  }, {
    key: "validators",
    value: function validators(_validators2) {
      return _validators2;
    }
  }, {
    key: "create",
    value: function create(fn) {
      return fn;
    }
  }, {
    key: "save",
    value: function save(fn) {
      return fn;
    }
  }, {
    key: "map",
    value: function map(fn) {
      return fn;
    }
  }, {
    key: "setter",
    value: function setter(fn) {
      return fn;
    }
  }, {
    key: "getMeta",
    value: function getMeta() {
      return this.meta;
    }
  }], [{
    key: "createMeta",
    value: function createMeta(entries, attrs, hooks) {
      var Constructor = /*#__PURE__*/function (_this3) {
        (0, _inherits2.default)(Constructor, _this3);

        var _super5 = _createSuper(Constructor);

        function Constructor() {
          (0, _classCallCheck2.default)(this, Constructor);
          return _super5.apply(this, arguments);
        }

        return (0, _createClass2.default)(Constructor);
      }(this);

      if (hooks) {
        Object.assign(Constructor.prototype, hooks);
      }

      var factory = new Constructor(entries, attrs);
      return factory.getMeta();
    }
    /**
     * create a meta by given entries
     * @param {ModelClass[] | ModelClass[][]} entries
     * @param {ModelClass[] => Model} select
     * @param {object} [attrs]
     * @param {object} [hooks]
     * @returns {Meta}
     */

  }, {
    key: "selectMeta",
    value: function selectMeta(entries, _select, attrs) {
      var hooks = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
      var isList = (0, _tsFns.isArray)(entries[0]);
      var items = isList ? entries[0] : entries;
      var input = isList ? items : items[0];
      return this.createMeta(input, attrs, _objectSpread(_objectSpread({}, hooks), {}, {
        select: function select(_, data, parent, key) {
          return _select(items, data, parent, key);
        },
        adapt: function adapt(_, data) {
          return items.some(function (Entry) {
            return (0, _tsFns.isInstanceOf)(data, Entry);
          });
        }
      }));
    }
    /**
     * create a chunk for model
     * @param {*} options
     * @param {function} options.data (...params) => Promise<data>
     * @param {function} [options.fromJSON] (data) => JSON
     * @param {function} [options.toJSON] (model: Model) => JSON
     * @param {function} [options.toData] (model: Model) => data
     * @returns
     */

  }, {
    key: "chunk",
    value: function chunk(options) {
      return new FactoryChunk(options);
    }
  }, {
    key: "toParams",
    value: function toParams(data, determine) {
      var output = (0, _tsFns.flat)(data, determine);
      return output;
    }
  }, {
    key: "toFormData",
    value: function toFormData(data, determine) {
      var params = Factory.toParams(data, determine);
      var formdata = new FormData();
      (0, _tsFns.each)(params, function (value, key) {
        formdata.append(key, value);
      });
      return formdata;
    }
  }]);
  return Factory;
}();

exports.Factory = Factory;