import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, Component, Output, Input, HostListener, NgModule } from '@angular/core';
import { Destroy } from '@deja-js/component/core';
import { Subject, tap, delay, debounce, timer, takeUntil, take } from 'rxjs';

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaSnackbarComponent extends Destroy {
    /**
     * Creates an instance of DejaSnackbarComponent.
     *
     * @param elementRef
     * @param renderer
     */
    constructor(elementRef) {
        super();
        this.elementRef = elementRef;
        /**
     * callback used to negate the boolean responsible for the presence of the snackbar on the dom (see demo)
     */
        // eslint-disable-next-line @angular-eslint/no-output-on-prefix
        this.onAnimationDone = new EventEmitter();
        /**
         * inner container
         */
        // @ViewChild('container') public host;
        /**
         * specify delay for the enter animation
         */
        this.delay = 0;
        /**
         * specify lifetime of the snackbar on the screen
         */
        this.duration = 0;
        /**
         * vertical space between snackbar
         */
        this.marginTop = 6;
        /**
         * snackbar creation timestamp, used for calculation, forthe adapt animation
         */
        this.timestamp = +new Date();
        /**
         * enter animation duration
         */
        this.enterAnimationDuration = 350;
        /**
         * leave animation duration
         */
        this.leaveAnimationDuration = 175;
        /**
         * adapt animation duration
         */
        this.adaptAnimationDuration = 225;
        /**
         * object representation of the alignment, used to filter incompatible alignments and build the string representation
         */
        this._alignments = {};
        this.animate$ = new Subject();
        if (!DejaSnackbarComponent.INSTANCES) {
            DejaSnackbarComponent.INSTANCES = [];
        }
        DejaSnackbarComponent.INSTANCES.push(this);
        const applyParams = (styles) => {
            Object.keys(styles)
                .forEach(key => {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any,  @typescript-eslint/no-unsafe-assignment,  @typescript-eslint/no-unsafe-member-access
                this.host.style[key] = styles[key];
            });
        };
        this.animate$sub = this.animate$.pipe(tap(animation => applyParams(animation.before)), delay(1), tap(animation => {
            this.host.style.transitionDuration = `${animation.duration}ms`;
            this.host.style.transitionTimingFunction = animation.easing;
            this.host.style.transitionProperty = Object.keys(animation.before).join(',');
        }), debounce(animation => timer(animation.delay || 1)), tap(animation => applyParams(animation.after)), debounce(animation => timer(animation.duration)), takeUntil(this.destroyed$)).subscribe(() => {
            this.host.style.transitionDuration = '';
            this.host.style.transitionTimingFunction = '';
            this.host.style.transitionProperty = '';
        });
    }
    /**
     * _alignments setter
     */
    set alignment(value) {
        this._alignments = {
            bottom: false,
            left: false,
            right: false,
            top: false
        };
        // set _alignments
        if (value) {
            value.split(/\s+/g).forEach((align) => this._alignments[align] = true);
        }
        // filter incompatible alignments
        this._alignments.bottom = this._alignments.top && this._alignments.bottom ? false : this._alignments.bottom;
        this._alignments.left = this._alignments.right && this._alignments.left ? false : this._alignments.left;
    }
    /**
     * used to recalculate the position of the snackbar on the X axis when resizing / changing from landscape to portrait and vice versa
     *
     * @param event
     */
    onResize() {
        this.setNewWidth();
    }
    /**
     * onInit hook
     */
    ngOnInit() {
        // Choose animation depending on alignment
        const anchors = [];
        Object.keys(this._alignments).forEach(key => {
            if (this._alignments[key]) {
                anchors.push(key);
            }
        });
        anchors.sort((x, y) => x > y ? 1 : -1);
        const anchor = anchors.reduce((acc, curr) => {
            if (acc === '') {
                acc += curr;
            }
            else {
                acc += `-${curr}`;
            }
            return acc;
        }, '');
        this.anchor = anchor;
    }
    /**
     * afterviewInit hook
     */
    ngAfterViewInit() {
        this.host = this.elementRef.nativeElement;
        if (!this.outerContainerElement) {
            // Set default outer container if none specified
            this.outerContainerElement = this.host.ownerDocument.body;
        }
        else {
            // Otherwise, set inner container position to absolute for correct placement of snackbars
            this.host.classList.add('absolute');
        }
        this.height = this.host.getBoundingClientRect().height;
        this.setPosition();
        this.launchEnterAnimation();
        // if a duration has been been specified, launch the 'leave' animation after snackbar's lifetime flow, then emit amination done
        timer(this.duration + this.delay).pipe(take(1), tap(() => {
            if (this.duration) {
                this.launchLeaveAnimation();
            }
        }), delay(this.leaveAnimationDuration), takeUntil(this.destroyed$)).subscribe(() => this.onAnimationDone.emit());
    }
    /**
     * onDestroy hook
     */
    ngOnDestroy() {
        super.ngOnDestroy();
        // check if snackbars have to move (if they were created after the one deleted)
        if (DejaSnackbarComponent.INSTANCES.length) {
            DejaSnackbarComponent.INSTANCES
                .filter(instance => this.outerContainerElement === instance.outerContainerElement)
                .filter(instance => this.anchor === instance.anchor)
                .forEach(instance => {
                if (instance.timestamp > this.timestamp) {
                    instance.launchAdaptAnimation(this.height);
                }
            });
        }
        // remove the soon to be destroyed snackbar from the instances array
        DejaSnackbarComponent.INSTANCES = DejaSnackbarComponent.INSTANCES
            .filter(instance => this !== instance);
        this.animate$sub.unsubscribe();
    }
    /**
     * emit animation done
     *
     * @param event
     */
    animationDone(event) {
        this.onAnimationDone.emit(event);
    }
    increaseElevation() {
        const zIndex = window.getComputedStyle(this.host).zIndex;
        this.host.style.zIndex = (+zIndex + 1).toString();
    }
    decreaseElevation() {
        const zIndex = window.getComputedStyle(this.host).zIndex;
        this.host.style.zIndex = (+zIndex - 1).toString();
    }
    /**
     * compute cumulated height of all snackbars, precedent instance height, width and height of the innerContainer
     *
     * @return cumulated height of all snackbars, precedent instance height, width and height of the innerContainer
     */
    computePosition() {
        // Inner container
        const innerContainerElementBounds = this.host.getBoundingClientRect();
        const innerContainerWidth = innerContainerElementBounds.width;
        const innerContainerHeight = innerContainerElementBounds.height;
        // Instances sharing the same outer container and the same anchor
        const instancesInSameZone = DejaSnackbarComponent.INSTANCES
            .filter((instance) => this.outerContainerElement === instance.outerContainerElement)
            .filter((instance) => this.anchor === instance.anchor)
            .filter((instance) => this !== instance);
        let precedentInstanceHeight = 0;
        if (instancesInSameZone) {
            const precedentInstance = instancesInSameZone[instancesInSameZone.length - 1];
            if (precedentInstance) {
                const innerContainerElement = precedentInstance.elementRef.nativeElement;
                precedentInstanceHeight = innerContainerElement.getBoundingClientRect().height;
            }
        }
        // computed height of inner containers, sharing the same outer container and the same anchor
        const computedHeight = instancesInSameZone
            .map((instance) => {
            const innerContainerElement = instance.elementRef.nativeElement;
            return innerContainerElement.getBoundingClientRect().height;
        })
            .reduce((acc, curr) => {
            acc += curr + this.marginTop;
            return acc;
        }, 0);
        return {
            innerContainerWidth,
            innerContainerHeight,
            precedentInstanceHeight,
            computedHeight
        };
    }
    /**
     * set the final position of the snackbar
     */
    setPosition() {
        const { innerContainerWidth, innerContainerHeight, computedHeight } = this.computePosition();
        if (this.anchor === 'left') {
            this.host.style.left = `${2}%`;
            this.host.style.bottom = `calc(${33}% + ${computedHeight}px)`;
        }
        if (this.anchor === 'right') {
            this.host.style.left = `calc(${98}% - ${innerContainerWidth}px)`;
            this.host.style.bottom = `calc(${33}% + ${computedHeight}px)`;
        }
        if (this.anchor === 'top') {
            this.host.style.left = `calc(${50}% - ${innerContainerWidth / 2}px )`;
            this.host.style.bottom = `calc(${92}% - ${innerContainerHeight}px)`;
        }
        if (this.anchor === 'bottom') {
            this.host.style.left = `calc(${50}% - ${innerContainerWidth / 2}px )`;
            this.host.style.bottom = `calc(${2}% + ${computedHeight}px)`;
        }
        if (this.anchor === 'bottom-left') {
            this.host.style.left = `${2}%`;
            this.host.style.bottom = `calc(${2}% + ${computedHeight}px)`;
        }
        if (this.anchor === 'bottom-right') {
            this.host.style.left = `calc(${98}% - ${innerContainerWidth}px)`;
            this.host.style.bottom = `calc(${2}% + ${computedHeight}px)`;
        }
        if (this.anchor === 'left-top') {
            this.host.style.left = `${2}%`;
            this.host.style.bottom = `calc(${92}% - ${innerContainerHeight}px - ${computedHeight}px)`;
        }
        if (this.anchor === 'right-top') {
            this.host.style.left = `calc(${98}% - ${innerContainerWidth}px)`;
            this.host.style.bottom = `calc(${92}% - ${innerContainerHeight}px - ${computedHeight}px)`;
        }
    }
    /**
     * recalculate X position for the snackbar (see @HostListener)
     */
    setNewWidth() {
        const { innerContainerWidth } = this.computePosition();
        if (this.anchor === 'top' || this.anchor === 'bottom') {
            this.elementRef.nativeElement.style.left = `calc(${50}% - ${innerContainerWidth / 2}px )`;
        }
    }
    /**
     * launch adapt animation (snackbar disposal trigger this method)
     * important note:
     * matrix retrieval is used instead of translate Y because using translateY in enter and adapt animation seems
     * to cause unexpected behavior (understand bug)
     * there is also a known bug, if you close a snackbar which share anchor and container with an other one created at the same moment
     * adaptation of the position will not be performed correctly, see demo for more information about how to avoid this behavior
     *
     * @param height
     */
    launchAdaptAnimation(height) {
        let direction = 1;
        if (this._alignments.top) {
            direction = -1;
        }
        const transform = window.getComputedStyle(this.host).transform;
        const sixth = parseFloat(transform
            .split(',')
            .slice(-1)
            .pop());
        this.animate$.next({
            before: {
                transform: `${transform}`
            },
            after: {
                transform: `matrix(1,0,0,1,0,${sixth + ((height + this.marginTop) * direction)})`
            },
            duration: this.adaptAnimationDuration,
            easing: 'ease'
        });
    }
    /**
     * launch enter animation (snackbar instanciation trigger this method)
     */
    launchEnterAnimation() {
        let direction = -1;
        if (this._alignments.top) {
            direction = 1;
        }
        this.animate$.next({
            before: {
                opacity: '0',
                transform: `translateY(${direction * 200}%)`
            },
            after: {
                opacity: '1',
                transform: 'translateY(0)'
            },
            delay: this.delay,
            duration: this.enterAnimationDuration,
            easing: 'ease'
        });
    }
    /**
     * launch leave animation (snackbar lifetime flow trigger this animation)
     */
    launchLeaveAnimation() {
        this.animate$.next({
            before: {
                opacity: '1'
            },
            after: {
                opacity: '0'
            },
            duration: this.leaveAnimationDuration,
            easing: 'ease'
        });
    }
}
/**
 * all snackbar instances
 */
DejaSnackbarComponent.INSTANCES = [];
DejaSnackbarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaSnackbarComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
DejaSnackbarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.1.3", type: DejaSnackbarComponent, selector: "deja-snackbar", inputs: { delay: "delay", duration: "duration", outerContainerElement: "outerContainerElement", alignment: "alignment" }, outputs: { onAnimationDone: "onAnimationDone" }, host: { listeners: { "window:resize": "onResize()" } }, usesInheritance: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{position:absolute;bottom:0;left:0;display:block;box-sizing:border-box;z-index:25}:host.absolute{position:absolute!important}\n"] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaSnackbarComponent, decorators: [{
            type: Component,
            args: [{ selector: 'deja-snackbar', template: '<ng-content></ng-content>', styles: [":host{position:absolute;bottom:0;left:0;display:block;box-sizing:border-box;z-index:25}:host.absolute{position:absolute!important}\n"] }]
        }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { onAnimationDone: [{
                type: Output
            }], delay: [{
                type: Input
            }], duration: [{
                type: Input
            }], outerContainerElement: [{
                type: Input
            }], alignment: [{
                type: Input
            }], onResize: [{
                type: HostListener,
                args: ['window:resize', []]
            }] } });

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaSnackbarModule {
}
DejaSnackbarModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaSnackbarModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
DejaSnackbarModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.1.3", ngImport: i0, type: DejaSnackbarModule, declarations: [DejaSnackbarComponent], imports: [CommonModule], exports: [DejaSnackbarComponent] });
DejaSnackbarModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaSnackbarModule, imports: [CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaSnackbarModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [DejaSnackbarComponent],
                    exports: [DejaSnackbarComponent],
                    imports: [
                        CommonModule
                    ]
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { DejaSnackbarComponent, DejaSnackbarModule };
//# sourceMappingURL=deja-js-component-snackbar.mjs.map
