import { useEffect, useLayoutEffect, useCallback, useMemo } from 'react';
import type { FiltersMap } from '@wix/bex-core';
import type { EditableTableState } from '../../state/EditableTable';
import { Focusable } from './types';

interface UseCellFocusAndEditingParams<T, F extends FiltersMap> {
  state: EditableTableState<T, F>;
  rowKey: string;
  columnId: string;
  cellRef: React.MutableRefObject<HTMLDivElement | null | undefined>;
  editInputRef: React.MutableRefObject<Focusable | null | undefined>;
}

export function useCellFocusAndEditing<T, F extends FiltersMap>({
  state,
  rowKey,
  columnId,
  cellRef,
  editInputRef,
}: UseCellFocusAndEditingParams<T, F>) {
  const { cellInteraction } = state;
  const column = state._columnMap.get(columnId);
  const resolved = column ? state.resolveCellTypeByColumnId(column.id) : null;
  const isFocused = cellInteraction.isCellFocused(rowKey, columnId);
  const isEditing = cellInteraction.isCellEditing(rowKey, columnId);
  const isEditable = state.isCellEditable(rowKey, columnId);

  // Register the focused cell's DOM element on the state so
  // scrollFocusedCellIntoView can use it. useLayoutEffect ensures
  // the element is set before the requestAnimationFrame in
  // scrollFocusedCellIntoView runs.
  useLayoutEffect(() => {
    if (isFocused) {
      cellInteraction.focusedCellElement = cellRef.current;
    }
    return () => {
      if (cellInteraction.focusedCellElement === cellRef.current) {
        cellInteraction.focusedCellElement = null;
      }
    };
  }, [isFocused, cellRef, cellInteraction]);

  // When focused (not editing): focus the cell div so it receives keyboard events.
  // When editing starts: focus the input, then place caret at end in a setTimeout
  // so it runs after the browser's own selection (e.g. from autoSelect) settles.
  // Skip caret placement if the input already has a non-collapsed selection.
  useEffect(() => {
    if (!isFocused) {
      return;
    }

    if (isEditing) {
      if (
        editInputRef.current &&
        (document.activeElement as
          | HTMLInputElement
          | HTMLTextAreaElement
          | null) !== editInputRef.current
      ) {
        editInputRef.current.focus();
      }
      setTimeout(() => {
        // Use document.activeElement rather than editInputRef.current because
        // editInputRef may point to a React component instance (e.g. InputArea)
        // rather than the underlying DOM element, which has no .value property.
        const input = document.activeElement as
          | HTMLInputElement
          | HTMLTextAreaElement
          | null;
        if (!input || !('selectionStart' in input)) {
          return;
        }
        if (input.selectionStart !== input.selectionEnd) {
          return;
        }
        const len = input.value?.length ?? 0;
        input.setSelectionRange(len, len);
      }, 0);
    } else {
      // Don't steal focus if a descendant already has it (e.g. image cell's
      // inner-focus buttons). The gridcell only needs focusing when it isn't
      // already within the active focus subtree.
      if (!cellRef.current?.contains(document.activeElement)) {
        cellRef.current?.focus({ preventScroll: true });
      }
    }
  }, [isFocused, isEditing, cellRef, editInputRef]);

  const handleActivate = useCallback(() => {
    if (resolved?.toggleValue) {
      state.cellValue.toggleCell(rowKey, columnId);
    } else {
      cellInteraction.startEdit();
    }
  }, [resolved, state, rowKey, columnId, cellInteraction]);

  // Handle Enter and type-to-edit at the cell level
  const onKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (!isFocused || !isEditable || isEditing) {
        return;
      }
      if (state.updateItemDisabled) {
        return;
      }

      if (e.key === 'Enter') {
        e.preventDefault();
        if (resolved?.showEditWhenFocused) {
          // EditComponent is always visible. Focus into its first interactive
          // element (inner focus) rather than entering full edit mode now —
          // the cell type calls onStartEdit() itself right before any external
          // UI (media manager / modal) opens, which is the only time isEditing
          // needs to be true for useClearFocusOnBlur protection.
          editInputRef.current?.focus();
        } else {
          handleActivate();
        }
        return;
      }

      // Type-to-edit: prevent the browser's default text insertion, start
      // edit mode, then set the value programmatically via React state.
      // startEdit() must come before setEditingValue() because it resets
      // editingValue to undefined.
      if (
        e.key.length === 1 &&
        !e.ctrlKey &&
        !e.metaKey &&
        !e.altKey &&
        !e.nativeEvent.isComposing
      ) {
        // Cell-type-level gate: only cell types that define onTypeToEdit support
        // type-to-edit. Absence of onTypeToEdit is the natural opt-out (e.g. boolean).
        if (!resolved?.onTypeToEdit) {
          return;
        }

        // Key-level gate: onTypeToEdit returning undefined means "block this key"
        // (e.g. number blocks non-digit keys).
        const typeResult = resolved.onTypeToEdit({
          key: e.key,
          validation: column?.validation,
        });
        if (typeResult === undefined) {
          return;
        }

        e.preventDefault();
        cellInteraction.startEdit(undefined, undefined, {
          kind: 'type',
          key: e.key,
        });

        if ('value' in typeResult) {
          // Cell type provides an explicit pre-populated value (e.g. first typed
          // char for text, or the digit for number).
          cellInteraction.setEditingValue(typeResult.value);
        }
        // onTypeToEdit returned {} (no value): enter edit mode with current value intact
        // (e.g. select/date open their picker without pre-populating).

        // Best-effort: editInputRef.current is null here because the Edit
        // component only mounts after isEditing flips. The useEffect above
        // is the reliable focus path once the component renders.
        editInputRef.current?.focus();
        e.stopPropagation();
      }
    },
    [
      editInputRef,
      isFocused,
      isEditable,
      isEditing,
      state,
      cellInteraction,
      resolved,
      handleActivate,
    ],
  );

  return useMemo(() => ({ onKeyDown }), [onKeyDown]);
}
