import { trackValue } from "../decorator-utils/track-value";

/**
 * Decorate a variable as a property of the custom-element.
 * @param {Object} opts
 * @param {boolean|string} opts.attr - Update the property when an attribute with specified name is set. Defaults to the property-name
 * @param {boolean} opts.reflect - Reflect the property back to attributes when the property is set
 */
export function prop(opts) {
    opts = opts || {};
    return function decorator(target, key, descriptor) {
        // Dev-note: Tis is run for every instance made of the decorated class ...
        // TODO could check the prototype if a markDirty function exists, throw an error if it doesn't
        
        // Register this prop on the class, so the VDOM-renderer can find this and set the prop instead of the attribute (if @meta is every introduced as a built-in decorator, this would be the place to use it)
        if (!target.constructor.props) {
            target.constructor.props = new Set();
        }
        target.constructor.props.add(key);
        
        let attrName = opts.attr !== false && typeof (opts.attr) === "string" ? opts.attr : key;
        
        // Prop behavior
        let trackedDecorator = trackValue({ target, key, descriptor }, function (value) {
            //console.log(`Prop ${key} was set: ` + JSON.stringify(value));
            if (opts.reflect) {
                if ((value ?? false) === false) {
                    if(this.hasAttribute(attrName)) this.removeAttribute(attrName);
                } else if (value === true) {
                    if(!this.hasAttribute(attrName)) this.setAttribute(attrName, "");
                } else {
                    let strValue = value?.toString() || "";
                    if(this.getAttribute(attrName)!== strValue) this.setAttribute(attrName, strValue);
                }
            }
            this.markDirty && this.markDirty();
        });
        
        // Registering as attr and subscribing to relevant callbacks
        if (opts.attr !== false || opts.attr === undefined) {
            let oldCallback = target.attributeChangedCallback;
            target.attributeChangedCallback = function (...args) {
                let [name, oldValue, newValue] = args;
                if (name === attrName) {
                    // This is our tracked prop, pipe the attribute-value the the property-setter
                    //console.log(`Attribute ${attrName} was set: ` + JSON.stringify(newValue));
                    trackedDecorator.set.call(this, newValue);
                }
                if (oldCallback) oldCallback.call(this, ...args);
            };
            
            let observedAttrs = Array.from(new Set([attrName, ...(target.constructor.observedAttributes || [])]));
            Object.defineProperty(target.constructor, 'observedAttributes', {
                get: () => observedAttrs,
                configurable: true
            });
        }
        
        return trackedDecorator
    }
}