import { describe, test, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
import { LinkNode, AutoLinkNode } from '@lexical/link';
import { ListNode, ListItemNode } from '@lexical/list';
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import TextEditorToolbar from './TextEditorToolbar.js';

// Helper component to set up editor state for testing.
function EditorStateHelper({ onReady }: { onReady?: () => void }) {
  const [editor] = useLexicalComposerContext();

  if (onReady) {
    editor.update(() => {
      onReady();
    });
  }

  return null;
}

// Wrapper component with full Lexical setup.
function TestWrapper({ children, onReady }: { children?: React.ReactNode; onReady?: () => void }) {
  const initialConfig = {
    namespace: 'TestEditor',
    theme: {},
    onError: (error: Error) => {
      console.error(error);
    },
    nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode, AutoLinkNode],
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <TextEditorToolbar />
      <RichTextPlugin
        contentEditable={<ContentEditable data-testid="lexical-editor" />}
        placeholder={<div>Enter text...</div>}
        ErrorBoundary={LexicalErrorBoundary}
      />
      <HistoryPlugin />
      <LinkPlugin />
      <ListPlugin />
      {onReady && <EditorStateHelper onReady={onReady} />}
      {children}
    </LexicalComposer>
  );
}

describe('TextEditorToolbar', () => {
  let user: ReturnType<typeof userEvent.setup>;

  beforeEach(() => {
    user = userEvent.setup();
    vi.clearAllMocks();
  });

  describe('Rendering', () => {
    test('renders all toolbar buttons', () => {
      render(<TestWrapper />);

      expect(screen.getByTitle('Undo')).toBeInTheDocument();
      expect(screen.getByTitle('Redo')).toBeInTheDocument();
      expect(screen.getByTitle('Bold')).toBeInTheDocument();
      expect(screen.getByTitle('Italic')).toBeInTheDocument();
      expect(screen.getByTitle('Link')).toBeInTheDocument();
      expect(screen.getByTitle('Bulleted List')).toBeInTheDocument();
      expect(screen.getByTitle('Numbered List')).toBeInTheDocument();
      expect(screen.getByTitle('Quote')).toBeInTheDocument();
      expect(screen.getByTitle('Remove Formatting')).toBeInTheDocument();
    });

    test('renders block format dropdown with all options', () => {
      render(<TestWrapper />);

      const select = screen.getByLabelText('Select block formatting');
      expect(select).toBeInTheDocument();
      expect(select).toHaveValue('paragraph');

      // eslint-disable-next-line testing-library/no-node-access -- enumerating select options
      const options = Array.from(select.querySelectorAll('option'));
      expect(options).toHaveLength(8);
      expect(options.map((opt) => opt.value)).toEqual([
        'paragraph',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'quote',
      ]);
    });

    test('renders dividers between button groups', () => {
      const { container } = render(<TestWrapper />);

      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking toolbar DOM structure
      const dividers = container.querySelectorAll('.text-editor-toolbar__divider');
      expect(dividers.length).toBeGreaterThan(0);
    });
  });

  describe('Text Formatting', () => {
    test('applies bold formatting when bold button is clicked', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const boldButton = screen.getByTitle('Bold');

      await user.click(editor);
      await user.keyboard('Hello World');
      await user.keyboard(
        '{Shift>}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{/Shift}',
      );

      await user.click(boldButton);

      await waitFor(() => {
        expect(boldButton).toHaveClass('text-editor-toolbar__button--active');
      });
    });

    test('applies italic formatting when italic button is clicked', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const italicButton = screen.getByTitle('Italic');

      await user.click(editor);
      await user.keyboard('Hello World');
      await user.keyboard(
        '{Shift>}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{/Shift}',
      );

      await user.click(italicButton);

      await waitFor(() => {
        expect(italicButton).toHaveClass('text-editor-toolbar__button--active');
      });
    });

    test('toggles bold formatting off when clicked again', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const boldButton = screen.getByTitle('Bold');

      await user.click(editor);
      await user.keyboard('Hello');
      await user.keyboard(
        '{Shift>}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{/Shift}',
      );

      await user.click(boldButton);
      await waitFor(() => {
        expect(boldButton).toHaveClass('text-editor-toolbar__button--active');
      });

      await user.click(boldButton);
      await waitFor(() => {
        expect(boldButton).not.toHaveClass('text-editor-toolbar__button--active');
      });
    });
  });

  describe('Block Formatting', () => {
    test('toggles quote formatting with quote button', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const quoteButton = screen.getByTitle('Quote');
      const select = screen.getByLabelText('Select block formatting');

      await user.click(editor);
      await user.keyboard('Test quote');

      await user.click(quoteButton);

      await waitFor(() => {
        expect(quoteButton).toHaveClass('text-editor-toolbar__button--active');
        expect(select).toHaveValue('quote');
      });

      await user.click(quoteButton);

      await waitFor(() => {
        expect(quoteButton).not.toHaveClass('text-editor-toolbar__button--active');
        expect(select).toHaveValue('paragraph');
      });
    });
  });

  describe('List Formatting', () => {
    test('creates bulleted list when button is clicked', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const bulletButton = screen.getByTitle('Bulleted List');

      await user.click(editor);
      await user.keyboard('List item');

      await user.click(bulletButton);

      await waitFor(() => {
        expect(bulletButton).toHaveClass('text-editor-toolbar__button--active');
      });
    });

    test('creates numbered list when button is clicked', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const numberedButton = screen.getByTitle('Numbered List');

      await user.click(editor);
      await user.keyboard('List item');

      await user.click(numberedButton);

      await waitFor(() => {
        expect(numberedButton).toHaveClass('text-editor-toolbar__button--active');
      });
    });

    test('removes list formatting when clicked again', async () => {
      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const bulletButton = screen.getByTitle('Bulleted List');

      await user.click(editor);
      await user.keyboard('List item');
      await user.click(bulletButton);

      await waitFor(() => {
        expect(bulletButton).toHaveClass('text-editor-toolbar__button--active');
      });

      await user.click(bulletButton);

      await waitFor(() => {
        expect(bulletButton).not.toHaveClass('text-editor-toolbar__button--active');
      });
    });
  });

  describe('Link Functionality', () => {
    test('prompts for URL when link button is clicked', async () => {
      const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue('https://example.com');

      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const linkButton = screen.getByTitle('Link');

      await user.click(editor);
      await user.keyboard('Click here');
      await user.keyboard(
        '{Shift>}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{/Shift}',
      );

      await user.click(linkButton);

      expect(promptSpy).toHaveBeenCalledWith('Enter URL:');

      promptSpy.mockRestore();
    });

    test('does not create link when prompt is cancelled', async () => {
      const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue(null);

      render(<TestWrapper />);

      const editor = screen.getByTestId('lexical-editor');
      const linkButton = screen.getByTitle('Link');

      await user.click(editor);
      await user.keyboard('Text');
      await user.keyboard('{Shift>}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{/Shift}');

      await user.click(linkButton);

      expect(promptSpy).toHaveBeenCalled();
      expect(linkButton).not.toHaveClass('text-editor-toolbar__button--active');

      promptSpy.mockRestore();
    });
  });

  describe('Accessibility', () => {
    test('all buttons have proper titles for screen readers', () => {
      render(<TestWrapper />);

      const buttons = [
        'Undo',
        'Redo',
        'Bold',
        'Italic',
        'Link',
        'Bulleted List',
        'Numbered List',
        'Quote',
        'Remove Formatting',
      ];

      buttons.forEach((title) => {
        expect(screen.getByTitle(title)).toBeInTheDocument();
      });
    });

    test('dropdown has aria-label', () => {
      render(<TestWrapper />);

      const select = screen.getByLabelText('Select block formatting');
      expect(select).toBeInTheDocument();
    });

    test('all buttons have type="button" to prevent form submission', () => {
      const { container } = render(<TestWrapper />);

      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking all toolbar buttons have type attribute
      const buttons = container.querySelectorAll('.text-editor-toolbar__button');
      buttons.forEach((button) => {
        expect(button).toHaveAttribute('type', 'button');
      });
    });
  });
});
