import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {
  Keyboard,
  LayoutChangeEvent,
  StyleSheet,
  TextInputProps,
  View,
} from 'react-native';
import {useTranslation} from 'react-i18next';
import {RichText, Toolbar, useBridgeState} from '@10play/tentap-editor';
import {BottomSheetTextInput} from '@gorhom/bottom-sheet';
import {
  IconBold,
  IconDots,
  IconItalic,
  IconTrashX,
} from '@tabler/icons-react-native';
import {Typography} from '@components/_core/Typography';
import {IconButton} from '@components/_HuGo/Button';
import {Dialog} from '@components/_HuGo/Dialog';
import {HtmlRender} from '@components/_custom';
import {useGetRichEditor} from '@hooks/useGetRichEditor';
import {useKeyboardHeight} from '@hooks/useKeyboardHeight';
import {SPACING, useTheme} from '@shared/theme';
import {windowDimensions} from '@shared/constants';

import {InputAccessibilities, InputCommonProps} from '../../interfaces';
import {
  useInputController,
  UseInputControllerProps,
} from '../../hooks/useInputController';
import {InputBox} from '../InputBox';
import {InputCore} from '../InputCore';
import {
  INPUT_EXTRA_HEIGHT,
  INPUT_MIN_HEIGHT,
  TEXT_AREA_MIN_HEIGHT,
} from '../../constants';
import {styles} from './styles';

interface InputRichTextAreaProps
  extends InputCommonProps,
    InputAccessibilities,
    TextInputProps {
  /**
   * Show letters counter. This should be used with "maxLength" prop
   */
  showCounter?: boolean;
  /**
   * Called when the footer button is pressed. The dialog closes (without
   * reverting unsaved edits, since pressing the footer button is treated as
   * a confirmation) and then this callback runs.
   */
  onPress?: () => void;
  /**
   * Overrides the footer button label. Defaults to `t('general.done')`.
   */
  footerButtonText?: string;
  /**
   * Extra action executed after the default close behavior when the dialog
   * is dismissed (e.g. pressing the X). The default close still runs.
   */
  onClose?: () => void;
  /**
   * Intercepts the clear icon press. When provided, the default clear
   * (`onChangeText('')`) is skipped and the consumer is in charge of
   * deciding whether to clear (e.g. after a confirmation dialog).
   */
  onClear?: () => void;
}

const FOOTER_HEIGHT = 152;
export const WINDOW_HEIGHT_LARGE = windowDimensions.height * 0.9;

const DialogToolbar = React.memo(function DialogToolbar({
  editor,
}: {
  editor: Parameters<typeof Toolbar>[0]['editor'];
}) {
  return <Toolbar editor={editor} hidden={false} />;
});

export function InputRichTextArea({
  value,
  onChangeText,
  label,
  helper,
  isError,
  isSuccess,
  disabled,
  showCounter,
  placeholder,
  onPress,
  footerButtonText,
  onClose,
  onClear,
}: InputRichTextAreaProps) {
  const {theme} = useTheme();
  const {t} = useTranslation();
  const [focused, setFocused] = useState(false);
  const [editorReady, setEditorReady] = useState(false);
  const [inputHeight, setInputHeight] = useState(INPUT_MIN_HEIGHT);
  const inputLength = showCounter ? value?.length || 0 : undefined;
  const showPlaceholder = placeholder && !value;

  // Keep the latest external value in a ref so we can sync the editor on
  // open without re-running the effect on every keystroke (which would jump
  // the caret).
  const valueRef = useRef(value);
  valueRef.current = value;
  // Snapshot of the value when the dialog opened, used to revert unsaved
  // edits if the user dismisses the dialog via the close button.
  const valueBeforeOpenRef = useRef<Nullable<string> | undefined>(undefined);
  const hasSyncedRef = useRef(false);

  const onPressInput = useCallback(() => {
    setEditorReady(false);
    setFocused(true);
    valueBeforeOpenRef.current = value;
  }, [value]);
  const onCloseModal = useCallback(() => {
    Keyboard.dismiss();
    setFocused(false);
    const initial = valueBeforeOpenRef.current;
    if (initial !== undefined && initial !== valueRef.current) {
      onChangeText?.(initial || '');
    }
    onClose?.();
  }, [onChangeText, onClose]);
  const onPressFooterButton = useCallback(() => {
    Keyboard.dismiss();
    setFocused(false);
    onPress?.();
  }, [onPress]);

  const editor = useGetRichEditor({
    onSetMessageBody: onChangeText,
    placeholder: placeholder || '',
    initialContent: value || '',
  });
  const editorState = useBridgeState(editor);

  // Sync the editor's content with `value` after the WebView has loaded the
  // editor. This handles the case where `value` was loaded asynchronously
  // after mount, since `useGetRichEditor` only captures `initialContent` at
  // creation time. Skip the call when there's nothing to inject: the editor
  // is already initialized empty, and pushing `''` resets internal state and
  // breaks focus + placeholder.
  useEffect(() => {
    if (focused && editorState.isReady && !hasSyncedRef.current) {
      if (valueRef.current) {
        editor.setContent(valueRef.current);
      }
      hasSyncedRef.current = true;
    }
    if (!focused) {
      hasSyncedRef.current = false;
    }
  }, [focused, editorState.isReady, editor]);
  const onClearInput = useCallback(() => {
    if (onClear) {
      onClear();
      return;
    }
    onChangeText?.('');
  }, [onChangeText, onClear]);

  const onLayout = useCallback((event: LayoutChangeEvent) => {
    const {height} = event.nativeEvent.layout;
    setInputHeight(prev =>
      height > TEXT_AREA_MIN_HEIGHT && height !== prev ? height : prev,
    );
  }, []);

  const commonIconProps = useMemo(
    () => ({
      style: styles.tool,
      color: theme.text.neutral.default,
    }),
    [theme.text.neutral.default],
  );

  const onFocusInput = useCallback(() => {
    editor.focus();
  }, [editor]);

  // Hide overlay once the sheet animation settles, then wait 2 frames for
  // the WebView to paint correctly after becoming visible.
  const onSheetChange = useCallback((index: number) => {
    if (index >= 0) {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => setEditorReady(true));
      });
    }
  }, []);

  const keyboardHeight = useKeyboardHeight();

  const contentHeight = useMemo(
    () => WINDOW_HEIGHT_LARGE - (keyboardHeight + SPACING.x7 + FOOTER_HEIGHT),
    [keyboardHeight],
  );

  // Once focus moves to the WebView editor, gorhom's BottomSheetTextInput
  // blur clears its keyboard `target`, so the keyboard SHOWN event is
  // cached but never applied. The footer never gets lifted, leaving the
  // toolbar and Done button stuck behind the keyboard. We compensate by
  // padding the footer manually with the keyboard height.
  const footerKeyboardPaddingBottom = useMemo(
    () => SPACING.x2 + keyboardHeight,
    [keyboardHeight],
  );

  const richTextStyle = useMemo(
    () => ({maxHeight: contentHeight}),
    [contentHeight],
  );

  const footer = useMemo(
    () => ({
      secondaryButton: {
        text: footerButtonText ?? t('general.done'),
        onPress: onPressFooterButton,
      },
      extraComponent: <DialogToolbar editor={editor} />,
    }),
    [footerButtonText, t, onPressFooterButton, editor],
  );

  const baseStyle = useMemo(
    () => ({color: theme.text.neutral.default}),
    [theme.text.neutral.default],
  );

  return (
    <>
      <InputCore
        label={label}
        helper={helper}
        isSuccess={isSuccess}
        isError={isError}
        focused={focused}
        disabled={disabled}
        inputLength={inputLength}>
        <InputBox
          isSuccess={isSuccess}
          isError={isError}
          disabled={disabled}
          focused={focused}
          onPress={onPressInput}
          style={[
            styles.inputBox,
            {minHeight: inputHeight + INPUT_EXTRA_HEIGHT},
          ]}>
          <View style={styles.flexShrink}>
            <View
              style={[
                styles.viewToolbar,
                {backgroundColor: theme.background.layout.default},
              ]}>
              <View style={styles.toolsContainer}>
                <IconBold {...commonIconProps} />
                <IconItalic {...commonIconProps} />
                <IconDots {...commonIconProps} />
              </View>
              <IconButton
                Icon={IconTrashX}
                variant="secondary"
                onPress={onClearInput}
                disabled={disabled || !value}
              />
            </View>
            <View onLayout={onLayout} style={{minHeight: INPUT_MIN_HEIGHT}}>
              {showPlaceholder ? (
                <Typography
                  style={styles.placeholder}
                  color={theme.text.neutral.lighter}>
                  {placeholder}
                </Typography>
              ) : (
                <HtmlRender html={value || ''} baseStyle={baseStyle} />
              )}
            </View>
          </View>
        </InputBox>
      </InputCore>
      <Dialog
        fullScreen
        isVisible={focused}
        onClose={onCloseModal}
        contentStyle={styles.flex}
        title={label}
        footerKeyboardPaddingBottom={footerKeyboardPaddingBottom}
        enableContentPanningGesture={false}
        onChange={onSheetChange}
        footer={footer}>
        <View style={styles.flex}>
          <RichText
            editor={editor}
            scrollEnabled
            showsVerticalScrollIndicator={false}
            style={richTextStyle}
          />
          {!editorReady && (
            <View
              pointerEvents="none"
              style={[
                StyleSheet.absoluteFillObject,
                {backgroundColor: theme.background.layout.tertiary},
              ]}
            />
          )}
        </View>
        {/* Add a text input to the dialog to can show footer on bottom of the
        keyboard */}
        <BottomSheetTextInput
          style={styles.inputStyle}
          autoFocus
          onFocus={onFocusInput}
        />
      </Dialog>
    </>
  );
}

type InputRichTextAreaControllerProps = InputRichTextAreaProps &
  UseInputControllerProps;

export function InputRichTextAreaController({
  name,
  showSuccess,
  helper,
  ...props
}: InputRichTextAreaControllerProps) {
  const {onChange, ...inputController} = useInputController({
    name,
    showSuccess,
    helperText: helper,
  });

  return (
    <InputRichTextArea
      {...props}
      {...inputController}
      onChangeText={onChange}
    />
  );
}
