/**
 * FocusableInput — spatial-navigation aware text input.
 *
 * REFACTORED (v1.4.5) — no longer intercepts arrow keys with
 * explicit `setFocus(target)` calls. That approach was brittle: when
 * the explicit target was unfocusable (e.g. the "Sign In" button was
 * disabled because the user hadn't typed yet) the call failed
 * silently AND we'd already called `stopPropagation`, so norigin's
 * own spatial-nav listener never ran — focus died.
 *
 * The new model is simpler and trusts norigin to do its job:
 *
 *   • OK / Enter on the wrapper hands native DOM focus to the inner
 *     `<input>` so the user can start typing.
 *   • While the input has DOM focus, the user types normally.
 *   • Pressing Up / Down / Enter while typing simply BLURS the
 *     input (so the caret stops eating arrow keys) and lets
 *     norigin's global keydown listener pick the next focusable in
 *     that direction — same as if focus were on a regular button.
 *   • Left / Right stay inside the field for caret movement, except
 *     at the caret boundaries (so D-pad users on TV can leave
 *     horizontally without having to scroll the entire word).
 *
 * Because we no longer call `setFocus` explicitly, **every action
 * button on the form must be a registered FocusableButton with
 * `focusable: true`** (true by default in v1.4.5) — otherwise
 * norigin won't find it.
 */
import {
  useEffect,
  useImperativeHandle,
  forwardRef,
  type ReactNode,
  type Ref,
} from "react";
import {
  useFocusable,
  setFocus as spatialSetFocus,
} from "@noriginmedia/norigin-spatial-navigation";
import { playMoveSound, playSelectSound } from "@/lib/navSounds";

export interface FocusableInputProps {
  focusKey: string;
  type?: "text" | "password" | "tel" | "email" | "url";
  value: string;
  onChange: (v: string) => void;
  placeholder?: string;
  icon?: ReactNode;
  testId?: string;
  /** Programmatically focus the inner <input> on mount (Login screen). */
  autoFocusOnMount?: boolean;
  autoComplete?: string;
  inputMode?: "numeric" | "decimal" | "text" | "search" | "tel" | "email" | "url";
  maxLength?: number;
  /**
   * When true, the inner `<input>` grabs native DOM focus AS SOON
   * AS spatial nav lands on the wrapper (no OK / Enter required).
   * This is the canonical Smart-TV pattern: the on-screen soft
   * keyboard pops up automatically, saving one remote-press per
   * field. Off by default to preserve the "navigate first, type
   * later" behaviour on desktop.
   */
  prefersDOMFocusOnSelect?: boolean;
  /** Triggered when user presses Enter inside the input (typically: submit). */
  onSubmit?: () => void;
  /**
   * @deprecated since v1.4.5 — kept for API compatibility with
   * existing call-sites. Norigin's spatial nav picks the next
   * focusable based on layout; explicit targets caused dead-focus
   * bugs when the target was disabled.
   */
  onArrowDownFocusKey?: string;
  /** @deprecated since v1.4.5 — see `onArrowDownFocusKey`. */
  onArrowUpFocusKey?: string;
  /** @deprecated since v1.4.5 — see `onArrowDownFocusKey`. */
  onArrowRightFocusKey?: string;
  /** @deprecated since v1.4.5 — see `onArrowDownFocusKey`. */
  onArrowLeftFocusKey?: string;
  /** Extra className for the wrapper. */
  className?: string;
  /** Extra className for the inner <input>. */
  inputClassName?: string;
  /** Tailwind utility added when the wrapper is the focused spatial node. */
  focusClassName?: string;
}

export interface FocusableInputHandle {
  focusInput: () => void;
  blurInput: () => void;
}

const FocusableInputImpl = (
  {
    focusKey,
    type = "text",
    value,
    onChange,
    placeholder,
    icon,
    testId,
    autoFocusOnMount,
    autoComplete,
    inputMode,
    maxLength,
    prefersDOMFocusOnSelect,
    // Deprecated explicit-target props (v1.4.5) — accepted for API
    // compatibility but ignored. Spatial nav now relies on layout.
    onArrowDownFocusKey: _onDown,
    onArrowUpFocusKey: _onUp,
    onArrowLeftFocusKey: _onLeft,
    onArrowRightFocusKey: _onRight,
    onSubmit,
    className = "",
    inputClassName = "",
    focusClassName = "ring-2 ring-primary/70 shadow-[0_0_28px_hsla(40,80%,55%,0.35)] border-primary/50",
  }: FocusableInputProps,
  ref: Ref<FocusableInputHandle>,
) => {
  const {
    ref: wrapperRef,
    focused,
    focusKey: resolvedFocusKey,
  } = useFocusable({
    focusKey,
    onEnterPress: () => {
      playSelectSound();
      const input = (
        wrapperRef.current as HTMLDivElement | null
      )?.querySelector("input") as HTMLInputElement | null;
      input?.focus();
    },
    onFocus: () => {
      playMoveSound();
      // Smart-TV soft-keyboard pattern: grab native focus on the
      // inner <input> the instant spatial nav lands on us so the
      // OS keyboard pops up without a separate OK press.
      if (prefersDOMFocusOnSelect) {
        const input = (
          wrapperRef.current as HTMLDivElement | null
        )?.querySelector("input") as HTMLInputElement | null;
        // Defer one frame so norigin's state has settled.
        window.setTimeout(() => {
          try {
            input?.focus({ preventScroll: false });
          } catch {
            /* ignore */
          }
        }, 0);
      }
    },
  });

  useImperativeHandle(
    ref,
    () => ({
      focusInput: () => {
        const input = (
          wrapperRef.current as HTMLDivElement | null
        )?.querySelector("input") as HTMLInputElement | null;
        input?.focus();
      },
      blurInput: () => {
        const input = (
          wrapperRef.current as HTMLDivElement | null
        )?.querySelector("input") as HTMLInputElement | null;
        input?.blur();
      },
    }),
    [wrapperRef],
  );

  // Optional auto-focus on mount (first input on a form).
  useEffect(() => {
    if (!autoFocusOnMount) return;
    const tryFocus = () => {
      try {
        if (resolvedFocusKey) spatialSetFocus(resolvedFocusKey);
      } catch {
        /* spatial nav not ready */
      }
      const input = (
        wrapperRef.current as HTMLDivElement | null
      )?.querySelector("input") as HTMLInputElement | null;
      const active = document.activeElement;
      const lost = !active || active === document.body;
      if (lost && input) {
        try {
          input.focus({ preventScroll: false });
        } catch {
          /* ignore */
        }
      }
    };
    const t1 = window.setTimeout(tryFocus, 100);
    const t2 = window.setTimeout(tryFocus, 400);
    return () => {
      window.clearTimeout(t1);
      window.clearTimeout(t2);
    };
  }, [autoFocusOnMount, resolvedFocusKey, wrapperRef]);

  /**
   * Minimal keydown handler.
   *   • Up / Down → blur and let norigin's global listener
   *     spatial-navigate. We `preventDefault` so the caret doesn't
   *     also jump, but we do NOT `stopPropagation` — norigin needs
   *     the event.
   *   • Enter   → optional `onSubmit`, then blur+propagate so norigin
   *     can step down to the primary action button.
   *   • Left / Right → escape only when caret is at the boundary so
   *     remote-control users can leave the field horizontally.
   *
   * NO `setFocus(target)` calls here — norigin's spatial algorithm
   * picks the right next element based on layout. This avoids the
   * "explicit target unfocusable → focus dies" trap from v1.4.4.
   */
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    const inputEl = e.currentTarget;
    const caretAtStart = inputEl.selectionStart === 0;
    const caretAtEnd = inputEl.selectionEnd === inputEl.value.length;

    if (e.key === "ArrowDown" || e.key === "ArrowUp") {
      e.preventDefault();
      inputEl.blur();
      return;
    }
    if (e.key === "Enter") {
      e.preventDefault();
      inputEl.blur();
      if (onSubmit) onSubmit();
      return;
    }
    if (e.key === "ArrowLeft" && caretAtStart) {
      e.preventDefault();
      inputEl.blur();
      return;
    }
    if (e.key === "ArrowRight" && caretAtEnd) {
      e.preventDefault();
      inputEl.blur();
      return;
    }
  };

  return (
    <div
      ref={wrapperRef as unknown as React.Ref<HTMLDivElement>}
      data-focused={focused || undefined}
      onMouseEnter={() => {
        if (resolvedFocusKey) {
          try {
            spatialSetFocus(resolvedFocusKey);
          } catch {
            /* ignore */
          }
        }
      }}
      onClick={() => {
        const input = (
          wrapperRef.current as HTMLDivElement | null
        )?.querySelector("input") as HTMLInputElement | null;
        input?.focus();
      }}
      className={`flex items-center gap-3 glass-input rounded-xl px-4 py-3 transition-all duration-200 ${
        focused ? focusClassName : ""
      } ${className}`}
    >
      {icon}
      <input
        data-testid={testId}
        type={type}
        placeholder={placeholder}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onKeyDown={handleKeyDown}
        autoComplete={autoComplete}
        inputMode={inputMode}
        maxLength={maxLength}
        className={`flex-1 min-w-0 bg-transparent text-foreground placeholder:text-muted-foreground outline-none text-sm ${inputClassName}`}
      />
    </div>
  );
};

export const FocusableInput = forwardRef<
  FocusableInputHandle,
  FocusableInputProps
>(FocusableInputImpl);
FocusableInput.displayName = "FocusableInput";

export default FocusableInput;
