"use client";

import { useEffect, type ReactNode } from "react";

import {
  isViewportGeometryObservation,
  observeBrowserViewport,
  type BrowserViewportMetrics,
} from "@/lib/browser/viewport_observer";
import { BrowserDetector } from "@/utils/browser-detect";
import { Logger } from "@/utils/logger";
import { PlatformDetector } from "@/utils/platform-detect";

const CSS_VAR_VIEWPORT_HEIGHT = "--app-viewport-height";
const CSS_VAR_VISIBLE_HEIGHT = "--app-visible-height";
const CSS_VAR_VISIBLE_OFFSET_TOP = "--app-visible-offset-top";
const CSS_VAR_SAFE_TOP = "--app-safe-top";
const CSS_VAR_SAFE_RIGHT = "--app-safe-right";
const CSS_VAR_SAFE_BOTTOM = "--app-safe-bottom";
const CSS_VAR_SAFE_LEFT = "--app-safe-left";
const IOS_KEYBOARD_VIEWPORT_DELTA_PX = 120;

const log = new Logger("ProvidersViewportCssVarsProvider");

export interface ViewportCssVarsProviderProps {
  children: ReactNode;
}

/**
 * Keeps viewport-related CSS variables in sync with the real visual viewport.
 *
 * CSS already has safe defaults (`100dvh` and `env(safe-area-inset-*)`). This
 * client provider improves Safari/PWA/WebView behavior after hydration without
 * pushing viewport measurements into React state.
 */
export function ViewportCssVarsProvider({
  children,
}: ViewportCssVarsProviderProps) {
  useEffect(() => {
    if (typeof window === "undefined") return;

    let frameId: number | null = null;
    let pendingMetrics: BrowserViewportMetrics | null = null;
    let stableLayoutHeight = 0;
    let stableVisibleHeight = 0;
    let stableVisibleOffsetTop = 0;
    let hasStableMetrics = false;
    let iosFacebookKeyboardSuppressed = false;
    const isIOSFacebookInAppBrowser =
      PlatformDetector.isIOS() && BrowserDetector.isFacebookInAppBrowser();

    const applyVars = () => {
      frameId = null;
      const metrics = pendingMetrics;
      if (!metrics) return;

      const root = document.documentElement;
      const layoutHeight = metrics.layoutHeight;
      const visibleHeight = metrics.visualHeight;
      const visibleWidth = metrics.visualWidth;
      const visibleOffsetTop = metrics.visualOffsetTop;
      if (!hasStableMetrics) {
        stableLayoutHeight = layoutHeight;
        stableVisibleHeight = visibleHeight;
        stableVisibleOffsetTop = visibleOffsetTop;
        hasStableMetrics = true;
      }
      const shouldSuppressIOSFacebookKeyboardViewport =
        isIOSFacebookInAppBrowser &&
        isEditableElementFocused() &&
        stableLayoutHeight - visibleHeight > IOS_KEYBOARD_VIEWPORT_DELTA_PX;
      const nextLayoutHeight = shouldSuppressIOSFacebookKeyboardViewport
        ? stableLayoutHeight
        : layoutHeight;
      const nextVisibleHeight = shouldSuppressIOSFacebookKeyboardViewport
        ? stableVisibleHeight
        : visibleHeight;
      const nextVisibleOffsetTop = shouldSuppressIOSFacebookKeyboardViewport
        ? stableVisibleOffsetTop
        : visibleOffsetTop;

      if (!shouldSuppressIOSFacebookKeyboardViewport) {
        stableLayoutHeight = layoutHeight;
        stableVisibleHeight = visibleHeight;
        stableVisibleOffsetTop = visibleOffsetTop;
        iosFacebookKeyboardSuppressed = false;
      } else if (!iosFacebookKeyboardSuppressed) {
        iosFacebookKeyboardSuppressed = true;
        log.debug("[viewport] suppress ios facebook keyboard viewport", {
          layoutHeight: Math.round(layoutHeight),
          visibleHeight: Math.round(visibleHeight),
          visibleOffsetTop: Math.round(visibleOffsetTop),
          stableLayoutHeight: Math.round(stableLayoutHeight),
          stableVisibleHeight: Math.round(stableVisibleHeight),
          stableVisibleOffsetTop: Math.round(stableVisibleOffsetTop),
        });
      }

      root.style.setProperty(CSS_VAR_VIEWPORT_HEIGHT, `${nextLayoutHeight}px`);
      root.style.setProperty(CSS_VAR_VISIBLE_HEIGHT, `${nextVisibleHeight}px`);
      root.style.setProperty(
        CSS_VAR_VISIBLE_OFFSET_TOP,
        `${nextVisibleOffsetTop}px`,
      );
      root.style.setProperty(CSS_VAR_SAFE_TOP, "env(safe-area-inset-top, 0px)");
      root.style.setProperty(
        CSS_VAR_SAFE_RIGHT,
        "env(safe-area-inset-right, 0px)",
      );
      root.style.setProperty(
        CSS_VAR_SAFE_BOTTOM,
        "env(safe-area-inset-bottom, 0px)",
      );
      root.style.setProperty(
        CSS_VAR_SAFE_LEFT,
        "env(safe-area-inset-left, 0px)",
      );
      root.dataset.viewportWidth = String(Math.round(visibleWidth));
      root.dataset.viewportHeight = String(Math.round(nextVisibleHeight));
    };

    const scheduleApply = (metrics: BrowserViewportMetrics) => {
      pendingMetrics = metrics;
      if (frameId != null) return;
      frameId = window.requestAnimationFrame(applyVars);
    };

    const stopObserving = observeBrowserViewport((observation) => {
      if (!isViewportGeometryObservation(observation.reason)) return;
      scheduleApply(observation.metrics);
    });

    return () => {
      if (frameId != null) window.cancelAnimationFrame(frameId);
      stopObserving();
    };
  }, []);

  return children;
}

function isEditableElementFocused(): boolean {
  const activeElement = document.activeElement;
  if (!(activeElement instanceof HTMLElement)) return false;

  const tagName = activeElement.tagName.toLowerCase();
  return (
    tagName === "input" ||
    tagName === "textarea" ||
    activeElement.isContentEditable
  );
}
