import type { Logger } from "@/utils";

import {
  FALLBACK_WATCHDOG_INTERVAL_MS,
  FALLBACK_WATCHDOG_LOG_INTERVAL_MS,
} from "./constants";
import type { KeyboardAdaptEnvironment, ViewportMetrics } from "./types";
import {
  didViewportMetricsChange,
  formatMetrics,
  getFocusedDurationMs,
  hasDetectedKeyboard,
  readViewportMetrics,
} from "./viewport-metrics";
import {
  getActiveElementTag,
  isKeyboardTargetActive,
} from "./keyboard-target";

interface KeyboardWatchdogOptions {
  environment: KeyboardAdaptEnvironment;
  log: Logger;
  getTarget: () => HTMLElement | null | undefined;
  getReactActive: () => boolean;
  getFallbackAllowed: () => boolean;
  getFallbackWasApplied: () => boolean;
  getLastInputLift: () => number;
  getFocusedAt: () => number;
  onPossibleClose: () => void;
  schedule: () => void;
}

export interface KeyboardWatchdog {
  update: (reason: string) => void;
  stop: (reason: string) => void;
  cleanup: () => void;
}

export function createKeyboardWatchdog({
  environment,
  log,
  getTarget,
  getReactActive,
  getFallbackAllowed,
  getFallbackWasApplied,
  getLastInputLift,
  getFocusedAt,
  onPossibleClose,
  schedule,
}: KeyboardWatchdogOptions): KeyboardWatchdog {
  let watchdogId: number | null = null;
  let lastWatchdogLogAt = 0;
  let lastWatchdogMetrics: ViewportMetrics | null = null;
  let lastWatchdogKeyboardDetected = false;

  const shouldRun = (): boolean => {
    if (!environment.usesXiaomiFacebookFallback) return false;
    return isKeyboardTargetActive(getTarget(), getReactActive());
  };

  const update = (reason: string): void => {
    if (shouldRun()) {
      start(reason);
      return;
    }
    stop(reason);
  };

  const start = (reason: string): void => {
    if (watchdogId != null) return;
    const metrics = readViewportMetrics();
    watchdogId = window.setInterval(
      run,
      FALLBACK_WATCHDOG_INTERVAL_MS,
    );
    lastWatchdogLogAt = performance.now();
    lastWatchdogMetrics = metrics;
    lastWatchdogKeyboardDetected = hasDetectedKeyboard(metrics);
    log.debug("[keyboard] watchdog started", {
      reason,
      environment,
      active: getReactActive(),
      fallbackAllowed: getFallbackAllowed(),
      fallbackWasApplied: getFallbackWasApplied(),
      lastInputLift: getLastInputLift(),
      keyboardDetected: lastWatchdogKeyboardDetected,
      metrics: formatMetrics(metrics),
    });
  };

  const stop = (reason: string): void => {
    if (watchdogId == null) return;
    window.clearInterval(watchdogId);
    watchdogId = null;
    lastWatchdogLogAt = 0;
    lastWatchdogMetrics = null;
    lastWatchdogKeyboardDetected = false;
    log.debug("[keyboard] watchdog stopped", {
      reason,
      environment,
      active: getReactActive(),
      fallbackAllowed: getFallbackAllowed(),
      fallbackWasApplied: getFallbackWasApplied(),
      lastInputLift: getLastInputLift(),
    });
  };

  const cleanup = (): void => {
    if (watchdogId == null) return;
    window.clearInterval(watchdogId);
    watchdogId = null;
  };

  const run = (): void => {
    const metrics = readViewportMetrics();
    const activeNow = isKeyboardTargetActive(getTarget(), getReactActive());
    const keyboardDetected = hasDetectedKeyboard(metrics);
    const metricsChanged = didViewportMetricsChange(
      lastWatchdogMetrics,
      metrics,
    );
    const now = performance.now();
    const shouldLog =
      metricsChanged ||
      now - lastWatchdogLogAt >= FALLBACK_WATCHDOG_LOG_INTERVAL_MS;

    if (!activeNow) {
      log.debug("[keyboard] watchdog inactive target", {
        environment,
        reactActive: getReactActive(),
        activeElement: getActiveElementTag(),
        metrics: formatMetrics(metrics),
      });
      stop("inactive-target");
      schedule();
      return;
    }

    if (lastWatchdogKeyboardDetected && !keyboardDetected) {
      onPossibleClose();
      log.debug("[keyboard] watchdog possible close detected", {
        environment,
        fallbackAllowed: getFallbackAllowed(),
        fallbackWasApplied: getFallbackWasApplied(),
        lastInputLift: getLastInputLift(),
        previousMetrics: lastWatchdogMetrics
          ? formatMetrics(lastWatchdogMetrics)
          : null,
        metrics: formatMetrics(metrics),
      });
      schedule();
    }

    if (shouldLog) {
      lastWatchdogLogAt = now;
      log.debug("[keyboard] watchdog tick", {
        environment,
        active: activeNow,
        reactActive: getReactActive(),
        fallbackAllowed: getFallbackAllowed(),
        fallbackWasApplied: getFallbackWasApplied(),
        lastInputLift: getLastInputLift(),
        keyboardDetected,
        metricsChanged,
        focusedForMs: getFocusedDurationMs(getFocusedAt()),
        activeElement: getActiveElementTag(),
        metrics: formatMetrics(metrics),
      });
    }

    lastWatchdogMetrics = metrics;
    lastWatchdogKeyboardDetected = keyboardDetected;
  };

  return { update, stop, cleanup };
}
