import React, {useEffect, useLayoutEffect, useRef, useState} from "react";
import {
    ComponentDataType,
    findTestable,
    NodeData,
    NodesStore,
    OfferData,
    RenderedNode,
    TestableCompositeData,
    TestableData,
    useNodesStore
} from "./store";
import {CardRenderer, cardRenderers} from "./CardRenderers";
import {CardRendererCategoryType, CardRendererFromCardComponentDataDecorator} from "./CardRenderer";
import {useAtom, useAtomValue, useSetAtom} from "jotai";
import {
    AddNewTreePopupWindowIsOpen,
    atomsStore, BuyXGetYNotInCartNotificationWindowStateAtom, BuyXGetYSpecificProductWindowStateAtom,
    DocumentDimensionsAtom, ExamplesContext, ExamplesPopupWindowStateAtom,
    getDefaultClosedPopupWindowState,
    HorizontalOffsetAtom,
    leftSidebarWidthAtom, PopupWindowStateContext,
    rightSidebarWidthAtom,
    SelectActionContext,
    SelectActionPopupWindowStateAtom,
    SelectMenuPopupWindowsAtom,
    sidebarEdgeOffsetAtom, TestableGroupModePopupWindowStateAtom, TestablesPopupWindowsAtom, TreeContextData,
    VerticalOffsetAtom
} from "./atoms";
import {Testables} from "./testables";
import {cloneDeep, merge} from "lodash";
import {Tree} from "./Tree";
import {Grove} from "./Grove";
import {Offers} from "./Offers";
import {ParentType} from "./components";
import {useOpenTestablesPopupWindow} from "./useOpenTestablesPopupWindow";
import {TestablesSaver} from "./TestablesSaver";
import {suggestedScopes} from "./suggestedScopes";
import type {ConditionColorPalette} from "./Colors";

export const useHover = <Element extends HTMLElement>({onMouseEnter, onMouseLeave}) => {
    const ref = useRef<Element>(null)

    useEffect(() => {
        const element = ref.current;

        if (!element) return;

        element.addEventListener('mouseenter', onMouseEnter);
        element.addEventListener('mouseleave', onMouseLeave);

        return () => {
            element.removeEventListener('mouseenter', onMouseEnter);
            element.removeEventListener('mouseleave', onMouseLeave);
        };
    }, [ref.current]);

    return ref;
}

export const useCardRenderer: (id: string | TestableData, where?: keyof NodesStore) => CardRenderer | undefined = (id, where) => {
    //const data = useNodesStore(findNode(id, where))
    const testables = useLazyTestables()
    const offers = useLazyOffers()
    let testableData: NodeData | undefined
    let partialIdToUse: string | undefined
    let isLocalData = typeof id == 'object';

    if (isLocalData) {
        testableData = id as TestableData
    } else if (where === 'testablePartials') {
        testableData = testables.getParent(id)

        partialIdToUse = id
        // @ts-ignore
        id = testableData?.id
    } else if (where === 'testables') {
        testableData = testables.getNode(id)
    } else if (where === 'offers') {
        testableData = offers.get(id)
    }

    if (!testableData) {
        return;
    }
    const [type, testableType, cardRenderer, resolvedId, partialId] = getRendererForContextTestable(
        id,
        testableData as TestableData,
        partialIdToUse
    )

    return new CardRendererFromCardComponentDataDecorator(cardRenderer, testableType, resolvedId, partialId, isLocalData ? id as TestableData | OfferData : undefined)
}

const getRendererForContextTestable = (id: TestableCompositeData['id'] | TestableData, data: TestableData | (OfferData & ParentType), partialId?: string): [string, ComponentDataType, CardRenderer, string, string | undefined] => {
    const {type} = data
    const parentType: ComponentDataType = ('parentType' in data ? (data.parentType as string).replace(/s$/, '') : data.testableType) as ComponentDataType
    const cardRenderer = cardRenderers[`${parentType}s`][type]

    return [
        type,
        parentType,
        cardRenderer,
        typeof id === 'object' ? id.id : id,
        partialId
    ]
};
const getRendererForRenderedNode = (tree: Tree, id: string, data: RenderedNode): [string, CardRendererCategoryType, CardRenderer, string, string?] => {
    const {type} = data
    const renderedNode = data!
    if (type === 'tiered-base-node') {
        const testable = tree.testables.getNode(renderedNode.data.testableID!) as TestableData
        return getRendererForContextTestable(tree, testable.id, testable);
    } else /*if(type === 'tiered-subchild-node')*/  {
        // this is the tier subchild node
        const testablePartialId = renderedNode.data.testablePartialId;
        const baseTestable = tree.testables.getParent(testablePartialId) as TestableData;

        return getRendererForContextTestable(tree, baseTestable?.id, baseTestable, testablePartialId);
    }
};


export const useDashboardDimensions = () => {
    const verticalOffset = useAtomValue(VerticalOffsetAtom)
    const horizontalOffset = useAtomValue(HorizontalOffsetAtom)
    const documentDimensions = useAtomValue(DocumentDimensionsAtom)

    return {
        y: verticalOffset,
        x: horizontalOffset,
        width: documentDimensions.width - horizontalOffset,
        // for some reason the height already includes the vertical offset
        height: documentDimensions.height,
    }
}

export type UseOnHeightCallback = (height: number) => void;
export const useOnHeight = (onHeight?: UseOnHeightCallback) => {
    const ref = useRef<HTMLDivElement>(null)

    useEffect(() => {
        if (ref.current) {
            onHeight && onHeight(ref.current.getBoundingClientRect().height)
        }
    }, [ref.current])

    return ref
}


export const useScreenNavigation = (defaultScreenId?: number) => {
    const screensNavigation = useState<number>(defaultScreenId || 1)

    return [
        ...screensNavigation,
        useNavigationActions(screensNavigation[0], screensNavigation[1])
    ]
}
export const useNavigationActions = (currentScreenId: number, setCurrentScreenId: (newScreenId: number) => void) => {
    return {
        next: () => {
            if (currentScreenId) {
                setCurrentScreenId(currentScreenId + 1)
            } else {
                setCurrentScreenId(1)
            }
        },
        prev: (): number => {
            let targetScreenId: number = currentScreenId;

            if (currentScreenId && currentScreenId > 1) {
                targetScreenId = currentScreenId - 1
            } else if (currentScreenId !== 1) {
                targetScreenId = 1
            }

            setCurrentScreenId(Math.max(1, targetScreenId - 1))

            return targetScreenId;
        },
    };
}

export const useTestableData = <Options = {}>(id: string) => {
    return useNodesStore(findTestable<Options>(id))
}

// will not cause a rerender when the store changes
// READ ONLY!
export const useLazyTestables = (): Testables => {
    return getLazyTestables();
}

export const getLazyTestables = (): Testables => {
    return new Testables(cloneDeep(useNodesStore.getState()))
}

export const useLazyOffers = (): Offers => {
    return new Offers(useNodesStore.getState());
}


export const useLazyGrove = () => {
    return Grove.fromState(useNodesStore.getState())
}
// will not cause a rerender when the store changes
// READ ONLY!
/*export const useLazyTree = (): Tree => {
    const state = cloneDeep(useNodesStore.getState())

    return new Tree(
        state,
        // we're not using useLazyTestables here so as to avoid cloning the state again
        new Testables(state),
    );
}*/

export const useCloseSelectActionsPopupWindow = () => {
    const setSelectActionsPopupWindowState = useSetAtom(SelectActionPopupWindowStateAtom)

    return () => {
        setSelectActionsPopupWindowState(getDefaultClosedPopupWindowState())
    }
}

export const useOpenSelectActionsPopupWindow = (action: SelectActionContext['action'] = null, targetBranchId: string, data: SelectActionContext['data'] = {}) => {
    const setSelectActionsPopupWindowState = useSetAtom(SelectActionPopupWindowStateAtom)

    return (_customData: SelectActionContext['data'] = {}) => {
        setSelectActionsPopupWindowState({
            isOpen: true,
            context: {
                action,
                targetBranchId,
                data: merge({}, data || {}, _customData)
            }
        })
    }
}

/**
 * Used outside a component
 */
export const closeSelectActionsPopupWindow = () => {
    atomsStore.set(SelectActionPopupWindowStateAtom, getDefaultClosedPopupWindowState())
}

export const useOpenExamplesPopupWindow = () => {
    const setExamplesPopupWindowState = useSetAtom(ExamplesPopupWindowStateAtom)

    return (id: ExamplesContext['id']) => {
        setExamplesPopupWindowState({
            isOpen: true,
            context: {id}
        })
    }
}


export const useTreeDashboardAvailableDimensions = () => {
    const leftSidebarWidth = useAtomValue(leftSidebarWidthAtom)
    const rightSidebarWidth = useAtomValue(rightSidebarWidthAtom)
    const edgeOffset = useAtomValue(sidebarEdgeOffsetAtom)

    const leftOffset = leftSidebarWidth + (edgeOffset * 2)
    const rightOffset = rightSidebarWidth + (edgeOffset * 2)

    return {
        leftOffset,
        rightOffset,
        edgeOffset,
    }
}

export const useViewPortDimensionsStyle = () => {
    const [topBarOffset, setTopBarOffset] = useState(0)
    const [leftMenuOffset, setLeftMenuOffset] = useState(0)

    useEffect(() => {
        const adminMenu = document.querySelector('#adminmenu');
        if (!adminMenu) return;

        const resizeObserver = new ResizeObserver(entries => {
            for (const entry of entries) {
                if (entry.target === adminMenu) {
                    setLeftMenuOffset(entry.contentRect.width);
                }
            }
        });

        resizeObserver.observe(adminMenu);

        return () => {
            resizeObserver.disconnect();
        };
    }, [])

    useLayoutEffect(() => {
        const topBarHeight = document.querySelector('#wpadminbar')?.getBoundingClientRect()?.height || 0
        const leftBarWidth = document.querySelector('#adminmenu')?.getBoundingClientRect()?.width || 0

        setTopBarOffset(topBarHeight)
        setLeftMenuOffset(leftBarWidth)
    }, [])

    return {
        top: topBarOffset,
        left: leftMenuOffset,
        height: `calc(100vh - ${topBarOffset}px)`,
        width: `calc(100vw - ${leftMenuOffset}px)`,
    }
}

/**
 * Hook to detect if the current viewport is mobile-sized (below md breakpoint: 768px)
 */
export const useIsMobile = (): boolean => {
    const [isMobile, setIsMobile] = useState(false)

    useLayoutEffect(() => {
        const mediaQuery = window.matchMedia('(max-width: 767px)')

        const handleChange = (e: MediaQueryListEvent | MediaQueryList) => {
            setIsMobile(e.matches)
        }

        // Set initial value
        handleChange(mediaQuery)

        // Listen for changes
        mediaQuery.addEventListener('change', handleChange)

        return () => {
            mediaQuery.removeEventListener('change', handleChange)
        }
    }, [])

    return isMobile
}


export const useOpenPopupsCount = () => {
    const testablePopUps = useAtomValue(TestablesPopupWindowsAtom)
    const testableGroupModePopupWindowState = useAtomValue(TestableGroupModePopupWindowStateAtom)
    const [{isOpen: selectIsOpen}] =  useAtom(SelectActionPopupWindowStateAtom)
    const [addNewTreeIsOpen, setIsOpen] = useAtom(AddNewTreePopupWindowIsOpen)
    const [getYProductsWindowContext, ] = useAtom(BuyXGetYSpecificProductWindowStateAtom)
    const [getYNotificationWindowContext, ] = useAtom(BuyXGetYNotInCartNotificationWindowStateAtom)
    const selectMenuPopups = useAtomValue(SelectMenuPopupWindowsAtom)

    return testablePopUps.length
        + (testableGroupModePopupWindowState.isOpen ? 1 : 0)
        + (selectIsOpen ? 1: 0)
        + (addNewTreeIsOpen? 1 : 0)
        + (getYProductsWindowContext? 1 : 0)
        + (getYNotificationWindowContext? 1 : 0)
        + selectMenuPopups.length
}

/**
 * Non-reactive version of useOpenPopupsCount. Reads current atom values
 * without subscribing — safe to call inside event handlers without
 * causing re-renders of the calling component.
 */
export const getOpenPopupsCount = () => {
    const testablePopUps = atomsStore.get(TestablesPopupWindowsAtom)
    const testableGroupModePopupWindowState = atomsStore.get(TestableGroupModePopupWindowStateAtom)
    const {isOpen: selectIsOpen} = atomsStore.get(SelectActionPopupWindowStateAtom)
    const addNewTreeIsOpen = atomsStore.get(AddNewTreePopupWindowIsOpen)
    const getYProductsWindowContext = atomsStore.get(BuyXGetYSpecificProductWindowStateAtom)
    const getYNotificationWindowContext = atomsStore.get(BuyXGetYNotInCartNotificationWindowStateAtom)
    const selectMenuPopups = atomsStore.get(SelectMenuPopupWindowsAtom)

    return testablePopUps.length
        + (testableGroupModePopupWindowState.isOpen ? 1 : 0)
        + (selectIsOpen ? 1: 0)
        + (addNewTreeIsOpen? 1 : 0)
        + (getYProductsWindowContext? 1 : 0)
        + (getYNotificationWindowContext? 1 : 0)
        + selectMenuPopups.length
}

export const useOpenEditTestablePopup = ({
    testableType,
    supportsMultipleComponents,
    scope = 'tree-node',
    supportedComponents,
    unsupportedComponents,
    conditionColorPalette,
    showTabs = false,
}: {
    testableType: ComponentDataType,
    supportsMultipleComponents: boolean,
    scope?: PopupWindowStateContext<TreeContextData>['scope'],
    supportedComponents?: string[],
    unsupportedComponents?: string[],
    conditionColorPalette?: ConditionColorPalette,
    showTabs?: boolean,
}) => {
    const openTestablePopupWindow = useOpenTestablesPopupWindow();

    return (targetTestableId: string, suggestedScope: typeof suggestedScopes[number]['id']) => {

        // @ts-ignore
        const context = {
            id: 'context-edit',
            scope,
            data: {
                componentType: testableType,
                supportedComponents,
                conditionColorPalette,
                targetType: 'testableComposite',
                supportsMultipleComponents,
                unsupportedComponents,
                mode: 'edit',
                targetId: targetTestableId,
                showTabs,
                extraData: {
                    suggestedScope,
                }
            }
        } as PopupWindowStateContext<TreeContextData>

        openTestablePopupWindow({
            context,
            onClose: (data) => {
                if (data.status !== 'success' || !data.components) {
                    return
                }
                // @ts-ignore
                const saver = new TestablesSaver(context)

                saver.update(data.components)
            }
        });
    }
}

export const useOpenEditOfferPopup = (offerId: string) => {
    const openTestablePopupWindow = useOpenTestablesPopupWindow();
    const updateOffer = useNodesStore(store => store.updateOffer)


    return () => {
        const context = {
            id: 'offer-card-edit',
            scope: 'tree-node',
            data: {
                componentType: 'offer',
                targetType: 'testableComposite',
                supportsMultipleComponents: false,
                mode: 'edit',
                targetId: offerId,
            }
        } as PopupWindowStateContext<TreeContextData>

        openTestablePopupWindow({
            context,
            onClose: (data) => {
                const offerToUpdate = data.components?.[0] as unknown as OfferData;
                if (data.status === 'success' && offerToUpdate) {
                    updateOffer(offerToUpdate)
                }
            }
        })
    }
}
