
/** Helper class to mark an initializer-value to be used on first get of a value**/
class InitializerValue{ constructor(value){ this.value = value; } }


/**
 * This callback type is called `requestCallback` and is displayed as a global symbol.
 *
 * @callback trackValueCallback
 * @param {any} value
 * @param {PropertyKey} prop
 */

/**
 * This is an implementation of https://github.com/tc39/proposal-decorators/blob/master/NEXTBUILTINS.md#tracked
 * to use until support for the new decorators proposal lands in @babel
 *
 * @param {Object} decoratorArgs
 * @param {any}  decoratorArgs.target
 * @param {PropertyKey}  decoratorArgs.key
 * @param {PropertyDescriptor} decoratorArgs.descriptor
 * @param {trackValueCallback} cb
 * @return {PropertyDescriptor}
 */
export function trackValue({target, key, descriptor}, cb){
    let {get: oldGet, set: oldSet} = descriptor;
    let valueKey='__'+key;
    
    if(!cb) throw Error("No callback given to track property. No point in decorating the prop");
    // TODO the story here of handling an initializer value is a bit dirty... Ideally, we'd want to run the value initializer before the class-constructor is called..
    
    // Rewrite the property as if using getters and setters (if needed)
    descriptor.get = oldGet = oldGet || function(){
        let val = this[valueKey];
        if(val instanceof InitializerValue){
            this[valueKey] = val = val.value.call(this);
        }
        return val;
    };
    oldSet = oldSet || function(newVal){
        this[valueKey]=newVal;
        return newVal;
    };
    // Overwrite the setter to call markDirty whenever it is used
    descriptor.set = function(newValue){
        let oldvalue = oldGet.call(this);
        let result = oldSet.call(this, newValue);
        if(oldvalue!==newValue) cb.call(this,newValue, key);
        return result;
    };
    
    // Update the descriptor to match with using getters and setters
    target.kind = 'method'; // update to get and set if need be..
    delete descriptor.writable;
    
    // Catch usage of initial-value or  value-initalizers and handle'em
    if(descriptor.value){
        Object.defineProperty(target, valueKey, {
            writable: true,
            value: descriptor.value
        });
        delete descriptor.value;
    }else if(descriptor.initializer){
        Object.defineProperty(target, valueKey, {
            writable: true,
            value: new InitializerValue(descriptor.initializer)
        });
        delete descriptor.initializer;
    }
    
    return descriptor;
}