import { render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import Link from '@tiptap/extension-link';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { describe, it, expect, vi, beforeAll } from 'vitest';

import { EditorToolbar } from './EditorToolbar';

// Mock DOM methods that TipTap/ProseMirror needs but JSDOM doesn't provide
beforeAll(() => {
  // Mock getClientRects
  if (!Element.prototype.getClientRects) {
    Element.prototype.getClientRects = vi.fn(() => ({
      length: 1,
      item: () => ({ top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0 }),
      [0]: { top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0 },
    })) as unknown as () => DOMRectList;
  }

  // Mock getBoundingClientRect
  if (!Element.prototype.getBoundingClientRect) {
    Element.prototype.getBoundingClientRect = vi.fn(() => ({
      top: 0,
      left: 0,
      bottom: 0,
      right: 0,
      width: 0,
      height: 0,
      x: 0,
      y: 0,
      toJSON: () => ({}),
    }));
  }

  // Mock elementFromPoint
  if (!Document.prototype.elementFromPoint) {
    Document.prototype.elementFromPoint = vi.fn(() => null);
  }

  // Mock caretRangeFromPoint
  if (!Document.prototype.caretRangeFromPoint) {
    Document.prototype.caretRangeFromPoint = vi.fn(() => null);
  }
});

// Test wrapper component to create editor instance
function ToolbarWrapper({ initialContent = '' }: { initialContent?: string }) {
  const editor = useEditor({
    extensions: [
      StarterKit.configure({
        heading: {
          levels: [1, 2, 3, 4, 5, 6],
        },
        link: false,
      }),
      Link.configure({
        openOnClick: false,
      }),
    ],
    content: initialContent,
  });

  return <EditorToolbar editor={editor} />;
}

describe('EditorToolbar', () => {
  // ============================================
  // Rendering tests
  // ============================================

  describe('rendering', () => {
    it('renders null when editor is null', () => {
      const { container } = render(<EditorToolbar editor={null} />);

      expect(container.firstChild).toBeNull();
    });

    it('renders toolbar with formatting buttons', () => {
      render(<ToolbarWrapper />);

      expect(screen.getByLabelText('Toggle bold')).toBeInTheDocument();
      expect(screen.getByLabelText('Toggle italic')).toBeInTheDocument();
      expect(screen.getByLabelText('Toggle heading 1')).toBeInTheDocument();
      expect(screen.getByLabelText('Toggle heading 2')).toBeInTheDocument();
      expect(screen.getByLabelText('Toggle heading 3')).toBeInTheDocument();
      expect(screen.getByLabelText('Add link')).toBeInTheDocument();
    });

    it('applies custom className', () => {
      // Test wrapper component to avoid hook violation
      function CustomClassWrapper() {
        const editor = useEditor({
          extensions: [StarterKit],
          content: '',
        });

        return <EditorToolbar editor={editor} className="custom-class" />;
      }

      const { container } = render(<CustomClassWrapper />);

      const toolbar = container.firstChild as HTMLElement;
      expect(toolbar).toHaveClass('custom-class');
    });
  });

  // ============================================
  // Bold formatting tests
  // ============================================

  describe('bold formatting', () => {
    it('bold button is clickable', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const boldButton = screen.getByLabelText('Toggle bold');
      expect(boldButton).toBeEnabled();

      await user.click(boldButton);
      // Button should remain in the document after click
      expect(boldButton).toBeInTheDocument();
    });

    it('shows bold button as active when text is bold', async () => {
      render(<ToolbarWrapper initialContent="<p><strong>Bold text</strong></p>" />);

      await waitFor(() => {
        const boldButton = screen.getByLabelText('Toggle bold');
        expect(boldButton).toHaveAttribute('data-state', 'on');
      });
    });
  });

  // ============================================
  // Italic formatting tests
  // ============================================

  describe('italic formatting', () => {
    it('italic button is clickable', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const italicButton = screen.getByLabelText('Toggle italic');
      expect(italicButton).toBeEnabled();

      await user.click(italicButton);
      // Button should remain in the document after click
      expect(italicButton).toBeInTheDocument();
    });

    it('shows italic button as active when text is italic', async () => {
      render(<ToolbarWrapper initialContent="<p><em>Italic text</em></p>" />);

      await waitFor(() => {
        const italicButton = screen.getByLabelText('Toggle italic');
        expect(italicButton).toHaveAttribute('data-state', 'on');
      });
    });
  });

  // ============================================
  // Heading formatting tests
  // ============================================

  describe('heading formatting', () => {
    it('heading 1 button is clickable', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const h1Button = screen.getByLabelText('Toggle heading 1');
      expect(h1Button).toBeEnabled();

      await user.click(h1Button);
      expect(h1Button).toBeInTheDocument();
    });

    it('heading 2 button is clickable', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const h2Button = screen.getByLabelText('Toggle heading 2');
      expect(h2Button).toBeEnabled();

      await user.click(h2Button);
      expect(h2Button).toBeInTheDocument();
    });

    it('heading 3 button is clickable', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const h3Button = screen.getByLabelText('Toggle heading 3');
      expect(h3Button).toBeEnabled();

      await user.click(h3Button);
      expect(h3Button).toBeInTheDocument();
    });

    it('shows H1 button as active when content is heading 1', async () => {
      render(<ToolbarWrapper initialContent="<h1>Heading 1</h1>" />);

      await waitFor(() => {
        const h1Button = screen.getByLabelText('Toggle heading 1');
        expect(h1Button).toHaveAttribute('data-state', 'on');
      });
    });

    it('shows H2 button as active when content is heading 2', async () => {
      render(<ToolbarWrapper initialContent="<h2>Heading 2</h2>" />);

      await waitFor(() => {
        const h2Button = screen.getByLabelText('Toggle heading 2');
        expect(h2Button).toHaveAttribute('data-state', 'on');
      });
    });

    it('shows H3 button as active when content is heading 3', async () => {
      render(<ToolbarWrapper initialContent="<h3>Heading 3</h3>" />);

      await waitFor(() => {
        const h3Button = screen.getByLabelText('Toggle heading 3');
        expect(h3Button).toHaveAttribute('data-state', 'on');
      });
    });
  });

  // ============================================
  // Link functionality tests
  // ============================================

  describe('link functionality', () => {
    it('shows link input when link button is clicked', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const linkButton = screen.getByLabelText('Add link');

      await user.click(linkButton);

      await waitFor(() => {
        expect(screen.getByPlaceholderText('https://example.com')).toBeInTheDocument();
        expect(screen.getByText('Set Link')).toBeInTheDocument();
        expect(screen.getByText('Cancel')).toBeInTheDocument();
      });
    });

    it('sets link when URL is entered and Set Link is clicked', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const linkButton = screen.getByLabelText('Add link');
      await user.click(linkButton);

      const input = screen.getByPlaceholderText('https://example.com');
      await user.type(input, 'https://example.com');

      const setLinkButton = screen.getByText('Set Link');
      await user.click(setLinkButton);

      await waitFor(() => {
        expect(screen.queryByPlaceholderText('https://example.com')).not.toBeInTheDocument();
      });
    });

    it('sets link when Enter key is pressed in input', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const linkButton = screen.getByLabelText('Add link');
      await user.click(linkButton);

      const input = screen.getByPlaceholderText('https://example.com');
      await user.type(input, 'https://example.com{Enter}');

      await waitFor(() => {
        expect(screen.queryByPlaceholderText('https://example.com')).not.toBeInTheDocument();
      });
    });

    it('cancels link input when Cancel button is clicked', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const linkButton = screen.getByLabelText('Add link');
      await user.click(linkButton);

      const cancelButton = screen.getByText('Cancel');
      await user.click(cancelButton);

      await waitFor(() => {
        expect(screen.queryByPlaceholderText('https://example.com')).not.toBeInTheDocument();
      });
    });

    it('cancels link input when Escape key is pressed', async () => {
      const user = userEvent.setup();
      render(<ToolbarWrapper initialContent="<p>Test text</p>" />);

      const linkButton = screen.getByLabelText('Add link');
      await user.click(linkButton);

      const input = screen.getByPlaceholderText('https://example.com');
      await user.type(input, '{Escape}');

      await waitFor(() => {
        expect(screen.queryByPlaceholderText('https://example.com')).not.toBeInTheDocument();
      });
    });

    it('shows link button as active when text has link', async () => {
      render(
        <ToolbarWrapper initialContent='<p><a href="https://example.com">Link text</a></p>' />,
      );

      await waitFor(() => {
        const linkButton = screen.getByLabelText('Add link');
        expect(linkButton).toHaveAttribute('data-state', 'on');
      });
    });

    it('shows remove link button when text has link', async () => {
      render(
        <ToolbarWrapper initialContent='<p><a href="https://example.com">Link text</a></p>' />,
      );

      await waitFor(() => {
        expect(screen.getByLabelText('Remove link')).toBeInTheDocument();
      });
    });

    it('removes link when remove link button is clicked', async () => {
      const user = userEvent.setup();
      render(
        <ToolbarWrapper initialContent='<p><a href="https://example.com">Link text</a></p>' />,
      );

      await waitFor(() => {
        expect(screen.getByLabelText('Remove link')).toBeInTheDocument();
      });

      const removeButton = screen.getByLabelText('Remove link');
      expect(removeButton).toBeEnabled();

      await user.click(removeButton);

      // Button should be clickable
      expect(removeButton).toBeInTheDocument();
    });

    it('pre-fills link input with existing URL when editing link', async () => {
      const user = userEvent.setup();
      render(
        <ToolbarWrapper initialContent='<p><a href="https://example.com">Link text</a></p>' />,
      );

      await waitFor(() => {
        expect(screen.getByLabelText('Add link')).toBeInTheDocument();
      });

      const linkButton = screen.getByLabelText('Add link');
      await user.click(linkButton);

      await waitFor(() => {
        const input = screen.getByPlaceholderText('https://example.com') as HTMLInputElement;
        expect(input.value).toBe('https://example.com');
      });
    });

    it('removes link when empty URL is set', async () => {
      const user = userEvent.setup();
      render(
        <ToolbarWrapper initialContent='<p><a href="https://example.com">Link text</a></p>' />,
      );

      await waitFor(() => {
        expect(screen.getByLabelText('Add link')).toBeInTheDocument();
      });

      const linkButton = screen.getByLabelText('Add link');
      await user.click(linkButton);

      const input = screen.getByPlaceholderText('https://example.com');
      await user.clear(input);

      const setLinkButton = screen.getByText('Set Link');
      await user.click(setLinkButton);

      await waitFor(() => {
        expect(screen.queryByPlaceholderText('https://example.com')).not.toBeInTheDocument();
      });
    });
  });

  // ============================================
  // Separator tests
  // ============================================

  describe('separators', () => {
    it('renders separators between button groups', () => {
      const { container } = render(<ToolbarWrapper />);

      // Separators have role="none" per Radix UI Separator component
      const separators = container.querySelectorAll('[role="none"]');
      expect(separators.length).toBeGreaterThan(0);
    });
  });
});
