"use strict";

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

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

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

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

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

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

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

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

var _rules = require("./rules.js");

var _tuple = require("./tuple.js");

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

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 Ty = /*#__PURE__*/function () {
  function Ty() {
    (0, _classCallCheck2.default)(this, Ty);
    this._listeners = [];
    this._silent = false;
  }

  (0, _createClass2.default)(Ty, [{
    key: "bind",
    value: function bind(fn) {
      if ((0, _tsFns.isFunction)(fn)) {
        this._listeners.push(fn);
      }

      return this;
    }
  }, {
    key: "unbind",
    value: function unbind(fn) {
      var _this = this;

      this._listeners.forEach(function (item, i) {
        if (item === fn) {
          _this._listeners.splice(i, 1);
        }
      });

      return this;
    }
  }, {
    key: "dispatch",
    value: function dispatch(error) {
      var _this2 = this;

      this._listeners.forEach(function (fn) {
        Promise.resolve().then(function () {
          return fn.call(_this2, error);
        });
      });

      return this;
    }
  }, {
    key: "silent",
    value: function silent(value) {
      this._silent = !!value;
    }
  }, {
    key: "throw",
    value: function _throw(error) {
      if (this._translate && error.translate) {
        error.translate(this._translate);
      }

      this.dispatch(error);

      if (!this._silent) {
        throw error;
      }
    }
  }, {
    key: "try",
    value: function _try(fn, translate) {
      this._translate = translate;
      fn();
      this._translate = null;
    }
    /**
     * @example
     * ty.expect(10).to.match(Number)
     */

  }, {
    key: "expect",
    value: function expect(value) {
      var _this3 = this;

      return {
        to: {
          match: function match(_type) {
            var type = (0, _rules.createType)(_type);

            try {
              type.assert(value);
              return true;
            } catch (e) {
              _this3.throw(e);

              return false;
            }
          },
          be: function be(type) {
            return _this3.expect(value).to.match(type);
          }
        }
      };
    }
    /**
     * @example
     * let error = ty.catch(10).by(Number)
     */

  }, {
    key: "catch",
    value: function _catch(value) {
      var _this4 = this;

      return {
        by: function by(_type) {
          var type = (0, _rules.createType)(_type);
          var error = type.catch(value);

          if (error) {
            _this4.dispatch(error);
          }

          return error;
        }
      };
    }
    /**
     * @example
     * ty.trace('10').by(Number)
     */

  }, {
    key: "trace",
    value: function trace(value) {
      var _this5 = this;

      return {
        by: function by(_type) {
          var type = (0, _rules.createType)(_type);
          return type.trace(value).catch(function (error) {
            return _this5.throw(error);
          });
        }
      };
    }
    /**
     * @example
     * ty.track('10').by(Number)
     */

  }, {
    key: "track",
    value: function track(value) {
      var _this6 = this;

      return {
        by: function by(_type) {
          var type = (0, _rules.createType)(_type);
          return type.track(value).catch(function (error) {
            return _this6.throw(error);
          });
        }
      };
    }
    /**
     * determine whether type match
     * @example
     * let bool = ty.is(Number).typeof(10)
     * let bool = ty.is(10).of(Number)
     */

  }, {
    key: "is",
    value: function is(arg) {
      var _this7 = this;

      return {
        typeof: function _typeof(value) {
          var type = (0, _rules.createType)(arg);
          var error = type.catch(value);

          if (error) {
            _this7.dispatch(error);
          }

          return !error;
        },
        of: function of(type) {
          return _this7.is(type).typeof(arg);
        }
      };
    }
    /**
     * @param {string|undefined} which input|output
     * @example
     * @ty.decorate('input').with((value) => SomeType.assert(value))
     */

  }, {
    key: "decorate",
    get: function get() {
      var $this = this;

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

        var type = types[0],
            type2 = types[1];
        var chekcer = (0, _rules.createType)(type, null, true);

        var check = function check() {
          $this.try(function () {
            return $this.expect(target).to.be(chekcer);
          }, "{keyPath} should match {should} but receive {receive}");
        };

        if ((0, _tsFns.isObject)(target)) {
          var proxy = (0, _tsFns.createProxy)(target, {
            set: function set(keyPath, value) {
              (0, _tsFns.assign)(target, keyPath, value);
              check();
              return value;
            }
          });
          return proxy;
        } else if ((0, _tsFns.isArray)(target)) {
          var _proxy = (0, _tsFns.createProxy)(target, {
            set: function set(keyPath, value) {
              (0, _tsFns.assign)(target, keyPath, value);
              check();
              return value;
            },
            push: function push(keyPath, items) {
              if (keyPath.length) {
                return;
              }

              $this.try(function () {
                return $this.expect(items).to.be(type);
              }, "push items {keyPath} should match {should} but receive {receive}");
            },
            splice: function splice(keyPath, _ref) {
              var _ref2 = (0, _toArray2.default)(_ref),
                  _start = _ref2[0],
                  _end = _ref2[1],
                  items = _ref2.slice(2);

              if (!keyPath.length && items.length) {
                $this.try(function () {
                  return $this.expect(items).to.be(type);
                }, "splice insert items {keyPath} should match {should} but receive {receive}");
              }
            },
            fill: function fill(keyPath, _ref3) {
              var _ref4 = (0, _slicedToArray2.default)(_ref3, 3),
                  value = _ref4[0],
                  _start = _ref4[1],
                  _end = _ref4[2];

              if (keyPath.length) {
                return;
              }

              $this.try(function () {
                return $this.expect([value]).to.be(type);
              }, "fill value should match {should} but receive {receive}");
            }
          });

          return _proxy;
        } // isConstructor should must come before isFunction
        else if ((0, _tsFns.isConstructor)(target, 2)) {
          return /*#__PURE__*/function (_target) {
            (0, _inherits2.default)(_class, _target);

            var _super = _createSuper(_class);

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

              (0, _classCallCheck2.default)(this, _class);
              var tupl = (0, _tsFns.isInstanceOf)(type, _tuple.Tupl) ? type : new _tuple.Tupl(type);
              $this.try(function () {
                return $this.expect(args).to.be(tupl);
              }, "".concat(target.name, " constructor parameters should match {should} but receive {receive}"));
              return _super.call.apply(_super, [this].concat(args));
            }

            return (0, _createClass2.default)(_class);
          }(target);
        } else if ((0, _tsFns.isFunction)(target)) {
          var tupl = (0, _tsFns.isInstanceOf)(type, _tuple.Tupl) ? type : new _tuple.Tupl(type);
          return function () {
            for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
              args[_key3] = arguments[_key3];
            }

            $this.try(function () {
              return $this.expect(args).to.be(tupl);
            }, "".concat(title, " parameters {keyPath} should match {should} but receive {receive}"));
            var result = target.apply(this, args);
            $this.try(function () {
              return $this.expect(result).to.be(type2);
            }, "".concat(title, " returns should match {should} but receive {receive}"));
            return result;
          };
        } else {
          $this.expect(target).to.be(type);
          return target;
        }
      }

      function describe() {
        for (var _len4 = arguments.length, types = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
          types[_key4] = arguments[_key4];
        }

        return function (target, prop, descriptor) {
          (0, _utils.onlySupportLegacy)(target);
          var type = types[0]; // decorate class constructor

          if (target && !prop) {
            return wrap.apply(void 0, ["".concat(target.constructor.name, ".constructor"), target].concat(types));
          } // decorate class member
          else if (target && prop) {
            // computed property with setter
            if (descriptor.set || descriptor.get) {
              var get = descriptor.get ? wrap("".concat(target.constructor.name, ".").concat(prop, " getter"), descriptor.get, [], type) : void 0;
              var set = descriptor.set ? wrap("".concat(target.constructor.name, ".").concat(prop, " setter"), descriptor.set, [type]) : void 0;
              return _objectSpread(_objectSpread({}, descriptor), {}, {
                set: set,
                get: get
              });
            } // function method
            else if (descriptor.writable && (0, _tsFns.isFunction)(descriptor.value)) {
              return _objectSpread(_objectSpread({}, descriptor), {}, {
                value: wrap.apply(void 0, ["".concat(target.constructor.name, ".").concat(prop), descriptor.value].concat(types))
              });
            } // normal property with initializer
            else if (descriptor.writable && descriptor.initializer) {
              var value = descriptor.initializer();
              $this.try(function () {
                return $this.expect(value).to.be(type);
              }, "".concat(target.name, ".").concat(prop, " should be {should} but receive {receive}"));
              return {
                enumerable: descriptor.enumerable,
                configurable: descriptor.configurable,
                set: function set(v) {
                  $this.try(function () {
                    return $this.expect(v).to.be(type);
                  }, "".concat(target.name, ".").concat(prop, " should be {should} but receive {receive}"));
                  value = v;
                },
                get: function get() {
                  return value;
                }
              };
            } // normal property
            else if (descriptor.writable) {
              var _value = descriptor.value;
              return {
                enumerable: descriptor.enumerable,
                configurable: descriptor.configurable,
                set: function set(v) {
                  $this.try(function () {
                    return $this.expect(v).to.be(type);
                  }, "".concat(target.name, ".").concat(prop, " should be {should} but receive {receive}"));
                  _value = v;
                },
                get: function get() {
                  return _value;
                }
              };
            } else {
              return descriptor;
            }
          } else {
            return descriptor;
          }
        };
      }

      function decorate(source) {
        return {
          with: function _with() {
            for (var _len5 = arguments.length, types = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
              types[_key5] = arguments[_key5];
            }

            return wrap.apply(void 0, ["function ".concat(source.name), source].concat(types));
          }
        };
      }

      decorate.with = function () {
        return describe.apply(void 0, arguments);
      };

      return decorate;
    }
  }]);
  return Ty;
}();

exports.Ty = Ty;
var ty = new Ty();
Ty.expect = ty.expect.bind(ty);
Ty.catch = ty.catch.bind(ty);
Ty.trace = ty.trace.bind(ty);
Ty.track = ty.track.bind(ty);
Ty.is = ty.is.bind(ty);
Ty.create = _rules.createType;
Object.defineProperty(Ty, 'decorate', {
  get: function get() {
    return ty.decorate;
  }
});