import { useEffect, useCallback, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useEditor, EditorContent } from '@tiptap/react';
import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text';
import Placeholder from '@tiptap/extension-placeholder';
import { Zap } from 'lucide-react';
import { VariableNode } from './tiptap/VariableNode';
import { SuggestionDropdown } from './tiptap/SuggestionDropdown';
import { AvailableContext, ContextVariable, ContextVariableType } from '../types/workflow-context';
import {
  deserializeToTipTap,
  deserializeMultilineToTipTap,
  serializeFromTipTap,
} from './tiptap/variableUtils';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
import './tiptap/tiptap.css';

interface VariableInputProps {
  /** Current value of the input */
  value: string;
  /** Callback when value changes */
  onChange: (value: string) => void;
  /** Available context for variable picker */
  availableContext: AvailableContext;
  /** Placeholder text */
  placeholder?: string;
  /** Use multiline textarea instead of single-line input */
  multiline?: boolean;
  /** Additional CSS classes */
  className?: string;
  /** Whether the input is disabled */
  disabled?: boolean;
  /** Optional type filter - only show variables of these types */
  allowedTypes?: ContextVariableType[];
}

/**
 * Rich text input with integrated variable picker.
 *
 * Uses TipTap to render {{Namespace.field}} variables as inline chips
 * while maintaining plain text serialization for storage.
 */
export function VariableInput({
  value,
  onChange,
  availableContext,
  placeholder,
  multiline = false,
  className,
  disabled = false,
  allowedTypes,
}: VariableInputProps) {
  // Track if we're updating from external source to prevent loops
  const isExternalUpdate = useRef(false);
  const containerRef = useRef<HTMLDivElement>(null);

  // Suggestion dropdown state
  const [suggestionState, setSuggestionState] = useState<{
    isOpen: boolean;
    position: { top: number; left: number } | null;
    query: string;
    triggerPos: number | null;
  }>({
    isOpen: false,
    position: null,
    query: '',
    triggerPos: null,
  });

  // Memoize initial content
  const initialContent = useMemo(() => {
    return multiline
      ? deserializeMultilineToTipTap(value, availableContext)
      : deserializeToTipTap(value, availableContext);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // Only compute on mount

  const editor = useEditor({
    extensions: [
      Document,
      Paragraph,
      Text,
      VariableNode,
      Placeholder.configure({
        placeholder: placeholder || 'Enter value...',
        emptyEditorClass: 'is-editor-empty',
      }),
    ],
    content: initialContent,
    editable: !disabled,
    editorProps: {
      attributes: {
        class: `outline-none ${multiline ? 'min-h-[80px]' : ''}`,
      },
      handleKeyDown: multiline
        ? undefined
        : (_view, event) => {
            if (event.key === 'Enter') {
              event.preventDefault();
              return true;
            }
            return false;
          },
    },
    onUpdate: ({ editor }) => {
      if (isExternalUpdate.current) return;
      const json = editor.getJSON();
      const serialized = serializeFromTipTap(json);
      onChange(serialized);
    },
  });

  // Update editor content when external value changes
  useEffect(() => {
    if (!editor) return;

    const currentSerialized = serializeFromTipTap(editor.getJSON());
    if (currentSerialized !== value) {
      isExternalUpdate.current = true;
      const newContent = multiline
        ? deserializeMultilineToTipTap(value, availableContext)
        : deserializeToTipTap(value, availableContext);
      editor.commands.setContent(newContent, { emitUpdate: false });
      isExternalUpdate.current = false;
    }
  }, [value, editor, multiline, availableContext]);

  // Update editable state when disabled changes
  useEffect(() => {
    if (editor) {
      editor.setEditable(!disabled);
    }
  }, [disabled, editor]);

  // Get cursor coordinates from editor
  const getCursorCoords = useCallback(() => {
    if (!editor) return null;

    const { from } = editor.state.selection;
    const coords = editor.view.coordsAtPos(from);

    return {
      top: coords.bottom + 4, // 4px below cursor
      left: coords.left,
    };
  }, [editor]);

  // Open suggestion via icon click
  const openSuggestionFromIcon = useCallback(() => {
    if (!editor || disabled) return;

    editor.chain().focus().run();

    // Get icon position for dropdown
    const iconButton = containerRef.current?.querySelector('[data-variable-icon]');
    if (iconButton) {
      const rect = iconButton.getBoundingClientRect();
      setSuggestionState({
        isOpen: true,
        position: { top: rect.bottom + 4, left: rect.left - 280 }, // Align dropdown right edge with icon
        query: '',
        triggerPos: null, // null means opened via icon, not @ trigger
      });
    }
  }, [editor, disabled]);

  // Close suggestion dropdown
  const closeSuggestion = useCallback(() => {
    setSuggestionState({
      isOpen: false,
      position: null,
      query: '',
      triggerPos: null,
    });
  }, []);

  // Handle variable selection from picker
  const handleVariableSelect = useCallback(
    (variable: ContextVariable) => {
      if (!editor) return;

      // If opened via @ trigger, delete the @ and query text
      if (suggestionState.triggerPos !== null) {
        const { from } = editor.state.selection;
        editor
          .chain()
          .focus()
          .deleteRange({ from: suggestionState.triggerPos - 1, to: from })
          .insertVariable({
            path: variable.path,
            label: variable.label,
            type: variable.type,
          })
          .run();
      } else {
        // Opened via icon, just insert at cursor
        editor
          .chain()
          .focus()
          .insertVariable({
            path: variable.path,
            label: variable.label,
            type: variable.type,
          })
          .run();
      }

      closeSuggestion();
    },
    [editor, suggestionState.triggerPos, closeSuggestion],
  );

  // Listen for @ keypress to open suggestion
  useEffect(() => {
    if (!editor) return;

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === '@' && !suggestionState.isOpen) {
        // Let the character be inserted first, then open suggestion
        setTimeout(() => {
          const position = getCursorCoords();
          const { from } = editor.state.selection;

          setSuggestionState({
            isOpen: true,
            position,
            query: '',
            triggerPos: from,
          });
        }, 0);
      }
    };

    const editorElement = editor.view.dom;
    editorElement.addEventListener('keydown', handleKeyDown);

    return () => {
      editorElement.removeEventListener('keydown', handleKeyDown);
    };
  }, [editor, suggestionState.isOpen, getCursorCoords]);

  // Track query text after @ trigger
  useEffect(() => {
    if (!editor || !suggestionState.isOpen || suggestionState.triggerPos === null) return;

    const handleUpdate = () => {
      const { from } = editor.state.selection;
      const triggerPos = suggestionState.triggerPos!;

      // Check if we're still after the @ position
      if (from < triggerPos) {
        closeSuggestion();
        return;
      }

      // Extract query (text between @ and cursor)
      const doc = editor.state.doc;
      const textAfterTrigger = doc.textBetween(triggerPos, from, ' ');

      // Check if @ was deleted
      const textIncludingTrigger = doc.textBetween(Math.max(0, triggerPos - 1), from, ' ');

      if (!textIncludingTrigger.startsWith('@')) {
        closeSuggestion();
        return;
      }

      // Close if space typed (end of mention)
      if (textAfterTrigger.includes(' ')) {
        closeSuggestion();
        return;
      }

      // Update query
      setSuggestionState((prev) => ({
        ...prev,
        query: textAfterTrigger,
      }));
    };

    editor.on('update', handleUpdate);
    editor.on('selectionUpdate', handleUpdate);

    return () => {
      editor.off('update', handleUpdate);
      editor.off('selectionUpdate', handleUpdate);
    };
  }, [editor, suggestionState.isOpen, suggestionState.triggerPos, closeSuggestion]);

  const hasVariables = availableContext.groups.length > 0;

  return (
    <div className="space-y-2" ref={containerRef}>
      <div className="relative">
        <div
          className={`rounded-md border border-slate-400 bg-white px-3 py-2 pr-10
            focus-within:border-cyan-600 focus-within:ring-1 focus-within:ring-cyan-600
            ${disabled ? 'bg-gray-50 cursor-not-allowed opacity-60' : ''}
            ${multiline ? 'min-h-[100px]' : 'min-h-[38px]'}
            ${className ?? ''}`}
        >
          <EditorContent editor={editor} />
        </div>
        {/* Inline variable icon button */}
        <TooltipProvider>
          <Tooltip>
            <TooltipTrigger asChild>
              <button
                type="button"
                data-variable-icon
                onClick={openSuggestionFromIcon}
                disabled={disabled || !hasVariables}
                className={`absolute right-2 top-2 p-1 rounded transition-colors
                  ${
                    disabled || !hasVariables
                      ? 'text-gray-300 cursor-not-allowed'
                      : 'text-gray-400 hover:text-cyan-600 hover:bg-slate-100'
                  }`}
              >
                <Zap className="w-4 h-4" />
              </button>
            </TooltipTrigger>
            <TooltipContent side="left">
              {hasVariables ? 'Insert variable (or type @)' : 'No variables available'}
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      </div>

      {/* Suggestion dropdown portal */}
      {suggestionState.isOpen &&
        suggestionState.position &&
        createPortal(
          <SuggestionDropdown
            availableContext={availableContext}
            onSelect={handleVariableSelect}
            onClose={closeSuggestion}
            position={suggestionState.position}
            initialQuery={suggestionState.query}
            allowedTypes={allowedTypes}
          />,
          document.body,
        )}
    </div>
  );
}
