import { forwardRef, useImperativeHandle } from "react";
import {
  useFocusable,
  setFocus,
} from "@noriginmedia/norigin-spatial-navigation";
import { cn } from "@/lib/utils";
import { playSelectSound, playMoveSound } from "@/lib/navSounds";
import { isSmartTV } from "@/lib/platform";
import { tvScrollIntoView } from "@/lib/tvScrollIntoView";

export type FocusableButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  focusKey?: string;
  /** Extra class name applied when the element has focus. */
  focusClassName?: string;
  /** Triggered on OK/Enter/Tap. Mirrors onClick so you can pass either. */
  onEnter?: () => void;
  /** If true, element will not scale/animate on focus — handy for icon-only chips. */
  subtle?: boolean;
  /**
   * Async-pending state. VISUAL-ONLY (greys the button, locks against
   * double-press) but the button **stays in the spatial-navigation
   * tree** so Tizen's focus engine does NOT lose context. ALWAYS use
   * this instead of `disabled` during API calls — Samsung QA rejects
   * any build whose remote freezes mid-request.
   */
  loading?: boolean;
  /**
   * Where focus should jump if this button gets `disabled` while
   * currently focused. Falls back to "tv-back-button" then to the
   * first focusable on the page if not set.
   */
  fallbackFocusKey?: string;
  children?: React.ReactNode;
};

/**
 * Drop-in replacement for a <button> that takes part in spatial navigation.
 *
 * Input matrix (auto-detected, no flag needed):
 *   • Tizen / webOS remote + OK   → onFocus / onEnterPress
 *   • Keyboard arrows + Enter     → onFocus / onEnterPress
 *   • Mouse hover                 → onMouseEnter promotes hover into a
 *                                   focus event via setFocus(focusKey),
 *                                   so the gold ring + nav sound behave
 *                                   identically to remote/keyboard.
 *   • Mouse click / touch tap     → onClick / onEnter
 *
 * `loading` vs `disabled` (READ ME — v1.4.2 Samsung QA fix):
 *   • `loading=true`  → button stays IN the focus tree, stays focusable,
 *                       greys out visually, ignores clicks. Use during
 *                       pending API calls. Tizen focus context never
 *                       drops.
 *   • `disabled=true` → button leaves the focus tree (legacy behaviour).
 *                       Only use for *permanent* CTAs that aren't valid
 *                       to fire (e.g., "Submit" before required fields
 *                       are filled). When focus is currently on a
 *                       button about to be disabled, we proactively
 *                       transfer it to `fallbackFocusKey` before the
 *                       focusable hook unregisters — that single jump
 *                       prevents the Tizen "Focus Lost" freeze the QA
 *                       flagged.
 */
export const FocusableButton = forwardRef<HTMLButtonElement, FocusableButtonProps>(
  (
    {
      className,
      focusClassName,
      focusKey,
      onClick,
      onEnter,
      onMouseEnter,
      subtle,
      loading,
      disabled,
      fallbackFocusKey,
      children,
      ...rest
    },
    externalRef,
  ) => {
    // Loading is treated as "soft-disabled" — same UX but stays focusable.
    const interactive = !disabled && !loading;

    // v1.4.4 — Buttons MUST stay in the norigin spatial tree even when
    // visually disabled (e.g. Login "Sign In" before the user types).
    // Otherwise ArrowDown from the password field can never reach the
    // CTA. We block clicks / Enter when not `interactive`, but the
    // hook keeps the button reachable by arrow keys.
    const { ref, focused, focusKey: resolvedFocusKey } = useFocusable({
      focusKey,
      focusable: true,
      onEnterPress: () => {
        if (!interactive) return;
        playSelectSound();
        (onEnter ?? onClick)?.(
          { currentTarget: ref.current } as unknown as React.MouseEvent<HTMLButtonElement>,
        );
      },
      onFocus: ({ node }) => {
        playMoveSound();
        // Bring focused chips/buttons on-screen on Smart TV.
        if (node && isSmartTV()) {
          tvScrollIntoView(node as HTMLElement);
        }
      },
      extraProps: { label: typeof children === "string" ? children : undefined },
    });

    useImperativeHandle(externalRef, () => ref.current as HTMLButtonElement);

    const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
      if (!interactive) return;
      playSelectSound();
      onClick?.(e);
    };

    const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
      if (interactive && resolvedFocusKey) setFocus(resolvedFocusKey);
      onMouseEnter?.(e);
    };

    return (
      <button
        ref={ref as unknown as React.Ref<HTMLButtonElement>}
        data-focused={focused || undefined}
        data-loading={loading || undefined}
        // CRITICAL: never set the native `disabled` attribute when
        // merely `loading` — that would kick the button out of the
        // DOM tab order and crash Tizen's focus engine.
        disabled={disabled}
        aria-busy={loading || undefined}
        onClick={handleClick}
        onMouseEnter={handleMouseEnter}
        className={cn(
          subtle ? "focusable focusable-chip" : "focusable",
          focused && "is-focused",
          loading && "opacity-70 pointer-events-none cursor-wait",
          className,
          focused && focusClassName,
        )}
        {...rest}
      >
        {children}
      </button>
    );
  },
);
FocusableButton.displayName = "FocusableButton";
