// Copyright (c) Meta Platforms, Inc. and affiliates.

'use client';

/**
 * @file useInputContainer.ts
 * @input Container ref, input ref
 * @output onClick and onMouseUp handlers for the input container wrapper
 * @position Hook for input wrapper containers that delegate focus to the inner input/textarea
 *
 * Uses useClickableContainer to safely handle nested interactive elements
 * (clear buttons, calendar toggles, etc.) while focusing the input when
 * clicking non-interactive areas (icons, padding, status indicators).
 */

import {useCallback, type RefObject} from 'react';
import {useClickableContainer} from './useClickableContainer';

/**
 * Input types that should receive `.focus()` when the container is clicked.
 * Other input types (e.g. checkbox, radio, file) use `.click()` instead.
 */
const FOCUS_INPUT_TYPES = new Set([
  'text',
  'password',
  'email',
  'number',
  'search',
  'tel',
  'url',
  'date',
  'datetime-local',
  'month',
  'time',
  'week',
]);

/**
 * `aria-haspopup` values that advertise a control opens a popup on activation.
 * https://www.w3.org/TR/wai-aria-1.2/#aria-haspopup
 */
const HASPOPUP_VALUES = new Set([
  'true',
  'menu',
  'listbox',
  'tree',
  'grid',
  'dialog',
]);

/**
 * Whether an element is a popup trigger — a combobox or any control that
 * advertises `aria-haspopup`. Such controls activate their popup on *click*
 * (e.g. DateInput's `<input type="text" role="combobox" aria-haspopup="dialog">`
 * opens its calendar via `onClick`, with no `onFocus` opener). Forwarding a
 * container click to `.focus()` would focus the control but leave the popup
 * closed — so we `.click()` these instead. Plain text inputs (no popup) are
 * unaffected and keep `.focus()`.
 */
function isPopupTrigger(el: HTMLElement): boolean {
  if (el.getAttribute('role') === 'combobox') {
    return true;
  }
  const haspopup = el.getAttribute('aria-haspopup');
  return haspopup != null && HASPOPUP_VALUES.has(haspopup);
}

export interface UseInputContainerOptions {
  /** Ref to the outer container/wrapper element */
  containerRef: RefObject<HTMLElement | null>;
  /** Ref to the inner input or textarea element */
  inputRef: RefObject<
    HTMLInputElement | HTMLTextAreaElement | HTMLElement | null
  >;
  /** Whether the input is disabled */
  disabled?: boolean;
}

/**
 * Hook that makes an input container wrapper clickable, delegating focus
 * to the inner input/textarea when the user clicks non-interactive areas
 * (icons, padding, status indicators).
 *
 * Nested interactive elements (clear buttons, links) are handled safely
 * via useClickableContainer — clicking them does NOT steal focus.
 *
 * @compositionHint Use inside input wrapper components (TextInput,
 * NumberInput, TimeInput, TextArea, etc.).
 *
 * @example
 * ```
 * const containerRef = useRef<HTMLDivElement>(null);
 * const inputRef = useRef<HTMLInputElement>(null);
 * const { onClick, onMouseUp } = useInputContainer({
 *   containerRef,
 *   inputRef,
 * });
 * return (
 *   <div ref={containerRef} onClick={onClick} onMouseUp={onMouseUp}>
 *     <Icon icon="search" />
 *     <input ref={inputRef} />
 *   </div>
 * );
 * ```
 */
export function useInputContainer({
  containerRef,
  inputRef,
  disabled = false,
}: UseInputContainerOptions) {
  const onClick = useCallback(() => {
    const input = inputRef.current;
    if (input == null) {
      return;
    }
    // Popup triggers (combobox / aria-haspopup) activate their popup on
    // *click*, not focus — so check this BEFORE the type check, otherwise a
    // `type="text"` combobox (DateInput) would `.focus()` and never open.
    if (input instanceof HTMLElement && isPopupTrigger(input)) {
      input.click();
    } else if (
      input instanceof HTMLInputElement &&
      FOCUS_INPUT_TYPES.has(input.type)
    ) {
      input.focus();
    } else if (input instanceof HTMLTextAreaElement) {
      input.focus();
    } else if (input instanceof HTMLElement) {
      input.click();
    } else if ('focus' in input) {
      (input as unknown as {focus: () => void}).focus();
    }
  }, [inputRef]);

  return useClickableContainer({
    containerRef,
    interactiveRef: inputRef,
    onClick,
    disabled,
  });
}
