import type { TooltipPlacement, TooltipProps } from '@redocly/theme/core/types';

const PLACEMENT_MARGIN = 10;
const COUNTER_CLOCKWISE: TooltipPlacement[] = ['top', 'left', 'bottom', 'right'];

export function getDefaultFallbackPlacements(placement: TooltipPlacement): TooltipPlacement[] {
  const index = COUNTER_CLOCKWISE.indexOf(placement);
  const result: TooltipPlacement[] = [];
  for (let i = 1; i < COUNTER_CLOCKWISE.length; i++) {
    result.push(COUNTER_CLOCKWISE[(index + i) % COUNTER_CLOCKWISE.length]);
  }
  return result;
}

export function calcAnchorPoint(
  triggerRect: DOMRect,
  placement: TooltipPlacement,
  arrowPosition: TooltipProps['arrowPosition'],
): { top: number; left: number } {
  const horizontalLeft = (): number =>
    arrowPosition === 'left'
      ? triggerRect.left - 24
      : arrowPosition === 'right'
        ? triggerRect.right + 24
        : triggerRect.left + triggerRect.width / 2;

  const verticalTop = (): number => triggerRect.top + triggerRect.height / 2;

  switch (placement) {
    case 'top':
      return { top: triggerRect.top, left: horizontalLeft() };
    case 'bottom':
      return { top: triggerRect.bottom, left: horizontalLeft() };
    case 'left':
      return { top: verticalTop(), left: triggerRect.left };
    case 'right':
      return { top: verticalTop(), left: triggerRect.right };
  }
}

type FitsInViewportParams = {
  anchor: { top: number; left: number };
  tooltipWidth: number;
  tooltipHeight: number;
  placement: TooltipPlacement;
  arrowPosition: TooltipProps['arrowPosition'];
};

export function fitsInViewport({
  anchor,
  tooltipWidth,
  tooltipHeight,
  placement,
  arrowPosition,
}: FitsInViewportParams): boolean {
  const horizontalLeft = (): number =>
    arrowPosition === 'left'
      ? anchor.left
      : arrowPosition === 'right'
        ? anchor.left - tooltipWidth
        : anchor.left - tooltipWidth / 2;

  const verticalTop = (): number => anchor.top - tooltipHeight / 2;

  let top: number;
  let left: number;

  switch (placement) {
    case 'top':
      top = anchor.top - tooltipHeight - PLACEMENT_MARGIN;
      left = horizontalLeft();
      break;
    case 'bottom':
      top = anchor.top + PLACEMENT_MARGIN;
      left = horizontalLeft();
      break;
    case 'left':
      top = verticalTop();
      left = anchor.left - tooltipWidth - PLACEMENT_MARGIN;
      break;
    case 'right':
      top = verticalTop();
      left = anchor.left + PLACEMENT_MARGIN;
      break;
  }

  return (
    top >= 0 &&
    left >= 0 &&
    left + tooltipWidth <= window.innerWidth &&
    top + tooltipHeight <= window.innerHeight
  );
}

type ResolvePlacementParams = {
  triggerRect: DOMRect;
  tooltipWidth: number;
  tooltipHeight: number;
  placement: TooltipPlacement;
  arrowPosition: TooltipProps['arrowPosition'];
  fallbackPlacements: TooltipPlacement[] | undefined;
};

/**
 * Given the trigger rect, tooltip dimensions, primary placement/arrow, and
 * fallback list, returns the first placement that keeps the tooltip fully
 * inside the viewport. Falls back to the primary when nothing fits.
 */
export function resolvePlacement({
  triggerRect,
  tooltipWidth,
  tooltipHeight,
  placement,
  arrowPosition,
  fallbackPlacements,
}: ResolvePlacementParams): TooltipPlacement {
  if (!fallbackPlacements?.length || tooltipWidth === 0 || tooltipHeight === 0) {
    return placement;
  }

  const candidates: TooltipPlacement[] = [placement, ...fallbackPlacements];

  for (const candidate of candidates) {
    const candidateArrow = candidate === placement ? arrowPosition : 'center';
    const pos = calcAnchorPoint(triggerRect, candidate, candidateArrow);

    if (
      fitsInViewport({
        anchor: pos,
        tooltipWidth,
        tooltipHeight,
        placement: candidate,
        arrowPosition: candidateArrow,
      })
    ) {
      return candidate;
    }
  }

  return placement;
}
