"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.tryGetRealValue = exports.parseKey = exports.isInlineExp = exports.getExp = exports.createFn = exports.Loader = void 0;

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 _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));

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

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

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

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

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

var _parser = require("./parser.js");

var _model = require("../model.js");

var _utils = require("../shared/utils.js");

var _scopex = require("scopex");

var _validator = require("../validator.js");

var _factory = require("../factory.js");

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

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; } }

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; }

var getFinalExp = function getFinalExp(exp) {
  return exp.trim().replace(/^\.\./g, '$views.') // -> { ..a.value }
  .replace(/(\s)\.\./g, ' $views.') // -> { a + ..b.value }
  .replace(/(.)\.\./g, '$1.$views.');
}; // -> { $parent..b.value }


var parseKey = function parseKey(str) {
  var matched = str.match(/([a-zA-Z0-9_$]+)(\((.*?)\))?/);

  var _matched = (0, _slicedToArray2.default)(matched, 4),
      _ = _matched[0],
      name = _matched[1],
      _p = _matched[2],
      _params = _matched[3];

  var params = (0, _tsFns.isString)(_params) ? _params.split(',').map(function (item) {
    return item.trim();
  }).filter(function (item) {
    return !!item;
  }) : void 0; // params: undefined -> string key; [] -> function without params; [...] -> function with params

  return [name, params];
};

exports.parseKey = parseKey;

var isInlineExp = function isInlineExp(str) {
  return str[0] === '{' && str[str.length - 1] === '}';
};

exports.isInlineExp = isInlineExp;

var getInlineExp = function getInlineExp(str) {
  return str.substring(1, str.length - 1).trim();
};

var getExp = function getExp(str) {
  var line = isInlineExp(str) ? getInlineExp(str) : str;
  var exp = getFinalExp(line);
  return exp;
};

exports.getExp = getExp;

var tryGetRealValue = function tryGetRealValue(exp) {
  try {
    return JSON.parse(exp);
  } catch (e) {
    var scope = new _scopex.ScopeX({});

    try {
      return scope.parse(exp);
    } catch (e) {
      return exp;
    }
  }
};

exports.tryGetRealValue = tryGetRealValue;

var createFn = function createFn(scope, exp, params) {
  return function () {
    for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) {
      args[_key2] = arguments[_key2];
    }

    if (!params) {
      var _output = scope.parse(exp);

      return _output;
    }

    var locals = {};
    params.forEach(function (param, i) {
      var value = args[i];
      locals[param] = value;
    });
    var newScope = scope.$new(locals);
    var output = newScope.parse(exp);
    return output;
  };
};

exports.createFn = createFn;

var Loader = /*#__PURE__*/function () {
  function Loader() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    (0, _classCallCheck2.default)(this, Loader);
    var _options$global = options.global,
        globalVars = _options$global === void 0 ? {} : _options$global,
        _options$filters = options.filters,
        thisFilters = _options$filters === void 0 ? {} : _options$filters;
    var defs = this.defs();
    var filters = this.filters();
    this.globalScope = (0, _scopex.createScope)(_objectSpread(_objectSpread({}, globalVars), defs), {
      filters: _objectSpread(_objectSpread({}, thisFilters), filters)
    });
    var types = this.types();
    this.typeParser = new _parser.Parser(types);
  }
  /**
   *
   * @param {object} json {
   *   "schema": {
   *     "name": {
   *       "default": "",
   *       "type": "string",
   *       "required()": "age > 0",
   *     },
   *     "age": {
   *       "default": 0,
   *       "type": "number",
   *     },
   *   },
   *   "state": {
   *     "is_found": false
   *   },
   *   "attrs": {},
   *   "methods": {
   *     "doSome(v)": "name = v",
   *   },
   * }
   */


  (0, _createClass2.default)(Loader, [{
    key: "parse",
    value: function parse(json) {
      var loader = this;
      var globalScope = this.globalScope,
          typeParser = this.typeParser;
      var schema = json.schema,
          _json$state = json.state,
          state = _json$state === void 0 ? {} : _json$state,
          _json$attrs = json.attrs,
          attrs = _json$attrs === void 0 ? {} : _json$attrs,
          _json$methods = json.methods,
          methods = _json$methods === void 0 ? {} : _json$methods;

      var tryGetInScope = function tryGetInScope(exp) {
        var scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalScope;

        try {
          var isIn = false;
          var res = scope.parse(exp, function (deps) {
            return isIn = !!deps.length;
          });
          return [isIn, res];
        } catch (e) {
          return [];
        }
      };

      var parseAsyncGetter = function parseAsyncGetter(value, scope) {
        if ((0, _tsFns.isString)(value) && /:fetch\(.*?\)/.test(value)) {
          var _value$match = value.match(/(\$\((\w+)\):)?(.*?):fetch\((.*?)\)(.*?)/),
              _value$match2 = (0, _slicedToArray2.default)(_value$match, 6),
              _all = _value$match2[0],
              _deps = _value$match2[1],
              deps = _value$match2[2],
              before = _value$match2[3],
              matched = _value$match2[4],
              after = _value$match2[5];

          var defaultValue = tryGetRealValue(before);
          var reactors = (0, _tsFns.decideby)(function () {
            if (deps) {
              return deps.split(',');
            } // find deps automaticly


            var mayBeDeps = (0, _tsFns.uniqueArray)((0, _toConsumableArray2.default)(value.replace(/['"].*?['"]/g, ' ').matchAll(/\W(\w+(\.\w+)*?)\W?/g) || []).map(function (item) {
              var str = item[1] || '';

              var _str$split = str.split('.'),
                  _str$split2 = (0, _slicedToArray2.default)(_str$split, 1),
                  field = _str$split2[0];

              return field;
            }).filter(function (item) {
              return (0, _tsFns.inObject)(item, schema);
            }));
            return mayBeDeps.length ? mayBeDeps : null;
          });
          return (0, _utils.createAsyncRef)(defaultValue, function () {
            var url = scope.parse(matched);
            return loader.fetch(url).then(function (data) {
              if (data && (0, _typeof2.default)(data) === 'object' && after && after[0] === '.') {
                var keyPath = after.substr(1);
                return (0, _tsFns.parse)(data, keyPath);
              } else {
                return data;
              }
            });
          }, reactors);
        }

        return value;
      };

      var parseSubModel = function parseSubModel(exp) {
        // if string, only support inline exp
        if ((0, _tsFns.isString)(exp) && isInlineExp(exp)) {
          var _tryGetInScope = tryGetInScope(getInlineExp(exp)),
              _tryGetInScope2 = (0, _slicedToArray2.default)(_tryGetInScope, 2),
              isIn = _tryGetInScope2[0],
              res = _tryGetInScope2[1];

          if (isIn && res && (0, _tsFns.isInstanceOf)(res, _model.Model)) {
            return res;
          }
        } else if (exp && !(0, _tsFns.isArray)(exp) && (0, _typeof2.default)(exp) === 'object') {
          var sub = loader.parse(exp);
          return sub;
        }
      };

      var createComposeFn = function createComposeFn(params, exp) {
        var isInjected = /\W?await fetch\(.*?\)/.test(exp);
        return function () {
          for (var _len2 = arguments.length, args = new Array(_len2), _key3 = 0; _key3 < _len2; _key3++) {
            args[_key3] = arguments[_key3];
          }

          var scope = globalScope.$new(this);

          if (isInjected) {
            return new Promise(function (resolve, reject) {
              var _ref = isInjected ? exp.match(/(.*\W)?await fetch\((.*?)\)(.*)/) : [],
                  _ref2 = (0, _slicedToArray2.default)(_ref, 4),
                  _all = _ref2[0],
                  before = _ref2[1],
                  _url = _ref2[2],
                  after = _ref2[3];

              var url = createFn(scope, _url, params).apply(void 0, args);
              loader.fetch(url).then(function (data) {
                var subScope = scope.$new({
                  __await__: data
                });
                var subExp = [before, '__await__', after].join('');
                var res = createFn(subScope, subExp, params).apply(void 0, args);
                resolve(res);
              }).catch(reject);
            });
          }

          var res = createFn(scope, exp, params).apply(void 0, args);
          return res;
        };
      };

      var createValue = function createValue(scope, value, params) {
        // not a string like: `"default()": {}`, it will be `default() { return {} }`
        if (!(0, _tsFns.isString)(value)) {
          return params ? function () {
            return value;
          } : value;
        }

        var isInline = isInlineExp(value);

        if (!params && !isInline) {
          return parseAsyncGetter(value, scope);
        }

        var line = isInline ? getInlineExp(value) : value;
        var exp = getFinalExp(line);
        var getter = parseAsyncGetter(exp, scope);

        if (getter !== exp) {
          return getter;
        } // only inline exp
        // { "some": "{ field }" }


        if (!params && isInline) {
          return createFn(scope, exp, []);
        } // function attr

        /**
         * {
         *   "drop(v)": "v > 6 && age > 20"
         * }
         */

        /**
         * {
         *   "default": "content text",
         *   "type": "string",
         *   "validators": [
         *     {
         *       "determine(value)": "value > 0"
         *       "validate(value)": "value > 5",
         *       "message": "should greater then 5"
         *     }
         *   ]
         * }
         */


        return createComposeFn(params, exp);
      };

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

        var _super = _createSuper(LoadedModel);

        function LoadedModel() {
          (0, _classCallCheck2.default)(this, LoadedModel);
          return _super.apply(this, arguments);
        }

        (0, _createClass2.default)(LoadedModel, [{
          key: "_takeState",
          value: function _takeState() {
            var _this = this;

            var stat = {};
            (0, _tsFns.each)(state, function (value, key) {
              stat[key] = parseAsyncGetter(value, globalScope.$new(_this));
            });
            return stat;
          }
        }, {
          key: "_takeAttrs",
          value: function _takeAttrs() {
            var originalAttrs = (0, _get2.default)((0, _getPrototypeOf2.default)(LoadedModel.prototype), "_takeAttrs", this).call(this);
            return _objectSpread(_objectSpread({}, originalAttrs), attrs);
          }
        }, {
          key: "_takeSchema",
          value: function _takeSchema() {
            var scope = globalScope.$new(this);
            var metas = {};
            var submodels = {};
            var factories = {};
            (0, _tsFns.each)(schema, function (def, field) {
              // sub model(s)
              if (/^<.+?>$/.test(field)) {
                var name = field.substring(1, field.length - 1);

                if ((0, _tsFns.isArray)(def)) {
                  var items = def.map(function (item) {
                    return parseSubModel(item);
                  }).filter(function (item) {
                    return !!item;
                  });

                  if (items.length) {
                    submodels[name] = items;
                  }
                } else {
                  var sub = parseSubModel(def);

                  if (sub) {
                    submodels[name] = sub;
                  }
                }

                return;
              }

              if (!(0, _tsFns.isObject)(def)) {
                return;
              }

              var meta = {};
              var defi = loader.meta(def);
              (0, _tsFns.each)(defi, function (_exp, attr) {
                var _parseKey = parseKey(attr),
                    _parseKey2 = (0, _slicedToArray2.default)(_parseKey, 2),
                    _key = _parseKey2[0],
                    _params = _parseKey2[1];

                var _loader$attr = loader.attr(_key, _params, _exp),
                    _loader$attr2 = (0, _slicedToArray2.default)(_loader$attr, 3),
                    key = _loader$attr2[0],
                    params = _loader$attr2[1],
                    exp = _loader$attr2[2];

                if (key === 'type') {
                  var type = typeParser.parse(exp);
                  meta.type = type;
                  return;
                }

                if (key === 'validators') {
                  if (!(0, _tsFns.isArray)(exp)) {
                    return;
                  }

                  var _items = [];
                  var builtinValidators = globalScope.$new(_validator.Validator);
                  exp.forEach(function (validator) {
                    if ((0, _tsFns.isString)(validator)) {
                      // i.e. validators: [ "required('some is required!')" ]
                      var _parseKey3 = parseKey(validator),
                          _parseKey4 = (0, _slicedToArray2.default)(_parseKey3, 2),
                          _key4 = _parseKey4[0],
                          _params2 = _parseKey4[1];

                      if (_validator.Validator[_key4] && _params2) {
                        _items.push(builtinValidators.parse(validator));
                      }

                      return;
                    }

                    if (!(0, _tsFns.isObject)(validator)) {
                      return;
                    }

                    var item = {};
                    (0, _tsFns.each)(validator, function (_exp, attr) {
                      var _parseKey5 = parseKey(attr),
                          _parseKey6 = (0, _slicedToArray2.default)(_parseKey5, 2),
                          _key = _parseKey6[0],
                          _params = _parseKey6[1];

                      var _loader$validator = loader.validator(_key, _params, _exp),
                          _loader$validator2 = (0, _slicedToArray2.default)(_loader$validator, 3),
                          key = _loader$validator2[0],
                          params = _loader$validator2[1],
                          exp = _loader$validator2[2];

                      if ((0, _tsFns.isFunction)(exp)) {
                        item[key] = exp;
                        return;
                      }

                      item[key] = createValue(scope, exp, params);
                    });

                    _items.push(item);
                  });
                  meta.validators = _items;
                  return;
                }

                meta[key] = createValue(scope, exp, params);
              }); // DEPARTED Compatible for old version
              // |submodel| sub model(s) factory, should must be used with <submodel>

              if (/^\|.+?\|$/.test(field)) {
                var key = field.substring(1, field.length - 1);
                factories[key] = meta;
                return;
              }

              if (!(0, _tsFns.isEmpty)(meta)) {
                metas[field] = meta;
              }
            }); // factory wrapper should must come after schema finished, or schema sub model will not be ready

            (0, _tsFns.each)(submodels, function (model, field) {
              var attrs = factories[field] || metas[field]; // -> new version support submodel field name directly

              if (attrs) {
                metas[field] = _factory.Factory.createMeta(model, attrs);
              } else {
                metas[field] = model;
              }
            });
            return metas;
          }
        }]);
        return LoadedModel;
      }(_model.Model);

      (0, _tsFns.each)(methods, function (value, attr) {
        if (!(0, _tsFns.isString)(value)) {
          return;
        }

        var _parseKey7 = parseKey(attr),
            _parseKey8 = (0, _slicedToArray2.default)(_parseKey7, 2),
            _key = _parseKey8[0],
            _params = _parseKey8[1];

        if (!_params) {
          return;
        }

        var _exp = getExp(value);

        var _loader$method = loader.method(_key, _params, _exp),
            _loader$method2 = (0, _slicedToArray2.default)(_loader$method, 3),
            key = _loader$method2[0],
            params = _loader$method2[1],
            exp = _loader$method2[2];

        if ((0, _tsFns.isFunction)(exp)) {
          LoadedModel.prototype[key] = exp;
          return;
        }

        if (!(0, _tsFns.isString)(exp)) {
          return;
        }

        LoadedModel.prototype[key] = createComposeFn(params, exp);
      });
      return this.extend(LoadedModel) || LoadedModel;
    } // set types

  }, {
    key: "types",
    value: function types() {
      return {};
    } // set defs

  }, {
    key: "defs",
    value: function defs() {
      return {};
    } // global vars

  }, {
    key: "global",
    value: function global() {
      return {};
    } // set filters

  }, {
    key: "filters",
    value: function filters() {
      return {};
    } // you can modify json meta object here

  }, {
    key: "meta",
    value: function meta(_meta) {
      return _meta;
    } // you can modify an attribute json here

  }, {
    key: "attr",
    value: function attr(key, params, exp) {
      return [key, params, exp];
    } // you can modify an validator json here

  }, {
    key: "validator",
    value: function validator(key, params, exp) {
      return [key, params, exp];
    } // you can modify a method json here

  }, {
    key: "method",
    value: function method(key, params, exp) {
      return [key, params, exp];
    } // extend LoadedModel

  }, {
    key: "extend",
    value: function extend(Model) {
      return Model;
    } // define fetch factory

  }, {
    key: "fetch",
    value: function (_fetch) {
      function fetch(_x) {
        return _fetch.apply(this, arguments);
      }

      fetch.toString = function () {
        return _fetch.toString();
      };

      return fetch;
    }(function (url) {
      return fetch(url).then(function (res) {
        return res.json();
      });
    })
  }, {
    key: "load",
    value: function load(url) {
      var _this2 = this;

      return this.fetch(url).then(function (json) {
        return _this2.parse(json);
      });
    }
    /**
     * parse attrs for a meta,
     * `this` inside is not supported
     * @param {*} attrs
     * @returns
     */

  }], [{
    key: "parseAttrs",
    value: function parseAttrs(attrs) {
      var scope = (0, _scopex.createScope)({});

      var parse = function parse(data) {
        var meta = {};
        (0, _tsFns.each)(data, function (value, key) {
          if (key === 'validators') {
            if (!(0, _tsFns.isArray)(value)) {
              return;
            }

            var items = [];
            var builtinValidators = scope.$new(_validator.Validator);
            value.forEach(function (validator) {
              if ((0, _tsFns.isString)(validator)) {
                // i.e. validators: [ "required('some is required!')" ]
                var _parseKey9 = parseKey(validator),
                    _parseKey10 = (0, _slicedToArray2.default)(_parseKey9, 2),
                    _key5 = _parseKey10[0],
                    _params3 = _parseKey10[1];

                if (_validator.Validator[_key5] && _params3) {
                  items.push(builtinValidators.parse(validator));
                }

                return;
              }

              if (!(0, _tsFns.isObject)(validator)) {
                return;
              }

              var item = parse(validator);
              items.push(item);
            });
            meta.validators = items;
            return;
          }

          var _parseKey11 = parseKey(key),
              _parseKey12 = (0, _slicedToArray2.default)(_parseKey11, 2),
              name = _parseKey12[0],
              params = _parseKey12[1];

          var isStr = typeof value === 'string';

          if (params) {
            var getter = function getter() {
              if (!isStr) {
                return value;
              }

              var exp = getExp(value);
              var localScope = scope.$new(this);
              var fn = createFn(localScope, exp, params);
              return fn.apply(void 0, arguments);
            };

            meta[name] = getter;
          } else if (isInlineExp(value)) {
            var _getter = function _getter() {
              if (!isStr) {
                return value;
              }

              var exp = getExp(value);
              var localScope = scope.$new(this);
              var fn = createFn(localScope, exp, []);
              return fn.apply(void 0, arguments);
            };

            meta[name] = _getter;
          } else {
            meta[name] = isStr ? tryGetRealValue(value) : value;
          }
        });
        return meta;
      };

      return parse(attrs);
    }
  }, {
    key: "createModel",
    value: function createModel(url) {
      var Constructor = this;
      return new Constructor().load(url);
    }
  }]);
  return Loader;
}();

exports.Loader = Loader;