import {
  FALLBACK_CLOSE_GRACE_MS,
  FALLBACK_KEYBOARD_MAX,
  FALLBACK_KEYBOARD_MIN,
  FALLBACK_KEYBOARD_RATIO,
  FOCUS_GAP_PX,
} from "./constants";
import { shouldUseInAppKeyboardFallback } from "./environment";
import type { ViewportMetrics } from "./types";
import { getVirtualKeyboard } from "./virtual-keyboard";

export function readViewportMetrics(): ViewportMetrics {
  const layoutHeight = window.innerHeight;
  const vv = window.visualViewport;
  const virtualKeyboard = getVirtualKeyboard();
  const virtualKeyboardRect = virtualKeyboard?.boundingRect ?? null;
  const virtualKeyboardHeight = virtualKeyboardRect?.height ?? 0;

  if (!vv) {
    const visibleBottom =
      virtualKeyboardHeight > 0
        ? layoutHeight - virtualKeyboardHeight
        : layoutHeight;
    return {
      hasVisualViewport: false,
      hasVirtualKeyboard: virtualKeyboard != null,
      layoutHeight,
      visibleHeight: visibleBottom,
      visibleBottom,
      keyboardHeight: virtualKeyboardHeight,
    };
  }

  const visualBottom = vv.offsetTop + vv.height;
  const keyboardTop =
    virtualKeyboardHeight > 0
      ? Math.min(
          virtualKeyboardRect?.y ?? layoutHeight,
          layoutHeight - virtualKeyboardHeight,
        )
      : layoutHeight;
  const visibleBottom = Math.min(visualBottom, keyboardTop);
  const visibleHeight = Math.min(vv.height, visibleBottom);

  return {
    hasVisualViewport: true,
    hasVirtualKeyboard: virtualKeyboard != null,
    layoutHeight,
    visibleHeight,
    visibleBottom,
    keyboardHeight: Math.max(
      0,
      layoutHeight - visibleBottom,
      virtualKeyboardHeight,
    ),
  };
}

export function computeTargetOverlap(
  target: HTMLElement | null | undefined,
  visibleBottom: number,
): number {
  if (!target) return 0;
  const rect = target.getBoundingClientRect();
  return Math.max(0, rect.bottom + FOCUS_GAP_PX - visibleBottom);
}

export function computeFallbackLift(metrics: ViewportMetrics): number {
  if (!shouldUseInAppKeyboardFallback()) return 0;

  const viewportAlreadyShrank = metrics.visibleHeight < metrics.layoutHeight * 0.82;
  const keyboardAlreadyDetected = metrics.keyboardHeight > 80;
  if (viewportAlreadyShrank || keyboardAlreadyDetected) return 0;

  return clamp(
    Math.round(metrics.layoutHeight * FALLBACK_KEYBOARD_RATIO),
    FALLBACK_KEYBOARD_MIN,
    FALLBACK_KEYBOARD_MAX,
  );
}

export function shouldReleaseKeyboardState(
  metrics: ViewportMetrics,
  focusedAt: number,
): boolean {
  if (focusedAt <= 0) return false;
  if (performance.now() - focusedAt < FALLBACK_CLOSE_GRACE_MS) return false;
  return !hasDetectedKeyboard(metrics);
}

export function getFocusedDurationMs(focusedAt: number): number | null {
  if (focusedAt <= 0) return null;
  return Math.round(performance.now() - focusedAt);
}

export function getReleaseSkipReason(
  metrics: ViewportMetrics,
  focusedAt: number,
): string {
  if (focusedAt <= 0) return "not-focused-by-hook";
  if (performance.now() - focusedAt < FALLBACK_CLOSE_GRACE_MS) {
    return "within-close-grace-window";
  }
  if (hasDetectedKeyboard(metrics)) return "keyboard-still-detected";
  return "unknown";
}

export function didViewportMetricsChange(
  previous: ViewportMetrics | null,
  current: ViewportMetrics,
): boolean {
  if (!previous) return true;
  return (
    Math.round(previous.layoutHeight) !== Math.round(current.layoutHeight) ||
    Math.round(previous.visibleHeight) !== Math.round(current.visibleHeight) ||
    Math.round(previous.visibleBottom) !== Math.round(current.visibleBottom) ||
    Math.round(previous.keyboardHeight) !== Math.round(current.keyboardHeight)
  );
}

export function hasDetectedKeyboard(metrics: ViewportMetrics): boolean {
  return (
    metrics.keyboardHeight > 80 ||
    metrics.visibleHeight < metrics.layoutHeight * 0.82
  );
}

export function formatMetrics(metrics: ViewportMetrics) {
  return {
    hasVisualViewport: metrics.hasVisualViewport,
    hasVirtualKeyboard: metrics.hasVirtualKeyboard,
    layoutHeight: Math.round(metrics.layoutHeight),
    visibleHeight: Math.round(metrics.visibleHeight),
    visibleBottom: Math.round(metrics.visibleBottom),
    keyboardHeight: Math.round(metrics.keyboardHeight),
  };
}

export function getLiftSource(inputLift: number, fallbackLift: number): string {
  if (inputLift <= 0) return "none";
  if (fallbackLift > 0 && inputLift === fallbackLift) {
    return "xiaomi-facebook-fallback";
  }
  if (fallbackLift > 0) return "overlap-and-fallback";
  return "target-overlap";
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, value));
}
