/*!
 * (C) Ionic http://ionicframework.com - MIT License
 */
import { readTask, writeTask } from "@stencil/core";
import { clamp } from "../../utils/helpers";
const TRANSITION = 'all 0.2s ease-in-out';
const ROLE_NONE = 'none';
const ROLE_BANNER = 'banner';
export const cloneElement = (tagName) => {
    const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
    if (getCachedEl !== null) {
        return getCachedEl;
    }
    const clonedEl = document.createElement(tagName);
    clonedEl.classList.add('ion-cloned-element');
    clonedEl.style.setProperty('display', 'none');
    document.body.appendChild(clonedEl);
    return clonedEl;
};
export const createHeaderIndex = (headerEl) => {
    if (!headerEl) {
        return;
    }
    const toolbars = headerEl.querySelectorAll('ion-toolbar');
    return {
        el: headerEl,
        toolbars: Array.from(toolbars).map((toolbar) => {
            const ionTitleEl = toolbar.querySelector('ion-title');
            return {
                el: toolbar,
                background: toolbar.shadowRoot.querySelector('.toolbar-background'),
                ionTitleEl,
                innerTitleEl: ionTitleEl ? ionTitleEl.shadowRoot.querySelector('.toolbar-title') : null,
                ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')),
            };
        }),
    };
};
export const handleContentScroll = (scrollEl, scrollHeaderIndex, contentEl) => {
    readTask(() => {
        const scrollTop = scrollEl.scrollTop;
        const scale = clamp(1, 1 + -scrollTop / 500, 1.1);
        // Native refresher should not cause titles to scale
        const nativeRefresher = contentEl.querySelector('ion-refresher.refresher-native');
        if (nativeRefresher === null) {
            writeTask(() => {
                scaleLargeTitles(scrollHeaderIndex.toolbars, scale);
            });
        }
    });
};
export const setToolbarBackgroundOpacity = (headerEl, opacity) => {
    /**
     * Fading in the backdrop opacity
     * should happen after the large title
     * has collapsed, so it is handled
     * by handleHeaderFade()
     */
    if (headerEl.collapse === 'fade') {
        return;
    }
    if (opacity === undefined) {
        headerEl.style.removeProperty('--opacity-scale');
    }
    else {
        headerEl.style.setProperty('--opacity-scale', opacity.toString());
    }
};
const handleToolbarBorderIntersection = (ev, mainHeaderIndex, scrollTop) => {
    if (!ev[0].isIntersecting) {
        return;
    }
    /**
     * There is a bug in Safari where overflow scrolling on a non-body element
     * does not always reset the scrollTop position to 0 when letting go. It will
     * set to 1 once the rubber band effect has ended. This causes the background to
     * appear slightly on certain app setups.
     *
     * Additionally, we check if user is rubber banding (scrolling is negative)
     * as this can mean they are using pull to refresh. Once the refresher starts,
     * the content is transformed which can cause the intersection observer to erroneously
     * fire here as well.
     */
    const scale = ev[0].intersectionRatio > 0.9 || scrollTop <= 0 ? 0 : ((1 - ev[0].intersectionRatio) * 100) / 75;
    setToolbarBackgroundOpacity(mainHeaderIndex.el, scale === 1 ? undefined : scale);
};
/**
 * If toolbars are intersecting, hide the scrollable toolbar content
 * and show the primary toolbar content. If the toolbars are not intersecting,
 * hide the primary toolbar content and show the scrollable toolbar content
 */
export const handleToolbarIntersection = (ev, // TODO(FW-2832): type (IntersectionObserverEntry[] triggers errors which should be sorted)
mainHeaderIndex, scrollHeaderIndex, scrollEl) => {
    writeTask(() => {
        const scrollTop = scrollEl.scrollTop;
        handleToolbarBorderIntersection(ev, mainHeaderIndex, scrollTop);
        const event = ev[0];
        const intersection = event.intersectionRect;
        const intersectionArea = intersection.width * intersection.height;
        const rootArea = event.rootBounds.width * event.rootBounds.height;
        const isPageHidden = intersectionArea === 0 && rootArea === 0;
        const leftDiff = Math.abs(intersection.left - event.boundingClientRect.left);
        const rightDiff = Math.abs(intersection.right - event.boundingClientRect.right);
        const isPageTransitioning = intersectionArea > 0 && (leftDiff >= 5 || rightDiff >= 5);
        if (isPageHidden || isPageTransitioning) {
            return;
        }
        if (event.isIntersecting) {
            setHeaderActive(mainHeaderIndex, false);
            setHeaderActive(scrollHeaderIndex);
        }
        else {
            /**
             * There is a bug with IntersectionObserver on Safari
             * where `event.isIntersecting === false` when cancelling
             * a swipe to go back gesture. Checking the intersection
             * x, y, width, and height provides a workaround. This bug
             * does not happen when using Safari + Web Animations,
             * only Safari + CSS Animations.
             */
            const hasValidIntersection = (intersection.x === 0 && intersection.y === 0) || (intersection.width !== 0 && intersection.height !== 0);
            if (hasValidIntersection && scrollTop > 0) {
                setHeaderActive(mainHeaderIndex);
                setHeaderActive(scrollHeaderIndex, false);
                setToolbarBackgroundOpacity(mainHeaderIndex.el);
            }
        }
    });
};
export const setHeaderActive = (headerIndex, active = true) => {
    const headerEl = headerIndex.el;
    const toolbars = headerIndex.toolbars;
    const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
    if (active) {
        headerEl.setAttribute('role', ROLE_BANNER);
        headerEl.classList.remove('header-collapse-condense-inactive');
        ionTitles.forEach((ionTitle) => {
            if (ionTitle) {
                ionTitle.removeAttribute('aria-hidden');
            }
        });
    }
    else {
        /**
         * There can only be one banner landmark per page.
         * By default, all ion-headers have the banner role.
         * This causes an accessibility issue when using a
         * condensed header since there are two ion-headers
         * on the page at once (active and inactive).
         * To solve this, the role needs to be toggled
         * based on which header is active.
         */
        headerEl.setAttribute('role', ROLE_NONE);
        headerEl.classList.add('header-collapse-condense-inactive');
        /**
         * The small title should only be accessed by screen readers
         * when the large title collapses into the small title due
         * to scrolling.
         *
         * Originally, the header was given `aria-hidden="true"`
         * but this caused issues with screen readers not being
         * able to access any focusable elements within the header.
         */
        ionTitles.forEach((ionTitle) => {
            if (ionTitle) {
                ionTitle.setAttribute('aria-hidden', 'true');
            }
        });
    }
};
export const scaleLargeTitles = (toolbars = [], scale = 1, transition = false) => {
    toolbars.forEach((toolbar) => {
        const ionTitle = toolbar.ionTitleEl;
        const titleDiv = toolbar.innerTitleEl;
        if (!ionTitle || ionTitle.size !== 'large') {
            return;
        }
        titleDiv.style.transition = transition ? TRANSITION : '';
        titleDiv.style.transform = `scale3d(${scale}, ${scale}, 1)`;
    });
};
export const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
    readTask(() => {
        const scrollTop = scrollEl.scrollTop;
        const baseElHeight = baseEl.clientHeight;
        const fadeStart = condenseHeader ? condenseHeader.clientHeight : 0;
        /**
         * If we are using fade header with a condense
         * header, then the toolbar backgrounds should
         * not begin to fade in until the condense
         * header has fully collapsed.
         *
         * Additionally, the main content should not
         * overflow out of the container until the
         * condense header has fully collapsed. When
         * using just the condense header the content
         * should overflow out of the container.
         */
        if (condenseHeader !== null && scrollTop < fadeStart) {
            baseEl.style.setProperty('--opacity-scale', '0');
            scrollEl.style.setProperty('clip-path', `inset(${baseElHeight}px 0px 0px 0px)`);
            return;
        }
        const distanceToStart = scrollTop - fadeStart;
        const fadeDuration = 10;
        const scale = clamp(0, distanceToStart / fadeDuration, 1);
        writeTask(() => {
            scrollEl.style.removeProperty('clip-path');
            baseEl.style.setProperty('--opacity-scale', scale.toString());
        });
    });
};
/**
 * Get the role type for the ion-header.
 *
 * @param isInsideMenu If ion-header is inside ion-menu.
 * @param isCondensed If ion-header has collapse="condense".
 * @param theme The current theme.
 * @returns 'none' if inside ion-menu or if condensed in md
 * theme, otherwise 'banner'.
 */
export const getRoleType = (isInsideMenu, isCondensed, theme) => {
    // If the header is inside a menu, it should not have the banner role.
    if (isInsideMenu) {
        return ROLE_NONE;
    }
    /**
     * Only apply role="none" to `md` & `ionic` theme condensed headers
     * since the large header is never shown.
     */
    if (isCondensed && theme !== 'ios') {
        return ROLE_NONE;
    }
    // Default to banner role.
    return ROLE_BANNER;
};
