import React, { PureComponent } from 'react';
import { ResizeSensor } from 'css-element-queries';
import { st, classes, stVars, vars } from './Page.st.css.js';
import { PageContext } from './PageContext';
import { PageScrollContext } from './PageScrollContext';
import PageHeader from './components/Header';
import Section from './components/Section';
import Footer from './components/Footer';
import Content from './Content';
import Tail from './Tail';
import { PageSticky } from './PageSticky';
import FixedFooter from './FixedFooter';
import ScrollableContainer from '../common/ScrollableContainer';
const FixedContent = (props) => props.children;
FixedContent.displayName = 'Page.FixedContent';
class Page extends PureComponent {
    constructor(props) {
        super(props);
        this._renderFixedFooter = () => {
            const { children } = this.props;
            const childrenObject = getChildrenObject(children);
            const { FixedFooter: FixedFooterChild, PageContent } = childrenObject;
            const contentFullScreen = PageContent && PageContent.props.fullScreen;
            const pageDimensionsStyle = contentFullScreen
                ? undefined
                : this._getPageDimensionsStyle();
            if (FixedFooterChild) {
                return (React.createElement("div", { className: classes.fixedFooter, ref: ref => {
                        this.footerWrapperRef = ref;
                    }, style: pageDimensionsStyle }, React.cloneElement(FixedFooterChild, {})));
            }
            return null;
        };
        this.scrollableContainerRef = React.createRef();
        this.headerWrapperRef = null;
        this.headerContainerRef = null;
        this.pageHeaderTailRef = null;
        this.pageRef = null;
        this.footerWrapperRef = null;
        this.pageScrollContextValue = {
            scrollableContentRef: this.scrollableContainerRef,
        };
        this._handleScroll = this._handleScroll.bind(this);
        this._handleWidthResize = this._handleWidthResize.bind(this);
        this._handleWindowResize = this._handleWindowResize.bind(this);
        this._calculateComponentsHeights =
            this._calculateComponentsHeights.bind(this);
        this.state = {
            headerContainerHeight: 0,
            headerWrapperHeight: 0,
            tailHeight: 0,
            footerHeight: 0,
            minimized: false,
        };
    }
    componentDidMount() {
        this._calculateComponentsHeights();
        const resizeElement = this._getScrollContainer()?.childNodes[0];
        this.contentResizeListener = new ResizeSensor(resizeElement, this._handleWidthResize);
        this._handleWidthResize();
        window.addEventListener('resize', this._handleWindowResize);
        // TODO: Hack to fix cases where initial measurement of headerWrapperHeight is not correct (need to investigate)
        // Happens in PageTestStories -> PageWithScroll -> 5. Scroll - Trigger Mini Header
        // Maybe there is a transition
        const ARBITRARY_SHORT_DURATION_MS = 100;
        this.timerId = setTimeout(this._calculateComponentsHeights, ARBITRARY_SHORT_DURATION_MS);
        // This is done for backward compatibility only,
        // Notifying current users that passed the `scrollableContentRef` prop about the ref current value.
        // New users should be encouraged to use the new event handlers onScrollChanged/onScrollAreaChanged
        // according to their use case.
        this.props.scrollableContentRef &&
            this.props.scrollableContentRef(this.scrollableContainerRef.current);
    }
    componentDidUpdate() {
        this._calculateComponentsHeights();
    }
    componentWillUnmount() {
        window.removeEventListener('resize', this._handleWindowResize);
        this.contentResizeListener?.detach(this._handleWidthResize);
        if (this.timerId) {
            clearTimeout(this.timerId);
        }
    }
    _getNamedChildren() {
        return getChildrenObject(this.props.children);
    }
    _calculateComponentsHeights() {
        const { headerContainerHeight, headerWrapperHeight, tailHeight, pageHeight, footerHeight, minimized, } = this.state;
        const newHeaderWrapperHeight = this.headerWrapperRef && !minimized
            ? this.headerWrapperRef.getBoundingClientRect().height
            : headerWrapperHeight;
        const newHeaderContainerHeight = this.headerWrapperRef && !minimized && this.headerContainerRef
            ? this.headerContainerRef.getBoundingClientRect().height
            : headerContainerHeight;
        const newTailHeight = this.pageHeaderTailRef
            ? this.pageHeaderTailRef.offsetHeight
            : 0;
        const newPageHeight = this.pageRef ? this.pageRef.offsetHeight : 0;
        const newFooterHeight = this.footerWrapperRef
            ? this.footerWrapperRef.offsetHeight
            : 0;
        if (headerContainerHeight !== newHeaderContainerHeight ||
            headerWrapperHeight !== newHeaderWrapperHeight ||
            tailHeight !== newTailHeight ||
            pageHeight !== newPageHeight ||
            footerHeight !== newFooterHeight) {
            this.setState({
                headerContainerHeight: newHeaderContainerHeight,
                headerWrapperHeight: newHeaderWrapperHeight,
                tailHeight: newTailHeight,
                pageHeight: newPageHeight,
                footerHeight: newFooterHeight,
            });
        }
    }
    _getScrollContainer() {
        return this.scrollableContainerRef.current;
    }
    _getMinimizedHeaderWrapperHeight() {
        if (!this._hasHeader()) {
            return 0;
        }
        return this._hasTail()
            ? parseInt(stVars.minimizedHeaderWrapperWithTailHeightPx, 10)
            : parseInt(stVars.minimizedHeaderWrapperHeightPx, 10);
    }
    _getMinimizationDiff() {
        const { headerWrapperHeight } = this.state;
        return headerWrapperHeight
            ? headerWrapperHeight - this._getMinimizedHeaderWrapperHeight()
            : null;
    }
    _handleScroll(e) {
        const containerScrollTop = this._getScrollContainer()?.scrollTop ?? 0;
        const { minimized } = this.state;
        const minimizationDiff = this._getMinimizationDiff();
        const nextDisplayMiniHeader = !!(minimizationDiff && containerScrollTop >= minimizationDiff);
        if (minimized !== nextDisplayMiniHeader) {
            this.setState({
                minimized: nextDisplayMiniHeader,
            });
        }
        const onScrollChanged = this.props.scrollProps?.onScrollChanged;
        if (onScrollChanged) {
            onScrollChanged(e);
        }
    }
    _handleWidthResize() { }
    _handleWindowResize() {
        // TODO: Optimize : https://developer.mozilla.org/en-US/docs/Web/Events/resize
        // Taken from here: https://github.com/kunokdev/react-window-size-listener/blob/d64c077fba4d4e0ce060464078c5fc19620528e6/src/index.js#L66
        const windowHeight = window.innerHeight ||
            document.documentElement.clientHeight ||
            document.body.clientHeight;
        if (this.state.windowHeight !== windowHeight) {
            // We are not using windowHeight directly, since we need to measure the `<Page/>`'s height,
            // But we hold it in the state to avoid rendering when only window.width changes
            this.setState({ windowHeight });
        }
    }
    _safeGetChildren(element) {
        if (!element || !element.props || !element.props.children) {
            return [];
        }
        return element.props.children;
    }
    _getPageDimensionsStyle() {
        const { maxWidth, sidePadding } = this.props;
        // TODO: Simplify - maxWidth is always truthy (from defaultProp)
        if (!maxWidth && !sidePadding && sidePadding !== 0) {
            return undefined;
        }
        const styles = {};
        if (maxWidth) {
            styles.maxWidth = `${maxWidth}px`;
        }
        if (sidePadding || sidePadding === 0) {
            styles.paddingLeft = `${sidePadding}px`;
            styles.paddingRight = `${sidePadding}px`;
        }
        return styles;
    }
    _hasBackgroundImage() {
        return !!this.props.backgroundImageUrl;
    }
    _hasGradientClassName() {
        return !!this.props.gradientClassName && !this.props.backgroundImageUrl;
    }
    _renderContentHorizontalLayout(props) {
        const { PageContent } = this._getNamedChildren();
        const contentFullScreen = PageContent && PageContent.props.fullScreen;
        const { className, horizontalScroll, style, ...rest } = props;
        const pageDimensionsStyle = contentFullScreen
            ? null
            : this._getPageDimensionsStyle();
        return (React.createElement("div", { className: st(classes.contentHorizontalLayout, {
                contentFullWidth: contentFullScreen,
                horizontalScroll,
            }, className), style: { ...pageDimensionsStyle, ...style }, ...rest }, props.children));
    }
    _renderHeader() {
        const { minimized } = this.state;
        const { PageHeader: PageHeaderChild } = this._getNamedChildren();
        const dataHook = 'page-header-wrapper';
        return (PageHeaderChild && (React.createElement("div", { key: dataHook, "data-hook": dataHook, className: st(classes.headerWrapper, {
                minimized,
            }), ref: ref => {
                this.headerWrapperRef = ref;
            } }, React.cloneElement(PageHeaderChild, {
            minimized,
            hasBackgroundImage: this._hasBackgroundImage(),
        }))));
    }
    _renderHeaderContainer() {
        const { minimized } = this.state;
        // placeholder when header is minimized
        const placeholder = (React.createElement("div", { key: "placeholder", style: {
                height: `${minimized ? this._getMinimizationDiff() : 0}px`,
            } }));
        /**
         * HeaderContainer has position sticky. The `top` value is negative, in order to let
         * the container scroll out of view before the minimization occurs.
         */
        const top = minimized ? `-${this._getMinimizationDiff()}px` : 0;
        return (React.createElement("div", { "data-hook": "page-header-container", className: st(classes.pageHeaderContainer, {
                minimized,
                hasTail: this._hasTail(),
            }), style: {
                [vars.minimizationTop]: top,
            }, ref: ref => {
                this.headerContainerRef = ref;
            } }, this._renderContentHorizontalLayout({
            children: [placeholder, this._renderHeader(), this._renderTail()],
        })));
    }
    _renderScrollableContainer() {
        const onScrollAreaChanged = this.props.scrollProps?.onScrollAreaChanged;
        return (React.createElement(ScrollableContainer, { className: st(classes.scrollableContainer, {
                hasTail: this._hasTail(),
            }), dataHook: "page-scrollable-content", "data-class": "page-scrollable-content", ref: this.scrollableContainerRef, onScrollAreaChanged: onScrollAreaChanged, onScrollChanged: this._handleScroll },
            React.createElement("div", { "data-hook": "safari-12-13-sticky-fix" },
                this._renderScrollableBackground(),
                this._renderHeaderContainer(),
                this._renderContentContainer(),
                this._renderFixedFooter())));
    }
    _hasTail() {
        return !!this._getNamedChildren().PageTail;
    }
    _hasHeader() {
        return !!this._getNamedChildren().PageHeader;
    }
    _renderScrollableBackground() {
        const { headerContainerHeight, tailHeight } = this.state;
        const backgroundHeight = `${headerContainerHeight -
            tailHeight +
            (this._hasTail() ? 0 : parseInt(stVars.backgroundCoverContentPx, 10))}px`;
        if (this._hasBackgroundImage()) {
            return (React.createElement("div", { className: classes.imageBackgroundContainer, style: { height: backgroundHeight }, "data-hook": "page-background-image" },
                React.createElement("div", { className: classes.imageBackground, style: { backgroundImage: `url(${this.props.backgroundImageUrl})` } })));
        }
        if (this._hasGradientClassName()) {
            return (React.createElement("div", { "data-hook": "page-gradient-class-name", className: st(classes.gradientBackground, {}, this.props.gradientClassName), style: { height: backgroundHeight } }));
        }
        return null;
    }
    _renderTail() {
        const { PageTail } = this._getNamedChildren();
        const dataHook = 'page-tail';
        return (PageTail && (React.createElement("div", { "data-hook": dataHook, key: dataHook, className: classes.tail, ref: r => {
                this.pageHeaderTailRef = r;
            } }, PageTail)));
    }
    _renderContentContainer() {
        const { footerHeight } = this.state;
        const { children } = this.props;
        const childrenObject = getChildrenObject(children);
        const { PageContent, PageFixedContent } = childrenObject;
        return (React.createElement(PageContext.Provider, { value: {
                stickyStyle: {
                    top: `${this._getMinimizedHeaderWrapperHeight() + this.state.tailHeight}px`,
                },
                scrollableContentRef: this.scrollableContainerRef,
            } },
            React.createElement(PageScrollContext.Provider, { value: this.pageScrollContextValue }, this._renderContentHorizontalLayout({
                className: classes.contentContainer,
                style: {
                    paddingBottom: footerHeight || '48px',
                },
                horizontalScroll: this.props.horizontalScroll,
                children: (React.createElement("div", { className: classes.contentFloating },
                    PageFixedContent && (React.createElement(PageSticky, { "data-hook": "page-fixed-content" }, React.cloneElement(PageFixedContent))),
                    this._safeGetChildren(PageContent))),
            }))));
    }
    render() {
        const { dataHook, className, minWidth, zIndex, height } = this.props;
        return (React.createElement("div", { "data-hook": dataHook, className: st(classes.root, {}, className), style: { zIndex, height } },
            React.createElement("div", { "data-hook": "page", className: classes.page, style: {
                    minWidth: `calc(${minWidth}px + 2 * ${stVars.pageSidePadding})`,
                }, ref: ref => {
                    this.pageRef = ref;
                } }, this._renderScrollableContainer())));
    }
    /**
     * Scrolls the page to a particular set of coordinates
     * @param {ScrollToOptions} scrollTo { left: number, top: number, behavior: 'smooth' | 'auto' }
     */
    scrollTo(scrollTo) {
        const scrollContainer = this._getScrollContainer();
        scrollContainer?.scrollTo(scrollTo);
    }
}
Page.displayName = 'Page';
Page.defaultProps = {
    minWidth: parseInt(stVars.mainContainerMinWidth, 10),
    maxWidth: parseInt(stVars.mainContainerMaxWidth, 10),
    scrollProps: {},
};
Page.Header = PageHeader;
Page.Section = Section;
Page.Content = Content;
Page.FixedContent = FixedContent; // TODO: deprecate, use Page.Sticky instead
Page.Tail = Tail;
Page.FixedFooter = FixedFooter;
Page.Sticky = PageSticky;
Page.Footer = Footer;
function getChildrenObject(children) {
    return React.Children.toArray(children).reduce((acc, child) => {
        if (!React.isValidElement(child)) {
            return acc;
        }
        const displayName = typeof child.type === 'string' || typeof child.type === 'symbol'
            ? undefined
            : child.type.displayName;
        switch (displayName) {
            case 'Page.Header': {
                acc.PageHeader = child;
                break;
            }
            case 'Page.Section': {
                acc.Section = child;
                break;
            }
            case 'Page.Content': {
                acc.PageContent = child;
                break;
            }
            case 'Page.FixedContent': {
                acc.PageFixedContent = child;
                break;
            }
            case 'Page.Tail': {
                acc.PageTail = child;
                break;
            }
            case 'Page.FixedFooter': {
                acc.FixedFooter = child;
                break;
            }
            default: {
                break;
            }
        }
        return acc;
    }, {});
}
export default Page;
//# sourceMappingURL=Page.js.map