"use strict";

exports.__esModule = true;
exports.default = undefined;

var _createClass = function () { 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _es6Symbol = require("es6-symbol");

var _es6Symbol2 = _interopRequireDefault(_es6Symbol);

var _lodash = require("lodash.clone");

var _lodash2 = _interopRequireDefault(_lodash);

var _lodash3 = require("lodash.clonedeep");

var _lodash4 = _interopRequireDefault(_lodash3);

var _lodash5 = require("lodash.isobject");

var _lodash6 = _interopRequireDefault(_lodash5);

var _lodash7 = require("lodash.isstring");

var _lodash8 = _interopRequireDefault(_lodash7);

var _lodash9 = require("lodash.result");

var _lodash10 = _interopRequireDefault(_lodash9);

var _akutils = require("akutils");

var _Access = require("./Access");

var _Access2 = _interopRequireDefault(_Access);

var _extendProperty = require("./extendProperty");

var _extendProperty2 = _interopRequireDefault(_extendProperty);

var _isShadow = require("./isShadow");

var _isShadow2 = _interopRequireDefault(_isShadow);

var _reshadow = require("./reshadow");

var _reshadow2 = _interopRequireDefault(_reshadow);

var _debug = require("./debug");

var _debug2 = _interopRequireDefault(_debug);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var debug = (0, _debug2.default)(_debug.ShadowImplKey);

// instance variable names
var _access = (0, _es6Symbol2.default)('access');
// object to store expensive derived values
var _cache = (0, _es6Symbol2.default)('cache');
// flag indicating property has pending updates. Not safe to rely on this[_futureState] as it could be
// undefined if that is the next value
var _changed = (0, _es6Symbol2.default)('changed');
var _date = (0, _es6Symbol2.default)('date');
var _dead = (0, _es6Symbol2.default)('dead');
var _didShadowCalled = (0, _es6Symbol2.default)('didShadowCalled');
var _futureState = (0, _es6Symbol2.default)('futureState');
var _getSetter = (0, _es6Symbol2.default)('getSetter');
var _hasFutureState = (0, _es6Symbol2.default)('hasFutureStatee');
var _invalid = (0, _es6Symbol2.default)('invalid');
var _name = (0, _es6Symbol2.default)('name');
var _nextName = (0, _es6Symbol2.default)('nextName');
var _path = (0, _es6Symbol2.default)('path');

// flag marks this property as obsolete and thus no longer to effect updates on the
// next data model
var _preventUpdates = (0, _es6Symbol2.default)('preventUpdates');
var _previousTime = (0, _es6Symbol2.default)('previousTime');
var _property = (0, _es6Symbol2.default)('property');
var _readonly = (0, _es6Symbol2.default)('readonly');
var _replaced = (0, _es6Symbol2.default)('replaced');
var _scheduled = (0, _es6Symbol2.default)('scheduled');
var _shadow = (0, _es6Symbol2.default)('shadow');
var _state = (0, _es6Symbol2.default)('state');
var _time = (0, _es6Symbol2.default)('time');

// private method symbols
var _createShadow = (0, _es6Symbol2.default)('createShadow');
var _defineProperty = (0, _es6Symbol2.default)('defineProperty');
var _modelForUpdate = (0, _es6Symbol2.default)('modelForUpdate');
var _scheduleUpdate = (0, _es6Symbol2.default)('scheduleUpdate');
var _setupShadow = (0, _es6Symbol2.default)('setupShadow');

/*
	Todos:
		* Isolated support
			-

		* Reduce memory footprint:
			1) investigate _time and _previousTime really needed
			2) investigate getting access from property (reduce object creation and memory footprint)

		* Investigate replacing impl with Proxy
*/

/**
	The base class for {@link Shadow} backing objects. Each shadow property has an 'impl' that
	performs the f.lux bookkeeping to enable the shadow state to work properly. The 'impl' is
	broken out from the shadow proper to prevent polluting the namespace with a bunch of crazy
	looking variables and methods.

	A shadow property's 'impl' is available through the {@link Shadow.__} method. Direct access
	to the 'impl' is rarely needed by custom properties, shadows, or application logic. And there
	almost certainly no reason to directly subclass this class.

	@see {@link Shadow.__}
*/

var ShadowImpl = function () {
	function ShadowImpl(time, property, name, state, parent, shader, prev) {
		_classCallCheck(this, ShadowImpl);

		this[_property] = property;
		this[_name] = name;
		this[_state] = state;
		this[_time] = time;

		if (prev) {
			this[_previousTime] = prev[_time];
		}

		// TODO: quick hack till have unit tests and thought out life-cycle design
		// didShadow() is being called multiple times which is causing a problem with property
		// initialization that should only occur once.
		this[_didShadowCalled] = false;
	}

	/**
 	Alias for {@link ShadowImpl.shadow}.
 */


	_createClass(ShadowImpl, [{
		key: "_",
		value: function _() {
			return this.shadow();
		}
	}, {
		key: "access",
		value: function access() {
			var parent = this.parent();

			if (!this[_access]) {
				if (parent && parent.access().create$ForChild) {
					// property does not know about this impl yet. So impl.property() will work but property.__() will not
					this[_access] = parent.access().create$ForChild(this);
				} else {
					this[_access] = this[_property].create$(this);
				}
			}

			return this[_access];
		}

		/**
  	Replace the value of this property. This will result in this property tree being recreated.
  		Note: This value will be used directly (not copied) so ensure the state is not altered.
  */

	}, {
		key: "assign",
		value: function assign(nextState, name) {
			nextState = (0, _isShadow2.default)(nextState) ? nextState.__().state() : nextState;

			//create a deep copy so not shared with the passed in value
			//this.deepcopy() will use current model if no value passed or value passed is null or undefined
			//in case of assigned those are legal values so must check explicitly
			return this.update(function (state) {
				return { name: name || "assign()", nextState: nextState, replace: true };
			});
		}

		/**
  	Prevents all children from being able to obtain model in update() callbacks. Update callbacks
  	should invoke this method when they perform wholesale
  */

	}, {
		key: "blockFurtherChildUpdates",
		value: function blockFurtherChildUpdates() {
			if (!this.hasChildren()) {
				return;
			}

			var children = this.children();

			for (var i = 0, child; child = children[i]; i++) {
				child.blockFurtherUpdates(true);
			}
		}

		/**
  	Prevents this property and descendents from providing a model to update() callbacks.
  		The update() method invokes this method when the callback returns a different object than the
  	one passed into the callback.
  */

	}, {
		key: "blockFurtherUpdates",
		value: function blockFurtherUpdates(replaced) {
			this[_preventUpdates] = true;
			this.invalidate(null, this);

			if (replaced) {
				this[_replaced] = true;
			}

			this.blockFurtherChildUpdates();
		}
	}, {
		key: "changeParent",
		value: function changeParent(newParent) {
			var _this = this;

			(0, _akutils.assert)(function (a) {
				return a.is(_this.isValid(), "Property must be valid to change parent: " + _this.dotPath()).not(_this.isRoot(), "Root properties do not have parents: " + _this.dotPath());
			});

			debug(function (d) {
				return d("changeParent(): " + _this.dotPath());
			});

			// clear cache
			delete this[_cache];

			// setup access through shadows
			this[_defineProperty]();
		}
	}, {
		key: "cache",
		value: function cache() {
			if (!this[_cache]) {
				this[_cache] = {};
			}

			return this[_cache];
		}

		/**
  	Create a copy of the internals during reshadowing when the property has not changed during the
  	update process but some descendant has been modified.
  */

	}, {
		key: "createCopy",
		value: function createCopy(time, newModel, parentImpl) {
			var property = this[_property];
			var ImplClass = property.implementationClass();
			var name = this.nextName();
			var shader = this.shader(newModel);

			return new ImplClass(time, property, name, newModel, parentImpl, shader, this);
		}
	}, {
		key: "didShadow",
		value: function didShadow(time, newRoot) {
			var storeRootImpl = this.store().__;

			if (this[_time] == time && !this[_didShadowCalled] && storeRootImpl === this.root()) {
				this[_didShadowCalled] = true;

				if (this.isRoot()) {
					if (this[_previousTime] || !newRoot) {
						this[_property].onPropertyDidUpdate();
					} else {
						this[_property].onPropertyDidShadow();
					}
				} else {
					this[_previousTime] ? this[_property].onPropertyDidUpdate() : this[_property].onPropertyDidShadow();
				}

				if (this.hasChildren()) {
					var children = this.children();
					var childImpl;

					for (var i = 0, len = children.length; i < len; i++) {
						var _childImpl = children[i];

						if (_childImpl) {
							_childImpl.didShadow(time);
						}
					}
				}
			}
		}

		/**
  	Intended for use by update() and replaying actions.
  */

	}, {
		key: "dispatchUpdate",
		value: function dispatchUpdate(action) {
			if (!this[_preventUpdates] && this.isUpdatable() && this.isActive()) {
				var name = action.name;
				var nextState = action.nextState;
				var replace = action.replace;

				// Sending to store first ensures:
				// 1) nextState() returns value from before this udpate
				// 2) middleware provided chance to make changes to action

				this.store().onPreStateUpdate(action, this);

				// replacing the current object prevents further next state changes for sub-properties
				if (replace) {
					this[_replaced] = true;
					// block child updates because replacement makes them unreachable
					this.blockFurtherChildUpdates();
					this.onReplaced();
				}

				// set the next model data
				this[_futureState] = nextState;
				this[_hasFutureState] = true;

				// update the parent's future state to reference the state returned by the action
				if (!this.isRoot()) {
					var parentNextData = this.owner()[_modelForUpdate]();

					// do nothing if parentNextData is not assignable
					if (parentNextData && !(0, _akutils.isPrimitive)(parentNextData)) {
						parentNextData[this[_name]] = nextState;
					}
				}

				this.invalidate(null, this);

				this.store().onPostStateUpdate(action, this);
				this.root()[_scheduleUpdate]();
			}
		}

		/**
  	Helpful debugging utility that returns the path joined by '.'. The root node will return the
  	word 'root' for the path.
  */

	}, {
		key: "dotPath",
		value: function dotPath() {
			var cache = this.cache();

			if (!cache.dotPath) {
				var path = this.path();

				cache.dotPath = path.length ? path.join('.') : 'root';
			}

			return cache.dotPath;
		}
	}, {
		key: "ensureMounted",
		value: function ensureMounted() {
			if (this.isRoot() || this.isIsolated() || this.__getCalled__) {
				return;
			}

			(0, _lodash10.default)(this.store().shadow, this.dotPath());
		}
	}, {
		key: "findByPath",
		value: function findByPath(path) {
			if (path.length === 0) {
				return this;
			}

			var next = this.getChild(path[0]);

			return next && next.findByPath(path.slice(1));
		}

		/**
  	Gets if an update has occurred directly to this property.
  */

	}, {
		key: "hasPendingChanges",
		value: function hasPendingChanges() {
			return !!this[_changed];
		}

		/**
  	Marks property and ancestors as invalid. This means this property or one of its children
  	has been updated. The invalid flag is set to the earliest timestamp when this property
  	or one of its children was changed.
  		Parameters:
  		childImpl - the child implementation triggering this call or undefined if this implementation
  			started the invalidation process
  		source - the shadow implementation that triggered the invalidation
  */

	}, {
		key: "invalidate",
		value: function invalidate(childImpl) {
			var source = arguments.length <= 1 || arguments[1] === undefined ? this : arguments[1];

			var owner = this.owner();

			if (childImpl) {
				this[_property].onChildInvalidated(childImpl.property(), source.property());
			}

			if (this.isValid() && this.isActive()) {
				this[_invalid] = true;

				if (owner) {
					owner.invalidate(this, source);
				}

				this.onInvalidated();
			}
		}

		/**
  	Gets if the property represents live data.
  */

	}, {
		key: "isActive",
		value: function isActive() {
			return !this[_dead];
		}
	}, {
		key: "isIsolated",
		value: function isIsolated() {
			return this.property().isIsolated();
		}
	}, {
		key: "isLeaf",
		value: function isLeaf() {
			return !this.hasChildren();
		}
	}, {
		key: "isRoot",
		value: function isRoot() {
			return this.property().isRoot();
		}

		/**
  	Gets if this property or one of its child properties has pending updates. Returns true if there are no
  	pending updates.
  */

	}, {
		key: "isValid",
		value: function isValid() {
			return !this[_invalid];
		}
	}, {
		key: "latest",
		value: function latest() {
			return this.store().findByPath(this.path());
		}
	}, {
		key: "name",
		value: function name() {
			return this[_name];
		}

		/**
  	Gets the name after all model updates are performed.
  */

	}, {
		key: "nextName",
		value: function nextName() {
			return this[_nextName] !== undefined ? this[_nextName] : this[_name];
		}

		/**
  	Gets the model as it will be once all pending changes are recorded with the store. This must
  	not be altered.
  */

	}, {
		key: "nextState",
		value: function nextState() {
			return this[_hasFutureState] ? this[_futureState] : this.state();
		}

		/**
  	Marks this property as obsolete. Once marked obsolete a property may not interact with the store.
  	A property becomes obsolete after it's value or ancestor's value has changed and the update process
  	has completed.
  		This method does not affect subproperties.
  */

	}, {
		key: "obsolete",
		value: function obsolete(callback) {
			if (callback) {
				callback(this);
			}

			this[_dead] = true;

			this.onObsolete();
		}
	}, {
		key: "obsoleteChildren",
		value: function obsoleteChildren() {
			if (this.hasChildren()) {
				var children = this.children();

				for (var i = 0, len = children.length; i < len; i++) {
					var child = children[i];

					if (child) {
						child.obsoleteTree();
					}
				}
			}
		}

		/**
  	Marks the entire subtree as inactive, aka dead.
  */

	}, {
		key: "obsoleteTree",
		value: function obsoleteTree(callback) {
			if (!this[_dead]) {
				this.obsolete(callback);
				this.obsoleteChildren();
			}
		}
	}, {
		key: "owner",
		value: function owner() {
			var ownerProperty = this[_property].owner();

			return ownerProperty && ownerProperty.__();
		}
	}, {
		key: "parent",
		value: function parent() {
			var parentProperty = this.property().parent();

			return parentProperty && parentProperty.__();
		}

		/**
  	Gets an array with the property names/indices from the root to this property.
  */

	}, {
		key: "path",
		value: function path() {
			var cache = this.cache();

			if (this.isRoot()) {
				return [];
			} else if (!cache.path) {
				cache.path = this.parent().path().concat(this[_name]);
			}

			return cache.path;
		}
	}, {
		key: "property",
		value: function property() {
			return this[_property];
		}
	}, {
		key: "readonly",
		value: function readonly() {
			return this[_readonly] === undefined ? this[_property].isReadonly() : this[_readonly];
		}
	}, {
		key: "replaced",
		value: function replaced() {
			return !!this[_replaced];
		}

		/**
  	Invoked by reshadow() function for invalid parent property implementations when the directly
  	managed state did not change.
  		Calls the onReshadow(prev) method to provide subclasses an oppotunity to setup for futher
  	action after a parent change.
  */

	}, {
		key: "reshadowed",
		value: function reshadowed(prev) {
			var _this2 = this;

			debug(function (d) {
				return d("reshadowed(): " + _this2.dotPath() + ", mapped=" + prev.isMapped() + ", time=" + _this2[_time] + ", prevTime=" + prev[_time]);
			});

			if (prev.__getCalled__) {
				this[_setupShadow](prev, true);
			}

			this.onReshadow(prev);
		}
	}, {
		key: "root",
		value: function root() {
			if (this[_property].isRoot()) {
				return this;
			}

			var cache = this.cache();

			if (!cache.root || !cache.root.isActive()) {
				cache.root = this.owner().root();
			}

			return cache.root;
		}

		/**
  	Sets the readonly flag which will prevent a 'set' function being set in defineProeprty().
  		Note: this method must be called before defineProperty() is invoked or it will have no affect.
  */

	}, {
		key: "setReadonly",
		value: function setReadonly(readonly) {
			this[_readonly] = readonly;
		}

		/**
  	Creates shadow properties for root properties and sets this property on the parent property for
  	non-root properties.
  		Note: This method is called by shadowProperty() and reshadow() functions.
  */

	}, {
		key: "setupPropertyAccess",
		value: function setupPropertyAccess(prev) {
			var property = this[_property];

			if (this.isRoot() || this.isIsolated()) {
				this[_setupShadow](prev);
			} else {
				this[_defineProperty](prev);
			}
		}

		/**
  	Gets the shader needed to recreate the shadow property for the state.
  */

	}, {
		key: "shader",
		value: function shader(state) {
			return this[_property].shader(state);
		}

		/**
  	Gets the user facing property represented by this implementation object.
  */

	}, {
		key: "shadow",
		value: function shadow() {
			// Not ok to map this property if parent is not yet mapped
			if (!this.isMapped() && this.parent() && !this.parent().isMapped()) {
				throw new Error("Property implementation not mapped: " + this.dotPath());
			}

			//		if (!this.isMapped()) { throw new Error(`Property implementation not mapped: ${this.dotPath()}`) }

			return this[_setupShadow]();
		}

		/**
  	Helpful debugging utility that returns the path joined by '.'. The root node will return the
  	word 'root' for the path.
  */

	}, {
		key: "slashPath",
		value: function slashPath() {
			var cache = this.cache();

			if (!cache.slashPath) {
				var path = this.path();

				cache.slashPath = path.length ? "/" + path.join('/') : '/';
			}

			return cache.slashPath;
		}
	}, {
		key: "state",
		value: function state() {
			return this[_state];
		}
	}, {
		key: "store",
		value: function store() {
			return this[_property].store();
		}

		/**
  	Transfers the nextName to the name attribute.
  */

	}, {
		key: "switchName",
		value: function switchName() {
			if (this[_nextName] !== undefined) {
				this[_name] = this[_nextName];
				delete this[_nextName];
			}
		}
	}, {
		key: "time",
		value: function time() {
			return this[_time];
		}

		/**
  	Gets a compact version of this internal's state. It does NOT provide a JSON representation of the
  	model state. The actual Property.toJSON() method returns the model JSON representation.
  */

	}, {
		key: "toJSON",
		value: function toJSON() {
			return {
				name: this[_name],
				path: this.dotPath(),
				active: !this[_dead],
				valid: this.isValid(),
				state: this.state()
			};
		}

		//Gets a stringified version of the toJSON() method.

	}, {
		key: "toString",
		value: function toString() {
			return JSON.stringify(this);
		}

		/**
  	Makes changes to the next property state. The callback should be pure (no side affects) but that
  	is not a requirement. The callback must be of the form:
  			(state) => return { nextState, replace }
  		where:
  		state - the next property state
  		nextState - the state following the callback
  		replace - boolean for whether nextState replaces the current value. The implication of true
  			is that this property and all of it's children will not be able to make future changes
  			to the model.
  		To understand the reasoning behind the replace flag consider the following example:
  			const model = { a: { b: { c: 1 } } }
  		const oldB = model.a.b
  			model.a.b = "foo"
  		oldB.c = 5
  			model.a.b.c === undefined
  		Thus, oldB.c may change oldB'c property 'c' to 5 but model.a.b is still "foo".
  */

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

			(0, _akutils.assert)(function (a) {
				return a.is(_this3.isActive(), "Property is not active: " + _this3.dotPath());
			});

			if (!this[_preventUpdates] && this.isUpdatable() && this.isActive()) {
				var next = this[_modelForUpdate]();

				// invoke callback without bind context to reduce overhead
				var action = callback(next);
				var nextState = action.nextState;
				var replace = action.replace;

				// mark property as having pending updates if the action callback returns a different
				// object/value or requests a replacement be created. An example where neither would be
				// true is a property touch() call because its shadow function signature changed.

				if (nextState !== next || replace) {
					this[_changed] = true;
				}

				this.dispatchUpdate(action);

				return true;
			}

			return false;
		}

		/**
  	Marks this property as dead. Once marked obsolete a property may not accept further updates.
  	A property is updated when the state changes but not a wholesale replacement or a descendents's
  	value has changed and the update process has completed.
  		This method does not affect subproperties.
  */

	}, {
		key: "updated",
		value: function updated() {
			this[_dead] = true;

			this.onUpdate();
		}

		/**
  	Changes the name this property will have after updates. This is used when moving properties
  	around in the model, such as when splice is used on an array. The nextName() method
  	will return the property name for after updates are applied.
  		Note: this method does not have any side effects beyond setting the _nextName instance
  		variable. Subclasss will need to perform any book keeping associated with sub-properties.
  */

	}, {
		key: "updateName",
		value: function updateName(name) {
			this[_nextName] = name;
		}

		/**
  	Gets if shadow property is allowing state updates.
  		@return {boolean} `false` if the property or its parent has been replaced, `true` otherwise.
  */

	}, {
		key: "updatesAllowed",
		value: function updatesAllowed() {
			return !this[_preventUpdates] && !this[_replaced];
		}

		/**
  	Invokes a callback once all pending changes have occurred. The callback should have the form:
  			callback(property, implementation)
  		where the property and implementation arguments are the latest version if they still exist.
  		This method is safe to call on a dead property.
  */

	}, {
		key: "waitFor",
		value: function waitFor(callback) {
			var _this4 = this;

			if (this.isValid() && this.isActive()) {
				// short circuit if no changes pending
				callback(this.shadow());
			} else {
				this.store().waitFor(function () {
					var latest = _this4.latest();

					callback(latest && latest.shadow(), latest);
				});
			}
		}

		/**
  	Invoked by the shadowing process to invoke appropriate {@link Property} life-cycle methods.
  	The method name is a reflection that shadow state tree invocation chain for `willShadow()`
  	occurs when the {@link Store} is going to shadow that state.
  		@param {boolean} parentWillUnshadow - `true` when parent property is unshadowing.
  */

	}, {
		key: "willShadow",
		value: function willShadow(parentWillUnshadow) {
			var willUnshadow = parentWillUnshadow || false;

			if (parentWillUnshadow) {
				// all properties under an unshadowed proeprty also get unshadowed
				this[_property].onPropertyWillUnshadow();
				willUnshadow = true;
			} else if (this.isValid()) {
				// nothing else to do since this property and all subproperties must be fine
				return;
			} else if (this[_replaced] || this[_preventUpdates]) {
				this[_property].onPropertyWillUnshadow();
				willUnshadow = true;
			}

			if (this.hasChildren()) {
				var children = this.children();
				var childImpl;

				for (var i = 0, len = children.length; i < len; i++) {
					var _childImpl2 = children[i];

					if (_childImpl2) {
						_childImpl2.willShadow(willUnshadow);
					}
				}
			}
		}

		//------------------------------------------------------------------------------------------------------
		//	Methods with base implementations that subclasses may need to override - no need to call super
		//------------------------------------------------------------------------------------------------------

		/**
  	Creates a deep clone of the current property state.
  */

	}, {
		key: "copyState",
		value: function copyState() {
			return (0, _lodash4.default)(this.state());
		}

		/**
  	Invoked on shadow getter access to obtain the get value.
  		The default implementation returns the shadow.
  		@return the shadow or other f.lux representative value for the shadow proeprty.
  */

	}, {
		key: "definePropertyGetValue",
		value: function definePropertyGetValue(state) {
			return this[_createShadow]();
		}

		/**
  	Invoked on shadow property assignment to perform the replacement f.lux action.
  		The default implementation is to assign the new value with no checking.
  		@param newValue - the new state value
  */

	}, {
		key: "definePropertySetValue",
		value: function definePropertySetValue(newValue) {
			this.assign(newValue);
		}

		/**
  	Gets if the property has an child properties (not whether child properties are supported).
  */

	}, {
		key: "hasChildren",
		value: function hasChildren() {
			return this.childCount() != 0;
		}

		/**
  	Gets if this property type reprsents a primitive javascript type.
  */

	}, {
		key: "isPrimitive",
		value: function isPrimitive() {
			return false;
		}

		/**
  	Gets whether the property value supports calls to update().
  */

	}, {
		key: "isUpdatable",
		value: function isUpdatable() {
			return true;
		}

		/**
  	Property has been invalidated.
  */

	}, {
		key: "onInvalidated",
		value: function onInvalidated() {}

		/**
  	Property has been removed from the shadow state.
  */

	}, {
		key: "onObsolete",
		value: function onObsolete() {}

		/**
  	Property has just been reshadowed.
  */

	}, {
		key: "onReshadow",
		value: function onReshadow(prev) {}

		/**
  	Hook for when this property is no longer represented in the system state.
  */

	}, {
		key: "onReplaced",
		value: function onReplaced() {}

		/**
  	Hook for when this property is no longer represented in the system state due to a state
  	update - not a replacement.
  */

	}, {
		key: "onUpdate",
		value: function onUpdate() {}

		//------------------------------------------------------------------------------------------------------
		//	Methods that ShadowImpl subclasses must be implemented by subclasses
		//------------------------------------------------------------------------------------------------------

		/**
  	Merges a new state into this property by using the 'state' parameter to set default values, ie it
  	will not overwrite any existing values. Useful when model objects arrive from external sources,
  	such as an asyncrhonous save or a websocket based update.
  */

	}, {
		key: "defaults",
		value: function defaults(state) {
			throw new Error("ShadowImpl subclasses must implement defaults()");
		}

		/**
  	Merges a new state into this property. Useful when model objects arrive from external
  	sources, such as an asyncrhonous save or a websocket based update.
  */

	}, {
		key: "merge",
		value: function merge(state) {
			throw new Error("ShadowImpl subclasses must implement merge()");
		}

		//------------------------------------------------------------------------------------------------------
		//	Methods that ShadowImpl subclasses with children must implement
		//------------------------------------------------------------------------------------------------------

		/**
  	Invoked during defineProperty() to define children properties marked for automount
  */

	}, {
		key: "automountChildren",
		value: function automountChildren() {}
		//		throw new Error("ShadowImpl subclasses with children must implement children()");


		/**
  	Subclasses should implement this method in such a way as not to trigger a mapping.
  */

	}, {
		key: "childCount",
		value: function childCount() {
			return 0;
		}

		/**
  	Gets the implementation objects managed by this property.
  */

	}, {
		key: "children",
		value: function children() {
			throw new Error("ShadowImpl subclasses with children must implement children()");
		}

		/**
  	Gets a child implementation matching a property name or undefined if no such property exists.
  */

	}, {
		key: "getChild",
		value: function getChild(name) {
			return undefined;
		}

		/**
  	Maps all child properties onto this property using Object.defineProperty().
  		@param {ShadowImpl} prev - the previous property shadow implementation instance.
  	@param {boolean} inCtor - `true` if call occuring during shadowing process.
  */

	}, {
		key: "defineChildProperties",
		value: function defineChildProperties(prev, inCtor) {}

		/**
  	Gets if defineChildProperties() has been invoked.
  */

	}, {
		key: "isMapped",
		value: function isMapped() {
			return true;
		}

		/**
  	Gets the keys/indices for this property.
  		Implementation note: Subclasses should implement this method in such a way as not to trigger a mapping.
  */

	}, {
		key: "keys",
		value: function keys() {
			throw new Error("ShadowImpl subclasses with children must implement keys()");
		}

		//------------------------------------------------------------------------------------------------------
		//	Private functions - should not be called by code outside this file.
		//------------------------------------------------------------------------------------------------------

	}, {
		key: _createShadow,
		value: function value() {
			if (!this[_shadow]) {
				var ShadowClass = this[_property].shadowClass();

				this[_shadow] = new ShadowClass(this);
				(0, _extendProperty2.default)(this[_property], this, this[_shadow]);
			}

			return this[_shadow];
		}

		/**
  	Maps the getter and setter (if appropriate) onto the parent property.
  */

	}, {
		key: _defineProperty,
		value: function value(prev) {
			var _this5 = this;

			if (this.isRoot() || this.isIsolated()) {
				return;
			}

			// names with a leading '_' are not enumerable (way of hiding them)
			var enumerable = !((0, _lodash8.default)(this[_name]) && this[_name].startsWith('_'));
			var parentShadow = this.parent().shadow();
			var state = this.state();
			var set = this[_getSetter]();

			try {
				Object.defineProperty(parentShadow, this[_name], {
					enumerable: enumerable,
					get: function get() {
						if ((0, _akutils.isSomething)(state)) {
							return _this5[_setupShadow]();
						} else {
							return state;
						}
					},
					set: set
				});
			} catch (error) {
				console.warn("_defineProperty() Error: name=" + this[_name] + ", parent=" + this.parent().dotPath(), error.stack);
				debugger;
			}

			this.automountChildren(prev);
		}
	}, {
		key: _getSetter,
		value: function value() {
			var _this6 = this;

			if (this.readonly()) {
				return;
			}

			var parentShadowCls = this.parent().property().shadowClass();
			var propDescriptor = Object.getOwnPropertyDescriptor(parentShadowCls.prototype, this[_name]);

			// support shadow defined setters to override our canned setter to enable easy definition of
			// complex setting behaviors, such as validation or side effects for internal object correctness.
			// Shadow defined getters are not supported since the canned getter generates and attaches the
			// shadow instance that manipulates the actual state.
			if (process.env.NODE_ENV !== 'production') {
				if (propDescriptor && propDescriptor.get && !parentShadowCls.__getter__warning__printed__) {
					console.warn("Shadow class defined getter ignored: " + parentShadowCls.name);
					parentShadowCls.__getter__warning__printed__ = true;
				}
			}

			// use shadow class setter if defined, otherwise define one
			if (propDescriptor && propDescriptor.set) {
				return propDescriptor.set;
			} else {
				return function (newValue) {
					if (!_this6.isActive()) {
						if (process.env.NODE_ENV !== 'production') {
							console.error("Attempting to set value on inactive property: " + _this6.dotPath(), newValue);
						}
					} else {
						return _this6.definePropertySetValue(newValue);
					}
				};
			}
		}

		/**
  	Gets the next model state for the property. This value is used for performing property updates through
  	the update() function.
  		Calls to update() trigger an update through the dispatcher upon which the new object will be mapped
  	and the store informed of the change.
  */

	}, {
		key: _modelForUpdate,
		value: function value() {
			if (!this[_hasFutureState]) {
				if (this.isRoot()) {
					// next data will be a shallow copy of current model
					this[_futureState] = (0, _lodash2.default)(this.state());
				} else {
					var parentNextState = this.owner()[_modelForUpdate]();

					// Primitive parent models do not support adding properties
					if ((0, _akutils.isPrimitive)(parentNextState)) {
						return undefined;
					}

					// next data is a shallow copy of the parent's value of this property
					this[_futureState] = (0, _lodash2.default)(parentNextState[this[_name]]);

					// place a shallow clone in place of current value
					parentNextState[this.nextName()] = this[_futureState];
				}
			}

			this[_hasFutureState] = true;

			return this[_futureState];
		}

		/**
  	Schedules an UPDATE action with the store. On action execution, the new property will be generated
  	and returned to the store.
  */

	}, {
		key: _scheduleUpdate,
		value: function value() {
			var _this7 = this;

			if (!this.isRoot()) {
				return this.root()[_scheduleUpdate]();
			}

			if (!this[_scheduled] && !this.isValid() && !this[_dead]) {
				// flag never gets cleared
				this[_scheduled] = true;

				this.store().dispatchUpdate(function (time) {
					return (0, _reshadow2.default)(time, _this7.nextState(), _this7);
				});
			}
		}
	}, {
		key: _setupShadow,
		value: function value(prev, inCtor) {
			var _this8 = this;

			if (!this.__getCalled__) {
				var state = this.state();

				debug(function (d) {
					return d("_setupShadow(): " + _this8.dotPath() + ", time=" + _this8[_time]);
				});

				this.__getCalled__ = true;
				var shadow = this.__getResonse__ = this.definePropertyGetValue(state);

				this.defineChildProperties(prev, inCtor);

				// freeze shadows in dev mode to provide check not assigning to non-shadowed property
				// this can have performance penalties so skip in production mode
				if (process.env.NODE_ENV !== 'production') {
					(0, _lodash6.default)(shadow) && !Object.isFrozen(shadow) && Object.freeze(shadow);
				}
			}

			return this.__getResonse__;
		}
	}]);

	return ShadowImpl;
}();

exports.default = ShadowImpl;