/* eslint-disable @typescript-eslint/no-unnecessary-condition */
// import 'reflect-metadata';
import { Equals } from '../../Equality/Equals';
/**
 * @public
 */
export function Property(options) {
    return (protoOrDescriptor, name) => name !== undefined
        ? createLegacyProperty(protoOrDescriptor, name, getDefaultOptions(name, options))
        : createStandardProperty(protoOrDescriptor, getDefaultOptions(protoOrDescriptor.key, options));
}
/**
 * @private
 */
function getDefaultOptions(propertyKey, options) {
    options = {
        reflect: false,
        // camelCaseToKebabCase attribute name converter
        attribute: propertyKey.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-')
            .toLowerCase(),
        hasChanged: (value, oldValue) => {
            const hasChanges = oldValue !== value;
            return hasChanges;
        },
        ...options
    };
    switch (options.type) {
        case Array:
            return {
                hasChanged: (value, oldValue) => Equals.areArraysEqual(value, oldValue),
                converter: {
                    fromAttribute: (value) => Array.isArray(value) ? value.split(' ') : value ?? false
                },
                ...options
            };
        case String:
            return {
                converter: {
                    toAttribute: (value) => value ? value : null
                },
                ...options
            };
        case Boolean:
            return {
                converter: {
                    // Mimic standard HTML boolean attributes + support "false" attribute values
                    // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
                    toAttribute: (value) => value ? '' : null,
                    fromAttribute: (value) => value !== null && value !== undefined && `${value}` !== 'false'
                },
                ...options
            };
        case Date: {
            return {
                // Parse date strings from attributes but do not reflect back into attribute
                converter: {
                    fromAttribute: (value) => new Date(value)
                },
                ...options
            };
        }
        case Object:
            return {
                hasChanged: (value, oldValue) => Equals.areObjectsEqual(value, oldValue),
                ...options
            };
        case Number:
        default:
            return {
                ...options
            };
    }
}
/**
 * @private
 */
function createStandardProperty(element, options) {
    // When decorating an accessor, pass it through and add property metadata.
    // Note, the `hasOwnProperty` check in `createProperty` ensures we don't
    // stomp over the user's accessor.
    if (element.kind === 'method' && element.descriptor && !('value' in element.descriptor)) {
        const originalKey = element.key;
        let descriptor = {
            ...element,
            finisher: (ctor) => {
                createProperty(ctor, element.key, options);
                if (options?.isStyle) {
                    const originalConnectedCallback = ctor.prototype.connectedCallback;
                    ctor.prototype.connectedCallback = function () {
                        if (originalConnectedCallback) {
                            originalConnectedCallback.call(this);
                        }
                        const value = this[element.key];
                        const styleValue = value instanceof Date ? value.toISOString() : String(value);
                        this.style.setProperty(`--${String(options.attribute)}`, styleValue);
                    };
                }
            }
        };
        // If we want trigger requestUpdate() on property change, we need to define a descriptor.
        if (options?.requestUpdate) {
            descriptor = {
                ...descriptor,
                descriptor: {
                    get: () => this[`_${String(originalKey)}`],
                    set: (value) => {
                        const oldValue = this[`_${String(originalKey)}`];
                        if (oldValue !== value) {
                            this[`_${String(originalKey)}`] = value;
                            if (options.isStyle) {
                                const styleValue = value instanceof Date ? value.toISOString() : String(value);
                                this.style.setProperty(`--${String(options.attribute)}`, styleValue);
                            }
                            this.requestUpdate(String(originalKey), oldValue);
                        }
                    },
                    configurable: true,
                    enumerable: true
                }
            };
        }
        return descriptor;
    }
    // createProperty() takes care of defining the property, but we still
    // must return some kind of descriptor, so return a descriptor for an
    // unused prototype field. The finisher calls createProperty().
    return {
        kind: 'field',
        key: Symbol('unusedPrototypeField'),
        placement: 'own',
        descriptor: {},
        // store the original key so subsequent decorators have access to it.
        originalKey: element.key,
        initializer() {
            if (typeof element.initializer === 'function') {
                this[element.key] = element.initializer.call(this);
            }
        },
        finisher(ctor) {
            createProperty(ctor, element.key, options);
            if (options?.isStyle) {
                const originalConnectedCallback = ctor.prototype.connectedCallback;
                ctor.prototype.connectedCallback = function () {
                    if (originalConnectedCallback) {
                        originalConnectedCallback.call(this);
                    }
                    const value = this[element.key];
                    const styleValue = value instanceof Date ? value.toISOString() : String(value);
                    this.style.setProperty(`--${String(options.attribute)}`, styleValue);
                };
            }
        }
    };
}
/**
 * @private
 */
function createLegacyProperty(proto, name, options) {
    // If we want trigger requestUpdate() on property change, we need to define a descriptor.
    if (options?.requestUpdate) {
        const privatePropertyName = `_${String(name)}`;
        Object.defineProperty(proto, name, {
            get: () => this[privatePropertyName],
            set: (value) => {
                const oldValue = this[privatePropertyName];
                if (oldValue !== value) {
                    this[privatePropertyName] = value;
                    if (options.isStyle) {
                        const styleValue = value instanceof Date ? value.toISOString() : String(value);
                        this.style.setProperty(`--${String(options.attribute)}`, styleValue);
                    }
                    this.requestUpdate(String(name), oldValue);
                }
            },
            configurable: true,
            enumerable: true
        });
    }
    createProperty(proto.constructor, name, options);
}
/**
 * @private
 */
function createProperty(element, name, options) {
    element.createProperty(name, options);
}
//# sourceMappingURL=PropertyDecorator.js.map