'use client';

import { type RefObject, useEffect, useRef } from 'react';

import { useChatContextOptional, type ComposerHandle } from '../context';

/** Anything with a `.focus()` method — covers HTMLElement, the
 *  composer's textareaRef from `useChatComposer`, and any custom
 *  imperative handle exposing the same shape. */
export interface Focusable {
  focus: () => void;
}

export interface UseAutoFocusOnStreamEndOptions {
  /** True while an assistant reply is streaming. The hook fires the
   *  focus() call on the true → false transition.
   *
   *  When omitted, the hook reads `isStreaming` from `useChatContext`.
   *  Pass it explicitly only if you're driving stream state from your
   *  own store (cmdop's Wails event bus, for example). */
  isStreaming?: boolean;
  /** Ref / handle to focus when the reply lands.
   *
   *  When omitted, the hook uses the composer handle registered in
   *  the chat context — the built-in `<Composer>` registers itself
   *  automatically, custom composers can opt in via
   *  `useRegisterComposer`. Pass `targetRef` only when you need to
   *  focus something other than the composer (e.g. an "approve"
   *  button, a quick-reply chip). */
  targetRef?: RefObject<Focusable | HTMLElement | null>;
  /** Opt-out. Default true. Pass false to disable without unmounting
   *  the hook (e.g. user preference). */
  enabled?: boolean;
  /** Delay the focus call by this many ms. Default 0 = next animation
   *  frame, which lets the streaming bubble's final commit settle
   *  before focus pulls scroll. Bump to 50-150ms for layouts that
   *  re-mount the composer after the final chunk. */
  delayMs?: number;
}

/**
 * Refocus the chat composer the moment the assistant reply finishes
 * streaming. Standard chat UX: the user types → sends → reads the
 * reply → starts typing again without reaching for the mouse.
 *
 * Default (zero-config) usage — works the moment a `<Composer>` is
 * mounted inside a `<ChatProvider>`:
 *
 *   useAutoFocusOnStreamEnd();
 *
 * Custom composer / advanced wiring:
 *
 *   const ref = useRef<{ focus: () => void } | null>(null);
 *   useAutoFocusOnStreamEnd({ targetRef: ref });
 *
 * Driving stream state yourself:
 *
 *   useAutoFocusOnStreamEnd({ isStreaming: myExternalStreaming });
 *
 * Only the true → false transition fires focus — toggling `enabled`
 * mid-stream won't steal focus while the user is reading.
 */
export function useAutoFocusOnStreamEnd(
  options: UseAutoFocusOnStreamEndOptions = {},
): void {
  const { isStreaming: isStreamingProp, targetRef, enabled = true, delayMs = 0 } = options;
  const ctx = useChatContextOptional();
  // Prefer the prop (caller knows best), fall back to context.
  const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;

  // Keep latest ctx-composer in a ref so the focus effect always
  // sees the freshest registered handle without re-firing when
  // composers re-mount.
  const composerHandleRef = useRef<ComposerHandle | null>(null);
  composerHandleRef.current = ctx?.composer ?? null;

  const prevStreamingRef = useRef(isStreaming);

  useEffect(() => {
    const wasStreaming = prevStreamingRef.current;
    prevStreamingRef.current = isStreaming;

    if (!enabled) return;
    if (!(wasStreaming && !isStreaming)) return;

    const focusNow = () => {
      // Resolve target in priority order: explicit ref > registered
      // composer handle. Refs may carry an HTMLElement (raw DOM) or
      // any object with `.focus()`; both are handled by the same
      // call site below.
      const explicit = targetRef?.current as Focusable | null;
      const target: Focusable | null = explicit ?? composerHandleRef.current;
      target?.focus();
    };

    if (delayMs > 0) {
      const id = window.setTimeout(focusNow, delayMs);
      return () => window.clearTimeout(id);
    }
    const raf = requestAnimationFrame(focusNow);
    return () => cancelAnimationFrame(raf);
  }, [isStreaming, enabled, delayMs, targetRef]);
}

/**
 * Helper for custom composers (anything that's NOT the built-in
 * `<Composer>`) to register their focus() with the chat context so
 * `useAutoFocusOnStreamEnd()` and other consumers work without
 * prop-drilling.
 *
 * Usage inside your custom composer:
 *
 *   const focus = useCallback(() => {
 *     myEditorRef.current?.commands.focus();
 *   }, []);
 *   useRegisterComposer(focus);
 *
 * No-op when called outside a `<ChatProvider>`.
 */
export function useRegisterComposer(focus: () => void): void {
  const ctx = useChatContextOptional();
  const register = ctx?.registerComposer;
  useEffect(() => {
    if (!register) return;
    register({ focus });
    return () => register(null);
  }, [register, focus]);
}
