/**
 * useEditorContent Hook Tests
 *
 * Tests for the useEditorContent custom hook that manages unified editor content state
 */

import React from 'react';
import { render, screen, act } from '@testing-library/react';
import '@testing-library/jest-dom';
import useEditorContent from '../useEditorContent';

// Test wrapper component
const TestComponent = ({ initialContent, options, onStateChange }) => {
  const editorState = useEditorContent(initialContent, options);

  React.useEffect(() => {
    if (onStateChange) {
      onStateChange(editorState);
    }
  });

  return (
    <div>
      <div data-testid="content">{editorState.content}</div>
      <div data-testid="is-dirty">{String(editorState.isDirty)}</div>
      <div data-testid="is-empty">{String(editorState.isEmpty)}</div>
      <div data-testid="character-count">{editorState.characterCount}</div>
      <div data-testid="can-save">{String(editorState.canSave)}</div>
      <div data-testid="is-large">{String(editorState.isLargeContent)}</div>
      <div data-testid="auto-save-enabled">{String(editorState.isAutoSaveEnabled)}</div>
      <button onClick={() => editorState.updateContent('Updated content')} data-testid="update">
        Update
      </button>
      <button onClick={() => editorState.resetContent()} data-testid="reset">
        Reset
      </button>
      <button onClick={() => editorState.saveContent()} data-testid="save">
        Save
      </button>
    </div>
  );
};

describe('useEditorContent', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.runOnlyPendingTimers();
    jest.useRealTimers();
  });

  describe('Initial State', () => {
    it('initializes with default content when no initial content provided', () => {
      render(<TestComponent />);

      expect(screen.getByTestId('content')).toHaveTextContent('<!DOCTYPE html>');
      expect(screen.getByTestId('is-dirty')).toHaveTextContent('false');
      expect(screen.getByTestId('is-empty')).toHaveTextContent('false');
    });

    it('initializes with provided initial content', () => {
      render(<TestComponent initialContent="<h1>Test</h1>" />);

      expect(screen.getByTestId('content')).toHaveTextContent('<h1>Test</h1>');
      expect(screen.getByTestId('is-dirty')).toHaveTextContent('false');
    });

    it('initializes with auto-save enabled by default', () => {
      render(<TestComponent />);

      expect(screen.getByTestId('auto-save-enabled')).toHaveTextContent('true');
    });

    it('respects auto-save option', () => {
      render(<TestComponent options={{ autoSave: false }} />);

      expect(screen.getByTestId('auto-save-enabled')).toHaveTextContent('false');
    });
  });

  describe('Content Updates', () => {
    it('updates content when updateContent is called', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      act(() => {
        editorState.updateContent('<p>New content</p>');
      });

      expect(screen.getByTestId('content')).toHaveTextContent('<p>New content</p>');
      expect(screen.getByTestId('is-dirty')).toHaveTextContent('true');
    });

    it('marks content as dirty after update', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      expect(screen.getByTestId('is-dirty')).toHaveTextContent('false');

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      expect(screen.getByTestId('is-dirty')).toHaveTextContent('true');
    });

    it('does not update if content is the same', () => {
      const onChange = jest.fn();
      let editorState;
      render(<TestComponent
        initialContent="<p>Same</p>"
        options={{ onChange }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Same</p>');
      });

      // onChange should not be called if content hasn't changed
      expect(onChange).not.toHaveBeenCalled();
    });

    it('calls onChange callback when content updates', () => {
      const onChange = jest.fn();
      let editorState;
      render(<TestComponent
        options={{ onChange }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>New</p>');
      });

      act(() => {
        jest.advanceTimersByTime(300);
      });

      expect(onChange).toHaveBeenCalled();
    });
  });

  describe('Content State', () => {
    it('reports isEmpty correctly for empty content', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      // Update to empty content
      act(() => {
        editorState.updateContent('');
      });

      expect(screen.getByTestId('is-empty')).toHaveTextContent('true');
    });

    it('reports isEmpty correctly for non-empty content', () => {
      render(<TestComponent initialContent="<p>Content</p>" />);

      expect(screen.getByTestId('is-empty')).toHaveTextContent('false');
    });

    it('calculates character count correctly', () => {
      const content = '<p>Test</p>';
      render(<TestComponent initialContent={content} />);

      expect(screen.getByTestId('character-count')).toHaveTextContent(String(content.length));
    });

    it('detects large content', async () => {
      // Create content larger than threshold (make it 100KB to be safe)
      const largeContent = 'a'.repeat(100000);
      let editorState;
      const { rerender } = render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      await act(async () => {
        editorState.updateContent(largeContent);
        // Wait for state update
        await Promise.resolve();
      });

      // Force TestComponent to re-render and call onStateChange with updated state
      rerender(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      // Now check the state
      expect(editorState.isLargeContent).toBe(true);
    });
  });

  describe('Reset Functionality', () => {
    it('resets to initial content', () => {
      const initialContent = '<h1>Initial</h1>';
      let editorState;
      render(<TestComponent
        initialContent={initialContent}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      expect(screen.getByTestId('content')).toHaveTextContent('<p>Modified</p>');
      expect(screen.getByTestId('is-dirty')).toHaveTextContent('true');

      act(() => {
        editorState.resetContent();
      });

      expect(screen.getByTestId('content')).toHaveTextContent(initialContent);
      expect(screen.getByTestId('is-dirty')).toHaveTextContent('false');
    });

    it('resets to default content if no initial content', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      act(() => {
        editorState.resetContent();
      });

      expect(screen.getByTestId('content')).toHaveTextContent('<!DOCTYPE html>');
      expect(screen.getByTestId('is-dirty')).toHaveTextContent('false');
    });
  });

  describe('Save Functionality', () => {
    it('reports canSave as false when no onSave provided', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      expect(screen.getByTestId('can-save')).toHaveTextContent('false');
    });

    it('reports canSave as true when onSave provided and content is dirty', () => {
      const onSave = jest.fn();
      let editorState;
      render(<TestComponent
        options={{ onSave }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      expect(screen.getByTestId('can-save')).toHaveTextContent('true');
    });

    it('calls onSave callback when saveContent is called', async () => {
      const onSave = jest.fn().mockResolvedValue(true);
      let editorState;
      render(<TestComponent
        options={{ onSave }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      await act(async () => {
        await editorState.saveContent();
      });

      expect(onSave).toHaveBeenCalledWith('<p>Modified</p>');
    });

    it('marks content as saved after successful save', async () => {
      const onSave = jest.fn().mockResolvedValue(true);
      let editorState;
      render(<TestComponent
        options={{ onSave }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      expect(screen.getByTestId('is-dirty')).toHaveTextContent('true');

      await act(async () => {
        await editorState.saveContent();
      });

      expect(screen.getByTestId('is-dirty')).toHaveTextContent('false');
    });

    it('does not save if content is not dirty', async () => {
      const onSave = jest.fn().mockResolvedValue(true);
      let editorState;
      render(<TestComponent
        options={{ onSave }}
        onStateChange={(state) => { editorState = state; }}
      />);

      const result = await act(async () => {
        return await editorState.saveContent();
      });

      expect(result).toBe(false);
      expect(onSave).not.toHaveBeenCalled();
    });
  });

  describe('Auto-save', () => {
    it('toggles auto-save state', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      expect(screen.getByTestId('auto-save-enabled')).toHaveTextContent('true');

      act(() => {
        editorState.toggleAutoSave();
      });

      expect(screen.getByTestId('auto-save-enabled')).toHaveTextContent('false');

      act(() => {
        editorState.toggleAutoSave();
      });

      expect(screen.getByTestId('auto-save-enabled')).toHaveTextContent('true');
    });

    it('auto-saves after interval when enabled and dirty', async () => {
      const onSave = jest.fn().mockResolvedValue(true);
      let editorState;
      render(<TestComponent
        options={{ onSave, autoSaveInterval: 5000 }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      expect(onSave).not.toHaveBeenCalled();

      await act(async () => {
        jest.advanceTimersByTime(5000);
        await Promise.resolve();
      });

      expect(onSave).toHaveBeenCalled();
    });

    it('does not auto-save when auto-save is disabled', async () => {
      const onSave = jest.fn().mockResolvedValue(true);
      let editorState;
      render(<TestComponent
        options={{ onSave, autoSave: false, autoSaveInterval: 5000 }}
        onStateChange={(state) => { editorState = state; }}
      />);

      act(() => {
        editorState.updateContent('<p>Modified</p>');
      });

      await act(async () => {
        jest.advanceTimersByTime(5000);
        await Promise.resolve();
      });

      expect(onSave).not.toHaveBeenCalled();
    });
  });

  describe('Validation Integration', () => {
    it('registers and calls validation callback', () => {
      const validationCallback = jest.fn();
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      act(() => {
        editorState.registerValidationCallback(validationCallback);
      });

      act(() => {
        editorState.validateContent();
      });

      expect(validationCallback).toHaveBeenCalled();
    });

    it('unregisters validation callback', () => {
      const validationCallback = jest.fn();
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      act(() => {
        editorState.registerValidationCallback(validationCallback);
        editorState.unregisterValidationCallback();
        editorState.validateContent();
      });

      expect(validationCallback).not.toHaveBeenCalled();
    });

    it('calls validation callback on content update with debounce', () => {
      const validationCallback = jest.fn();
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      act(() => {
        editorState.registerValidationCallback(validationCallback);
        editorState.updateContent('<p>New</p>');
      });

      expect(validationCallback).not.toHaveBeenCalled();

      act(() => {
        jest.advanceTimersByTime(500);
      });

      expect(validationCallback).toHaveBeenCalled();
    });
  });

  describe('Computed Properties', () => {
    it('provides content analysis', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      expect(editorState.contentAnalysis).toBeDefined();
      expect(editorState.contentAnalysis).toHaveProperty('size');
      expect(editorState.contentAnalysis).toHaveProperty('isLarge');
    });

    it('provides performance warning flag', () => {
      let editorState;
      render(<TestComponent onStateChange={(state) => { editorState = state; }} />);

      expect(editorState.shouldShowPerformanceWarning).toBeDefined();
      expect(typeof editorState.shouldShowPerformanceWarning).toBe('boolean');
    });
  });
});

