// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var NumberCounterElement_1;
import { CssTime } from '@breadstone/mosaik-themes';
import { RendererServiceLocator } from '../../../../Dom/Renderer';
import { Disableable } from '../../../Behaviors/Disableable';
import { Themeable } from '../../../Behaviors/Themeable';
import { Variantable } from '../../../Behaviors/Variantable';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { TextAlignment } from '../../../Types/TextAlignment';
import { CustomElement } from '../../Abstracts/CustomElement';
import { numberCounterElementTemplate } from './NumberCounterElementTemplate';
import { numberCounterElementCosmopolitanStyle } from './Themes/NumberCounterElement.Cosmopolitan';
import { numberCounterElementJoyStyle } from './Themes/NumberCounterElement.Joy';
import { numberCounterElementMemphisStyle } from './Themes/NumberCounterElement.Memphis';
// #endregion
/**
 * Number - The number element is used to display a number.
 *
 * @description
 * TBD
 *
 * @element mosaik-number-counter
 * @category Display
 *
 * @csspart counter - The counter part.
 *
 * @cssprop {String} --number-counter-font-family - The counter font family CSS custom property.
 * @cssprop {String} --number-counter-font-letter-spacing - The counter font letter spacing CSS custom property.
 * @cssprop {String} --number-counter-font-line-height - The counter font line height CSS custom property.
 * @cssprop {String} --number-counter-font-size - The counter font size CSS custom property.
 * @cssprop {String} --number-counter-font-text-decoration - The counter font text decoration CSS custom property.
 * @cssprop {String} --number-counter-font-text-transform - The counter font text transform CSS custom property.
 * @cssprop {String} --number-counter-font-weight - The counter font weight CSS custom property.
 * @cssprop {String} --number-counter-gap - The counter gap CSS custom property.
 * @cssprop {String} --number-counter-padding-bottom - The counter padding bottom CSS custom property.
 * @cssprop {String} --number-counter-padding-left - The counter padding left CSS custom property.
 * @cssprop {String} --number-counter-padding-right - The counter padding right CSS custom property.
 * @cssprop {String} --number-counter-padding-top - The counter padding top CSS custom property.
 * @cssprop {String} --number-counter-shadow - The counter shadow CSS custom property.
 * @cssprop {String} --number-counter-shadow-blur - The counter shadow blur CSS custom property.
 * @cssprop {String} --number-counter-shadow-color - The counter shadow color CSS custom property.
 * @cssprop {String} --number-counter-shadow-offset-x - The counter shadow offset x CSS custom property.
 * @cssprop {String} --number-counter-shadow-offset-y - The counter shadow offset y CSS custom property.
 * @cssprop {String} --number-counter-shadow-spread - The counter shadow spread CSS custom property.
 * @cssprop {String} --number-counter-transition-duration - The counter transition duration CSS custom property.
 * @cssprop {String} --number-counter-transition-mode - The counter transition mode CSS custom property.
 * @cssprop {String} --number-counter-transition-property - The counter transition property CSS custom property.
 * @cssprop {String} --number-counter-translate - The counter translate CSS custom property.
 *
 * @example
 * ```html
 * <mosaik-number-counter from="0" to="100">
 * </mosaik-number-counter>
 * ```
 *
 * @public
 */
let NumberCounterElement = class NumberCounterElement extends Themeable(Variantable(Disableable(CustomElement))) {
    static { NumberCounterElement_1 = this; }
    // #region Fields
    static _propertyRegistered = false;
    _from = 0;
    _to;
    _duration = '1s';
    _readonly;
    _alignment;
    _animationFrameId = 0;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `NumberCounterElement` class.
     *
     * @public
     */
    constructor() {
        super();
        this._from = 0;
        this._to = 0;
        this._duration = '1s';
        this._readonly = false;
        this._alignment = TextAlignment.Left;
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `is` property.
     *
     * @public
     * @static
     * @readonly
    */
    static get is() {
        return 'mosaik-number-counter';
    }
    /**
     * Gets or sets the `from` property.
     *
     * @public
     * @attr
     */
    get from() {
        return this._from;
    }
    set from(value) {
        if (this._from !== value) {
            this._from = value;
            this.requestUpdate('from');
        }
    }
    /**
     * Gets or sets the `to` property.
     *
     * @public
     * @attr
     */
    get to() {
        return this._to;
    }
    set to(value) {
        if (this._to !== value) {
            this._to = value;
            this.requestUpdate('to');
        }
    }
    /**
     * Gets or sets the `duration` property.
     *
     * @public
     * @attr
     */
    get duration() {
        return this._duration;
    }
    set duration(value) {
        if (this._duration !== value) {
            this._duration = value;
            this.requestUpdate('duration');
        }
    }
    /**
     * Gets or sets the `readonly` property.
     *
     * @public
     * @attr
     */
    get readonly() {
        return this._readonly;
    }
    set readonly(value) {
        if (this._readonly !== value) {
            this._readonly = value;
            this.requestUpdate('readonly');
        }
    }
    /**
     * Gets or sets the `alignment` property.
     *
     * Possible values are:
     * * `center`
     * * `justify`
     * * `left`
     * * `right`
     *
     * @public
     * @attr
     */
    get alignment() {
        return this._alignment;
    }
    set alignment(value) {
        if (this._alignment !== value) {
            this._alignment = value;
            this.requestUpdate('alignment');
        }
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        NumberCounterElement_1.registerAnimationProperty();
        requestAnimationFrame(() => {
            const counterPart = this.shadowRoot?.querySelector('[part="counter"]');
            if (counterPart && counterPart.childElementCount === 0) {
                this.updateDigits(counterPart, this._from, 'up');
                if (this._to !== this._from) {
                    this.startDigitAnimation(this._from, this._to);
                }
            }
        });
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        if (this._animationFrameId) {
            cancelAnimationFrame(this._animationFrameId);
            this._animationFrameId = 0;
        }
    }
    /**
     * @protected
     */
    onFromPropertyChanged(_prev, next) {
        if (next !== undefined) {
            RendererServiceLocator.current.setStyle(this, '--from', next.toString());
            const direction = this._to >= next ? 'up' : 'down';
            this.setAttribute('direction', direction);
            this.restartCssAnimation();
            this.startDigitAnimation(next, this._to);
        }
    }
    /**
     * @protected
     */
    onToPropertyChanged(_prev, next) {
        if (next !== undefined) {
            const fromValue = _prev ?? this._from;
            RendererServiceLocator.current.setStyle(this, '--from', fromValue.toString());
            RendererServiceLocator.current.setStyle(this, '--to', next.toString());
            const direction = next >= fromValue ? 'up' : 'down';
            this.setAttribute('direction', direction);
            this.restartCssAnimation();
            this.startDigitAnimation(fromValue, next);
        }
    }
    /**
     * @protected
     */
    onDurationPropertyChanged(_prev, next) {
        if (next !== undefined) {
            RendererServiceLocator.current.setStyle(this, '--duration', next.toString());
        }
    }
    /**
     * Registers the `--n` CSS custom property as `<integer>` so the
     * browser can interpolate it inside `@keyframes counter`.
     *
     * @private
     * @static
     */
    static registerAnimationProperty() {
        if (NumberCounterElement_1._propertyRegistered) {
            return;
        }
        try {
            CSS.registerProperty({
                name: '--n',
                syntax: '<integer>',
                inherits: false,
                initialValue: '0',
            });
            NumberCounterElement_1._propertyRegistered = true;
        }
        catch {
            // Already registered or CSS.registerProperty not supported
        }
    }
    /**
     * Restarts the CSS counter animation for themes that use the
     * CSS counter mechanism (base, Memphis, Cosmopolitan).
     *
     * @private
     */
    restartCssAnimation() {
        if (!this.isConnected) {
            return;
        }
        const counterPart = this.shadowRoot?.querySelector('[part="counter"]');
        if (counterPart) {
            counterPart.style.animation = 'none';
            void counterPart.offsetHeight;
            counterPart.style.animation = '';
        }
    }
    /**
     * Starts a requestAnimationFrame-based digit animation that
     * interpolates from `from` to `to` and updates individual digit
     * spans, giving each changed digit its own elastic snap.
     *
     * @private
     */
    startDigitAnimation(from, to) {
        if (!this.isConnected) {
            return;
        }
        if (this._animationFrameId) {
            cancelAnimationFrame(this._animationFrameId);
        }
        const counterPart = this.shadowRoot?.querySelector('[part="counter"]');
        if (!counterPart) {
            return;
        }
        const durationMs = this.parseDuration(this._duration);
        const startTime = performance.now();
        const direction = to >= from ? 'up' : 'down';
        const animate = (currentTime) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / durationMs, 1);
            const eased = 1 - Math.pow(1 - progress, 3);
            const currentValue = Math.round(from + (to - from) * eased);
            this.updateDigits(counterPart, currentValue, direction);
            if (progress < 1) {
                this._animationFrameId = requestAnimationFrame(animate);
            }
            else {
                this._animationFrameId = 0;
            }
        };
        this._animationFrameId = requestAnimationFrame(animate);
    }
    /**
     * Creates or updates individual digit `<span>` elements inside
     * the counter part. Changed digits receive a bounce animation class.
     *
     * @private
     */
    updateDigits(container, value, direction) {
        const valueStr = value.toString();
        while (container.childElementCount > valueStr.length) {
            container.removeChild(container.lastElementChild);
        }
        while (container.childElementCount < valueStr.length) {
            const span = document.createElement('span');
            span.className = 'digit';
            container.appendChild(span);
        }
        for (let i = 0; i < valueStr.length; i++) {
            const span = container.children[i];
            const char = valueStr[i];
            if (span.textContent !== char) {
                span.textContent = char;
                const bounceClass = direction === 'up' ? 'bounce-up' : 'bounce-down';
                span.classList.remove('bounce-up', 'bounce-down');
                void span.offsetHeight;
                span.classList.add(bounceClass);
            }
        }
    }
    /**
     * Parses a CSS time value (e.g. '1s', '500ms') to milliseconds.
     *
     * @private
     */
    parseDuration(duration) {
        const str = String(duration);
        if (str.endsWith('ms')) {
            return parseFloat(str);
        }
        return parseFloat(str) * 1000;
    }
};
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], NumberCounterElement.prototype, "from", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], NumberCounterElement.prototype, "to", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], NumberCounterElement.prototype, "duration", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], NumberCounterElement.prototype, "readonly", null);
__decorate([
    Attribute({ type: TextAlignment }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], NumberCounterElement.prototype, "alignment", null);
__decorate([
    Watch('from'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], NumberCounterElement.prototype, "onFromPropertyChanged", null);
__decorate([
    Watch('to'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], NumberCounterElement.prototype, "onToPropertyChanged", null);
__decorate([
    Watch('duration'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", void 0)
], NumberCounterElement.prototype, "onDurationPropertyChanged", null);
NumberCounterElement = NumberCounterElement_1 = __decorate([
    Component({
        selector: 'mosaik-number-counter',
        template: numberCounterElementTemplate,
        themes: {
            joy: numberCounterElementJoyStyle,
            memphis: numberCounterElementMemphisStyle,
            cosmopolitan: numberCounterElementCosmopolitanStyle
        },
        imports: []
    }),
    __metadata("design:paramtypes", [])
], NumberCounterElement);
export { NumberCounterElement };
//# sourceMappingURL=NumberCounterElement.js.map