/* eslint-disable @typescript-eslint/no-explicit-any */
// #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 { flash } from '../../../../Animations/Presets/Flash';
import { RendererServiceLocator } from '../../../../Dom/Renderer';
import { Emit } from '../../../../Events/Decorators/EmitDecorator';
import { on, HTMLElementEventEmitter } from '@breadstone/mosaik-elements';
import { Dimensionable } from '../../../Behaviors/Dimensionable';
import { Disableable } from '../../../Behaviors/Disableable';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { QueryChild } from '../../../Decorators/QueryChildDecorator';
import { Property } from '../../../Decorators/PropertyDecorator';
import { CameraCaptureFormat } from '../../../Types/CameraCaptureFormat';
import { CameraRecorderFormat } from '../../../Types/CameraRecorderFormat';
import { CameraType } from '../../../Types/CameraType';
import { CustomElement } from '../../Abstracts/CustomElement';
import { cameraElementTemplate } from './CameraElementTemplate';
import { cameraElementJoyStyle } from './Themes/CameraElement.Joy';
import { cameraElementRetroStyle } from './Themes/CameraElement.Retro';
import { cameraElementCosmopolitanStyle } from './Themes/CameraElement.Cosmopolitan';
import { Themeable } from '../../../Behaviors/Themeable';
/**
 * The `{@link CameraElement}` element.
 *
 * @slot underlay - The underlay slot.
 * @slot overlay - The overlay slot.
 *
 * @fires captured - Raised when a image was captured.
 * @fires recorded - Raised when a video was recorded.
 *
 * @public
 */
let CameraElement = class CameraElement extends Themeable(Disableable(Dimensionable(CustomElement))) {
    // #region Fields
    _videoElement;
    _captured;
    _recorded;
    _type;
    _recorderFormat;
    _captureFormat;
    _src;
    _poster;
    _autoPlay;
    _autoFullScreen;
    _isRecording;
    _recorder;
    _dataAvailableEventSubscription;
    _frame;
    // #endregion
    // #region Ctor
    /**
     * @public
     */
    constructor() {
        super();
        this._type = CameraType.Front;
        this._recorderFormat = CameraRecorderFormat.Webm;
        this._captureFormat = CameraCaptureFormat.Jpeg;
        this._src = null;
        this._poster = '';
        this._autoPlay = false;
        this._autoFullScreen = false;
        this._isRecording = false;
        this._recorder = null;
        this._frame = null;
        this._captured = new HTMLElementEventEmitter(this, 'captured');
        this._recorded = new HTMLElementEventEmitter(this, 'recorded');
        this._dataAvailableEventSubscription = null;
    }
    // #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-camera';
    }
    /**
     * Gets or sets the `type` property.
     *
     * @public
     * @attr
     */
    get type() {
        return this._type;
    }
    set type(value) {
        if (this._type !== value) {
            this._type = value;
            this.requestUpdate('type');
            this.load();
        }
    }
    /**
     * Gets or sets the `recorderFormat` property.
     *
     * @public
     * @attr
     */
    get recorderFormat() {
        return this._recorderFormat;
    }
    set recorderFormat(value) {
        if (this._recorderFormat !== value) {
            this._recorderFormat = value;
            this.requestUpdate('recorderFormat');
        }
    }
    /**
     * Gets or sets the `captureFormat` property.
     *
     * @public
     * @attr
     */
    get captureFormat() {
        return this._captureFormat;
    }
    set captureFormat(value) {
        if (this._captureFormat !== value) {
            this._captureFormat = value;
            this.requestUpdate('captureFormat');
        }
    }
    /**
     * Gets or sets the `src` property.
     *
     * @public
     */
    get src() {
        return this._src;
    }
    set src(value) {
        if (this._src !== value) {
            this._src = value;
            this.requestUpdate('src');
        }
    }
    /**
     * Gets or sets the `poster` property.
     *
     * @public
     */
    get poster() {
        return this._poster;
    }
    set poster(value) {
        if (this._poster !== value) {
            this._poster = value;
            this.requestUpdate('poster');
        }
    }
    /**
     * Gets or sets the `autoPlay` property.
     *
     * @public
     * @attr
     */
    get autoPlay() {
        return this._autoPlay;
    }
    set autoPlay(value) {
        if (this._autoPlay !== value) {
            this._autoPlay = value;
            this.requestUpdate('autoPlay');
        }
    }
    /**
     * Gets or sets the `autoFullScreen` property.
     *
     * @public
     * @attr
     */
    get autoFullScreen() {
        return this._autoFullScreen;
    }
    set autoFullScreen(value) {
        if (this._autoFullScreen !== value) {
            this._autoFullScreen = value;
            this.requestUpdate('autoFullScreen');
        }
    }
    /**
     * A camera frame info object
     * When the camera frame is set, the camera will be displayed as a frame.
     * If a picture will captured, only the inner part of the frame will be captured.
     *
     * @public
     * @attr
     * @remarks
     * A frame can be defined as a string (*e.g.* `16:9`) or as an object (*e.g.* `{ width: 16, height: 9 }`).
     */
    get frame() {
        return this._frame;
    }
    set frame(value) {
        if (this._frame !== value) {
            this._frame = value;
            this.requestUpdate('frame');
        }
    }
    /**
     * Returns the `isSupported` property.
     *
     * @public
     * @readonly
     */
    get isSupported() {
        return 'mediaDevices' in window.navigator && 'getUserMedia' in window.navigator.mediaDevices;
    }
    /**
     * Returns the `isSupported` property.
     *
     * @public
     * @readonly
     */
    get isRecorderSupported() {
        return 'MediaRecorder' in window;
    }
    /**
     * Fires when a image was captured.
     * Provides reference to `{@link ICameraCaptureEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get captured() {
        return this._captured;
    }
    /**
     * Fires when a video was recorded.
     * Provides reference to `{@link ICameraRecordEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get recorded() {
        return this._recorded;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        this._dataAvailableEventSubscription?.dispose();
        super.disconnectedCallback();
    }
    /**
     * Record a stream of the current video element.
     *
     * @public
     */
    record() {
        if (!this.isRecorderSupported) {
            throw new Error('MediaRecorder is not supported.');
        }
        if (!(this.src instanceof MediaStream)) {
            throw new Error('src must be a media stream.');
        }
        if (!this._isRecording) {
            this._recorder = new MediaRecorder(this.src, { mimeType: `video/${this._recorderFormat}` });
            this._dataAvailableEventSubscription = on(this._recorder, 'dataavailable', (event) => {
                if (event.data.size > 0) {
                    const blob = new Blob([event.data], { type: `video/${this._recorderFormat}` });
                    this.onRecorded({
                        data: blob
                    });
                }
            });
            this._recorder.start();
            this._isRecording = true;
        }
        else {
            this._recorder?.stop();
            this._recorder = null;
            this._isRecording = false;
        }
    }
    /**
     * Captures a image of the current video element.
     *
     * @public
     */
    capture() {
        this.getTemplatePart('flash').animate(flash.steps, flash.options);
        const canvas = RendererServiceLocator.current.createElement('canvas');
        canvas.width = this._videoElement.offsetWidth;
        canvas.height = this._videoElement.offsetHeight;
        const ctx = canvas.getContext('2d');
        if (ctx) {
            ctx.drawImage(this._videoElement, 0, 0);
            // const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            canvas.toBlob((blob) => {
                if (blob) {
                    this.onCaptured({
                        data: blob
                    });
                    canvas.remove();
                }
            }, `image/${this._captureFormat}`, 1);
        }
    }
    /**
     * @protected
     * @override
     */
    onApplyTemplate() {
        super.onApplyTemplate();
        this.load();
    }
    /**
     * Emits the {@link captured} event.
     *
     * @protected
     */
    onCaptured(args) {
        this._captured.emit(args);
    }
    /**
     * Emits the {@link recorded} event.
     *
     * @protected
     */
    onRecorded(args) {
        this._recorded.emit(args);
    }
    /**
     * @private
     */
    load() {
        void this.updateComplete.then(() => {
            window.navigator.mediaDevices
                .getUserMedia({
                audio: false,
                video: true
            })
                .then((x) => {
                this.src = x;
                this._videoElement.srcObject = x;
                this._videoElement.load();
            })
                .catch((error) => {
                if (error instanceof Error) {
                    this.dispatchEvent(new CustomEvent('error', {
                        detail: {
                            error: error
                        }
                    }));
                }
            });
        });
    }
    /**
     * @private
     */
    cameraTypeToFacingMode(type) {
        switch (type) {
            case CameraType.Front:
                return 'user';
            case CameraType.Back:
                return 'environment';
            default:
                throw new Error('Invalid camera type.');
        }
    }
};
__decorate([
    QueryChild('[part="video"]'),
    __metadata("design:type", HTMLVideoElement)
], CameraElement.prototype, "_videoElement", void 0);
__decorate([
    Attribute({ type: CameraType }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CameraElement.prototype, "type", null);
__decorate([
    Attribute({ type: CameraRecorderFormat }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CameraElement.prototype, "recorderFormat", null);
__decorate([
    Attribute({ type: CameraCaptureFormat }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CameraElement.prototype, "captureFormat", null);
__decorate([
    Property({ type: Object }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], CameraElement.prototype, "src", null);
__decorate([
    Property({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CameraElement.prototype, "poster", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CameraElement.prototype, "autoPlay", null);
__decorate([
    Attribute({
        type: Boolean,
        useDefault: true
    }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [Boolean])
], CameraElement.prototype, "autoFullScreen", null);
__decorate([
    Attribute({ type: Object }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], CameraElement.prototype, "frame", null);
__decorate([
    Emit({ eventName: 'captured' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], CameraElement.prototype, "captured", null);
__decorate([
    Emit({ eventName: 'recorded' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], CameraElement.prototype, "recorded", null);
CameraElement = __decorate([
    Component({
        selector: 'mosaik-camera',
        template: cameraElementTemplate,
        themes: {
            joy: cameraElementJoyStyle,
            retro: cameraElementRetroStyle,
            cosmopolitan: cameraElementCosmopolitanStyle
        },
        imports: []
    }),
    __metadata("design:paramtypes", [])
], CameraElement);
export { CameraElement };
//# sourceMappingURL=CameraElement.js.map