import React, {FC, useEffect, useLayoutEffect, useMemo, useRef, useState} from "react";
import {findDataForNode, TestableCompositeData, useNodesStore} from './store';
import {useShallow} from 'zustand/react/shallow';
import {Context, ContextProps} from "./Context";
import {testableTypeLabel} from "./testables";
import classNames from "classnames";
import {useLazyTestables} from "./hooks";
import {ColorOptionWithCustomTint} from "./Color";
import {atom, useAtomValue, useSetAtom} from "jotai";
import {HoveredTestableCompositeIDAtom, OverlayOpenCompositeAtom, ProUpgradePopupWindowIsOpen} from "./atoms";
import {TestableCompositeRemoveButton} from "./TestableCompositeRemoveButton";
import {PlaceholderContext} from "./PlaceholderContext";
import {AnimatePresence, motion} from 'motion/react';
import {TestableGroupSeparator} from "./TestableGroupSeparator";
import {useSuggestedScopeForTestableId} from "./getSuggestedScopeForTestable";
import {__, hasProInstalled} from "../globals";
import {FloatingMarker} from "./FloatingMarker";
import {polygonContains} from "d3-polygon";
import {EditHintTextStrip} from "./EditHintTextStrip";
import {RightContextButtons} from "./RightContextButtons";
import {getColorForComponentType} from "./Colors";

export const InsideOverlayContext = React.createContext(false);

export type TestableCompositeProps =
    Pick<TestableCompositeData, 'id'>
    & Partial<Pick<ContextProps, 'buttonsDisplay' | 'buttonsAreEnabled' | 'showRightContextButtonsLabel'>>
    & Partial<ColorOptionWithCustomTint>
    & Partial<Pick<ContextProps, 'supportsMultipleComponents' | 'unsupportedComponents'>>
    & {
    isRoot?: boolean,
    hardHighlight?: boolean,
    removable?: boolean,
    confirmRemoval?: boolean,
    floatButtons?: boolean,
    forceExpanded?: boolean,
}

interface NodeData {
    data: TestableCompositeData;
    children: TestableCompositeData['id'][];
}
const BORDER_PROXIMITY_PADDING = 40;

export const TestableComposite: FC<TestableCompositeProps> = React.memo(({
                                                                  id,
                                                                  color,
                                                                  buttonsDisplay,
                                                                  buttonsAreEnabled,
                                                                  showRightContextButtonsLabel,
                                                                  hardHighlight,
                                                                  isRoot = false,
                                                                  removable = true,
                                                                  confirmRemoval,
                                                                  floatButtons,
                                                                  forceExpanded,
                                                                 supportsMultipleComponents,
                                                                 unsupportedComponents
                                                              }) => {
    const {data, children}: NodeData = useNodesStore(useShallow(findDataForNode(id)))
    // this is the id of the testable composite that might be used inside callbacks where they define the id of the current iterable item
    const originalID = id
    const setHoveredTestableCompositeID = useSetAtom(HoveredTestableCompositeIDAtom)
    const testableCompositeRef = useRef<HTMLDivElement>(null)
    const testableCompositeBorderRef = useRef<HTMLDivElement>(null)
    const [canShowPlaceholder, setCanShowPlaceholder] = useState(false)
    const [isWithinBorderProximity, setIsWithinBorderProximity] = useState(false)
    const testables = useLazyTestables()
    const testablesRef = useRef(testables)
    testablesRef.current = testables
    const myRootId = testables.getRootNode(id)?.id ?? id
    const isGroupHoveredAtom = useMemo(() => atom(get => {
        const hoveredId = get(HoveredTestableCompositeIDAtom)
        if (!hoveredId) return false
        const hoveredRootId = testablesRef.current.getRootNode(hoveredId)?.id ?? hoveredId
        return hoveredRootId === myRootId
    }), [myRootId])
    const isGroupHovered = useAtomValue(isGroupHoveredAtom)
    const isExactHoveredAtom = useMemo(() => atom(get => get(HoveredTestableCompositeIDAtom) === id), [id])
    const isExactHovered = useAtomValue(isExactHoveredAtom)
    const isInsideOverlay = React.useContext(InsideOverlayContext)
    const isOverlayOpenAtom = useMemo(() => atom(get => {
        const overlay = get(OverlayOpenCompositeAtom)
        return !!overlay && overlay.rootId === myRootId
    }), [myRootId])
    const isOverlayOpen = useAtomValue(isOverlayOpenAtom)
    const setOverlayState = useSetAtom(OverlayOpenCompositeAtom)
    const [frozenDims, setFrozenDims] = useState<{width: number, height: number} | null>(null)
    useLayoutEffect(() => {
        if (isOverlayOpen && !forceExpanded && !isInsideOverlay && testableCompositeRef.current) {
            const rect = testableCompositeRef.current.getBoundingClientRect()
            setFrozenDims({width: rect.width, height: rect.height})
        } else if (!isOverlayOpen) {
            setFrozenDims(null)
        }
    }, [isOverlayOpen, forceExpanded, isInsideOverlay])
    const setIsProUpgradePopupOpen = useSetAtom(ProUpgradePopupWindowIsOpen)
    // hard highlighted means a darker, full color
    // soft highlighted means a lighter, color used for general hovering
    const [isHardHighlighted, setIsHardHighlighted] = useState(false)
    const contextsParentRef = useRef<HTMLDivElement>(null);
    const suggestedScope = useSuggestedScopeForTestableId(originalID)
    const onRightButtonsClick = !hasProInstalled ? (_type: string) => setIsProUpgradePopupOpen({
        isOpen: true,
        title: __('Create complex product combinations with Pro'),
    }) : undefined
    useEffect(() => {
        if (children.length <= 1) {
            setIsWithinBorderProximity(false)
            return;
        }

        const onMouseMove = (event: MouseEvent) => {
            const element = testableCompositeRef.current;

            if (!element) {
                setIsWithinBorderProximity(false)
                return;
            }

            const rect = element.getBoundingClientRect()
            const boxApproximation: [number, number][] = [
                [rect.left - BORDER_PROXIMITY_PADDING, rect.top - BORDER_PROXIMITY_PADDING],
                [rect.right + BORDER_PROXIMITY_PADDING, rect.top - BORDER_PROXIMITY_PADDING],
                [rect.right + BORDER_PROXIMITY_PADDING, rect.bottom + BORDER_PROXIMITY_PADDING],
                [rect.left - BORDER_PROXIMITY_PADDING, rect.bottom + BORDER_PROXIMITY_PADDING],
            ];
            const isInsideApproximation = polygonContains(boxApproximation, [event.clientX, event.clientY])

            setIsWithinBorderProximity(currentState => currentState === isInsideApproximation ? currentState : isInsideApproximation)
        };

        const onWindowMouseLeave = () => {
            setIsWithinBorderProximity(false)
        };

        window.addEventListener('mousemove', onMouseMove, {passive: true});
        window.addEventListener('mouseleave', onWindowMouseLeave);

        return () => {
            window.removeEventListener('mousemove', onMouseMove);
            window.removeEventListener('mouseleave', onWindowMouseLeave);
        }
    }, [children.length]);
    useEffect(() => {
        if (forceExpanded) return;

        const element = testableCompositeRef.current;

        if (!element) {
            return
        }

        const onNativeClick = (event: MouseEvent) => {
            if (event.button !== 0) {
                return;
            }

            const target = event.target as Element | null

            if (target?.closest('button, a, input, textarea, select, [role="button"]')) {
                return;
            }

            //event.stopPropagation();

            if (children.length > 1 && isRoot && !isInsideOverlay) {
                const rect = element.getBoundingClientRect();
                setOverlayState({id, rootId: myRootId, rect});
            } else {
                setHoveredTestableCompositeID(id)
            }
        };

        element.addEventListener('click', onNativeClick);

        return () => {
            element.removeEventListener('click', onNativeClick);
        }
    }, [id, children.length, isRoot, myRootId, isInsideOverlay, forceExpanded, setHoveredTestableCompositeID, setOverlayState]);
    useEffect(() => {
        if (!isExactHovered || forceExpanded) {
            return
        }

        const onDocumentClick = (event: MouseEvent) => {
            const element = testableCompositeRef.current;
            const target = event.target as Node | null;

            if (!element || !target) {
                setHoveredTestableCompositeID(null)
                return;
            }

            if (!element.contains(target)) {
                setHoveredTestableCompositeID(null)
            }
        };

        document.addEventListener('click', onDocumentClick);

        return () => {
            document.removeEventListener('click', onDocumentClick);
        }
    }, [isExactHovered, forceExpanded, setHoveredTestableCompositeID]);

    if (!data) {
        return
    }

    //else re-draw each child as a testable composite
    const isDefaultGroup = children.length === 1;
    const normalizedType = `${data.type ?? ''}`.toLowerCase()
    const isAndType = normalizedType === 'and'
    const isAnyOrType = normalizedType.includes('or')
    const isRootSingleContextGroup = isRoot && isDefaultGroup
    let isHovered = (forceExpanded || isInsideOverlay) ? true : (!isInsideOverlay && isOverlayOpen ? false : false /*isGroupHovered*/);
    const resolvedColor = color || getColorForComponentType(
        data.mode === 'test' ? 'condition' : data.mode,
        {conditionPalette: data.conditionColorPalette}
    )
    // Compute effective value: respect explicit false from external callers,
    // otherwise derive from own hover state (each child reads its own atom).
    const effectiveButtonsAreEnabled = buttonsAreEnabled ?? (isRootSingleContextGroup || isHovered)
    const childButtonsAreEnabled =
        buttonsAreEnabled === false
            ? false
            : ((buttonsAreEnabled === true || isRootSingleContextGroup) ? true : undefined)

    //if context, draw a context
    if (normalizedType === 'context') {
        return <Context id={data.id}
                        data={data}
                        color={resolvedColor}
                        removable={removable}
                        confirmRemoval={confirmRemoval}
                        buttonsDisplay={buttonsDisplay}
                        highlight={hardHighlight}
                        buttonsAreEnabled={effectiveButtonsAreEnabled}
                        onRightButtonsClick={onRightButtonsClick}
                        floatButtons={floatButtons}
                        showRightContextButtonsLabel={showRightContextButtonsLabel ?? isRootSingleContextGroup}
                        supportsMultipleComponents={supportsMultipleComponents}
                        unsupportedComponents={unsupportedComponents}
        />;
    }

    const shouldBeHardHighlighted = hardHighlight || isHardHighlighted;
    const shouldShowStyledContainer = !isDefaultGroup;
    const shouldShowGroupRightContextButtons = effectiveButtonsAreEnabled && !isDefaultGroup && !frozenDims && (isInsideOverlay || isRoot || isExactHovered)
    const styledContainerStyles = shouldShowStyledContainer ? {
        paddingTop: 16,
        paddingBottom: 16,
        paddingLeft: 14,
        paddingRight: 14,
        borderRadius: 24,
    } : undefined
    // Snappier spring for placeholder reveal/dismiss
    const revealSpring = {type: 'spring', stiffness: 420, damping: 28, mass: 0.6}
    const separator = <TestableGroupSeparator testableCompositeID={id} type={data.type}
                                              style={isAndType ? 'icon' : 'text'}>
        {testableTypeLabel(data.type)}
    </TestableGroupSeparator>;

    return <motion.div ref={testableCompositeRef}
                       initial={false}
                       animate={styledContainerStyles && !frozenDims ? styledContainerStyles : undefined}
                       transition={frozenDims ? {duration: 0} : {type: 'spring', stiffness: 350, damping: 30, mass: 0.8}}
                       whileTap={shouldShowStyledContainer && !isInsideOverlay  && !frozenDims ? {
                           scale: 0.965,
                           transition: {type: 'spring', stiffness: 500, damping: 25, mass: 0.4},
                       } : undefined}
                       className={classNames('testable-composite relative group', {
                            'bg-gray-400 bg-opacity-[.05] hover:bg-opacity-[.15] ring-1 ring-gray-150 ring-offset-1 hover:ring-offset-[3px] hover:cursor-pointer': shouldShowStyledContainer && !isInsideOverlay,
                            'bg-gray-600 bg-opacity-[.20]': shouldShowStyledContainer && isInsideOverlay,
                        })}
                       key={id}
                       style={{
                           ...(shouldShowStyledContainer ? {
                                backdropFilter: isInsideOverlay ? 'blur(10.45px)' : 'blur(2px)',
                                ...styledContainerStyles,
                            } : {}),
                           ...(frozenDims ? {width: frozenDims.width, height: frozenDims.height, overflow: 'hidden', pointerEvents: 'none' as const} : {}),
                       }}
    >
        {isRoot && shouldShowStyledContainer && !frozenDims && !isInsideOverlay && <EditHintTextStrip
            label={!isHovered? __('click to edit') : __('click outside to close')}
            className="absolute overflow-visible pointer-events-none top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[calc(100%+36px)] h-[calc(100%+36px)] origin-center scale-[.94] opacity-0 group-hover:opacity-100 group-hover:scale-100 transition-all duration-200 ease-out"
        />}
        {removable && !isDefaultGroup && (
            <TestableCompositeRemoveButton
                entityID={id}
                show={isHovered}
                onRequestToHighlight={() => setIsHardHighlighted(true)}
                onRequestToUnHighlight={() => setIsHardHighlighted(false)}
            />
        )}
        <AnimatePresence initial={false}>
            {shouldShowGroupRightContextButtons && <motion.div
                key="group-right-context-buttons"
                className="absolute left-full top-1/2 z-[2] -translate-y-1/2"
                initial={{opacity: 0, x: -8}}
                animate={{opacity: 1, x: 0}}
                exit={{opacity: 0, x: -8}}
                transition={{duration: 0.25, ease: 'easeOut'}}
            >
                <RightContextButtons
                    color={resolvedColor}
                    contextID={id}
                    displayType={buttonsDisplay ?? 'always'}
                    isOpened={true}
                    parentData={data}
                    onClick={onRightButtonsClick}
                    showLabel={showRightContextButtonsLabel ?? false}
                />
            </motion.div>}
        </AnimatePresence>
        <div className="relative flex items-center">
            {isRoot && !isInsideOverlay && suggestedScope === 'bogo' && (<FloatingMarker prefix={__('BOGO')} label={__('Buy')} />)}
            <div ref={contextsParentRef}
                         className={classNames('testable-composite-group relative flex gap-2', {
                              'flex-row items-center': isAnyOrType,
                              'flex-col items-center': isAndType,
                          })}>
                {children.map((childId, index) => {
                    const isLast = index + 1 === children.length;

                    return <div key={childId}
                                        className={classNames('relative flex gap-2', {
                                             'flex-row items-center': isAnyOrType,
                                             'flex-col items-center': isAndType,
                                        })}>
                        <TestableComposite
                            id={childId}
                            color={resolvedColor}
                            hardHighlight={shouldBeHardHighlighted}
                            buttonsDisplay={'always'}
                            buttonsAreEnabled={childButtonsAreEnabled}
                            showRightContextButtonsLabel={isRootSingleContextGroup}
                            floatButtons={(isAndType && !isDefaultGroup)  || isRootSingleContextGroup}
                        />
                        {!isLast && separator}
                        {!isDefaultGroup && isLast && (
                            <AnimatePresence initial={false}>
                                {isHovered && <motion.div
                                    key="placeholder-group"
                                    className={classNames('flex gap-2 items-center', {
                                        'flex-col': isAndType,
                                        'flex-row': isAnyOrType,
                                    })}
                                    style={{
                                        overflow: 'hidden',
                                        transformOrigin: isAnyOrType ? 'left center' : 'center top',
                                    }}
                                    initial={isAnyOrType
                                        ? {width: 0, opacity: 0}
                                        : {height: 0, opacity: 0}}
                                    animate={isAnyOrType
                                        ? {width: 'auto', opacity: 1}
                                        : {height: 'auto', opacity: 1}}
                                    exit={isAnyOrType
                                        ? {width: 0, opacity: 0}
                                        : {height: 0, opacity: 0}}
                                    transition={{
                                        ...revealSpring,
                                        opacity: {duration: 0.2, ease: [0.22, 1, 0.36, 1]},
                                    }}
                                >
                                    {separator}
                                    <div className="relative z-[1000000000] cp-placeholder">
                                        <PlaceholderContext testableCompositeId={originalID}/>
                                    </div>
                                </motion.div>}
                            </AnimatePresence>
                        )}
                    </div>
                })}
            </div>
        </div>
    </motion.div>
})

const getTheHighestWidthExcludingPlaceholder = (element: HTMLElement | null) => {
    if (element) {
        let highestWidth = 0;
        for (let i = 0; i < element.children.length; i++) {
            const child = element.children[i];
            if (!child.className.includes('cp-placeholder')) {
                const currentWidth = child.getBoundingClientRect().width;
                if (highestWidth < currentWidth) {
                    highestWidth = currentWidth
                }
            }
        }

        return highestWidth
    }
}
