// This program has been developed by students from the bachelor Computer Science at Utrecht
// University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences)
import {act} from '@testing-library/react';
import useFlowStore from '../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';
import { mockReactFlow } from '../../../setupFlowTests.ts';





beforeAll(() => {
  mockReactFlow();
});

describe("UndoRedo Middleware", () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  test("pushSnapshot adds a snapshot to past and clears future", () => {
    const store = useFlowStore;

    store.setState({
      nodes: [{
        id: 'A',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'A'}
      }],
      edges: [],
      past: [],
      future: [{
        nodes: [
          {
            id: 'A',
            type: 'default',
            position: {x: 0, y: 0},
            data: {label: 'A'}
          }
        ],
        edges: [],
        warnings: {
          warningRegistry: new Map(),
          severityIndex: new Map()
        }
      }],
      ruleRegistry: new Map(),
      editorWarningRegistry: new Map(),
      severityIndex: new Map()
    });

    act(() => {
      store.getState().pushSnapshot();
    })

    const state = store.getState();
    expect(state.past.length).toBe(1);
    expect(state.past[0]).toEqual({
      nodes: [{
        id: 'A',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'A'}
      }],
      edges: [],
      warnings: {
        warningRegistry: {},
        severityIndex: {}
      }
    });
    expect(state.future).toEqual([]);
  });

  test("pushSnapshot does nothing during batch action", () => {
    const store = useFlowStore;

    act(() => {
      store.setState({ isBatchAction: true });
      store.getState().pushSnapshot();
    })

    expect(store.getState().past.length).toBe(0);
  });

  test("undo restores last snapshot and pushes current snapshot to future", () => {
    const store = useFlowStore;

    // initial state
    store.setState({
      nodes: [{
        id: 'A',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'A'}
      }],
      edges: [],
      editorWarningRegistry: new Map(),
      severityIndex: new Map()
    });

    act(() => {
      store.getState().pushSnapshot();

      // modified state
      store.setState({
        nodes: [{
          id: 'B',
          type: 'default',
          position: {x: 0, y: 0},
          data: {label: 'B'}
        }],
        edges: []
      });

      store.getState().undo();
    })

    expect(store.getState().nodes).toEqual([{
      id: 'A',
      type: 'default',
      position: {x: 0, y: 0},
      data: {label: 'A'}
    }]);
    expect(store.getState().future.length).toBe(1);
    expect(store.getState().future[0]).toEqual({
      nodes: [{
        id: 'B',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'B'}
      }],
      edges: [],
      warnings: {
        warningRegistry: {},
        severityIndex: {}
      }
    });
  });

  test("undo does nothing when past is empty", () => {
    const store = useFlowStore;

    store.setState({past: []});

    act(() => { store.getState().undo(); });

    expect(store.getState().nodes).toEqual([]);
    expect(store.getState().future).toEqual([]);
  });

  test("redo restores last future snapshot and pushes current to past", () => {
    const store = useFlowStore;

    // initial
    store.setState({
      nodes: [{
        id: 'A',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'A'}
      }],
      edges: [],
      editorWarningRegistry: new Map(),
      severityIndex: new Map()
    });

    act(() => {
      store.getState().pushSnapshot();
      store.setState({
        nodes: [{
          id: 'B',
          type: 'default',
          position: {x: 0, y: 0},
          data: {label: 'B'}
        }],
        edges: []
      });


      store.getState().undo();

      // redo should restore node with id 'B'
      store.getState().redo();
    })

    expect(store.getState().nodes).toEqual([{
      id: 'B',
      type: 'default',
      position: {x: 0, y: 0},
      data: {label: 'B'}
    }]);
    expect(store.getState().past.length).toBe(1); // snapshot A stored
    expect(store.getState().past[0]).toEqual({
      nodes: [{
        id: 'A',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'A'}
      }],
      edges: [],
      warnings: {
        warningRegistry: {},
        severityIndex: {}
      }
    });
  });

  test("redo does nothing when future is empty", () => {
    const store = useFlowStore;

    store.setState({past: []});
    act(() => { store.getState().redo(); });

    expect(store.getState().nodes).toEqual([]);
  });

  test("beginBatchAction pushes snapshot and sets isBatchAction=true", () => {
    const store = useFlowStore;

    store.setState({
      nodes: [{
        id: 'A',
        type: 'default',
        position: {x: 0, y: 0},
        data: {label: 'A'}
      }],
      edges: [],
      editorWarningRegistry: new Map(),
      severityIndex: new Map()
    });

    act(() => { store.getState().beginBatchAction(); });

    expect(store.getState().isBatchAction).toBe(true);
    expect(store.getState().past.length).toBe(1);
  });

  test("endBatchAction sets isBatchAction=false after timeout", () => {
    const store = useFlowStore;

    store.setState({ isBatchAction: true });
    act(() => { store.getState().endBatchAction(); });

    // isBatchAction should remain true before the timer has advanced
    expect(store.getState().isBatchAction).toBe(true);

    jest.advanceTimersByTime(10);

    // it should now be set to false as the timer has advanced enough
    expect(store.getState().isBatchAction).toBe(false);
  });

  test("multiple beginBatchAction calls clear the timeout", () => {
    const store = useFlowStore;

    act(() => {
      store.getState().beginBatchAction();
      store.getState().endBatchAction(); // starts timeout
      store.getState().beginBatchAction(); // should clear previous timeout
    });


    jest.advanceTimersByTime(10);

    // After advancing the timers, isBatchAction should still be true,
    // as the timeout should have been cleared
    expect(store.getState().isBatchAction).toBe(true);
  });
});
