import React, { FC, ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Drawer } from "vaul";
import { useDashboardDimensions, useViewPortDimensionsStyle } from "./tree/hooks";
import { PopupWindowHeader } from "./PopupWindowHeader";
import { BottomButtons, ButtonMeta } from "./BottomButtons";
import { PopupWindowContent, PopupWindowContentProps } from "./PopupWindowContent";
import { usePreviousDistinct } from "react-use";
import { useAtomValue, useSetAtom } from "jotai";
import { ActivePopupWindowIdsAtom } from "./tree/atoms";
import {Color, ColorWithCustomTint} from "./tree/Color";

export type NavigationDirection = "forwards" | "backwards";

export interface ScreenProps {
    id: number;
    title: string | (() => ReactNode);
    icon?: () => ReactNode;
    description?: string | ReactNode | (() => ReactNode);
    content: PopupWindowContentProps['content'];
    contentWidth?: () => number;
    showBottomNavigation?: boolean | (() => boolean);
    //When going back to THIS screen
    primaryBottom?: () => ButtonMeta;
    secondaryBottom?: () => ButtonMeta | undefined;
    onBackToThisScreen?: () => void;
    backTo?: () => null | {
        id: number,
        label?: string,
    },
    canNavigateToThisScreen?: (direction: NavigationDirection) => boolean | (() => any),
    disableScreenTransition?: boolean,
}

export type ScreenNavigation = {
    screenId: number,
    setCurrentScreenId: (id: number) => void,
}

export type PopupWindowProps = ScreenNavigation & {
    id: string,
    isOpen: boolean,
    onClose: (context: 'success' | 'fail') => void,
    defaultScreenId: number,
    screens: ScreenProps[]/* | (([currentScreenId, setCurrentScreenId]: [number, (id: number) => void]) => ScreenProps[]),*/
    canGoBack?: (to: number) => boolean,
    showBackNavigation?: boolean,
    zIndex?: number,
    customTop?: (existingTop: number) => number,
    customWidth?: (existingWidth: number) => number,
    headerCenteredContent?: ReactNode,
    color?: Color | ColorWithCustomTint,
}

type PopupCloseContext = 'success' | 'fail'
type ScreenTransitionDirection = -1 | 0 | 1
const DRAWER_OPEN_ANIMATION_MS = 480
export const POPUP_CLOSE_ANIMATION_MS = 520
const DRAWER_CLOSE_ANIMATION_MS = POPUP_CLOSE_ANIMATION_MS
const DRAWER_OPEN_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)'
const DRAWER_CLOSE_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)'
const STACKED_POPUP_DROP_PX = 80
const STACKED_POPUP_SHIFT_ANIMATION_MS = 590
const STACKED_POPUP_SHIFT_EASING = 'cubic-bezier(0.16, 1, 0.3, 1)'

const SCREEN_TRANSITION_PUSH_OFFSET = '120%'
const SCREEN_TRANSITION_PUSH_OFFSET_NEGATIVE = '-120%'
const SCREEN_TRANSITION_EASING = [0.32, 0.72, 0, 1]
const SCREEN_TRANSITION_DURATION = .3
const SCREEN_TRANSITION_ENTER_OPACITY = .8
const SCREEN_TRANSITION_EXIT_OPACITY = .3
const SCREEN_TRANSITION_VARIANTS = {
    initial: (direction: ScreenTransitionDirection) => ({
        zIndex: direction > 0 ? 2 : 1,
        opacity: direction === 0 ? 1 : SCREEN_TRANSITION_ENTER_OPACITY,
        x: direction === 0
            ? 0
            : direction > 0
                ? SCREEN_TRANSITION_PUSH_OFFSET
                : SCREEN_TRANSITION_PUSH_OFFSET_NEGATIVE,
    }),
    animate: (direction: ScreenTransitionDirection) => ({
        zIndex: direction > 0 ? 2 : 1,
        opacity: 1,
        x: 0,
        transition: {
            duration: SCREEN_TRANSITION_DURATION,
            ease: SCREEN_TRANSITION_EASING,
        },
    }),
    exit: (direction: ScreenTransitionDirection) => ({
        zIndex: direction > 0 ? 1 : 2,
        opacity: direction === 0 ? 1 : SCREEN_TRANSITION_EXIT_OPACITY,
        x: direction === 0
            ? 0
            : direction > 0
                ? SCREEN_TRANSITION_PUSH_OFFSET_NEGATIVE
                : SCREEN_TRANSITION_PUSH_OFFSET,
        transition: {
            duration: SCREEN_TRANSITION_DURATION,
            ease: SCREEN_TRANSITION_EASING,
        },
    }),
}

export const PopupWindow: FC<PopupWindowProps> = ({
    id,
    defaultScreenId,
    screens,
    isOpen,
    onClose,
    canGoBack,
    screenId: currentScreenId,
    setCurrentScreenId,
    showBackNavigation,
    zIndex = 1000000,
    customTop,
    customWidth,
    headerCenteredContent,
    color,
}) => {
    const { width, height, y, x } = useDashboardDimensions()
    const activePopupWindowIds = useAtomValue(ActivePopupWindowIdsAtom)
    const setActivePopupWindowIds = useSetAtom(ActivePopupWindowIdsAtom)
    const activePopupsCount = activePopupWindowIds.length
    const { left: wpAdminLeftOffset } = useViewPortDimensionsStyle()
    const [headerHeight, setHeaderHeight] = useState(0)
    const [footerHeight, setFooterHeight] = useState(0)
    const [isDrawerOpen, setIsDrawerOpen] = useState(isOpen)
    const [isClosing, setIsClosing] = useState(false)
    const [areScreenTransitionsEnabled, setAreScreenTransitionsEnabled] = useState(false)
    const [renderedScreenId, setRenderedScreenId] = useState(currentScreenId)
    const [isScreenTransitioning, setIsScreenTransitioning] = useState(false)
    const canAnimateScreenTransitions = areScreenTransitionsEnabled && isDrawerOpen && isOpen && !isClosing
    const closeTimeoutRef = useRef<number | null>(null)
    const enableScreenTransitionsTimeoutRef = useRef<number | null>(null)
    const openedAtPopupCountRef = useRef<number | null>(null)
    //const [currentScreenId, setCurrentScreenId] = useState<number | null>(defaultScreenId || 1)
    /*const currentScreenId = UseCurrentScreenId(id, defaultScreenId)
    const setCurrentScreenId = useSetCurrentScreenId(id)*/

    const screensArray = (screens).filter(Boolean)
    const previousScreenId = usePreviousDistinct<number>(currentScreenId) ?? currentScreenId
    const shouldAnimateBetweenScreens = (fromScreenId: number, toScreenId: number) => {
        if (!canAnimateScreenTransitions || fromScreenId === toScreenId) {
            return false
        }

        const fromScreen = screensArray.find(screen => screen.id === fromScreenId)
        const toScreen = screensArray.find(screen => screen.id === toScreenId)

        return !fromScreen?.disableScreenTransition && !toScreen?.disableScreenTransition
    }

    const resolveNavigableScreenId = (targetID: number, fromScreenId?: number) => {
        let idToNavigateTo: number = targetID
        let checkedScreens = 0

        while (checkedScreens < screensArray.length) {
            const direction: NavigationDirection = idToNavigateTo < (fromScreenId ?? currentScreenId) ? "backwards" : "forwards"
            const targetScreen = screensArray.find(screen => screen.id === idToNavigateTo)

            if (!targetScreen?.canNavigateToThisScreen) {
                break
            }

            const canNavigateToThisScreenResult = targetScreen.canNavigateToThisScreen(direction)

            if (canNavigateToThisScreenResult === false) {
                idToNavigateTo = direction === "forwards" ? idToNavigateTo + 1 : idToNavigateTo - 1
                checkedScreens += 1
                continue
            }

            if (typeof canNavigateToThisScreenResult === 'function') {
                return fromScreenId ?? targetID
            }

            break
        }

        return idToNavigateTo
    }
    const resolvedCurrentScreenId = resolveNavigableScreenId(currentScreenId, previousScreenId)

    useLayoutEffect(() => {
        navigateToScreen(currentScreenId, previousScreenId)
    }, [currentScreenId, previousScreenId])

    const shouldBeActiveInStack = isOpen && !isClosing

    useEffect(() => {
        setActivePopupWindowIds((previousIds) => {
            const hasCurrentPopup = previousIds.includes(id)

            if (shouldBeActiveInStack && !hasCurrentPopup) {
                return [...previousIds, id]
            }

            if (!shouldBeActiveInStack && hasCurrentPopup) {
                return previousIds.filter((popupId) => popupId !== id)
            }

            return previousIds
        })

        return () => {
            setActivePopupWindowIds((previousIds) => {
                if (!previousIds.includes(id)) {
                    return previousIds
                }

                return previousIds.filter((popupId) => popupId !== id)
            })
        }
    }, [id, setActivePopupWindowIds, shouldBeActiveInStack])

    useEffect(() => {
        if (isOpen) {
            if (closeTimeoutRef.current !== null) {
                window.clearTimeout(closeTimeoutRef.current)
                closeTimeoutRef.current = null
            }

            if (openedAtPopupCountRef.current === null) {
                openedAtPopupCountRef.current = activePopupsCount + 1
            }

            setIsClosing(false)
            setIsDrawerOpen(true)
            return
        }

        setIsClosing(false)
        openedAtPopupCountRef.current = null
        setIsDrawerOpen(false)
    }, [isOpen])

    useEffect(() => {
        if (enableScreenTransitionsTimeoutRef.current !== null) {
            window.clearTimeout(enableScreenTransitionsTimeoutRef.current)
            enableScreenTransitionsTimeoutRef.current = null
        }

        if (!isDrawerOpen) {
            setAreScreenTransitionsEnabled(false)
            return
        }

        enableScreenTransitionsTimeoutRef.current = window.setTimeout(() => {
            setAreScreenTransitionsEnabled(true)
            enableScreenTransitionsTimeoutRef.current = null
        }, DRAWER_OPEN_ANIMATION_MS)

        return () => {
            if (enableScreenTransitionsTimeoutRef.current !== null) {
                window.clearTimeout(enableScreenTransitionsTimeoutRef.current)
                enableScreenTransitionsTimeoutRef.current = null
            }
        }
    }, [isDrawerOpen])

    const closeWithAnimation = (context: PopupCloseContext) => {
        if (!isDrawerOpen || closeTimeoutRef.current !== null) return

        setIsClosing(true)
        setIsDrawerOpen(false)
        closeTimeoutRef.current = window.setTimeout(() => {
            closeTimeoutRef.current = null
            onClose(context)
        }, DRAWER_CLOSE_ANIMATION_MS)
    }

    useEffect(() => {
        return () => {
            if (closeTimeoutRef.current !== null) {
                window.clearTimeout(closeTimeoutRef.current)
            }
            if (enableScreenTransitionsTimeoutRef.current !== null) {
                window.clearTimeout(enableScreenTransitionsTimeoutRef.current)
            }
        }
    }, [])

    const navigateToScreen = (targetID: number, fromScreenId?: number) => {
        let idToNavigateTo: number = targetID
        let checkedScreens = 0

        while (checkedScreens < screensArray.length) {
            const direction: NavigationDirection = idToNavigateTo < (fromScreenId ?? currentScreenId) ? "backwards" : "forwards"
            const targetScreen = screensArray.find(screen => screen.id === idToNavigateTo)

            if (!targetScreen?.canNavigateToThisScreen) {
                break
            }

            const canNavigateToThisScreenResult = targetScreen.canNavigateToThisScreen(direction)
            if (canNavigateToThisScreenResult === false) {
                idToNavigateTo = direction === "forwards" ? idToNavigateTo + 1 : idToNavigateTo - 1
                checkedScreens += 1
                continue
            }

            if (typeof canNavigateToThisScreenResult === 'function') {
                canNavigateToThisScreenResult()
                return
            }

            break
        }

        if (currentScreenId !== idToNavigateTo) {
            setIsScreenTransitioning(shouldAnimateBetweenScreens(currentScreenId, idToNavigateTo))
            setCurrentScreenId(idToNavigateTo)
        }
    }

    useLayoutEffect(() => {
        // here we need to call the onbacktothissrceen if it exists on the screen
        let wereBack = resolvedCurrentScreenId <= previousScreenId;

        if (wereBack) {
            const targetScreen = screensArray.find(screen => screen.id === resolvedCurrentScreenId) || {}
            const { onBackToThisScreen } = targetScreen as ScreenProps
            //const {onBackToThisScreen} = screens[toScreenId - 2] || {}

            if (onBackToThisScreen) {
                onBackToThisScreen()
            }
        }

    }, [previousScreenId, resolvedCurrentScreenId]);

    const currentScreen = screensArray.find(screen => screen.id === resolvedCurrentScreenId)

    useEffect(() => {
        if (currentScreen && renderedScreenId !== resolvedCurrentScreenId) {
            setRenderedScreenId(resolvedCurrentScreenId)
        }
    }, [currentScreen, resolvedCurrentScreenId, renderedScreenId])

    useEffect(() => {
        if (!canAnimateScreenTransitions) {
            setIsScreenTransitioning(false)
        }
    }, [canAnimateScreenTransitions])

    useLayoutEffect(() => {
        if (!shouldAnimateBetweenScreens(previousScreenId, currentScreenId)) {
            setIsScreenTransitioning(false)
            return
        }

        setIsScreenTransitioning(true)
    }, [currentScreenId, previousScreenId, shouldAnimateBetweenScreens])

    const renderedScreen = currentScreen || screensArray.find(screen => screen.id === renderedScreenId)

    if (!renderedScreen) {
        return null;
    }

    const {
        title,
        description,
        icon,
        content,
        showBottomNavigation = true,
        contentWidth,
        backTo,
        primaryBottom,
        secondaryBottom,
    } = renderedScreen

    const { label: customBackLabel, id: idToBackTo } = backTo?.() || {}

    const canShowBottomNavigation = !!(typeof showBottomNavigation === 'function' ? showBottomNavigation() : showBottomNavigation);
    const reservedFooterHeight = canShowBottomNavigation ? footerHeight : 0

    useEffect(() => {
        if (canShowBottomNavigation || footerHeight === 0) {
            return
        }

        setFooterHeight(0)
    }, [canShowBottomNavigation, footerHeight])

    let top = 16 * 3
    let defaultWidth = 132 * 4
    const stackedDrop = openedAtPopupCountRef.current === null
        ? 0
        : Math.max(0, activePopupsCount - openedAtPopupCountRef.current) * STACKED_POPUP_DROP_PX
    const renderedTop = (customTop?.(top) ?? top) + stackedDrop
    const renderedWidth = customWidth?.(defaultWidth) ?? defaultWidth;
    const renderedLeft = wpAdminLeftOffset + ((window.innerWidth - wpAdminLeftOffset - renderedWidth) / 2)
    const drawerAnimationMs = isDrawerOpen ? DRAWER_OPEN_ANIMATION_MS : DRAWER_CLOSE_ANIMATION_MS
    const drawerAnimationEasing = isDrawerOpen ? DRAWER_OPEN_EASING : DRAWER_CLOSE_EASING
    const drawerBackdropFilter = isDrawerOpen ? 'blur(16px)' : 'blur(12px)'
    const drawerBoxShadow = isDrawerOpen ? '0 14px 220px rgba(0, 0, 0, 0.08)' : '0 10px 140px rgba(0, 0, 0, 0.07)'

    const animationStyle = {
        animationDuration: `${drawerAnimationMs}ms`,
        transitionDuration: `${drawerAnimationMs}ms, ${drawerAnimationMs}ms, ${STACKED_POPUP_SHIFT_ANIMATION_MS}ms`,
        animationTimingFunction: drawerAnimationEasing,
        transitionTimingFunction: `${drawerAnimationEasing}, ${drawerAnimationEasing}, ${STACKED_POPUP_SHIFT_EASING}`,
        transitionProperty: 'transform, opacity, top',
        willChange: 'transform, opacity',
    }
    const screenTransitionDirection: ScreenTransitionDirection = renderedScreen.id === previousScreenId
            ? 0
            : renderedScreen.id > previousScreenId
                ? 1
                : -1
    const shouldAnimateRenderedScreenTransition = shouldAnimateBetweenScreens(previousScreenId, renderedScreen.id)

    return (
        <Drawer.Root
            open={isDrawerOpen}
            dismissible={false}
            direction="bottom"
            fixed
        >
            <Drawer.Portal>
                <Drawer.Overlay
                    className="fixed inset-0"
                    style={{
                        ...animationStyle,
                        left: wpAdminLeftOffset,
                        zIndex: zIndex - 1,
                        willChange: 'opacity',
                    }}
                    onClick={() => closeWithAnimation('fail')}
                />
                <Drawer.Content
                    className="fixed flex justify-center rounded-[38px] border-px border-gray-150 outline-none"
                    style={{
                        ...animationStyle,
                        top: renderedTop,
                        left: renderedLeft,
                        width: renderedWidth,
                        height: height - (renderedTop / 2),
                        zIndex,
                        background: 'linear-gradient(90deg, rgba(0,0,0,.96) 0%, rgba(255,255,255,.96) 0%, rgba(255,255,255,.90) 89%, rgba(255,255,255,.88) 100%)',
                        backdropFilter: drawerBackdropFilter,
                        boxShadow: drawerBoxShadow,
                        transform: 'translateZ(0)',
                        overflow: isScreenTransitioning ? 'hidden' : undefined,
                    }}
                >
                    <div className="relative w-[calc(100%-80px)] h-full mx-4">
                        <AnimatePresence initial={false} mode="sync" custom={screenTransitionDirection}
                                         onExitComplete={() => setIsScreenTransitioning(false)}>
                            <motion.div
                                key={`${id}-screen-${renderedScreen.id}`}
                                className="absolute inset-0 h-full flex flex-col items-center"
                                custom={screenTransitionDirection}
                                variants={SCREEN_TRANSITION_VARIANTS}
                                initial={shouldAnimateRenderedScreenTransition ? "initial" : false}
                                animate="animate"
                                exit={shouldAnimateRenderedScreenTransition ? "exit" : undefined}
                            >
                                <PopupWindowHeader
                                    popupId={id}
                                    currentScreenId={renderedScreen.id}
                                    setCurrentScreenId={navigateToScreen}
                                    color={color}
                                    title={title}
                                    description={description}
                                    icon={icon?.()}
                                    screensLength={screensArray.length}
                                    onClose={() => closeWithAnimation('fail')}
                                    canGoBack={canGoBack}
                                    backLabel={customBackLabel}
                                    onBack={(fromScreenId, toScreenId) => {
                                        /*
                                                            const targetScreen = screensArray.find(screen => screen.id === toScreenId) || {}
                                                            const {onBackToThisScreen} = targetScreen as ScreenProps
                                                            //const {onBackToThisScreen} = screens[toScreenId - 2] || {}

                                                            if (onBackToThisScreen) {
                                                                onBackToThisScreen()
                                                            }
                                        */
                                    }}
                                    backId={idToBackTo}
                                    onHeight={(height) => {
                                        if (headerHeight === height) return

                                        setHeaderHeight(height)
                                    }}
                                    showNavigationButtons={showBackNavigation}
                                    centeredContent={headerCenteredContent}
                                />
                                <PopupWindowContent height={height - (headerHeight + reservedFooterHeight + 60)}
                                    content={content}
                                    width={contentWidth && contentWidth()}
                                    screenId={renderedScreen.id}
                                    setCurrentScreenId={navigateToScreen}
                                    onClose={closeWithAnimation}
                                />
                                {canShowBottomNavigation &&
                                    <BottomButtons currentScreenId={renderedScreen.id}
                                        setCurrentScreenId={navigateToScreen}
                                        buttons={!primaryBottom ? undefined : [primaryBottom?.(), secondaryBottom?.()].filter(Boolean) as ButtonMeta[]}
                                        popupId={id}
                                        screensLength={screensArray.length} onHeight={(height) => {
                                            if (footerHeight === height) return

                                            setFooterHeight(height)
                                        }} contentWidth={contentWidth && contentWidth()} />}
                            </motion.div>
                        </AnimatePresence>
                    </div>
                </Drawer.Content>
            </Drawer.Portal>
        </Drawer.Root>
    )
}
