import { useEffect } from "react";

/**
 * Global focus handler that keeps the focused field visible above the
 * iOS soft keyboard inside any `[role="dialog"]`.
 *
 * Why: `index.css` already promotes any focused dialog to a full-width
 * sheet sized to `100dvh` (the dynamic viewport that iOS shrinks above
 * the keyboard). But Safari does NOT auto-scroll the focused input
 * into view inside the dialog's own overflow container — it only tries
 * to scroll the document, which our `position: fixed` dialog ignores.
 * The field therefore stays wherever it sat in the dialog, often
 * behind the keyboard.
 *
 * Fix: when focus lands on an editable inside a `[role="dialog"]`,
 * walk up to the nearest scrollable ancestor (overflow-y auto/scroll)
 * inside the dialog and manually set its `scrollTop` so the target
 * sits in the middle of the visible band. We do NOT rely on
 * `scrollIntoView` because Safari iPadOS ignores it for descendants
 * of `position: fixed` containers — the scroll never happens. We run
 * the reveal twice (300ms / 600ms) to cover the iPad keyboard
 * animation window and the dialog's own `max-height: 100dvh` recalc.
 *
 * One global listener covers every dialog kind we use (Radix
 * DialogContent, the bespoke AdminFormDialog, ad-hoc role="dialog"
 * wrappers) without touching their JSX.
 */
export function useScrollFocusedDialogField(): void {
  useEffect(() => {
    // Gate to devices with any touch input. `pointer: coarse` alone
    // misses iPads connected to a Magic Keyboard / trackpad (their
    // *primary* pointer is reported as fine, so coarse never
    // matches), which is exactly the device class hitting this bug.
    // `any-pointer: coarse` catches "has touch at all", which is the
    // condition under which a soft keyboard can appear.
    if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
    const hasTouch =
      window.matchMedia("(pointer: coarse)").matches ||
      window.matchMedia("(any-pointer: coarse)").matches;
    if (!hasTouch) return;

    const isEditable = (el: Element | null): el is HTMLElement => {
      if (!el || !(el instanceof HTMLElement)) return false;
      const tag = el.tagName;
      if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
      return el.isContentEditable;
    };

    // Walk up from `target` to find the nearest ancestor that actually
    // scrolls vertically. Stops at the dialog root (we never want to
    // scroll outside the dialog). Falls back to the dialog itself if
    // no scrollable inner container exists.
    const findScrollableAncestor = (
      target: HTMLElement,
      dialog: Element,
    ): HTMLElement => {
      let node: HTMLElement | null = target.parentElement;
      while (node && node !== dialog) {
        const style = window.getComputedStyle(node);
        const oy = style.overflowY;
        if ((oy === "auto" || oy === "scroll") && node.scrollHeight > node.clientHeight) {
          return node;
        }
        node = node.parentElement;
      }
      return dialog as HTMLElement;
    };

    const scrollTimers = new Set<number>();

    const onFocusIn = (e: FocusEvent) => {
      const target = e.target as HTMLElement | null;
      if (!isEditable(target)) return;
      const dialog = target.closest('[role="dialog"]');
      if (!dialog) return;

      const reveal = () => {
        const container = findScrollableAncestor(target, dialog);
        const cRect = container.getBoundingClientRect();
        const tRect = target.getBoundingClientRect();
        // Distance from the container's top to the target's top in
        // *content* coordinates: current scrollTop + (target top in
        // viewport - container top in viewport).
        const targetTopInContent = container.scrollTop + (tRect.top - cRect.top);
        // Centre the target within the visible band of the container.
        const desired = targetTopInContent - (container.clientHeight - tRect.height) / 2;
        const max = container.scrollHeight - container.clientHeight;
        container.scrollTop = Math.max(0, Math.min(desired, max));
      };

      const schedule = (delay: number) => {
        const id = window.setTimeout(() => {
          scrollTimers.delete(id);
          reveal();
        }, delay);
        scrollTimers.add(id);
        return id;
      };
      const t1 = schedule(300);
      const t2 = schedule(600);

      const onBlur = () => {
        window.clearTimeout(t1);
        window.clearTimeout(t2);
        scrollTimers.delete(t1);
        scrollTimers.delete(t2);
        target.removeEventListener("blur", onBlur);
      };
      target.addEventListener("blur", onBlur, { once: true });
    };

    document.addEventListener("focusin", onFocusIn);
    return () => {
      document.removeEventListener("focusin", onFocusIn);
      for (const t of scrollTimers) window.clearTimeout(t);
      scrollTimers.clear();
    };
  }, []);
}
