import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { InputContainer } from '@os-design/core';
import type { WithSize } from '@os-design/styles';
import { useForwardedRef, useForwardedState } from '@os-design/utils';
import {
  Editor as DraftEditor,
  type EditorProps as DraftEditorProps,
  EditorState,
} from 'draft-js';
import { forwardRef, useCallback, useState } from 'react';
import BlockToolbar from './BlockToolbar.js';
import StyleToolbar from './StyleToolbar.js';
import type { BlockToolbarItem } from './blocks/types.js';
import useBlockToolbarProps from './hooks/useBlockToolbarProps.js';
import usePastedTextHandler from './hooks/usePastedTextHandler.js';
import useReturnHandler from './hooks/useReturnHandler.js';
import useStyleToolbarProps from './hooks/useStyleToolbarProps.js';
import defaultDraftJsStyles from './styles/defaultDraftJsStyles.js';
import overrideDraftJsStyles from './styles/overrideDraftJsStyles.js';
import createEmptyEditorState from './utils/createEmptyEditorState.js';
import defaultStyleToolbarItems, {
  type StyleToolbarItem,
} from './utils/defaultStyleToolbarItems.js';

export interface EditorProps
  extends Omit<DraftEditorProps, 'editorState' | 'onChange'>, WithSize {
  /**
   * Whether the editor is disabled.
   * @default false
   */
  disabled?: boolean;
  /**
   * Available styles in the toolbar.
   * @default undefined
   */
  styleToolbarItems?: StyleToolbarItem[];
  /**
   * Available blocks in the toolbar.
   * @default undefined
   */
  blockToolbarItems?: BlockToolbarItem[];
  /**
   * The editor state.
   * @default undefined
   */
  value?: EditorState;
  /**
   * The default value.
   * @default undefined
   */
  defaultValue?: EditorState;
  /**
   * The change event handler.
   * @default undefined
   */
  onChange?: (value: EditorState) => void;
}

const disabledStyles = (p) =>
  p.disabled &&
  css`
    cursor: not-allowed;
  `;

const Container = styled(InputContainer)`
  height: unset;
  cursor: text;

  padding: ${(p) => p.theme.editorPaddingVertical}em
    ${(p) => p.theme.inputPaddingHorizontal}em;
  min-height: ${(p) => p.theme.editorMinHeight}em;

  ${defaultDraftJsStyles};
  ${overrideDraftJsStyles};
  ${disabledStyles};
`;

/**
 * Rich text editor based on the Draft.js.
 */
const Editor = forwardRef<DraftEditor, EditorProps>(
  (
    {
      disabled = false,
      styleToolbarItems = defaultStyleToolbarItems,
      blockToolbarItems = [],
      value,
      defaultValue = createEmptyEditorState(),
      onChange,
      size,
      readOnly,
      handleReturn = () => 'not-handled',
      handlePastedText = () => 'not-handled',
      ...rest
    },
    ref
  ) => {
    const [editorRef, mergedEditorRef] = useForwardedRef(ref);
    const [forwardedValue, setForwardedValue] = useForwardedState({
      value,
      defaultValue,
      onChange,
    });

    // Used by ImageBlock to make the editor read-only
    // while an image is being uploaded
    const [innerReadOnly, setInnerReadOnly] = useState(false);

    // Get the trigger and visible props for the toolbars
    const styleToolbarProps = useStyleToolbarProps(
      forwardedValue || defaultValue
    );
    const blockToolbarProps = useBlockToolbarProps(
      forwardedValue || defaultValue,
      (blockToolbarItems || []).length > 0
    );

    // Move the caret to the end of the content when the focus event was fired
    const setEditorState = useCallback(
      (editorState: EditorState) => {
        if (!editorState.getSelection().getHasFocus()) {
          setForwardedValue(EditorState.moveSelectionToEnd(editorState));
          return;
        }
        setForwardedValue(editorState);
      },
      [setForwardedValue]
    );

    const blockRenderer = useCallback(
      (block) => {
        const blockConfig = blockToolbarItems.find(
          (item) => item.type === block.getType()
        );
        if (!blockConfig) return null;
        return { component: blockConfig.component };
      },
      [blockToolbarItems]
    );

    // Custom handlers
    const returnHandler = useReturnHandler(setForwardedValue, handleReturn);
    const pastedTextHandler = usePastedTextHandler(
      setForwardedValue,
      handlePastedText
    );

    return (
      <>
        <Container
          disabled={disabled}
          size={size}
          tabIndex={!disabled ? 0 : -1}
          role={!disabled ? 'textbox' : undefined}
          onFocus={() => {
            if (disabled || !editorRef.current) return;
            editorRef.current.focus();
          }}
        >
          <DraftEditor
            onChange={setEditorState}
            blockRendererFn={blockRenderer}
            handleReturn={returnHandler}
            handlePastedText={pastedTextHandler}
            readOnly={readOnly || innerReadOnly || disabled}
            stripPastedStyles
            {...rest}
            editorState={forwardedValue || defaultValue}
            ref={mergedEditorRef}
          />
        </Container>

        <StyleToolbar
          {...styleToolbarProps}
          size={size}
          items={styleToolbarItems}
          value={forwardedValue || defaultValue}
          onChange={setForwardedValue}
        />

        <BlockToolbar
          {...blockToolbarProps}
          placement='right'
          gap={2.5}
          size={size}
          items={blockToolbarItems}
          value={forwardedValue || defaultValue}
          onChange={setForwardedValue}
          setReadOnly={setInnerReadOnly}
        />
      </>
    );
  }
);

Editor.displayName = 'Editor';

export default Editor;
