import type { FC } from 'react';
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { GlyphIcon } from '@theorchard/suite-icons';
import cx from 'classnames';
import { BsOverlayTrigger, BsPopover, BsTooltip, BsOverlay } from '../bootstrap';

const CLASS_NAME = 'HelpTooltip';
const CLASS_NAME_OVERLAY = `${CLASS_NAME}-overlay`;

type OverlayTriggerEvent = 'hover' | 'click' | 'focus';

// Bootstrap container type
type DOMContainer<T extends HTMLElement = HTMLElement> =
    | T
    | React.RefObject<T>
    | null
    | (() => T | React.RefObject<T> | null);

export interface HelpTooltipProps {
    children?: JSX.Element;
    className?: string;
    hideOnHover?: boolean;
    id: string;
    message: string | JSX.Element;
    onClose?: () => void;
    onOpen?: () => void;
    overlayClassName?: string;
    overlayType?: 'popover' | 'tooltip';
    rootClose?: boolean;
    show?: boolean;
    testId?: string;
    tooltipPlacement?: 'top' | 'left' | 'bottom' | 'right';
    trigger?: OverlayTriggerEvent | OverlayTriggerEvent[];
    container?: DOMContainer<HTMLElement>;
    size?: 24 | 16 | 12;

    /**
     * Controls whether the tooltip can automatically flip to the opposite placement (e.g., top to bottom) if there's not enough space.
     */
    flip?: boolean;
}

/**
 * HelpTooltip combines the "help" GlyphIcon with a Tooltip component for the common use-case of showing information related to a field or column.
 *
 * @type molecule
 * @status live
 * @tags overlays, utilities
 * @variantOf tooltip
 *
 */
export const HelpTooltip: FC<HelpTooltipProps> = ({
    children,
    className,
    hideOnHover,
    id,
    message,
    onClose,
    onOpen,
    overlayClassName,
    overlayType,
    rootClose,
    show,
    testId = CLASS_NAME,
    tooltipPlacement,
    trigger,
    container,
    size = 12,
    flip,
}) => {
    const controlledShow = typeof show !== 'undefined';

    const [isShown, setIsShown] = useState(false);
    const targetRef = useRef(null);

    const setIfShown = useCallback(
        (defaultShow?: boolean) => {
            let shown = defaultShow;
            if (controlledShow) shown = show;
            setIsShown(shown ?? false);
        },
        [controlledShow, show]
    );

    useEffect(() => {
        setIfShown();
    }, [setIfShown]);

    const renderContent = () => children ?? <GlyphIcon name="info" size={size} />;

    const renderOverlayContent = () => {
        const OverlayClass: FC<{
            id: string;
            className?: string;
            children?: React.ReactNode;
        }> = overlayType === 'popover' ? BsPopover : BsTooltip;

        return (
            <OverlayClass
                id={id}
                className={cx(CLASS_NAME_OVERLAY, overlayClassName, {
                    'with-empty-message': !message,
                })}
                data-testid={`${testId}Overlay`}
            >
                {message}
            </OverlayClass>
        );
    };

    const classNames = cx(CLASS_NAME, className, {
        'with-icon': !children,
    });

    if (controlledShow || !hideOnHover)
        return (
            <span
                className={classNames}
                data-testid={testId}
                onMouseOver={() => setIfShown(true)}
                onMouseOut={() => setIfShown(false)}
                onFocus={() => setIfShown(true)}
                onBlur={() => setIfShown(false)}
                ref={targetRef}
            >
                <BsOverlay
                    show={isShown}
                    placement={tooltipPlacement}
                    target={targetRef.current}
                    onHide={onClose}
                    onEnter={onOpen}
                    container={container}
                    flip={flip}
                >
                    {renderOverlayContent()}
                </BsOverlay>

                {renderContent()}
            </span>
        );

    return (
        <BsOverlayTrigger
            overlay={renderOverlayContent()}
            placement={tooltipPlacement}
            rootClose={rootClose}
            trigger={trigger}
            onToggle={(toBeShown) => {
                if (toBeShown) onOpen?.();
                else onClose?.();
            }}
            container={container}
            flip={flip}
        >
            <span className={classNames} data-testid={testId}>
                {renderContent()}
            </span>
        </BsOverlayTrigger>
    );
};
