# src/IsolatedProperty.js

    import { assert, uuid } from "akutils";

    import IsolatedAccess from "./IsolatedAccess";
    import IsolatedShadow from "./IsolatedShadow";
    import IsolatedShadowImpl from "./IsolatedShadowImpl";
    import KeyedApi from "./KeyedApi";
    import MapProperty from "./MapProperty";
    import Property from "./Property";
    import StateType from "./StateType";

    // child property names
    export const _dataKey = "data";

    // private instance variable symbols
    const _id = Symbol("id");
    const _key = Symbol("key");
    const _owner = Symbol("owner");

    /**

    */
    export default class IsolatedProperty extends Property {
    	/**
    		If a {@link StateType} is not passed to this constructor then one is located using
    		{@link StateType.from} thus ensuring the f.lux shadowing process is defined for this
    		property.

    		@param {StateType} [stateType] - a specialized {@link StateType} instance describing how f.lux should
    			shadow this property.
    	*/
    	constructor(stateType) {
    		super(stateType);

    		assert( a => a.is(stateType.getManagedType(), "Managed type must be set") );

    		this._keyed = new KeyedApi(this);

    		this.setImplementationClass(IsolatedShadowImpl);
    		this.setShadowClass(IsolatedShadow);

    		const managedType = this.stateType().getManagedType();
    		const shader = this.shader();

    		shader.addProperty(_dataKey, managedType);

    		this[_id] = uuid("_");
    	}

    	/**
    		Factory function for creating an `MapProperty` subclass suitable for using with new. The generated
    		class will have the `type` {@link StateType} descriptor set upon return.

    		Example usage:
    		```
    		class SomeShadow extends MapShadow {
    		    // definition here
    		}

    		export default MapProperty.createClass(SomeShadow, type => {
    			// configure type variable
    		});
    		```

    		@param {Object|IsolatedShadow} [shadowType={}] - `MapShadow` subclass or object literal api definition.
    			If object literal specified, each property and function is mapped onto a Shadow subclass.
    		@param {function(type: StateType)} [typeCallback] - a callback function that will be passed the
    			{@link StateType} spec for additional customization, such as setting autoshadow, initial state,
    			or readonly.
    		@param {Object} [initialState={}] - the initial state for the new property.

    		@return {MapProperty} newly defined `MapProperty` subclass.
    	*/
    	static createClass(shadowType={}, typeCallback, initialState={}) {
    		return createPropertyClass(shadowType, initialState, typeCallback, IsolatedProperty);
    	}

    	/**
    		Factory function for setting up the {@link StateType} `type` class variable with an appropriately
    		configured intial state.

    		Example usage:
    		```
            export default class SomeProperty extends MapProperty {
    			// implement property here
            }

            class SomeShadow extends MapShadow {
    			// implement shadow api here
            }

            IsolatedProperty.defineType(SomeProperty, SomeShadow, type => {
    			// configure type variable
            });
    		```

    		@param {MapProperty} PropClass - MapProperty subclass
    		@param {Object|MapShadow} [ShadowType] - `MapShadow` subclass or object literal api definition.
    			If object literal specified, each property and function is mapped onto a Shadow subclass.
    		@param {function(type: StateType)} [typeCallback] - a callback function that will be passed the
    			{@link StateType} spec for additional customization, such as setting autoshadow, initial state, or
    			readonly.
    		@param {Object} [initialState={}] - the initial state for the new property.
    	*/
    	static defineType(PropClass, ShadowType, typeCallback, initialState={}) {
    		assert( a => a.is(IsolatedProperty.isPrototypeOf(PropClass), "PropClass must subclass IsolatedProperty") );

    		StateType.defineTypeEx(PropClass, ShadowType, typeCallback, initialState);
    	}

    	//------------------------------------------------------------------------------------------------------
    	// Life-cycle methods
    	//------------------------------------------------------------------------------------------------------

    	propertyChildInvalidated(childProperty, sourceProperty) {
    		this.store().isolated().invalidated(this);
    	}

    	propertyWillUnshadow() {
    		this.store().isolated().willUnshadow(this);
    	}

    	//------------------------------------------------------------------------------------------------------
    	// Isolated specific methods
    	//------------------------------------------------------------------------------------------------------

    	/**
    		Gets the ID used by the store to track this isolated property.

    		@return {string}
    	*/
    	isolationId() {
    		return this[_id];
    	}

    	key() {
    		return this[_key];
    	}

    	/**
    		Gets the property managing this isolated object.

    		@return {Property}
    	*/
    	owner() {
    		return this[_owner];
    	}

    	setKey(key) {
    		this[_key] = key;
    	}

    	/**
    		Sets the property that owns/contains this isolated property.

    		@param {Property} owner - the object containing/managing this isolated property.
    	*/
    	setOwner(owner) {
    		assert( a => a.not(this[_owner], "Owner already set") );

    		this[_owner] = owner;
    	}

    	//------------------------------------------------------------------------------------------------------
    	// Property class overrides - no need to call super
    	//
    	// These are mostly proxies to the owner property
    	//------------------------------------------------------------------------------------------------------

    	/**
    		Creates the object to be assigned to the shadow.$ property. Subclasses can override this method
    		to setup a chain of specialized accessors (`$()`). See {@link Access} for details on setting up
    		specialized accessors. This is an advanced feature and rarely required.

    		@return {Access} a property accessor instance.
    	*/
    	create$(impl) {
    		return new IsolatedAccess(impl);
    	}

    	/**
    		An isolated property returns owner's `dotPath()` value.

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

    		@see https://lodash.com/docs/4.17.4#result
    	*/
    	dotPath() {
    console.log("ISO path", this[_owner].path())
    		return this[_owner].dotPath();
    	}

    	/**
    		An isolated property returns owner's `isActive()` value.

    		@return {boolean}
    	*/
    	isActive() {
    		return this[_owner].isActive();
    	}

    	/**
    		Gets if property is an actual isolated property managed by the store.

    		@return {boolean}
    	*/
    	isIsolated() {
    		return true;
    	}

    	/**
    		An isolated property is never a root.

    		@return {boolean} `false`
    	*/
    	isRoot() {
    		return false;
    	}

    	/**
    		An isolated property returns owner property for the parent.

    		@return {[]}
    	*/
    	parent() {
    		return this[_owner];
    	}

    	//------------------------------------------------------------------------------------------------------
    	// Property class overrides - the tricky ones
    	//
    	// Developer Note: Use great caution when overriding these methods as not their original intent
    	//------------------------------------------------------------------------------------------------------

    	/**
    		An isolated property returns owner's `isReadonly()` value.

    		@return {boolean}
    	*/
    	isReadonly() {
    		const explicit = this.readonlyExplicit();
    		const readonly = explicit !== undefined ?explicit :this.stateType()._readonly;

    		// use readonly flag if explicitly set otherwise use value from owner
    		return readonly === undefined
    			? this[_owner].isReadonly()
    			: readonly;
    	}
    }

    StateType.defineType(IsolatedProperty, spec => {
    	spec.initialState({})
    		.typeName("IsolatedProperty")
    });