import {PropsWithChildren, useRef, useLayoutEffect, KeyboardEvent, CSSProperties} from 'react';
import cls from 'classnames';
import {clamp, renderForeground} from '@core0/utils';
import {Transition, TransitionType} from '@core0/transition';
import {Backdrop} from '@core0/backdrop';
import cn from './popover.module.styl';

export type HorizontalAlign = 'popover-left-to-anchor-left'
    | 'popover-left-to-anchor-right'
    | 'popover-center-to-anchor-center'
    | 'popover-right-to-anchor-left'
    | 'popover-right-to-anchor-right';

export type VerticalAlign = 'popover-top-to-anchor-top'
    | 'popover-top-to-anchor-bottom'
    | 'popover-center-to-anchor-center'
    | 'popover-bottom-to-anchor-top'
    | 'popover-bottom-to-anchor-bottom';

export interface PopoverProps<Output = unknown>
    extends Pick<
        CSSProperties,
        | 'paddingTop'
        | 'paddingRight'
        | 'paddingBottom'
        | 'paddingLeft'
        | 'width'
        | 'minWidth'
        | 'maxWidth'
        | 'height'
        | 'minHeight'
        | 'maxHeight'
    > {
    onClose?(output?: Output): void;
    anchorRect: DOMRect;
    nestedLayerIdx?: number;
    nestedGroupId?: string;
    noBackdrop?: boolean;
    transitionType?: TransitionType;
    verticalAlign?: VerticalAlign;
    horizontalAlign?: HorizontalAlign;
    backdropBackground?: string;
    backdropOpacity?: string;
    canBackdropClosePopover?: boolean;
}

export function Popover<Output = unknown>(props: PropsWithChildren<PopoverProps<Output>>): JSX.Element {
    const {
        onClose = () => {},
        anchorRect,
        nestedGroupId,
        nestedLayerIdx,
        transitionType = 'fade-in',
        verticalAlign = 'popover-center-to-anchor-center',
        horizontalAlign = 'popover-center-to-anchor-center',
        noBackdrop = false,
        canBackdropClosePopover = true,
        backdropBackground,
        backdropOpacity,
        paddingTop,
        paddingRight,
        paddingBottom,
        paddingLeft,
        width,
        minWidth,
        maxWidth,
        height,
        minHeight,
        maxHeight,
        children
    } = props;

    const hostElmRef = useRef<HTMLDivElement>(null);
    const backdropElmRef = useRef<HTMLDivElement>(null);
    const contentContainerElmRef = useRef<HTMLDivElement>(null);

    const transformOrigin =
        verticalAlign === 'popover-center-to-anchor-center'
            ? 'center'
            : verticalAlign === 'popover-top-to-anchor-bottom'
            ? 'top'
            : verticalAlign === 'popover-top-to-anchor-top'
            ? 'top'
            : verticalAlign === 'popover-bottom-to-anchor-bottom'
            ? 'bottom'
            : verticalAlign === 'popover-bottom-to-anchor-top'
            ? 'bottom'
            : undefined;

    /*
     ****************************************
     * Effect Hooks
     ****************************************
     */
    useLayoutEffect(() => {
        if (!anchorRect) return;

        const contentContainerElm = contentContainerElmRef.current;
        if (!contentContainerElm) return;

        const {top: anchorTop, left: anchorLeft, width: anchorWidth, height: anchorHeight} = anchorRect;
        const {offsetWidth: contentContainerElmWidth, offsetHeight: contentContainerElmHeight} = contentContainerElm;

        requestAnimationFrame(() => {
            const viewportWidth = window.innerWidth ?? document.documentElement.offsetWidth;
            const viewportHeight = window.innerHeight ?? document.documentElement.offsetHeight;

            const top =
                verticalAlign === 'popover-center-to-anchor-center'
                    ? anchorTop + (anchorHeight - contentContainerElmHeight) / 2
                    : verticalAlign === 'popover-bottom-to-anchor-top'
                    ? anchorTop - contentContainerElmHeight
                    : verticalAlign === 'popover-bottom-to-anchor-bottom'
                    ? anchorTop + anchorHeight - contentContainerElmHeight
                    : verticalAlign === 'popover-top-to-anchor-bottom'
                    ? anchorTop + anchorHeight
                    : verticalAlign === 'popover-top-to-anchor-top'
                    ? anchorTop
                    : 0;

            const left =
                horizontalAlign === 'popover-center-to-anchor-center'
                    ? anchorLeft + (anchorWidth - contentContainerElmWidth) / 2
                    : horizontalAlign === 'popover-left-to-anchor-left'
                    ? anchorLeft
                    : horizontalAlign === 'popover-left-to-anchor-right'
                    ? anchorLeft + anchorWidth
                    : horizontalAlign === 'popover-right-to-anchor-left'
                    ? anchorLeft - contentContainerElmWidth
                    : horizontalAlign === 'popover-right-to-anchor-right'
                    ? anchorLeft + anchorWidth - contentContainerElmWidth
                    : 0;

            const margin = 10;
            const clampedTop = clamp(margin, top, viewportHeight - contentContainerElmHeight - margin);
            const clampedLeft = clamp(margin, left, viewportWidth - contentContainerElmWidth - margin);
            contentContainerElm.style.top = `${clampedTop}px`;
            contentContainerElm.style.left = `${clampedLeft}px`;
            contentContainerElm.style.height =
                contentContainerElmHeight > viewportHeight - margin * 2 ? `${viewportHeight - margin * 2}px` : 'auto';

            contentContainerElm.style.width =
                contentContainerElmWidth > viewportWidth - margin * 2 ? `${viewportWidth - margin * 2}px` : 'auto';
        });
    }, []);

    useLayoutEffect(() => {
        if (noBackdrop) return;

        hostElmRef?.current?.focus();
    }, []);

    useLayoutEffect(() => {
        if (noBackdrop) return;

        const requestId = requestAnimationFrame(() => {
            if (backdropElmRef?.current?.style?.opacity && backdropOpacity) {
                backdropElmRef.current.style.opacity = backdropOpacity;
            }
        });
        return () => cancelAnimationFrame(requestId);
    }, []);

    /*
     ****************************************
     * Event Handlers
     ****************************************
     */
    function backdropClickHandler() {
        if (canBackdropClosePopover) onClose();
    }

    function hostElmKeyDownHandler(evt: KeyboardEvent) {
        if (evt.key.toLowerCase() === 'escape') onClose();
    }

    return renderForeground(
        <div
            ref={hostElmRef}
            tabIndex={0}
            role="popover"
            className={cls(cn.popover, {[cn.noBackdrop]: noBackdrop})}
            onKeyDown={hostElmKeyDownHandler}
        >
            {!noBackdrop && (
                <Backdrop
                    ref={backdropElmRef}
                    background={backdropBackground}
                    className={cls([cn.backdrop], 'cover')}
                    onClick={backdropClickHandler}
                />
            )}
            <Transition type={transitionType}>
                <div
                    ref={contentContainerElmRef}
                    className={cn.content}
                    style={{
                        transformOrigin,
                        paddingTop,
                        paddingRight,
                        paddingBottom,
                        paddingLeft,
                        width,
                        minWidth,
                        maxWidth,
                        height,
                        minHeight,
                        maxHeight
                    }}
                >
                    {children}
                </div>
            </Transition>
        </div>
    );
}
