"use client";

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

import { BrowserDetector, Logger, PlatformDetector } from "@/utils";

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 stableLayoutHeight = window.innerHeight;
    let stableVisibleHeight = window.visualViewport?.height ?? window.innerHeight;
    let stableVisibleOffsetTop = window.visualViewport?.offsetTop ?? 0;
    let iosFacebookKeyboardSuppressed = false;
    const isIOSFacebookInAppBrowser =
      PlatformDetector.isIOS() && BrowserDetector.isFacebookInAppBrowser();

    const applyVars = () => {
      frameId = null;
      const root = document.documentElement;
      const viewport = window.visualViewport;
      const layoutHeight = window.innerHeight;
      const layoutWidth = window.innerWidth;
      const visibleHeight = viewport?.height ?? layoutHeight;
      const visibleWidth = viewport?.width ?? layoutWidth;
      const visibleOffsetTop = viewport?.offsetTop ?? 0;
      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 = () => {
      if (frameId != null) return;
      frameId = window.requestAnimationFrame(applyVars);
    };

    applyVars();

    window.addEventListener("resize", scheduleApply);
    window.addEventListener("orientationchange", scheduleApply);
    window.visualViewport?.addEventListener("resize", scheduleApply);
    window.visualViewport?.addEventListener("scroll", scheduleApply);

    return () => {
      if (frameId != null) window.cancelAnimationFrame(frameId);
      window.removeEventListener("resize", scheduleApply);
      window.removeEventListener("orientationchange", scheduleApply);
      window.visualViewport?.removeEventListener("resize", scheduleApply);
      window.visualViewport?.removeEventListener("scroll", scheduleApply);
    };
  }, []);

  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
  );
}
