import {
    TOUR_POPOVER_WIDTH,
    TOUR_SPOTLIGHT_PADDING,
    TOUR_VIEWPORT_PADDING,
} from '@/lib/application-tour/constants';

export type PopoverPlacement = 'top' | 'bottom' | 'left' | 'right';

export type TargetRect = {
    top: number;
    left: number;
    width: number;
    height: number;
};

export type PopoverPosition = {
    top: number;
    left: number;
    placement: PopoverPlacement;
};

const POPOVER_ESTIMATED_HEIGHT = 240;
const POPOVER_GAP = 12;

function fitsInViewport(
    top: number,
    left: number,
    width: number,
    height: number,
): boolean {
    return (
        top >= TOUR_VIEWPORT_PADDING &&
        left >= TOUR_VIEWPORT_PADDING &&
        top + height <= window.innerHeight - TOUR_VIEWPORT_PADDING &&
        left + width <= window.innerWidth - TOUR_VIEWPORT_PADDING
    );
}

function getPlacementPosition(
    rect: TargetRect,
    placement: PopoverPlacement,
): PopoverPosition {
    const paddedTop = rect.top - TOUR_SPOTLIGHT_PADDING;
    const paddedLeft = rect.left - TOUR_SPOTLIGHT_PADDING;
    const paddedWidth = rect.width + TOUR_SPOTLIGHT_PADDING * 2;
    const paddedHeight = rect.height + TOUR_SPOTLIGHT_PADDING * 2;

    switch (placement) {
        case 'top':
            return {
                top: paddedTop - POPOVER_ESTIMATED_HEIGHT - POPOVER_GAP,
                left:
                    paddedLeft +
                    paddedWidth / 2 -
                    TOUR_POPOVER_WIDTH / 2,
                placement,
            };
        case 'bottom':
            return {
                top: paddedTop + paddedHeight + POPOVER_GAP,
                left:
                    paddedLeft +
                    paddedWidth / 2 -
                    TOUR_POPOVER_WIDTH / 2,
                placement,
            };
        case 'left':
            return {
                top:
                    paddedTop +
                    paddedHeight / 2 -
                    POPOVER_ESTIMATED_HEIGHT / 2,
                left: paddedLeft - TOUR_POPOVER_WIDTH - POPOVER_GAP,
                placement,
            };
        case 'right':
            return {
                top:
                    paddedTop +
                    paddedHeight / 2 -
                    POPOVER_ESTIMATED_HEIGHT / 2,
                left: paddedLeft + paddedWidth + POPOVER_GAP,
                placement,
            };
    }
}

function clampPosition(position: PopoverPosition): PopoverPosition {
    const maxLeft =
        window.innerWidth - TOUR_POPOVER_WIDTH - TOUR_VIEWPORT_PADDING;
    const maxTop =
        window.innerHeight - POPOVER_ESTIMATED_HEIGHT - TOUR_VIEWPORT_PADDING;

    return {
        ...position,
        top: Math.max(
            TOUR_VIEWPORT_PADDING,
            Math.min(position.top, maxTop),
        ),
        left: Math.max(
            TOUR_VIEWPORT_PADDING,
            Math.min(position.left, maxLeft),
        ),
    };
}

export function computePopoverPosition(
    rect: TargetRect,
    preferred: PopoverPlacement | 'auto' = 'auto',
): PopoverPosition {
    const order: PopoverPlacement[] =
        preferred === 'auto'
            ? ['bottom', 'right', 'top', 'left']
            : [
                  preferred,
                  ...(['bottom', 'right', 'top', 'left'] as const).filter(
                      (placement) => placement !== preferred,
                  ),
              ];

    for (const placement of order) {
        const candidate = getPlacementPosition(rect, placement);

        if (
            fitsInViewport(
                candidate.top,
                candidate.left,
                TOUR_POPOVER_WIDTH,
                POPOVER_ESTIMATED_HEIGHT,
            )
        ) {
            return clampPosition(candidate);
        }
    }

    return clampPosition(getPlacementPosition(rect, 'bottom'));
}
