import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";

import { useHandleClickOutside } from "@ax/hooks";

import * as S from "./style";

const Tooltip = (props: ITooltipProps) => {
	const { content, children, hideOnClick = true, bottom, left, expanded, top, className } = props;

	const initialState: IState = {
		active: false,
		childrenWidth: 0,
		clicked: false,
		fixOutOfBounds: 0,
	};
	const [state, setState] = useState(initialState);
	const { active, childrenWidth, clicked, fixOutOfBounds } = state;

	const changeState = useCallback((value: Partial<IState>) => {
		setState((state) => ({ ...state, ...value }));
	}, []);

	const childrenRef = useRef<HTMLDivElement>(null);
	const tipRef = useRef<HTMLDivElement>(null);
	const timeoutRef = useRef<NodeJS.Timeout | null>(null);

	const showTip = () => {
		if (!clicked) {
			if (timeoutRef.current) {
				clearTimeout(timeoutRef.current);
			}
			timeoutRef.current = setTimeout(() => {
				changeState({ active: true });
			}, 1000);
		}
	};

	const hideTip = () => {
		if (timeoutRef.current) {
			clearTimeout(timeoutRef.current);
			timeoutRef.current = null;
		}
		changeState({ active: false, fixOutOfBounds: 0 });
	};

	const handleClick = () => {
		if (hideOnClick) {
			changeState({ clicked: !clicked });
			hideTip();
		}
	};

	const handleClickOutside = (e: MouseEvent | TouchEvent) => {
		if (childrenRef.current?.contains(e.target as Node)) {
			return;
		}
		changeState({ clicked: false });
	};

	useHandleClickOutside(clicked, handleClickOutside);

	useEffect(() => {
		return () => {
			if (timeoutRef.current) {
				clearTimeout(timeoutRef.current);
			}
		};
	}, []);

	useEffect(() => {
		if (childrenRef.current?.children[0]) {
			const childrenWidth = childrenRef.current.children[0].clientWidth;
			changeState({ childrenWidth });
		}
	}, [changeState]);

	useLayoutEffect(() => {
		if (active && tipRef.current) {
			const clientRect = tipRef.current.getBoundingClientRect();

			const { left, right } = clientRect;
			const windowSize = window.innerWidth;
			if (left < 0) {
				changeState({ fixOutOfBounds: left });
			} else if (right > windowSize) {
				changeState({ fixOutOfBounds: right - windowSize });
			} else {
				changeState({ fixOutOfBounds: 0 });
			}
		}
	}, [active, changeState]);

	if (!content) return <>{children}</>;

	return (
		<S.Tooltip
			data-testid="tooltip-component"
			onMouseEnter={showTip}
			onMouseLeave={hideTip}
			onMouseDown={handleClick}
			expanded={expanded}
			className={className}
		>
			<div ref={childrenRef}>{children}</div>
			<S.Tip
				data-testid="tip-component"
				active={active}
				childrenWidth={childrenWidth}
				bottom={bottom}
				ref={tipRef}
				fixOutOfBounds={fixOutOfBounds}
				left={left}
				expanded={expanded}
				top={top}
			>
				{content}
			</S.Tip>
		</S.Tooltip>
	);
};

interface IState {
	active: boolean;
	childrenWidth: number;
	clicked: boolean;
	fixOutOfBounds: number;
}

export interface ITooltipProps {
	content?: React.ReactNode;
	children: any;
	hideOnClick?: boolean;
	bottom?: boolean;
	left?: number;
	expanded?: boolean;
	top?: number;
	className?: string;
}

export default Tooltip;
