import {fireEvent, render, screen} from '@testing-library/react';
import '@testing-library/jest-dom';
import {useReactFlow, useStoreApi} from "@xyflow/react";
import {
  type EditorWarning,
  globalWarning
} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/EditorWarnings.tsx";
import {WarningsSidebar} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx";
import useFlowStore from "../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx";


jest.mock('@xyflow/react', () => ({
  useReactFlow: jest.fn(),
  useStoreApi: jest.fn(),
}));

jest.mock('../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx');

function makeWarning(
  overrides?: Partial<EditorWarning>
): EditorWarning {
  return {
    scope: { id: 'node-1' },
    type: 'MISSING_INPUT',
    severity: 'ERROR',
    description: 'Missing input',
    ...overrides,
  };
}

describe('WarningsSidebar', () => {
  let getStateSpy: jest.SpyInstance;

  const setCenter = jest.fn(() => Promise.resolve());
  const getNode = jest.fn();
  const addSelectedNodes = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();

    // React Flow hooks
    (useReactFlow as jest.Mock).mockReturnValue({
      getNode,
      setCenter,
    });
    (useStoreApi as jest.Mock).mockReturnValue({
      getState: () => ({ addSelectedNodes }),
    });

    // Use spyOn to override store
    const mockWarnings = [
      makeWarning({ description: 'Node warning', scope: { id: 'node-1' } }),
      makeWarning({
        description: 'Global warning',
        scope: { id: globalWarning },
        type: 'INCOMPLETE_PROGRAM',
        severity: 'WARNING',
      }),
      makeWarning({
        description: 'Info warning',
        scope: { id: 'node-2' },
        severity: 'INFO',
      }),
    ];

    getStateSpy = jest
      .spyOn(useFlowStore, 'getState')
      .mockReturnValue({
        getWarnings: () => mockWarnings,
      } as any);
  });

  afterEach(() => {
    getStateSpy.mockRestore();
  });

  it('renders warnings header', () => {
    render(<WarningsSidebar />);
    expect(screen.getByText('Warnings')).toBeInTheDocument();
  });

  it('renders all warning descriptions', () => {
    render(<WarningsSidebar />);
    expect(screen.getByText('Node warning')).toBeInTheDocument();
    expect(screen.getByText('Global warning')).toBeInTheDocument();
    expect(screen.getByText('Info warning')).toBeInTheDocument();
  });

  it('splits global and other warnings correctly', () => {
    render(<WarningsSidebar />);
    expect(screen.getByText('global:')).toBeInTheDocument();
    expect(screen.getByText('other:')).toBeInTheDocument();
  });

  it('shows empty state when no warnings exist', () => {
    getStateSpy.mockReturnValueOnce({
      getWarnings: () => [],
    } as any);

    render(<WarningsSidebar />);
    expect(screen.getByText('No warnings!')).toBeInTheDocument();
  });

  it('filters by severity', () => {
    render(<WarningsSidebar />);
    fireEvent.click(screen.getByText('ERROR'));

    expect(screen.getByText('Node warning')).toBeInTheDocument();
    expect(screen.queryByText('Global warning')).not.toBeInTheDocument();
    expect(screen.queryByText('Info warning')).not.toBeInTheDocument();
  });

  it('filters INFO severity correctly', () => {
    render(<WarningsSidebar />);
    fireEvent.click(screen.getByText('INFO'));

    expect(screen.getByText('Info warning')).toBeInTheDocument();
    expect(screen.queryByText('Node warning')).not.toBeInTheDocument();
    expect(screen.queryByText('Global warning')).not.toBeInTheDocument();
  });

  it('clicking global warning does NOT jump', () => {
    render(<WarningsSidebar />);
    fireEvent.click(screen.getByText('Global warning'));

    expect(setCenter).not.toHaveBeenCalled();
    expect(addSelectedNodes).not.toHaveBeenCalled();
  });

  it('does nothing if node does not exist', () => {
    getNode.mockReturnValue(undefined);

    render(<WarningsSidebar />);
    fireEvent.click(screen.getByText('Node warning'));

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