import type { Mocked, MockedFunction } from 'vitest';
import { DraftManagerClient } from '../../src/renderer/draft-manager-client';
import type { IpcRenderer, Config } from '../../src/renderer/types';

describe('DraftManager', () => {
  let draftManager: DraftManagerClient;
  let mockIpcRenderer: Mocked<IpcRenderer>;
  let mockGetText: MockedFunction<() => string>;
  let mockGetScrollTop: MockedFunction<() => number>;

  beforeEach(() => {
    mockIpcRenderer = {
      invoke: vi.fn(),
      send: vi.fn(),
      on: vi.fn()
    } as any;

    const mockElectronAPI = {
      draft: {
        save: vi.fn().mockImplementation((...args: any[]) => {
          return mockIpcRenderer.invoke('save-draft', ...args);
        }),
        clear: vi.fn().mockImplementation((...args: any[]) => {
          return mockIpcRenderer.invoke('clear-draft', ...args);
        })
      }
    };

    mockGetText = vi.fn();
    mockGetScrollTop = vi.fn().mockReturnValue(0) as MockedFunction<() => number>;
    draftManager = new DraftManagerClient(mockElectronAPI, mockGetText, mockGetScrollTop);

    // Clear only the call history, not the mock implementations
    mockIpcRenderer.invoke.mockClear();
    mockGetText.mockClear();
    mockGetScrollTop.mockClear();
  });

  afterEach(() => {
    // Clean up any timers to prevent Jest from hanging
    if (draftManager) {
      draftManager.cleanup();
    }
  });

  describe('debounced draft saving', () => {
    test('should save draft with default delay', () => new Promise<void>((resolve) => {
      mockGetText.mockReturnValue('test text');

      draftManager.saveDraftDebounced();

      setTimeout(() => {
        expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('save-draft', 'test text', 0);
        resolve();
      }, 550); // Default delay is 500ms
    }));

    test('should save draft with custom delay', () => new Promise<void>((resolve) => {
      const config: Config = { draft: { saveDelay: 100 } };
      draftManager.setConfig(config);
      mockGetText.mockReturnValue('test text');

      draftManager.saveDraftDebounced();

      setTimeout(() => {
        expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('save-draft', 'test text', 0);
        resolve();
      }, 150);
    }));

    test('should cancel previous debounced save', () => new Promise<void>((resolve) => {
      mockGetText.mockReturnValue('first text').mockReturnValueOnce('second text');
      const config: Config = { draft: { saveDelay: 100 } };
      draftManager.setConfig(config);

      draftManager.saveDraftDebounced();
      mockGetText.mockReturnValue('second text');
      draftManager.saveDraftDebounced();

      setTimeout(() => {
        expect(mockIpcRenderer.invoke).toHaveBeenCalledTimes(1);
        expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('save-draft', 'second text', 0);
        resolve();
      }, 150);
    }));
  });

  describe('immediate draft saving', () => {
    test('should save draft immediately', async () => {
      mockGetText.mockReturnValue('test text');

      await draftManager.saveDraftImmediate();

      expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('save-draft', 'test text', 0);
    });

    test('should save provided text immediately', async () => {
      await draftManager.saveDraftImmediate('provided text');

      expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('save-draft', 'provided text', 0);
      expect(mockGetText).not.toHaveBeenCalled();
    });

    test('should not save empty text', async () => {
      mockGetText.mockReturnValue('');

      await draftManager.saveDraftImmediate();

      expect(mockIpcRenderer.invoke).not.toHaveBeenCalled();
    });

    test('should not save whitespace-only text', async () => {
      mockGetText.mockReturnValue('   \n\t  ');

      await draftManager.saveDraftImmediate();

      expect(mockIpcRenderer.invoke).not.toHaveBeenCalled();
    });
  });

  describe('draft clearing', () => {
    test('should clear draft', async () => {
      await draftManager.clearDraft();

      expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('clear-draft');
    });
  });

  describe('draft value extraction', () => {
    test('should extract string draft value', () => {
      const result = draftManager.extractDraftValue('test string');

      expect(result).toBe('test string');
    });

    test('should extract object draft value', () => {
      const result = draftManager.extractDraftValue({ text: 'test object' });

      expect(result).toBe('test object');
    });

    test('should handle null draft value', () => {
      const result = draftManager.extractDraftValue(null);

      expect(result).toBe('');
    });

  });

  describe('cleanup', () => {
    test('should clear timeout on cleanup', () => {
      mockGetText.mockReturnValue('test text');
      
      draftManager.saveDraftDebounced();
      draftManager.cleanup();

      // Wait longer than the timeout would have been
      setTimeout(() => {
        expect(mockIpcRenderer.invoke).not.toHaveBeenCalled();
      }, 600);
    });

  });
});