"use client";
/**
 * 软键盘避让 hook。
 *
 * 目标环境：小米系设备上的 Facebook IAB 等软键盘覆盖页面但 viewport 信号不稳定的浏览器。
 *
 * 策略：
 *   1. 持续写入 --app-viewport-height，聊天页高度不再只依赖 100dvh。
 *   2. 优先使用 visualViewport 计算键盘高度。
 *   3. 输入框聚焦后测量目标元素是否越过可见底部，计算额外抬升。
 *   4. 小米系 Android + Facebook IAB 若 viewport 未可靠变化，启用保守 fallback 抬升；
 *      键盘收起后若焦点仍停留在 textarea，也要主动释放 fallback。
 */
import { useEffect, useRef } from "react";

import { Logger } from "@/utils";

import {
  RECHECK_DELAYS_MS,
  RECHECK_EVENT,
} from "./constants";
import {
  resetKeyboardVars,
  setInputLiftVar,
  setKeyboardHeightVar,
  setViewportHeightVar,
} from "./css-vars";
import {
  getKeyboardAdaptEnvironment,
  shouldEnableKeyboardAdaptation,
} from "./environment";
import {
  blurKeyboardTarget,
  isKeyboardTargetActive,
} from "./keyboard-target";
import { addKeyboardRecheckListeners } from "./keyboard-event-listeners";
import { createKeyboardWatchdog } from "./keyboard-watchdog";
import { isPossibleKeyboardCloseEvent } from "./release-events";
import type { UseKeyboardHeightOptions, ViewportMetrics } from "./types";
import {
  computeFallbackLift,
  computeTargetOverlap,
  formatMetrics,
  getFocusedDurationMs,
  getLiftSource,
  getReleaseSkipReason,
  readViewportMetrics,
  shouldReleaseKeyboardState,
} from "./viewport-metrics";

const log = new Logger("HooksUseKeyboardHeight");

export function useKeyboardHeight({
  targetRef,
  active = false,
  onKeyboardDismiss,
}: UseKeyboardHeightOptions = {}): void {
  const activeRef = useRef(active);
  const onKeyboardDismissRef = useRef(onKeyboardDismiss);
  const fallbackAllowedRef = useRef(false);
  const focusedAtRef = useRef(0);

  useEffect(() => {
    onKeyboardDismissRef.current = onKeyboardDismiss;
  }, [onKeyboardDismiss]);

  useEffect(() => {
    activeRef.current = active;
    fallbackAllowedRef.current = active;
    focusedAtRef.current = active ? performance.now() : 0;
    window.dispatchEvent(new Event(RECHECK_EVENT));
  }, [active]);

  useEffect(() => {
    if (typeof window === "undefined") return;

    let rafId: number | null = null;
    let timeoutIds: number[] = [];
    let lastKeyboardHeight = -1;
    let lastViewportHeight = -1;
    let lastInputLift = -1;
    let lastMayBeKeyboardClose = false;
    let fallbackWasApplied = false;
    let lastEventType = "initial";
    const environment = getKeyboardAdaptEnvironment();

    log.debug("[keyboard] environment", environment);
    if (!shouldEnableKeyboardAdaptation(environment)) {
      resetKeyboardVars();
      log.debug("[keyboard] disabled", {
        reason: "regular-browser",
        environment,
      });
      return;
    }

    const apply = () => {
      rafId = null;

      const metrics = readViewportMetrics();
      let activeNow = isKeyboardTargetActive(
        targetRef?.current,
        activeRef.current,
      );
      const shouldTryRelease =
        activeNow &&
        lastMayBeKeyboardClose &&
        shouldReleaseKeyboardState(metrics, focusedAtRef.current);

      if (activeNow && lastMayBeKeyboardClose && !shouldTryRelease) {
        log.debug("[keyboard] release skipped", {
          reason: getReleaseSkipReason(metrics, focusedAtRef.current),
          lastEventType,
          environment,
          active: activeNow,
          reactActive: activeRef.current,
          fallbackAllowed: fallbackAllowedRef.current,
          lastInputLift,
          fallbackWasApplied,
          focusedForMs: getFocusedDurationMs(focusedAtRef.current),
          metrics: formatMetrics(metrics),
        });
      }

      if (shouldTryRelease) {
        log.debug("[keyboard] release confirmed", {
          lastEventType,
          environment,
          active: activeNow,
          reactActive: activeRef.current,
          fallbackAllowed: fallbackAllowedRef.current,
          lastInputLift,
          fallbackWasApplied,
          focusedForMs: getFocusedDurationMs(focusedAtRef.current),
          metrics: formatMetrics(metrics),
        });
        releaseKeyboardState("keyboard-close-detected", metrics);
        activeNow = false;
      }
      lastMayBeKeyboardClose = false;

      const keyboardHeight = Math.round(metrics.keyboardHeight);
      const viewportHeight = Math.round(metrics.visibleHeight);
      const fallbackLift = fallbackAllowedRef.current
        ? computeFallbackLift(metrics)
        : 0;
      const inputLift = Math.round(
        activeNow
          ? Math.max(
              computeTargetOverlap(targetRef?.current, metrics.visibleBottom),
              fallbackLift,
            )
          : 0,
      );
      fallbackWasApplied =
        fallbackLift > 0 &&
        inputLift >= fallbackLift &&
        fallbackAllowedRef.current;

      if (keyboardHeight !== lastKeyboardHeight) {
        lastKeyboardHeight = keyboardHeight;
        setKeyboardHeightVar(keyboardHeight);
      }

      if (viewportHeight !== lastViewportHeight) {
        lastViewportHeight = viewportHeight;
        setViewportHeightVar(viewportHeight);
      }

      if (inputLift !== lastInputLift) {
        lastInputLift = inputLift;
        setInputLiftVar(inputLift);
        log.debug("[keyboard] apply", {
          environment,
          active: activeNow,
          reactActive: activeRef.current,
          inputLift,
          fallbackLift,
          fallbackAllowed: fallbackAllowedRef.current,
          source: getLiftSource(inputLift, fallbackLift),
          metrics: formatMetrics(metrics),
        });
      }
      watchdog.update("apply");
    };

    const schedule = () => {
      if (rafId != null) return;
      rafId = window.requestAnimationFrame(apply);
    };

    const scheduleRechecks = (event?: Event) => {
      lastEventType = event?.type ?? "manual";
      const hadInputLift = fallbackWasApplied || lastInputLift > 0;
      const possibleKeyboardClose =
        event != null && isPossibleKeyboardCloseEvent(event, hadInputLift);
      log.debug("[keyboard] schedule rechecks", {
        eventType: lastEventType,
        possibleKeyboardClose,
        hadInputLift,
        fallbackWasApplied,
        lastInputLift,
        active: activeRef.current,
        fallbackAllowed: fallbackAllowedRef.current,
        focusedForMs: getFocusedDurationMs(focusedAtRef.current),
      });
      if (possibleKeyboardClose) {
        lastMayBeKeyboardClose = true;
        log.debug("[keyboard] possible close detected", {
          eventType: lastEventType,
          hadInputLift,
          fallbackWasApplied,
          lastInputLift,
          focusedForMs: getFocusedDurationMs(focusedAtRef.current),
        });
      }
      timeoutIds.forEach((id) => window.clearTimeout(id));
      timeoutIds = RECHECK_DELAYS_MS.map((delay) =>
        window.setTimeout(schedule, delay),
      );
    };

    const releaseKeyboardState = (
      reason: string,
      metrics: ViewportMetrics,
    ): void => {
      activeRef.current = false;
      fallbackAllowedRef.current = false;
      focusedAtRef.current = 0;
      blurKeyboardTarget(targetRef?.current);
      onKeyboardDismissRef.current?.();
      log.debug("[keyboard] released", {
        reason,
        environment,
        metrics: formatMetrics(metrics),
      });
      watchdog.stop(reason);
    };

    const watchdog = createKeyboardWatchdog({
      environment,
      log,
      getTarget: () => targetRef?.current,
      getReactActive: () => activeRef.current,
      getFallbackAllowed: () => fallbackAllowedRef.current,
      getFallbackWasApplied: () => fallbackWasApplied,
      getLastInputLift: () => lastInputLift,
      getFocusedAt: () => focusedAtRef.current,
      onPossibleClose: () => {
        lastEventType = "watchdog";
        lastMayBeKeyboardClose = true;
      },
      schedule,
    });
    const removeKeyboardRecheckListeners =
      addKeyboardRecheckListeners(scheduleRechecks);

    scheduleRechecks();

    return () => {
      if (rafId != null) window.cancelAnimationFrame(rafId);
      watchdog.cleanup();
      timeoutIds.forEach((id) => window.clearTimeout(id));
      removeKeyboardRecheckListeners();
      resetKeyboardVars();
    };
  }, [targetRef]);
}

export type { UseKeyboardHeightOptions };
