# src/Access.js

    import has from "lodash.has";

    const _impl = Symbol('impl');

    /**
    	The base class for `Shadow.$()` method used for obtaining f.lux contextual information
    	concerning a non-primitive shadow state property.

    	The most commonly used methods include:

    	<ul>
    		<li>{@link Access#dotPath}</li>
    		<li>{@link Access#property}</li>
    		<li>{@link Access#pid}</li>
    		<li>{@link Access#state}</li>
    		<li>{@link Access#store}</li>
    		<li>{@link Access#waitFor}</li>
    	</ul>

    	This class provides access to the experimental {@link Property} checkpointing api. Checkpointing
    	allows the state to be recorded at a point in time and then later reset to that point. This is
    	handy when a form may accept changes and then allow the user to cancel the edit session.

    	@see {@link Shadow}
    */
    export default class Access {
    	constructor(impl) {
    		this[_impl] = impl;
    	}

    	/**
    		Get the backing {@link ShadowImpl} instance for this shadow property.

    		@return {ShadowImpl}
    	*/
    	__() {
    		return this[_impl];
    	}

    	/**
    		Copies the current actual state for later reset using {@link Property#resetToCheckpoint}. An existing
    		checkpoint will take precedence over subsequent calls.
    	*/
    	checkpoint() {
    		this.property().checkpoint();
    	}

    	/**
    		Clears an existing checkpoint created using {@link Property#checkpoint}.
    	*/
    	clearCheckpoint() {
    		this.property().clearCheckpoint();
    	}

    	/**
    		Gets the path from root property using a dot (`.`) separator. Suitable for using with the lodash `result()`
    		function.

    		@return {string} path with each component joined by a `.`

    		@see https://lodash.com/docs/4.17.4#result
    	*/
    	dotPath() {
    		return this[_impl].dotPath();
    	}

    	/**
    		Gets the checkpoint state previously recorded using {@link Property#checkpoint}.

    		@return {Object|Array} the checkpoint data if checkpoint is set.
    	*/
    	getCheckpoint() {
    		return this.property().getCheckpoint();
    	}

    	/**
    		Gets if an existing checkpoint has be created using {@link Property#checkpoint}.

    		@return {boolean} true if a checkpoint has been recorded.
    	*/
    	hasCheckpoint() {
    		return this.property().hasCheckpoint();
    	}

    	/**
    		Gets if the shadow state property is proxying the current actual state contained in the f.lux store.

    		@return {boolean} `true` if actively proxying the state.
    	*/
    	isActive() {
    		return this[_impl].isActive();
    	}

    	/**
    		Gets if the property allows for assignment through the shadow state, ie `todo.desc = "go skiing"`. The
    		readonly attribute is hierarchically determined through the parent property if not explicitly set.

    		@return {boolean} - true if assignment is not allowed
    	*/
    	isReadonly() {
    		return this.property().isReadonly();
    	}

    	/**
    		Gets if the shadow property has experienced a mutation action. This method will return `true` and
    		{@link Access#isActive} may also return true if the f.lux store has not yet been transitioned to
    		the next state.

    		@return {boolean} `true` if the state has not been changed.
    	*/
    	isValid() {
    		return this[_impl].isValid();
    	}

    	/**
    		Gets the current shadow state for this property. The shadow state referenced by this access object
    		could be stale if a javascript closure has a previous reference and then performed an asynchronous
    		operation like a network request.

    		@return {Shadow}
    	*/
    	latest() {
    		const impl = this[_impl].latest();

    		return impl && impl.shadow();
    	}

    	/** @ignore */
    	merge(data) {
    		this[_impl].merge(data);
    	}

    	/**
    		Gets the next actual state for this property following any pending actions.

    		@return {Object|Array}
    	*/
    	nextState() {
    		return this[_impl].nextState();
    	}

    	/**
    		Gets the {@link Property#name} components from the root property to this property.

    		@return {[]} array where each name component is either a `string` or `number` depending on the
    			each parent component's type.
    	*/
    	path() {
    		return this[_impl].path();
    	}

    	/**
    		Gets the unique f.lux ID for this property.

    		@return {number} the id
    	*/
    	pid() {
    		return this.property().pid();
    	}

    	/**
    		Gets the {@link Property} managing this shadow property.

    		@return {Property}
    	*/
    	property() {
    		return this[_impl].property();
    	}

    	/**
    		Replaces the current property state with a checkpoint state previously recorded using
    		{@link Property#checkpoint}. The checkpoint is cleared.
    	*/
    	resetToCheckpoint() {
    		this.property().resetToCheckpoint();
    	}

    	/**
    		Gets the top-level shadow from the {@link Store} containing this shadow state property.

    		@return {Shadow}
    	*/
    	rootShadow() {
    		return this.property().rootShadow();
    	}

    	/**
    		Gets the path from root property using a slash (`/`) separator.

    		@return {string} path with each component separated by a `/`
    	*/
    	slashPath() {
    		return this[_impl].slashPath();
    	}

    	/**

    		@return
    	*/
    	shadow() {
    		return this[_impl].shadow();
    	}

    	/**
    		Gets the actual state being shadowed.

    		@return {Object|Array}
    	*/
    	state() {
    		return this[_impl].state();
    	}

    	/**
    		Gets the {@Link Store} containing the application state.

    		@return {Store}
    	*/
    	store() {
    		return this.property().store();
    	}

    	/**
    		Performs an update on the state values.

    		The callback should be pure and have the form:

    		```
    			callback(nextState) : nextState
    		```
    		- or -

    		```
    			callback(nextState) : { name, nextState }
    		```

    		The `nextState` parameter is a javascript object (not a `Shadow`) that represents the next state starting
    		from the current state and having all the actions in the current tick applied to it before this `update()`
    		call.

    		The callback should return an object with the following properties:
    		<ul>
    			<li> `name` - a stort moniker identifying the update call purpose. This will passed to the store middleware.
    				This value is optional with the default value being '[path].$().update()'.</li>
    			<li>`nextState` - the value for the next state after the update functionality.</li>
    		</ul>
    	*/
    	update(callback) {
    		this[_impl].update( next => {
    			var result = callback(next);

    			result = has(result, "nextState") ?result :{ nextState: nexresultt };

    			// mark as a replacement (expensive but conservative) since very unlikely a caller through the access
    			// variable will have made all the book keeping updates and no way of knowing how deep their changes
    			// were in the object hierarchy.
    			return {
    				name: result.name || `${ this.slashPath() }.$().update()`,
    				nextState: result.nextState,
    				replace: true
    			};
    		})
    	}

    	/**
    		Registers a **one-time** no argument callback to be invoked after the next {@link Store} state change.

    		@param {function(shadow: Shadow)} callback - a callback to be invoked after all pending changes have been
    			reflected in the shadow state.
    	*/
    	waitFor(callback) {
    		this.store().waitFor( () => callback && callback(this.latest()) );
    	}
    }