import { describe, test, expect, vi } 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 { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
import { $generateNodesFromDOM } from '@lexical/html';
import CleanHtmlOnChangePlugin from './CleanHtmlOnChangePlugin.js';

const initialConfig = {
  namespace: 'TestEditor',
  onError: (error: Error) => console.error(error),
  nodes: [],
};

function SetContentButton({ content, label }: { content: string; label: string }) {
  const [editor] = useLexicalComposerContext();

  const handleClick = () => {
    editor.update(() => {
      const root = $getRoot();
      root.clear();

      if (content.includes('<')) {
        // Parse as HTML
        const parser = new DOMParser();
        const dom = parser.parseFromString(content, 'text/html');
        const nodes = $generateNodesFromDOM(editor, dom);
        root.append(...nodes);
      } else {
        // Plain text
        const paragraph = $createParagraphNode();
        paragraph.append($createTextNode(content));
        root.append(paragraph);
      }
    });
  };

  return <button onClick={handleClick}>{label}</button>;
}

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

describe('CleanHtmlOnChangePlugin', () => {
  test('calls onChange with cleaned HTML when editor content changes', async () => {
    const user = userEvent.setup();
    const handleChange = vi.fn();

    render(
      <TestEditor>
        <CleanHtmlOnChangePlugin onChange={handleChange} />
        <SetContentButton content="Hello world" label="Set Text" />
      </TestEditor>,
    );

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

    await waitFor(() => {
      expect(handleChange).toHaveBeenCalled();
      const lastCall = handleChange.mock.calls[handleChange.mock.calls.length - 1]![0];
      expect(lastCall).toContain('Hello world');
    });
  });

  test('removes inline styles from HTML output', async () => {
    const user = userEvent.setup();
    const handleChange = vi.fn();

    render(
      <TestEditor>
        <CleanHtmlOnChangePlugin onChange={handleChange} />
        <SetContentButton content='<p style="color: red;">Styled text</p>' label="Set Text" />
      </TestEditor>,
    );

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

    await waitFor(() => {
      expect(handleChange).toHaveBeenCalled();
      const lastCall = handleChange.mock.calls[handleChange.mock.calls.length - 1]![0];
      expect(lastCall).not.toContain('style=');
    });
  });

  test('does not throw when onChange callback is not provided', async () => {
    const user = userEvent.setup();

    render(
      <TestEditor>
        <CleanHtmlOnChangePlugin />
        <SetContentButton content="Hello" label="Set Text" />
      </TestEditor>,
    );

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

    // Should not throw
    await user.click(setButton);

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

  test('removes span elements from HTML output', async () => {
    const user = userEvent.setup();
    const handleChange = vi.fn();

    render(
      <TestEditor>
        <CleanHtmlOnChangePlugin onChange={handleChange} />
        <SetContentButton content="<p><span>Styled text</span></p>" label="Set HTML" />
      </TestEditor>,
    );

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

    await waitFor(() => {
      expect(handleChange).toHaveBeenCalled();
      const lastCall = handleChange.mock.calls[handleChange.mock.calls.length - 1]![0];
      expect(lastCall).not.toContain('<span');
    });
  });
});
