/**
 * @license
 * Copyright 2023 Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { getShadowElement, removeClass } from '@tylertech/forge-core';
import { BaseAdapter } from '../core/base/base-adapter';
import { ViewAnimationDirection, ViewSwitcherAnimationType, VIEW_SWITCHER_CONSTANTS } from './view-switcher-constants';
export class ViewSwitcherAdapter extends BaseAdapter {
    constructor(component) {
        super(component);
        this._rootElement = getShadowElement(component, VIEW_SWITCHER_CONSTANTS.selectors.ROOT);
        this._slotElement = getShadowElement(component, VIEW_SWITCHER_CONSTANTS.selectors.DEFAULT_SLOT);
    }
    getViewCount() {
        return this._getViews().length;
    }
    startViewObserver(callback) {
        this._viewObserver = new MutationObserver(callback);
        this._viewObserver.observe(this._component, { childList: true });
    }
    stopViewObserver() {
        if (this._viewObserver) {
            this._viewObserver.disconnect();
        }
    }
    /**
     * Initializes the views with styles for a slide transition.
     * @param index The active view index.
     */
    initializeSlideViews(index) {
        let views = this._getViews();
        views.forEach((view, i) => {
            // Make sure that we remove properties if they were set from another animation type
            view.style.removeProperty('opacity');
            view.style.removeProperty('display');
            // Ensure that the currently active view is visible
            if (i === index) {
                views[index].classList.remove(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
                view.style.removeProperty('visibility');
                return;
            }
            // We disable the transition when resetting the hidden views so that the animation doesn't occur
            view.style.transition = 'none';
            // Ensure the view is hidden
            view.classList.add(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
            view.style.visibility = 'hidden';
            // Reset the position of the view to the left or right of the switcher
            if (i > index) {
                view.style.transform = 'translate3d(100%, 0, 0)';
            }
            else {
                view.style.transform = 'translate3d(-100%, 0, 0)';
            }
        });
        // Wait a frame for the views to reposition, then re-enable the transition for all hidden views
        window.requestAnimationFrame(() => {
            views = this._getViews();
            views.forEach((v, i) => {
                if (i === index) {
                    return;
                }
                v.style.removeProperty('transition');
            });
        });
    }
    /**
     * Initializes the view styles when using a fade transition.
     * @param index The active view index.
     */
    initializeFadeViews(index) {
        const views = this._getViews();
        views.forEach((view, i) => {
            // Make sure that we remove properties if they were set from another animation type
            view.style.removeProperty('transform');
            view.style.removeProperty('display');
            // Ensure that the visible view doesn't have the hidden class
            if (i === index) {
                view.classList.remove(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
                view.style.removeProperty('visibility');
                return;
            }
            // Ensure the hidden views have the proper class
            view.classList.add(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
            view.style.visibility = 'hidden';
        });
        // Ensure that the visible view has the proper opacity
        if (views[index]) {
            views[index].style.opacity = '1';
        }
    }
    /**
     * This is used when no animation type is set to allow for snapping the view into place.
     * @param index The currently active view index.
     */
    setActiveView(index) {
        const views = this._getViews();
        views.forEach((view, i) => {
            if (i === index) {
                return;
            }
            // Hide all non-visible views
            view.classList.add(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
            view.style.display = 'none';
        });
        // Ensure that the active view is visible
        if (views[index]) {
            views[index].classList.remove(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
            views[index].style.removeProperty('display');
        }
    }
    /**
     * This is used when no animation type is specific to hide all non-visible views.
     * @param index The currently active view index.
     */
    hideInactiveViews(index) {
        const views = this._getViews();
        views.forEach((view, i) => {
            // Make sure that we remove properties if they were set from another animation type
            view.style.removeProperty('opacity');
            view.style.removeProperty('transform');
            view.style.removeProperty('visibility');
            // Ensure the active view is visible
            if (i === index) {
                view.classList.remove(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
                return;
            }
            // Ensure all non-active views are hidden
            view.classList.add(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
            view.style.display = 'none';
        });
    }
    /**
     * Initiates a slide transition from one view to another.
     * @param fromIndex The view index to transition from.
     * @param toIndex The view index to transition to.
     * @param direction The transition animation direction.
     */
    async transitionToView(fromIndex, toIndex, animationType, direction) {
        const views = this._getViews();
        const fromView = views[fromIndex];
        const toView = views[toIndex];
        return new Promise(resolve => {
            // Show the view so we can calculate the height and start the transition in the next frame
            toView.style.removeProperty('visibility');
            window.requestAnimationFrame(() => {
                let transitionProp;
                switch (animationType) {
                    case ViewSwitcherAnimationType.Slide:
                        this._slideToView(fromView, toView, direction);
                        transitionProp = 'transform';
                        break;
                    case ViewSwitcherAnimationType.Fade:
                        this._fadeToView(fromView, toView);
                        transitionProp = 'opacity';
                        break;
                    default:
                        return;
                }
                // Start the height transition (this will not animate if the height doesn't change)
                this._animateHeight(fromView, toView);
                if (fromView) {
                    // Wait for the transform transition to complete before resolving and completing transition
                    fromView.addEventListener('transitionend', transitionEndHandler);
                }
                else {
                    toView.classList.remove(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
                }
                function transitionEndHandler(evt) {
                    if (evt.propertyName === transitionProp) {
                        fromView.removeEventListener('transitionend', transitionEndHandler);
                        toView.classList.remove(VIEW_SWITCHER_CONSTANTS.classes.VIEW_HIDDEN);
                        resolve();
                    }
                }
            });
        });
    }
    /**
     * Initiates a slide transition from one view to another.
     * @param fromIndex The view index to transition from.
     * @param toIndex The view index to transition to.
     * @param direction The transition animation direction.
     */
    _slideToView(fromView, toView, direction) {
        // Use a CSS transform on the view to slide it in the requested direction
        if (direction === ViewAnimationDirection.Left) {
            if (fromView) {
                fromView.style.transform = 'translate3d(-100%, 0, 0)';
            }
            toView.style.transform = 'translate3d(0, 0, 0)';
        }
        else {
            if (fromView) {
                fromView.style.transform = 'translate3d(100%, 0, 0)';
            }
            toView.style.transform = 'translate3d(0, 0, 0)';
        }
    }
    _fadeToView(fromView, toView) {
        if (fromView) {
            fromView.style.opacity = '0';
        }
        toView.style.opacity = '1';
    }
    /**
     * Attempts to animate the height of our container element to match that of the next view.
     * @param fromIndex The view index we are transitioning from.
     * @param toIndex The view index we are transitioning to.
     */
    _animateHeight(fromView, toView) {
        const fromViewHeight = fromView ? this._getViewHeight(fromView) : 0;
        const toViewHeight = this._getViewHeight(toView);
        // We only animate the height if it is different...
        if (fromViewHeight !== toViewHeight) {
            this._rootElement.addEventListener('transitionend', this._onRootTransitionEnd.bind(this));
            // Set to the height of the current view so we can animate the height from something static
            this._setHeight(fromViewHeight);
            // We need to wait two frames before animating to the new height due to event loop
            window.requestAnimationFrame(() => {
                window.requestAnimationFrame(() => {
                    // Start the transition to the new view height
                    this._setHeight(toViewHeight);
                });
            });
        }
    }
    /**
     * Handles the root container transitionend event.
     * @param evt The transition event.
     */
    _onRootTransitionEnd(evt) {
        if (evt.propertyName === 'height') {
            this._rootElement.removeEventListener('transitionend', this._onRootTransitionEnd);
            // Removes the static height from the root element so that it can grow with content
            this._rootElement.style.removeProperty('height');
        }
    }
    /**
     * Resets the animation type on the root element.
     * @param type The animation type.
     */
    setAnimationType(type) {
        removeClass([VIEW_SWITCHER_CONSTANTS.classes.VIEW_SWITCHER_SLIDE, VIEW_SWITCHER_CONSTANTS.classes.VIEW_SWITCHER_FADE], this._component);
        switch (type) {
            case ViewSwitcherAnimationType.Slide:
                this._component.classList.add(VIEW_SWITCHER_CONSTANTS.classes.VIEW_SWITCHER_SLIDE);
                break;
            case ViewSwitcherAnimationType.Fade:
                this._component.classList.add(VIEW_SWITCHER_CONSTANTS.classes.VIEW_SWITCHER_FADE);
                break;
        }
    }
    /**
     * Get all view elements.
     */
    _getViews() {
        return this._slotElement.assignedNodes().filter(n => n.nodeType === 1);
    }
    /**
     * Sets the height on the root container element.
     * @param viewHeight The new height.
     */
    _setHeight(viewHeight) {
        this._rootElement.style.height = `${viewHeight}px`;
    }
    /**
     * Calculate the absolute height of a view.
     * @param view The view to calculate height from.
     */
    _getViewHeight(view) {
        let height = 0;
        const elements = view.children.length ? Array.from(view.children) : [view];
        for (const element of elements) {
            const styles = window.getComputedStyle(element);
            const margin = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
            height += Math.ceil(element.offsetHeight + margin);
        }
        return height;
    }
}
