import { type RefObject, useCallback, useRef } from "react";
import type { AnatomySegment } from "./types";

const HIGHLIGHT_DURATION_MS = 1200;
/**
 * Cap on the number of animation frames we are willing to wait
 * before giving up on finding the target row. Each frame is
 * ~16ms at 60Hz, so 12 frames gives ~200ms — enough headroom for
 * the cascade of `useEffect` -> `setExpanded` calls that fires
 * for each ancestor on the way down to the target.
 */
const MAX_HIGHLIGHT_ATTEMPTS = 12;

/**
 * Build a `jumpToSegment` callback that:
 *
 * 1. Optionally switches the active tab to the Request tab (so the
 *    target row is in the DOM and visible).
 * 2. Asks the JSON viewer to expand the path's ancestors via the
 *    `expandToPath` prop (which is reset to `null` shortly after).
 * 3. Polls via `requestAnimationFrame` for the target row to appear
 *    (the cascade of `useEffect` -> `setExpanded` calls needs
 *    multiple frames for deep paths), then scrolls the matching
 *    row into view and applies a 1.2 s primary-color ring
 *    highlight via a temporary class toggle.
 */
export function useAnatomyJump(options: {
  /** Ref to the DOM root that contains the JSON tree rows. */
  containerRef: RefObject<HTMLElement | null>;
  /** Setter for the `JsonViewer`'s `expandToPath` prop. */
  setExpandToPath: (path: string | null) => void;
  /**
   * Optional hook called before the DOM lookup so the caller can
   * make the Request tab active. The hook should be synchronous
   * (it triggers a React state update which is flushed by the
   * subsequent `requestAnimationFrame`).
   */
  ensureTabActive?: () => void;
  highlightMs?: number;
}): (segment: AnatomySegment) => void {
  const { containerRef, setExpandToPath, ensureTabActive, highlightMs } = options;
  const highlightTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  return useCallback(
    (segment: AnatomySegment) => {
      ensureTabActive?.();

      // Trigger the JSON viewer to expand ancestors of this path.
      // We pass the same path; the JsonViewer resets the prop via
      // the caller clearing the state after the scroll lands.
      setExpandToPath(segment.path);

      const applyHighlight = (target: HTMLElement): void => {
        target.scrollIntoView({ behavior: "smooth", block: "center" });
        target.classList.add(
          "ring-2",
          "ring-primary/60",
          "ring-offset-1",
          "ring-offset-background",
          "rounded-sm",
          "transition-shadow",
        );
        if (highlightTimer.current !== null) clearTimeout(highlightTimer.current);
        highlightTimer.current = setTimeout(() => {
          target.classList.remove(
            "ring-2",
            "ring-primary/60",
            "ring-offset-1",
            "ring-offset-background",
            "rounded-sm",
            "transition-shadow",
          );
          // Clear the expand signal AFTER the highlight fades so the
          // user sees the tree in its final (expanded) state, not
          // suddenly collapsing the moment we release control.
          setExpandToPath(null);
        }, highlightMs ?? HIGHLIGHT_DURATION_MS);
      };

      const escape = (value: string): string => {
        if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
          return CSS.escape(value);
        }
        return value.replace(/(["'\\[\](){}])/g, "\\$1");
      };
      const selector = `[data-anatomy-path="${escape(segment.path)}"]`;

      const tryFindTarget = (attemptsLeft: number): void => {
        const container = containerRef.current;
        if (container !== null) {
          const target = container.querySelector<HTMLElement>(selector);
          if (target !== null) {
            applyHighlight(target);
            return;
          }
        }
        if (attemptsLeft > 0) {
          window.requestAnimationFrame(() => tryFindTarget(attemptsLeft - 1));
        } else {
          // Path not in the DOM after a generous wait — reset state
          // and bail. This is the right behavior for malformed paths
          // (e.g. when the format adapter returns a path that does
          // not exist in the current request body).
          setExpandToPath(null);
        }
      };

      // Wait one frame for the tab-switch / expandToPath state
      // update to commit before we start probing the DOM.
      window.requestAnimationFrame(() => tryFindTarget(MAX_HIGHLIGHT_ATTEMPTS));
    },
    [containerRef, ensureTabActive, highlightMs, setExpandToPath],
  );
}
