'use client';

import { useCallback } from 'react';
import type { FieldErrors, FieldValues } from 'react-hook-form';

// react-hook-form's handleSubmit is generic across the input vs.
// transformed types. We don't need the strictness here - the wrapper
// just passes its handler through to whatever handleSubmit the caller
// gave us. Use a loose type so 2-arg and 3-arg useForm() both work.
type AnyHandleSubmit = (
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  onValid: any,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  onInvalid?: any,
) => (e?: React.BaseSyntheticEvent) => Promise<void>;

/**
 * Find the nearest scrolling ancestor of a node - accounts for the
 * common case of forms rendered inside a Sheet / Dialog body that owns
 * its own overflow-y. Falls back to `window` when no ancestor scrolls.
 */
function findScrollContainer(el: HTMLElement | null): HTMLElement | null {
  let cur: HTMLElement | null = el?.parentElement ?? null;
  while (cur) {
    const style = window.getComputedStyle(cur);
    const overflowY = style.overflowY;
    if ((overflowY === 'auto' || overflowY === 'scroll') && cur.scrollHeight > cur.clientHeight) {
      return cur;
    }
    cur = cur.parentElement;
  }
  return null;
}

/**
 * Wrap react-hook-form's `handleSubmit` so validation failures scroll
 * the first errored field into view and focus it. Critical on tall
 * drawers / dialogs where the failing field is below the fold -
 * without this the user is dropped at the top of the form with no
 * indication of what failed.
 *
 * Usage:
 * ```
 * const { handleSubmit, formState: { errors }, ... } = useForm(...);
 * const onSubmit = useFormScrollToError(handleSubmit, errors);
 * <form onSubmit={onSubmit(myHandler)}>...</form>
 * ```
 *
 * `errors` is taken from `formState` so the hook reads the FIRST key
 * (insertion order matches field render order in practice).
 */
export function useFormScrollToError<TFieldValues extends FieldValues>(
  handleSubmit: AnyHandleSubmit,
  errors: FieldErrors<TFieldValues>,
) {
  return useCallback(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (onValid: (data: any) => void | Promise<void>) => {
      return handleSubmit(onValid, () => {
        // react-hook-form calls this on validation failure. We already
        // have `errors` from formState - read the first key and scroll
        // its DOM node into view.
        const firstName = Object.keys(errors)[0];
        if (!firstName) return;
        // Find by `name` first (most input components forward `name`
        // from `register`), then by `id` (fallback for custom Inputs).
        const node =
          (document.querySelector(`[name="${firstName}"]`) as HTMLElement | null) ??
          (document.getElementById(firstName) as HTMLElement | null);
        if (!node) return;
        const container = findScrollContainer(node);
        if (container) {
          // Manually compute position so we scroll inside the
          // container, not the page.
          const cRect = container.getBoundingClientRect();
          const nRect = node.getBoundingClientRect();
          const offset = nRect.top - cRect.top + container.scrollTop - cRect.height / 2;
          container.scrollTo({ top: offset, behavior: 'smooth' });
        } else {
          node.scrollIntoView({ block: 'center', behavior: 'smooth' });
        }
        if (typeof node.focus === 'function') {
          // Defer focus until after the smooth scroll has started so
          // the focus ring is visible.
          window.setTimeout(() => node.focus({ preventScroll: true }), 50);
        }
      });
    },
    [handleSubmit, errors],
  );
}
