// Import necessary dependencies
import clsx from 'clsx';
import { withHistory } from 'slate-history';
import { Editor, Transforms, Range, createEditor } from 'slate';
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
import React, { useMemo, useEffect, useCallback, useState, useRef } from 'react';

// locals
import useTeam from '../../hooks/useTeam';
import ApiSignal from '../../signals/Api';
import useResults from '../../hooks/useResults';
import { useSignal } from '../../signal';
import SlashCombobox, { withSlashCommands } from './Slash';
import Mention, { withMentions, insertMention, MentionCombobox, serializeMention } from './Mention';

// craftiq system user
const craftiqMention = {
  user : {
    id   : 'system',
    name : 'CraftIQ',
  },
  id : 'system',
};

// Define the main editor component
const ComponentEditor = (props = {}) => {
  // sApi
  const [sApi] = useSignal(ApiSignal);

  // value
  const [value, setValue] = useState(JSON.parse(initialEditorValue));

  // team
  const team = useTeam();
  
  // State for mention functionality
  const [mentionTarget, setMentionTarget] = useState();
  const [mentionSearch, setMentionSearch] = useState('');
  const [mentionIndex, setMentionIndex] = useState(0);
  
  // State for slash command functionality
  const [slashTarget, setSlashTarget] = useState();
  const [slashSearch, setSlashSearch] = useState('');
  const [slashIndex, setSlashIndex] = useState(0);
  
  // items
  const {
    resetting,
    items : members,

    onItem,
    loadNext,
  } = useResults(sApi.Team.Member.list, 'members', {
    sort    : props.sort,
    team    : team?.id,
    mine    : false,
    search  : mentionSearch,
    disable : !team?.id,
  }, null, props.onResult);
  
  // Reference to the editor DOM element
  const editorRef = useRef(null);

  // Memoize callback functions to prevent unnecessary re-renders
  const renderLeaf = useCallback(props => <Leaf {...props} />, []);
  const renderElement = useCallback(props => <Element {...props} />, []);

  // Create and memoize the editor instance with plugins
  const editor = useMemo(() => withSlashCommands(withMentions(withReact(withHistory(createEditor())))), []);

  // Filter characters for mentions based on search input
  const filteredMentions = [craftiqMention, ...members].filter(c =>
    `${c.user?.name || ''} ${c.user?.email || ''}`.toLowerCase().startsWith(mentionSearch.toLowerCase())
  ).slice(0, 10);

  // Filter slash commands based on search input
  const filteredSlashCommands = SLASH_COMMANDS.filter(c =>
    c.name.toLowerCase().startsWith(slashSearch.toLowerCase())
  ).slice(0, 10);

  // Handle mention selection
  const handleMentionSelect = (char) => {
    // Select the target range in the editor
    Transforms.select(editor, mentionTarget);
    // Insert the mention at the selected range
    insertMention(editor, char);
    // Reset mention-related state
    setMentionTarget(null);
    setMentionIndex(0);
  };

  // Handle slash command selection
  const handleSlashSelect = (command) => {
    // Select the target range in the editor
    Transforms.select(editor, slashTarget);
    // Delete the slash command text
    Transforms.delete(editor);
    // Execute the selected command's action
    command.action(editor);
    // Reset slash command-related state
    setSlashTarget(null);
    setSlashIndex(0);
  };

  // Handle key down events for mention and slash command selection
  const handleKeyDown = useCallback((event) => {
    if (event.key === 'Enter' && !event.shiftKey && !mentionTarget && !slashTarget) {
      // Prevent default to avoid inserting a new line
      event.preventDefault();
      if (props.onSend) props.onSend(editor.children.map(customSerialize).join('\n'));
      return;
    }
    
    // If there's an active mention target
    if (mentionTarget) {
      if (event.key === 'ArrowDown') {
        // Move mention selection down
        event.preventDefault();
        setMentionIndex((mentionIndex + 1) % filteredMentions.length);
      } else if (event.key === 'ArrowUp') {
        // Move mention selection up
        event.preventDefault();
        setMentionIndex((mentionIndex - 1 + filteredMentions.length) % filteredMentions.length);
      } else if (event.key === 'Tab' || event.key === 'Enter') {
        // Select the current mention
        event.preventDefault();
        handleMentionSelect(filteredMentions[mentionIndex]);
      } else if (event.key === 'Escape') {
        // Cancel mention selection
        event.preventDefault();
        setMentionTarget(null);
        setMentionIndex(0);
      }
    } else if (slashTarget) {
      // If there's an active slash command target
      if (event.key === 'ArrowDown') {
        // Move slash command selection down
        event.preventDefault();
        setSlashIndex((slashIndex + 1) % filteredSlashCommands.length);
      } else if (event.key === 'ArrowUp') {
        // Move slash command selection up
        event.preventDefault();
        setSlashIndex((slashIndex - 1 + filteredSlashCommands.length) % filteredSlashCommands.length);
      } else if (event.key === 'Tab' || event.key === 'Enter') {
        // Select the current slash command
        event.preventDefault();
        handleSlashSelect(filteredSlashCommands[slashIndex]);
      } else if (event.key === 'Escape') {
        // Cancel slash command selection
        event.preventDefault();
        setSlashTarget(null);
        setSlashIndex(0);
      }
    }
  }, [mentionTarget, filteredMentions, mentionIndex, handleMentionSelect, slashTarget, filteredSlashCommands, slashIndex, handleSlashSelect]);

  // use effect
  useEffect(() => {
    // check editor
    if (!editor) return;
    if (!props.reset) return;

    // Delete the entire content of the editor
    Transforms.delete(editor, {
      at: {
        anchor: Editor.start(editor, []),
        focus: Editor.end(editor, []),
      },
    });

    // Insert the initial value
    const initialValue = JSON.parse(initialEditorValue);
    Transforms.insertNodes(editor, initialValue[0].children);

    // Move the cursor to the start of the editor
    Transforms.select(editor, Editor.start(editor, []));

    // Focus the editor
    ReactEditor.focus(editor);
  }, [editor, props.reset]);

  // return value
  return (
    // Wrap the editor with Slate context
    <Slate
      editor={editor}
      initialValue={JSON.parse(initialEditorValue)}
      onChange={(value) => {
        // set value
        setValue(value);
        
        // is change
        const isAstChange = editor.operations.some(
          op => 'set_selection' !== op.type
        );


        if (isAstChange) {
          // Serialize the value and call props.onChange
          const content = value.map(customSerialize).join('\n');
          if (props.onChange) props.onChange(content);
        }
        
        const { selection } = editor;

        if (selection && Range.isCollapsed(selection)) {
          const [start] = Range.edges(selection);
          const beforeRange = Editor.range(editor, Editor.start(editor, []), start);
          const beforeText = Editor.string(editor, beforeRange);
          const beforeMatch = beforeText.match(/\S+$/);
          const before = beforeMatch ? beforeMatch[0] : '';
          const beforeWordRange = beforeMatch ? Editor.range(editor, { path: start.path, offset: start.offset - before.length }, start) : null;

          const after = Editor.after(editor, start);
          const afterRange = Editor.range(editor, start, after);
          const afterText = Editor.string(editor, afterRange);
          const afterMatch = afterText.match(/^(\s|$)/);

          // Check if the current word starts with '/' and it's at the beginning of the text
          const isSlashAtStart = before === '/' && beforeText.trim() === '/';

          if (isSlashAtStart && afterMatch) {
            setSlashTarget(beforeWordRange);
            setSlashSearch('');
            setMentionTarget(null);
            setMentionSearch('');
          } else if (before.startsWith('@') && afterMatch) {
            setMentionTarget(beforeWordRange);
            setMentionSearch(before.slice(1));
            setSlashTarget(null);
            setSlashSearch('');
          } else {
            setSlashTarget(null);
            setSlashSearch('');
            setMentionTarget(null);
            setMentionSearch('');
          }
        }
      }}
    >
      <div className="relative" ref={editorRef}>
        {/* Editable component for the rich text editor */}
        <Editable
          className="focus:outline-none"
          renderElement={renderElement}
          renderLeaf={renderLeaf}
          placeholder={sApi.translate('common:editor.placeholder', 'Start typing... Use @ for mentions and / for commands')}
          onKeyDown={handleKeyDown}
        />
        {/* Render mention combobox if there's a mention target */}
        {mentionTarget && (
          <MentionCombobox
            items={filteredMentions}
            onSelect={handleMentionSelect}
            targetRange={mentionTarget}
            editor={editor}
            editorRef={editorRef}
            selectedIndex={mentionIndex}
          />
        )}
        {/* Render slash command combobox if there's a slash target */}
        {slashTarget && (
          <SlashCombobox
            items={filteredSlashCommands}
            onSelect={handleSlashSelect}
            targetRange={slashTarget}
            editor={editor}
            editorRef={editorRef}
            selectedIndex={slashIndex}
          />
        )}
      </div>
    </Slate>
  );
};

// Component for rendering text with formatting
const Leaf = ({ attributes, children, leaf }) => {
  // Apply appropriate classes based on text formatting
  const classes = clsx({
    'font-bold': leaf.bold,
    'italic': leaf.italic,
    'underline': leaf.underline,
    'font-mono bg-gray-200 rounded px-1': leaf.code,
  });

  return <span {...attributes} className={classes}>{children}</span>;
};

// Component for rendering block elements
const Element = props => {
  const { attributes, children, element } = props;
  
  // Render different elements based on their type
  if (element.type === 'mention') {
    return <Mention {...props} />;
  } else if (element.type === 'heading-one') {
    return <h1 {...attributes}>{children}</h1>;
  } else if (element.type === 'heading-two') {
    return <h2 {...attributes}>{children}</h2>;
  } else if (element.type === 'bulleted-list') {
    return <ul {...attributes}>{children}</ul>;
  } else {
    return <p {...attributes}>{children}</p>;
  }
};

// Custom serializer
const customSerialize = (node) => {
  if (node.type === 'mention') {
    return serializeMention(node)
  }
  if (node.children) {
    return node.children.map(customSerialize).join('')
  }
  return node.text
};

// Initial value
const initialEditorValue = JSON.stringify([
  {
    type: 'paragraph',
    children: [
      { text: '' },
    ],
  },
]);

// slash commands
const SLASH_COMMANDS = [
  {
    name: 'heading1',
    description: 'Add a large heading',
    action: (editor) => {
      Transforms.setNodes(editor, { type: 'heading-one' });
    },
  },
  {
    name: 'heading2',
    description: 'Add a medium heading',
    action: (editor) => {
      Transforms.setNodes(editor, { type: 'heading-two' });
    },
  },
  {
    name: 'bullet-list',
    description: 'Create a bulleted list',
    action: (editor) => {
      Transforms.setNodes(editor, { type: 'bulleted-list' });
    },
  },
  // Add more slash commands as needed
];

// Export the ComponentEditor
export default ComponentEditor;