// #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);
};
import { HTMLElementEventEmitter, on } from '@breadstone/mosaik-elements';
import { CssColor } from '@breadstone/mosaik-themes';
import SignaturePad from 'signature_pad';
import { RendererServiceLocator } from '../../../../Dom/Renderer';
import { Emit } from '../../../../events/Decorators/EmitDecorator';
import { Appearanceable } from '../../../Behaviors/Appearanceable';
import { Clearable } from '../../../Behaviors/Clearable';
import { Disableable } from '../../../Behaviors/Disableable';
import { Focusable } from '../../../Behaviors/Focusable';
import { Invalidable } from '../../../Behaviors/Invalidable';
import { Labelable } from '../../../Behaviors/Labelable';
import { Themeable } from '../../../Behaviors/Themeable';
import { Valueable } from '../../../Behaviors/Valueable';
import { Variantable } from '../../../Behaviors/Variantable';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { CustomElement } from '../../Abstracts/CustomElement';
import { ButtonElement } from '../../Buttons/Button/ButtonElement';
import { FocusRingElement } from '../../Primitives/FocusRing/FocusRingElement';
import { TextElement } from '../../Primitives/Text/TextElement';
import { signaturePadElementTemplate } from './SignaturePadElementTemplate';
import { signaturePadElementCosmopolitanStyle } from './Themes/SignaturePadElement.Cosmopolitan';
import { signaturePadElementJoyStyle } from './Themes/SignaturePadElement.Joy';
import { signaturePadElementMemphisStyle } from './Themes/SignaturePadElement.Memphis';
// #endregion
/**
 * Signature Pad - A user interface component for capturing and displaying digital signatures.
 *
 * @element mosaik-signaturepad
 *
 * @description
 * The Signature Pad component is designed to capture and display digital signatures. It provides a canvas or input area
 * where users can use a stylus, finger, or mouse to draw their signatures. Signature Pads are commonly used in various
 * applications, such as signing contracts, authorizing documents, or confirming transactions digitally.
 *
 * @category Forms
 *
 * @fires cleared {ClearedEvent} - Fired when the value is cleared.
 * @fires signStarted {SignStartedEvent} - Fired when the user starts signing.
 * @fires signEnded {SignEndedEvent} - Fired when the user ends signing.
 * @fires signStrokeBeforeUpdate {SignStrokeBeforeUpdateEvent} - Fired when the user starts signing.
 * @fires signStrokeAfterUpdate {SignStrokeAfterUpdateEvent} - Fired when the user ends signing.
 *
 * @public
 */
let SignaturePadElement = class SignaturePadElement extends Themeable(Clearable(Disableable(Focusable(Variantable(Invalidable(Valueable(Labelable(Appearanceable(CustomElement))))))))) {
    // #region Fields
    _signStarted;
    _signEnded;
    _signStrokeBeforeUpdate;
    _signStrokeAfterUpdate;
    _signaturePad;
    _width;
    _height;
    _placeholder;
    _minDistance;
    _throttle;
    _dotSize;
    _minWidth;
    _maxWidth;
    _velocityFilterWeight;
    _backgroundColor;
    _penColor;
    _required;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._width = 0;
        this._height = 0;
        this._placeholder = '';
        this._minDistance = 5;
        this._throttle = 16;
        this._dotSize = 0;
        this._minWidth = 0.5;
        this._maxWidth = 2.5;
        this._velocityFilterWeight = 0.7;
        this._backgroundColor = 'rgb(255, 255, 255)';
        this._penColor = 'rgb(0, 0, 0)';
        this._signaturePad = null;
        this._required = false;
        this._signStarted = new HTMLElementEventEmitter(this, 'signStarted');
        this._signEnded = new HTMLElementEventEmitter(this, 'signEnded');
        this._signStrokeBeforeUpdate = new HTMLElementEventEmitter(this, 'signStrokeBeforeUpdate');
        this._signStrokeAfterUpdate = new HTMLElementEventEmitter(this, 'signStrokeAfterUpdate');
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `{@link is}` property.
     * The `{@link is}` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-signaturepad';
    }
    /**
     * Gets or sets the `width` property.
     *
     * @public
     * @attr
     */
    get width() {
        return this._width;
    }
    set width(value) {
        if (this._width !== value) {
            this._width = value;
            RendererServiceLocator.current.setStyle(this, 'width', value);
            this.requestUpdate('width');
        }
    }
    /**
     * Gets or sets the `height` property.
     *
     * @public
     * @attr
     */
    get height() {
        return this._height;
    }
    set height(value) {
        if (this._height !== value) {
            this._height = value;
            RendererServiceLocator.current.setStyle(this, 'height', value);
            this.requestUpdate('height');
        }
    }
    /**
     * Gets or sets the `placeholder` property.
     *
     * @public
     * @attr
     */
    get placeholder() {
        return this._placeholder;
    }
    set placeholder(value) {
        if (this._placeholder !== value) {
            this._placeholder = value;
            this.requestUpdate('placeholder');
        }
    }
    /**
     * Gets or sets the `minDistance` property.
     *
     * @public
     * @attr
     */
    get minDistance() {
        return this._minDistance;
    }
    set minDistance(value) {
        if (this._minDistance !== value) {
            this._minDistance = value;
            this.requestUpdate('minDistance');
        }
    }
    /**
     * Gets or sets the `throttle` property.
     *
     * @public
     * @attr
     */
    get throttle() {
        return this._throttle;
    }
    set throttle(value) {
        if (this._throttle !== value) {
            this._throttle = value;
            this.requestUpdate('throttle');
        }
    }
    /**
     * Gets or sets the `dotSize` property.
     *
     * @public
     * @attr
     */
    get dotSize() {
        return this._dotSize;
    }
    set dotSize(value) {
        if (this._dotSize !== value) {
            this._dotSize = value;
            this.requestUpdate('dotSize');
        }
    }
    /**
     * Gets or sets the `minWidth` property.
     *
     * @public
     * @attr
     */
    get minWidth() {
        return this._minWidth;
    }
    set minWidth(value) {
        if (this._minWidth !== value) {
            this._minWidth = value;
            this.requestUpdate('minWidth');
        }
    }
    /**
     * Gets or sets the `maxWidth` property.
     *
     * @public
     * @attr
     */
    get maxWidth() {
        return this._maxWidth;
    }
    set maxWidth(value) {
        if (this._maxWidth !== value) {
            this._maxWidth = value;
            this.requestUpdate('maxWidth');
        }
    }
    /**
     * Gets or sets the `velocityFilterWeight` property.
     *
     * @public
     * @attr
     */
    get velocityFilterWeight() {
        return this._velocityFilterWeight;
    }
    set velocityFilterWeight(value) {
        if (this._velocityFilterWeight !== value) {
            this._velocityFilterWeight = value;
            this.requestUpdate('velocityFilterWeight');
        }
    }
    /**
     * Gets or sets the `backgroundColor` property.
     *
     * @public
     * @attr
     */
    get backgroundColor() {
        return this._backgroundColor;
    }
    set backgroundColor(value) {
        if (this._backgroundColor !== value) {
            this._backgroundColor = value;
            this.requestUpdate('backgroundColor');
        }
    }
    /**
     * Gets or sets the `penColor` property.
     *
     * @public
     * @attr
     */
    get penColor() {
        return this._penColor;
    }
    set penColor(value) {
        if (this._penColor !== value) {
            this._penColor = value;
            this.requestUpdate('penColor');
        }
    }
    /**
     * Gets or sets the `required` property.
     *
     * @public
     * @attr
     */
    get required() {
        return this._required;
    }
    set required(value) {
        if (this._required !== value) {
            this._required = value;
            this.requestUpdate('required');
        }
    }
    /**
     * Returns true if canvas is empty, otherwise returns false.
     *
     * @public
     * @readonly
     */
    get isEmpty() {
        return this._signaturePad?.isEmpty() ?? true;
    }
    /**
     * Called when the signing starts.
     * Provides reference to `{@link ISignStartedEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get signStarted() {
        return this._signStarted;
    }
    /**
     * Called when the signing ends.
     * Provides reference to `{@link ISignEndedEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get signEnded() {
        return this._signEnded;
    }
    /**
     * Called before the signing stroke is updated.
     * Provides reference to `{@link ISignStrokeEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get signStrokeBeforeUpdate() {
        return this._signStrokeBeforeUpdate;
    }
    /**
     * Called after the signing stroke is updated.
     * Provides reference to `{@link ISignStrokeEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get signStrokeAfterUpdate() {
        return this._signStrokeAfterUpdate;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        this._signaturePad?.off();
    }
    /**
     * Checks the validity of the element and returns `true` if it is valid; otherwise, `false`.
     *
     * @public
     * @override
     */
    checkValidity() {
        return this._required
            ? !this.isEmpty
            : true;
    }
    /**
     * Resets the value, all kinds of validation and errors.
     *
     * @public
     */
    reset() {
        this.value = false;
        this.invalid = false;
    }
    /**
     * Returns signature image as an array of point groups.
     *
     * @public
     */
    toData() {
        return this._signaturePad ? this._signaturePad.toData() : [];
    }
    /**
     * Returns signature image as data URL (see https://mdn.io/todataurl for the list of possible parameters).
     *
     * @public
     */
    toDataURL(imageType, quality) {
        // save image as data URL
        return this._signaturePad?.toDataURL(imageType, quality) ?? '';
    }
    /**
     * Returns signature image as data URL trimmed from white space.
     *
     * @public
     */
    toBlob() {
        return this.dataURLToBlob(this._signaturePad?.toDataURL() ?? '');
    }
    /**
     * Draws signature image from an array of point groups.
     *
     * @public
     */
    fromData(points) {
        this._signaturePad?.fromData(points);
    }
    /**
     * Draws signature image from data URL.
     *
     * @public
     */
    fromDataURL(dataURL, options) {
        // set default height and width on read data from URL
        if (options) {
            // TODO: maybe we should use the width and height automatically from the width and height.
        }
        return this._signaturePad?.fromDataURL(dataURL, options) ?? Promise.resolve();
    }
    /**
     * Clears the value of the element but not the validation.
     *
     * @public
     * @override
     */
    clear(force) {
        const isCleared = super.clear(force);
        if (isCleared) {
            this._signaturePad?.clear();
            this.requestUpdate('isEmpty');
            // afterwards focus the input
            this.focus();
        }
        return isCleared;
    }
    /**
     * @protected
     * @override
     */
    onApplyTemplate() {
        super.onApplyTemplate();
        const canvas = this.getTemplatePart('canvas');
        canvas.width = this._width;
        canvas.height = this._height;
        this._signaturePad = new SignaturePad(canvas, {
            minWidth: this._minWidth,
            maxWidth: this._maxWidth,
            minDistance: this._minDistance,
            dotSize: this._dotSize,
            throttle: this._throttle,
            velocityFilterWeight: this._velocityFilterWeight,
            penColor: this._penColor,
            backgroundColor: this._backgroundColor,
            compositeOperation: 'source-over'
        });
        on(this._signaturePad, 'beginStroke', () => this.onSignStarted());
        on(this._signaturePad, 'beforeUpdateStroke', () => this.onSignStrokeBeforeUpdated());
        on(this._signaturePad, 'afterUpdateStroke', () => this.onSignStrokeAfterUpdated());
        on(this._signaturePad, 'endStroke', () => this.onSignEnded());
    }
    /**
     * Emits the {@link signStarted} event.
     *
     * @protected
     */
    onSignStarted() {
        this._signStarted.emit();
    }
    /**
     * Emits the {@link signEnded} event.
     *
     * @protected
     */
    onSignEnded() {
        this._signEnded.emit();
    }
    /**
     * Emits the {@link signStrokeBeforeUpdated} event.
     *
     * @protected
     */
    onSignStrokeBeforeUpdated() {
        this._signStrokeBeforeUpdate.emit();
    }
    /**
     * Emits the {@link signStrokeAfterUpdated} event.
     *
     * @protected
     */
    onSignStrokeAfterUpdated() {
        this._signStrokeAfterUpdate.emit();
    }
    /**
     * @protected
     */
    onMinDistancePropertyChanged(_prev, newValue) {
        if (this._signaturePad) {
            this._signaturePad.minDistance = newValue;
        }
    }
    /**
     * @protected
     */
    onThrottlePropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.throttle = next;
        }
    }
    /**
     * @protected
     */
    onDotSizePropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.dotSize = next;
        }
    }
    /**
     * @protected
     */
    onMinWidthPropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.minWidth = next;
        }
    }
    /**
     * @protected
     */
    onMaxWidthPropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.maxWidth = next;
        }
    }
    /**
     * @protected
     */
    onVelocityFilterWeightPropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.velocityFilterWeight = next;
        }
    }
    /**
     * @protected
     */
    onBackgroundColorPropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.backgroundColor = next;
        }
    }
    /**
     * @protected
     */
    onPenColorPropertyChanged(_prev, next) {
        if (this._signaturePad) {
            this._signaturePad.penColor = next;
        }
    }
    /**
     * @private
     */
    dataURLToBlob(dataURL) {
        if (!dataURL) {
            return new Blob();
        }
        const parts = dataURL.split(',');
        const mimeType = parts[0].split(':')[1];
        const b64 = atob(parts[1]);
        let n = b64.length;
        const u8arr = new Uint8Array(n);
        while (n--) {
            u8arr[n] = b64.charCodeAt(n);
        }
        return new Blob([u8arr], { type: mimeType });
    }
    /**
     * @private
     */
    trimCanvas(canvas) {
        const ctx = canvas.getContext('2d');
        if (!ctx) {
            throw new Error('Failed to get 2d context from canvas.');
        }
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const { data } = imageData;
        let top = canvas.height;
        let bottom = 0;
        let left = canvas.width;
        let right = 0;
        for (let y = 0; y < canvas.height; y++) {
            for (let x = 0; x < canvas.width; x++) {
                const index = ((y * canvas.width) + x) * 4;
                const alpha = data[index + 3];
                if (alpha > 0) {
                    if (y < top) {
                        top = y;
                    }
                    if (y > bottom) {
                        bottom = y;
                    }
                    if (x < left) {
                        left = x;
                    }
                    if (x > right) {
                        right = x;
                    }
                }
            }
        }
        const trimmedWidth = right - left;
        const trimmedHeight = bottom - top;
        if (trimmedWidth <= 0 || trimmedHeight <= 0) {
            // Image is fully transparent
            return null;
        }
        const trimmedCanvas = document.createElement('canvas');
        trimmedCanvas.width = trimmedWidth;
        trimmedCanvas.height = trimmedHeight;
        const trimmedCtx = trimmedCanvas.getContext('2d');
        if (!trimmedCtx) {
            throw new Error('Failed to get 2d context from trimmed canvas.');
        }
        trimmedCtx.drawImage(canvas, left, top, trimmedWidth, trimmedHeight, 0, 0, trimmedWidth, trimmedHeight);
        return trimmedCanvas;
    }
};
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "width", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "height", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], SignaturePadElement.prototype, "placeholder", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "minDistance", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "throttle", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "dotSize", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "minWidth", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "maxWidth", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SignaturePadElement.prototype, "velocityFilterWeight", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], SignaturePadElement.prototype, "backgroundColor", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], SignaturePadElement.prototype, "penColor", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], SignaturePadElement.prototype, "required", null);
__decorate([
    Emit({ eventName: 'signStarted' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SignaturePadElement.prototype, "signStarted", null);
__decorate([
    Emit({ eventName: 'signEnded' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SignaturePadElement.prototype, "signEnded", null);
__decorate([
    Emit({ eventName: 'signStrokeBeforeUpdate' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SignaturePadElement.prototype, "signStrokeBeforeUpdate", null);
__decorate([
    Emit({ eventName: 'signStrokeAfterUpdate' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SignaturePadElement.prototype, "signStrokeAfterUpdate", null);
__decorate([
    Watch('minDistance', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onMinDistancePropertyChanged", null);
__decorate([
    Watch('throttle', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onThrottlePropertyChanged", null);
__decorate([
    Watch('dotSize', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onDotSizePropertyChanged", null);
__decorate([
    Watch('minWidth', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onMinWidthPropertyChanged", null);
__decorate([
    Watch('maxWidth', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onMaxWidthPropertyChanged", null);
__decorate([
    Watch('velocityFilterWeight', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onVelocityFilterWeightPropertyChanged", null);
__decorate([
    Watch('backgroundColor', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onBackgroundColorPropertyChanged", null);
__decorate([
    Watch('penColor', { waitUntilFirstUpdate: true }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", void 0)
], SignaturePadElement.prototype, "onPenColorPropertyChanged", null);
SignaturePadElement = __decorate([
    Component({
        selector: 'mosaik-signaturepad',
        template: signaturePadElementTemplate,
        themes: {
            joy: signaturePadElementJoyStyle,
            memphis: signaturePadElementMemphisStyle,
            cosmopolitan: signaturePadElementCosmopolitanStyle
        },
        host: {
            tabIndex: '0'
        },
        imports: [
            FocusRingElement,
            TextElement,
            ButtonElement
        ]
    }),
    __metadata("design:paramtypes", [])
], SignaturePadElement);
export { SignaturePadElement };
//# sourceMappingURL=SignaturePadElement.js.map