/**
 * useViewportController Hook Tests
 *
 * Tests the viewport controller hook including:
 * - Sidebar open/close viewport shift
 * - Early returns (no nodes, unchanged state)
 * - Viewport offset calculation
 * - ViewportController wrapper component
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useViewportController, ViewportController } from './useViewportController';
import { render } from '@testing-library/react';

// Mock useReactFlow
const mockGetViewport = vi.fn();
const mockSetViewport = vi.fn();

vi.mock('@xyflow/react', () => ({
  useReactFlow: () => ({
    getViewport: mockGetViewport,
    setViewport: mockSetViewport,
  }),
}));

describe('useViewportController', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
    mockGetViewport.mockReturnValue({ x: 0, y: 0, zoom: 1 });
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  // ==========================================================================
  // Initial State Tests
  // ==========================================================================

  describe('initial state', () => {
    it('does not adjust viewport on initial render', () => {
      renderHook(() =>
        useViewportController({
          sidebarOpen: false,
          nodeCount: 5,
        }),
      );

      act(() => {
        vi.advanceTimersByTime(100);
      });

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

    it('does not adjust viewport when sidebar is initially open', () => {
      renderHook(() =>
        useViewportController({
          sidebarOpen: true,
          nodeCount: 5,
        }),
      );

      act(() => {
        vi.advanceTimersByTime(100);
      });

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

  // ==========================================================================
  // Sidebar Open/Close Tests
  // ==========================================================================

  describe('sidebar open/close viewport shift', () => {
    it('shifts viewport left when sidebar opens', () => {
      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 5 } },
      );

      // Open sidebar
      rerender({ sidebarOpen: true, nodeCount: 5 });

      act(() => {
        vi.advanceTimersByTime(100);
      });

      expect(mockSetViewport).toHaveBeenCalledWith({ x: -288, y: 0, zoom: 1 }, { duration: 300 });
    });

    it('shifts viewport right when sidebar closes', () => {
      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: true, nodeCount: 5 } },
      );

      // Close sidebar
      rerender({ sidebarOpen: false, nodeCount: 5 });

      act(() => {
        vi.advanceTimersByTime(100);
      });

      expect(mockSetViewport).toHaveBeenCalledWith({ x: 288, y: 0, zoom: 1 }, { duration: 300 });
    });

    it('preserves current viewport y position and zoom', () => {
      mockGetViewport.mockReturnValue({ x: 100, y: 200, zoom: 1.5 });

      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 5 } },
      );

      rerender({ sidebarOpen: true, nodeCount: 5 });

      act(() => {
        vi.advanceTimersByTime(100);
      });

      expect(mockSetViewport).toHaveBeenCalledWith(
        { x: 100 - 288, y: 200, zoom: 1.5 },
        { duration: 300 },
      );
    });
  });

  // ==========================================================================
  // Early Return Tests
  // ==========================================================================

  describe('early returns', () => {
    it('does not adjust viewport when nodeCount is 0', () => {
      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 0 } },
      );

      rerender({ sidebarOpen: true, nodeCount: 0 });

      act(() => {
        vi.advanceTimersByTime(100);
      });

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

    it('does not adjust viewport when sidebarOpen does not change', () => {
      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 5 } },
      );

      // Rerender with same sidebarOpen value but different nodeCount
      rerender({ sidebarOpen: false, nodeCount: 10 });

      act(() => {
        vi.advanceTimersByTime(100);
      });

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

    it('adjusts viewport when nodeCount changes from 0 to positive and sidebar changes', () => {
      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 0 } },
      );

      // Change both sidebar and nodeCount
      rerender({ sidebarOpen: true, nodeCount: 5 });

      act(() => {
        vi.advanceTimersByTime(100);
      });

      expect(mockSetViewport).toHaveBeenCalled();
    });
  });

  // ==========================================================================
  // Timeout Behavior Tests
  // ==========================================================================

  describe('timeout behavior', () => {
    it('uses 50ms delay before adjusting viewport', () => {
      const { rerender } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 5 } },
      );

      rerender({ sidebarOpen: true, nodeCount: 5 });

      // Before 50ms - should not be called
      act(() => {
        vi.advanceTimersByTime(40);
      });

      expect(mockSetViewport).not.toHaveBeenCalled();

      // After 50ms - should be called
      act(() => {
        vi.advanceTimersByTime(20);
      });

      expect(mockSetViewport).toHaveBeenCalled();
    });

    it('clears timeout on unmount', () => {
      const { rerender, unmount } = renderHook(
        ({ sidebarOpen, nodeCount }) => useViewportController({ sidebarOpen, nodeCount }),
        { initialProps: { sidebarOpen: false, nodeCount: 5 } },
      );

      rerender({ sidebarOpen: true, nodeCount: 5 });

      // Unmount before timeout fires
      unmount();

      act(() => {
        vi.advanceTimersByTime(100);
      });

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

  // ==========================================================================
  // ViewportController Component Tests
  // ==========================================================================

  describe('ViewportController component', () => {
    it('renders null', () => {
      const { container } = render(<ViewportController sidebarOpen={false} nodeCount={5} />);

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

    it('adjusts viewport when sidebar state changes', () => {
      const { rerender } = render(<ViewportController sidebarOpen={false} nodeCount={5} />);

      rerender(<ViewportController sidebarOpen={true} nodeCount={5} />);

      act(() => {
        vi.advanceTimersByTime(100);
      });

      expect(mockSetViewport).toHaveBeenCalled();
    });
  });
});
