import {
  type ClipboardEvent,
  type ForwardedRef,
  type KeyboardEvent as ReactKeyboardEvent,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react';
import {
  type ControllerProps,
  useController,
  useFormContext,
} from 'react-hook-form';

import { useTheme } from '@material-hu/mui/styles';

import {
  dedupeCertificateFieldTokens,
  getCertificateFieldTextLength,
  getCertificateFieldTypeForToken,
  getCertificateFieldTypesInText,
  stripCertificateFieldTokens,
  stripCurlyBraceSegments,
} from 'src/pages/dashboard/Learning/Settings/Certificates/Create/utils';
import { useLokaliseTranslation } from 'src/utils/i18n';

import { getContainerSx } from './styles';
import { type CertificateMultilineHandle } from './types';
import {
  buildEditorDom,
  createPillElement,
  getActiveRange,
  insertNodeAtCaret,
  placeCaretAtEnd,
  readEditorValue,
} from './utils';

type UseTokenEditorArgs = {
  name: string;
  rules?: ControllerProps['rules'];
  maxLength: number;
  showClear: boolean;
};

/**
 * Owns all the contentEditable editor logic: form binding, DOM <-> value sync,
 * caret/range handling and the `insertToken` imperative handle. The component
 * only consumes the returned state and handlers to render.
 */
export const useTokenEditor = (
  { name, rules, maxLength, showClear }: UseTokenEditorArgs,
  ref: ForwardedRef<CertificateMultilineHandle>,
) => {
  const theme = useTheme();
  const { t } = useLokaliseTranslation('learning');
  const { control } = useFormContext();
  const { field, fieldState } = useController({ name, control, rules });

  const strValue = String(field.value ?? '');
  const disabled = !!field.disabled;
  const error = !!fieldState.error;

  const counterValue = useMemo(
    () => stripCertificateFieldTokens(strValue, t),
    [strValue, t],
  );

  const editableRef = useRef<HTMLDivElement | null>(null);
  const lastValueRef = useRef<string | null>(null);
  const savedRangeRef = useRef<Range | null>(null);
  const [focused, setFocused] = useState(false);

  const commit = useCallback(
    (value: string) => {
      lastValueRef.current = value;
      field.onChange(value);
    },
    [field],
  );

  const emit = useCallback(() => {
    const el = editableRef.current;
    if (!el) return;
    commit(readEditorValue(el));
  }, [commit]);

  const rebuild = useCallback(
    (value: string) => {
      const el = editableRef.current;
      if (el) buildEditorDom(el, value, t);
    },
    [t],
  );

  useEffect(() => {
    if (strValue === lastValueRef.current) return;
    rebuild(strValue);
    lastValueRef.current = strValue;
  }, [strValue, rebuild]);

  const insert = useCallback(
    (node: Node) => {
      const el = editableRef.current;
      if (!el) return;
      insertNodeAtCaret(el, node, savedRangeRef.current);
      emit();
    },
    [emit],
  );

  useImperativeHandle(
    ref,
    () => ({
      insertToken: (token: string) => {
        const el = editableRef.current;
        if (!el) return;
        const value = readEditorValue(el);
        const type = getCertificateFieldTypeForToken(token, t);
        if (type && getCertificateFieldTypesInText(value, t).has(type)) return;
        insert(createPillElement(token));
      },
    }),
    [insert, t],
  );

  const assignRef = useCallback(
    (el: HTMLDivElement | null) => {
      editableRef.current = el;
      field.ref(el);
    },
    [field],
  );

  const onInput = () => {
    const el = editableRef.current;
    if (!el) return;
    const value = readEditorValue(el);
    const deduped = dedupeCertificateFieldTokens(value, t);
    if (deduped !== value) {
      rebuild(deduped);
      placeCaretAtEnd(el);
      commit(deduped);
      return;
    }
    if (getCertificateFieldTextLength(value, t) > maxLength) {
      rebuild(lastValueRef.current ?? '');
      placeCaretAtEnd(el);
      return;
    }
    commit(value);
  };

  const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {
    if (e.key !== 'Enter') return;
    e.preventDefault();
    const el = editableRef.current;
    if (
      !el ||
      getCertificateFieldTextLength(readEditorValue(el), t) + 1 > maxLength
    )
      return;
    insert(document.createTextNode('\n'));
  };

  const onPaste = (e: ClipboardEvent<HTMLDivElement>) => {
    e.preventDefault();
    const el = editableRef.current;
    if (!el) return;
    const pasted = stripCurlyBraceSegments(
      e.clipboardData.getData('text/plain'),
    );
    const selectedLength = window.getSelection()?.toString().length ?? 0;
    const usedLength = getCertificateFieldTextLength(readEditorValue(el), t);
    const room = maxLength - (usedLength - selectedLength);
    const toInsert = room > 0 ? pasted.slice(0, room) : '';
    if (toInsert) insert(document.createTextNode(toInsert));
  };

  const onFocus = () => setFocused(true);

  const onBlur = () => {
    const el = editableRef.current;
    savedRangeRef.current = el ? getActiveRange(el) : null;
    setFocused(false);
    field.onBlur();
  };

  const onClear = () => {
    const el = editableRef.current;
    if (!el) return;
    el.textContent = '';
    el.focus();
    emit();
  };

  let borderColor = theme.palette.new.border.neutral.default;
  if (error) borderColor = theme.palette.new.border.states.error;
  else if (focused) borderColor = theme.palette.new.border.states.focus;

  const showClearButton =
    showClear && focused && strValue.length > 0 && !disabled;

  return {
    assignRef,
    strValue,
    counterValue,
    disabled,
    error,
    errorMessage: fieldState.error?.message,
    showClearButton,
    containerSx: getContainerSx(theme, borderColor, disabled),
    clearLabel: t('general:delete'),
    handlers: { onInput, onKeyDown, onPaste, onFocus, onBlur, onClear },
  };
};
