/**
 * Input Layer System — stack-based keyboard input capture for dialogs and overlays.
 *
 * Only "capturing" components (dialogs, overlays, import flows) register layers.
 * When any layer exists on the stack, all non-capturing input handlers are blocked.
 *
 * Hooks:
 * - `useInputLayer(id)`    — push a capturing layer (dialogs/overlays).
 * - `useLayeredInput(id, …)` — handle input for a specific capturing layer.
 * - `useBlockableInput(…)` — handle input for screens / global keys; auto-blocked
 *                             when any capturing layer is on the stack.
 * - `useIsInputCaptured()` — returns true when a capturing layer is present
 *                             (useful for disabling `focus` on child components).
 */

import React, {
  createContext,
  useContext,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from 'react';
import { useInput } from 'ink';

// ── Context ──────────────────────────────────────────────────────────────────

interface InputLayerContextType {
  /** Push a capturing layer. Returns a cleanup that pops it. */
  push: (layerId: string) => () => void;
  /** True when `layerId` is the topmost entry in the stack. */
  isTop: (layerId: string) => boolean;
  /** True when the stack has no entries (no dialog/overlay is capturing). */
  isStackEmpty: () => boolean;
  /** Monotonic counter — bumped on every push/pop so consumers re-render. */
  version: number;
}

const InputLayerContext = createContext<InputLayerContextType | null>(null);

// ── Provider ─────────────────────────────────────────────────────────────────

interface InputLayerProviderProps {
  children: ReactNode;
}

/**
 * Wraps the component tree and provides the input-layer stack.
 *
 * Place this inside your outermost providers but above any component
 * that calls the input-layer hooks.
 */
export function InputLayerProvider({ children }: InputLayerProviderProps): React.ReactElement {
  const stackRef = useRef<string[]>([]);
  const [version, setVersion] = useState(0);

  const bump = useCallback(() => setVersion((v) => v + 1), []);

  const push = useCallback(
    (layerId: string): (() => void) => {
      stackRef.current = [...stackRef.current, layerId];
      bump();
      return () => {
        stackRef.current = stackRef.current.filter((id) => id !== layerId);
        bump();
      };
    },
    [bump],
  );

  const isTop = useCallback(
    (layerId: string): boolean => {
      const s = stackRef.current;
      return s.length > 0 && s[s.length - 1] === layerId;
    },
    [],
  );

  const isStackEmpty = useCallback(
    (): boolean => stackRef.current.length === 0,
    [],
  );

  const value = useMemo<InputLayerContextType>(
    () => ({ push, isTop, isStackEmpty, version }),
    [push, isTop, isStackEmpty, version],
  );

  return (
    <InputLayerContext.Provider value={value}>
      {children}
    </InputLayerContext.Provider>
  );
}

// ── Hooks ────────────────────────────────────────────────────────────────────

/**
 * Register a **capturing** layer (dialog / overlay / import flow).
 *
 * Pushes on mount, pops on unmount. While this layer is present every
 * `useBlockableInput` handler in the tree is automatically disabled.
 *
 * @returns `{ isActive }` — true only when this layer is the topmost.
 */
export function useInputLayer(layerId: string): { isActive: boolean } {
  const ctx = useContext(InputLayerContext);
  if (!ctx) {
    throw new Error('useInputLayer must be used within an InputLayerProvider');
  }

  const { push } = ctx;
  useEffect(() => {
    const pop = push(layerId);
    return pop;
  }, [push, layerId]);

  return { isActive: ctx.isTop(layerId) };
}

/**
 * Input handler for a **capturing** layer.
 *
 * Only fires when `layerId` is the topmost entry in the stack.
 */
export function useLayeredInput(
  layerId: string,
  handler: (input: string, key: any) => void,
  options?: { isActive?: boolean },
): void {
  const ctx = useContext(InputLayerContext);
  if (!ctx) {
    throw new Error('useLayeredInput must be used within an InputLayerProvider');
  }

  const isTopLayer = ctx.isTop(layerId);
  const externalActive = options?.isActive !== false;
  useInput(handler, { isActive: isTopLayer && externalActive });
}

/**
 * Input handler for **non-capturing** components (screens, global keys).
 *
 * Fires only when the capture stack is empty (no dialog/overlay is open).
 * This is the hook screens should use instead of raw `useInput`.
 */
export function useBlockableInput(
  handler: (input: string, key: any) => void,
  options?: { isActive?: boolean },
): void {
  const ctx = useContext(InputLayerContext);

  const nothingCapturing = ctx ? ctx.isStackEmpty() : true;
  const externalActive = options?.isActive !== false;
  useInput(handler, { isActive: nothingCapturing && externalActive });
}

/**
 * Returns `true` when any capturing layer is on the stack.
 *
 * Use this to disable `focus` props on child components (e.g. ScrollableList)
 * so their internal `useInput` handlers don't fire while a dialog is open.
 */
export function useIsInputCaptured(): boolean {
  const ctx = useContext(InputLayerContext);
  return ctx ? !ctx.isStackEmpty() : false;
}
