import { describe, test, expect } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { useState } from 'react';
import { LexicalComposer, type InitialConfigType } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
import { $createParagraphNode, $createTextNode, $getRoot, type LexicalEditor } from 'lexical';
import SetTextPlugin from './SetTextPlugin.js';

function createInitialConfig(initialText?: string): InitialConfigType {
  return {
    namespace: 'TestEditor',
    onError: (error: Error, _editor: LexicalEditor) => {
      console.error(error);
    },
    ...(initialText && {
      editorState: (_editor: LexicalEditor) => {
        const root = $getRoot();
        const paragraph = $createParagraphNode();
        paragraph.append($createTextNode(initialText));
        root.append(paragraph);
      },
    }),
  };
}

function TestEditor({
  children,
  initialText,
}: {
  children: React.ReactNode;
  initialText?: string;
}) {
  return (
    <LexicalComposer initialConfig={createInitialConfig(initialText)}>
      <RichTextPlugin
        contentEditable={<ContentEditable data-testid="editor" />}
        ErrorBoundary={LexicalErrorBoundary}
      />
      {children}
    </LexicalComposer>
  );
}

describe('SetTextPlugin', () => {
  test('sets plain text when triggered', async () => {
    const user = userEvent.setup();

    const TestWrapper = () => {
      const [trigger, setTrigger] = useState<{ value: string; timestamp: number } | null>(null);

      return (
        <>
          <TestEditor initialText="Initial content">
            <SetTextPlugin setTextTrigger={trigger} />
          </TestEditor>
          <button onClick={() => setTrigger({ value: 'New plain text', timestamp: Date.now() })}>
            Set Text
          </button>
        </>
      );
    };

    render(<TestWrapper />);

    const editor = await screen.findByTestId('editor');

    await waitFor(() => {
      expect(editor.textContent).toContain('Initial content');
    });

    const setTextButton = screen.getByRole('button', { name: 'Set Text' });
    await user.click(setTextButton);

    await waitFor(() => {
      expect(editor.textContent).toBe('New plain text');
    });
  });

  test('sets HTML content when triggered', async () => {
    const user = userEvent.setup();

    const TestWrapper = () => {
      const [trigger, setTrigger] = useState<{ value: string; timestamp: number } | null>(null);

      return (
        <>
          <TestEditor initialText="Initial content">
            <SetTextPlugin setTextTrigger={trigger} />
          </TestEditor>
          <button
            onClick={() =>
              setTrigger({
                value: '<p><strong>Bold text</strong> and normal</p>',
                timestamp: Date.now(),
              })
            }
          >
            Set HTML
          </button>
        </>
      );
    };

    render(<TestWrapper />);

    const editor = await screen.findByTestId('editor');

    await waitFor(() => {
      expect(editor.textContent).toContain('Initial content');
    });

    const setHtmlButton = screen.getByRole('button', { name: 'Set HTML' });
    await user.click(setHtmlButton);

    await waitFor(() => {
      expect(editor.textContent).toContain('Bold text');
    });
    expect(editor.textContent).toContain('and normal');
    // eslint-disable-next-line testing-library/no-node-access -- checking Lexical rich text DOM structure
    expect(editor.querySelector('strong')).toBeInTheDocument();
  });

  test('does not modify editor when setTextTrigger is null', async () => {
    render(
      <TestEditor initialText="Content should remain">
        <SetTextPlugin setTextTrigger={null} />
      </TestEditor>,
    );

    const editor = await screen.findByTestId('editor');

    await waitFor(() => {
      expect(editor.textContent).toContain('Content should remain');
    });

    // Content should still be there since trigger is null
    expect(editor.textContent).toContain('Content should remain');
  });

  test('replaces content when triggered multiple times', async () => {
    const user = userEvent.setup();

    const TestWrapper = () => {
      const [trigger, setTrigger] = useState<{ value: string; timestamp: number } | null>(null);
      const [count, setCount] = useState(0);

      return (
        <>
          <TestEditor initialText="Initial content">
            <SetTextPlugin setTextTrigger={trigger} />
          </TestEditor>
          <button
            onClick={() => {
              const newCount = count + 1;
              setCount(newCount);
              setTrigger({ value: `Text version ${newCount}`, timestamp: Date.now() });
            }}
          >
            Set Text
          </button>
        </>
      );
    };

    render(<TestWrapper />);

    const editor = await screen.findByTestId('editor');
    const setTextButton = screen.getByRole('button', { name: 'Set Text' });

    await waitFor(() => {
      expect(editor.textContent).toContain('Initial content');
    });

    // First set
    await user.click(setTextButton);
    await waitFor(() => {
      expect(editor.textContent).toBe('Text version 1');
    });

    // Second set
    await user.click(setTextButton);
    await waitFor(() => {
      expect(editor.textContent).toBe('Text version 2');
    });

    // Third set
    await user.click(setTextButton);
    await waitFor(() => {
      expect(editor.textContent).toBe('Text version 3');
    });
  });

  test('clears previous content before setting new content', async () => {
    const user = userEvent.setup();

    const TestWrapper = () => {
      const [trigger, setTrigger] = useState<{ value: string; timestamp: number } | null>(null);

      return (
        <>
          <TestEditor initialText="Old content that should be replaced">
            <SetTextPlugin setTextTrigger={trigger} />
          </TestEditor>
          <button onClick={() => setTrigger({ value: 'New content', timestamp: Date.now() })}>
            Set Text
          </button>
        </>
      );
    };

    render(<TestWrapper />);

    const editor = await screen.findByTestId('editor');

    await waitFor(() => {
      expect(editor.textContent).toContain('Old content that should be replaced');
    });

    const setTextButton = screen.getByRole('button', { name: 'Set Text' });
    await user.click(setTextButton);

    await waitFor(() => {
      expect(editor.textContent).toBe('New content');
      expect(editor.textContent).not.toContain('Old content');
    });
  });

  test('handles empty string value', async () => {
    const user = userEvent.setup();

    const TestWrapper = () => {
      const [trigger, setTrigger] = useState<{ value: string; timestamp: number } | null>(null);

      return (
        <>
          <TestEditor initialText="Content to clear">
            <SetTextPlugin setTextTrigger={trigger} />
          </TestEditor>
          <button onClick={() => setTrigger({ value: '', timestamp: Date.now() })}>
            Clear With Empty
          </button>
        </>
      );
    };

    render(<TestWrapper />);

    const editor = await screen.findByTestId('editor');

    await waitFor(() => {
      expect(editor.textContent).toContain('Content to clear');
    });

    const clearButton = screen.getByRole('button', { name: 'Clear With Empty' });
    await user.click(clearButton);

    await waitFor(() => {
      expect(editor.textContent).toBe('');
    });
  });

  test('updates when same value but different timestamp', async () => {
    const user = userEvent.setup();

    const TestWrapper = () => {
      const [trigger, setTrigger] = useState<{ value: string; timestamp: number } | null>({
        value: 'Same text',
        timestamp: 1000,
      });

      return (
        <>
          <TestEditor>
            <SetTextPlugin setTextTrigger={trigger} />
          </TestEditor>
          <button onClick={() => setTrigger({ value: 'Same text', timestamp: Date.now() })}>
            Trigger Again
          </button>
        </>
      );
    };

    render(<TestWrapper />);

    const editor = await screen.findByTestId('editor');

    await waitFor(() => {
      expect(editor.textContent).toContain('Same text');
    });

    // Even with same text, a new timestamp should trigger the update
    const triggerButton = screen.getByRole('button', { name: 'Trigger Again' });
    await user.click(triggerButton);

    // The text should still be there (update was triggered but content is same)
    await waitFor(() => {
      expect(editor.textContent).toContain('Same text');
    });
  });
});
