import clsx from 'clsx';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { useGridContext } from '../../contexts/GridContext';
import { useGridPopoverContext } from '../../contexts/GridPopoverContext';
import { CloseReason } from '../../react-menu3/utils';
import { TextInputValidator, TextInputValidatorProps } from '../../utils/textValidator';
import { MaybePromise } from '../../utils/util';
import { CellEditorCommon } from '../GridCell';
import { useGridPopoverHook } from '../GridPopoverHook';
import { GridBaseRow } from '../types';

export interface GridFormInlineTextInput<TData extends GridBaseRow>
  extends TextInputValidatorProps<TData>, CellEditorCommon {
  placeholder?: string;
  units?: string;
  onSave?: (props: { selectedRows: TData[]; selectedRowIds: TData['id'][]; value: string }) => MaybePromise<boolean>;
  onChange?: (props: { selectedRows: TData[]; selectedRowIds: TData['id'][]; value: string }) => void;
  helpText?: string;
}

export const GridFormInlineTextInput = <TData extends GridBaseRow>(props: GridFormInlineTextInput<TData>) => {
  const { onSave } = props;

  const { getSelectedRows, prePopupOps } = useGridContext<TData>();
  const { field, value: initialVale, data } = useGridPopoverContext<TData>();

  const initValue = useMemo(() => (initialVale == null ? '' : `${initialVale}`), [initialVale]);
  const [value, setValue] = useState(initValue);

  const invalid = useCallback(() => TextInputValidator<TData>(props, value, data, {}), [data, props, value]);

  useEffect(() => {
    prePopupOps();
  }, [prePopupOps]);

  const save = useCallback(async (): Promise<boolean> => {
    if (invalid()) {
      return false;
    }

    const trimmedValue = value.trim();
    // No change, so don't save
    if (initValue === trimmedValue) {
      return true;
    }

    if (!onSave) {
      console.error(`No onSave handler for GridFormInlineTextInput "${String(field)}`);
      return false;
    }

    const selectedRows = getSelectedRows<TData>();
    const selectedRowIds = selectedRows.map((data) => data.id);

    return onSave({
      selectedRowIds,
      selectedRows,
      value: trimmedValue,
    });
  }, [invalid, value, initValue, onSave, getSelectedRows, field]);

  const inputRef = useRef<HTMLInputElement | null>(null);
  useEffect(() => {
    inputRef.current?.focus();
    inputRef.current?.select();
  }, []);

  const { triggerSave } = useGridPopoverHook({
    className: props.className,
    invalid,
    save,
  });

  const tabDirectionRef = useRef<string>(CloseReason.BLUR);

  const tabRecorder = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === 'Tab') {
        tabDirectionRef.current = e.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD;
        e.preventDefault();
        e.stopPropagation();
        void triggerSave(tabDirectionRef.current);
      }
    },
    [triggerSave],
  );

  useEffect(() => {
    const input = inputRef.current;
    input?.addEventListener('keydown', tabRecorder, { capture: true });
    return () => {
      input?.removeEventListener('keydown', tabRecorder, { capture: true });
    };
  }, [tabRecorder]);

  return (
    <div
      className={clsx('GridFormInlineTextInput', invalid() && 'GridFormInlineTextInput-error')}
      title={String(invalid() ?? '')}
    >
      <input
        ref={inputRef}
        type={'text'}
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
          if (props.onChange) {
            const selectedRows = getSelectedRows<TData>();
            const selectedRowIds = selectedRows.map((data) => data.id);
            props.onChange({ selectedRows, selectedRowIds, value: e.target.value });
          }
        }}
        placeholder={props.placeholder ?? 'Type here'}
        onBlur={() => {
          void triggerSave(CloseReason.BLUR);
        }}
        onKeyUp={(e) => {
          if (e.key === 'Enter') {
            void triggerSave(CloseReason.BLUR);
          }
        }}
      />
    </div>
  );
};
