function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var getInnerName = function getInnerName(name) {
  return "__" + name;
};

var Context = /*#__PURE__*/function () {
  function Context(_ref, ctxRef) {
    var _init;

    var use = _ref.use,
        set = _ref.set,
        addQueryableProperties = _ref.addQueryableProperties,
        init = _ref.init;
    this._parentContext = null;
    var ctx = use();
    var usedRef = typeof init === 'function' ? (_init = init(ctxRef, ctx)) !== null && _init !== void 0 ? _init : ctxRef : ctxRef;
    var queryableProperties = addQueryableProperties(usedRef);

    if (usedRef && typeof usedRef === 'object') {
      for (var key in queryableProperties) {
        if (Object.prototype.hasOwnProperty.call(usedRef, key)) {
          this[getInnerName(key)] = usedRef[key];
        }

        this.addLookupProperty(key);
      }
    }

    if (ctx) {
      this.setParentContext(ctx);
    }

    set(this);
  }

  Context.is = function is(value) {
    return value instanceof Context;
  };

  var _proto = Context.prototype;

  _proto.addLookupProperty = function addLookupProperty(key) {
    var innerName = getInnerName(key);
    Object.defineProperty(this, key, {
      get: function get() {
        return this.lookup(innerName);
      },
      set: function set(value) {
        throw new Error("Context: Unable to set \"" + key + "\" to `" + JSON.stringify(value) + "`. Context properties cannot be set directly. Use context.run() instead.");
      }
    });
  } // @ts-ignore - we actually do use lookup
  ;

  _proto.lookup = function lookup(key) {
    var ctx = this;

    do {
      if (ctx.hasOwnProperty(key)) {
        return ctx[key];
      }

      if (Context.is(ctx.parentContext)) {
        ctx = ctx.parentContext;
      } else {
        return;
      }
    } while (ctx);
  };

  _proto.setParentContext = function setParentContext(parentContext) {
    if (Context.is(this)) {
      this._parentContext = parentContext;
    }
  };

  _createClass(Context, [{
    key: "parentContext",
    get: function get() {
      return this._parentContext;
    }
  }]);

  return Context;
}();

function createContext(init) {
  var storage = {
    ctx: undefined
  };
  var queryableProperties = {};

  function addQueryableProperties(ctxRef) {
    if (!ctxRef || typeof ctxRef !== 'object') {
      return {};
    }

    for (var key in ctxRef) {
      if (Object.prototype.hasOwnProperty.call(ctxRef, key)) {
        queryableProperties[key] = true;
      }
    }

    return queryableProperties;
  }

  function use() {
    return storage.ctx;
  }

  function set(value) {
    return storage.ctx = value;
  }

  function clear() {
    var ctx = use();

    if (!Context.is(ctx)) {
      return;
    }

    set(ctx.parentContext);
  }

  function run(ctxRef, fn) {
    var ctx = new Context({
      set: set,
      use: use,
      addQueryableProperties: addQueryableProperties,
      init: init
    }, ctxRef);
    var res = fn(ctx);
    clear();
    return res;
  }

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

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

      return run(ctxRef, function () {
        return fn.apply(void 0, args.concat(runTimeArgs));
      });
    };
  }

  return {
    use: use,
    run: run,
    bind: bind
  };
}

export default createContext;
//# sourceMappingURL=context.esm.js.map
