// 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 { describe, it, beforeEach } from '@jest/globals';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
import TriggerNode, {
  TriggerReduce,
  type TriggerNodeData,
  TriggerConnectionSource, TriggerConnectionTarget
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode';
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
import type { Node } from '@xyflow/react';
import '@testing-library/jest-dom';
import { TriggerNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts';
import { BasicBeliefNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts';
import { defaultPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/Plan.default.ts';
import { NormNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts';
import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts';
import { act } from '@testing-library/react';

describe('TriggerNode', () => {

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

  describe('Rendering', () => {
    it('should render TriggerNode with keywords type', () => {
      const mockNode: Node<TriggerNodeData> = {
        id: 'trigger-1',
        type: 'trigger',
        position: { x: 0, y: 0 },
        data: {
          ...JSON.parse(JSON.stringify(TriggerNodeDefaults)),
        },
      };

      renderWithProviders(
        <TriggerNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      expect(screen.getByText(/Triggers when the condition is met/i)).toBeInTheDocument();
      expect(screen.getByText(/Belief is currently/i)).toBeInTheDocument();
      expect(screen.getByText(/Plan is currently/i)).toBeInTheDocument();
    });
  });

  describe('TriggerReduce Function', () => {
    it('should reduce a trigger node to its essential data', () => {
      const conditionNode: Node = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          ...JSON.parse(JSON.stringify(BasicBeliefNodeDefaults)),
        },
      };

      const triggerNode: Node = {
        id: 'trigger-1',
        type: 'trigger',
        position: { x: 0, y: 0 },
        data: {
          ...JSON.parse(JSON.stringify(TriggerNodeDefaults)),
          condition: "belief-1",
          plan: defaultPlan,
          name: "trigger-1"
        },
      };

      useFlowStore.setState({
        nodes: [conditionNode, triggerNode],
        edges: [],
      });

      useFlowStore.getState().onConnect({
        source: 'belief-1',
        target: 'trigger-1',
        sourceHandle: null,
        targetHandle: null,
      });

    const result = TriggerReduce(triggerNode, useFlowStore.getState().nodes);

    expect(result).toEqual({
      id: 'trigger-1',
      name: "trigger-1",
      condition: {
        id: "belief-1",
        keyword: "",
      },
      plan: {
        id: expect.anything(),
        steps: [],
      },});
    });
  });


  describe('TriggerConnects Function', () => {
    it('should handle connection without errors', () => {
      const node1: Node = {
        id: 'trigger-1',
        type: 'trigger',
        position: { x: 0, y: 0 },
        data: {
          ...JSON.parse(JSON.stringify(TriggerNodeDefaults)),
          label: 'Trigger 1',
        },
      };

      const node2: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 100, y: 0 },
        data: {
          ...JSON.parse(JSON.stringify(NormNodeDefaults)),
          label: 'Norm 1',
        },
      };

      expect(() => {
        TriggerConnectionSource(node1, node2.id);
        TriggerConnectionTarget(node1, node2.id);
      }).not.toThrow();
    });
  });

  describe('TriggerConnects Function', () => {
    it('should correctly remove a goal from the triggers plan after it has been disconnected', () => {
      // first, define the goal node and trigger node.
      const goal: Node = {
        id: 'g-1',
        type: 'goal',
        position: { x: 0, y: 0 },
        data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), name: 'Goal 1' },
      };

      const trigger: Node<TriggerNodeData> = {
        id: 'trigger-1',
        type: 'trigger',
        position: { x: 0, y: 0 },
        data: { ...JSON.parse(JSON.stringify(TriggerNodeDefaults)) },
      };

      // set initial store
      useFlowStore.setState({ nodes: [goal, trigger], edges: [] });

      // then, connect the goal to the trigger.
      act(() => {
        useFlowStore.getState().onConnect({ source: 'g-1', target: 'trigger-1', sourceHandle: null, targetHandle: null });
      });
      // expect the goal id to be part of a goal step of the plan.
      let updatedTrigger = useFlowStore.getState().nodes.find((n) => n.id === 'trigger-1');
      expect(updatedTrigger?.data.plan).toBeDefined();
      const plan = updatedTrigger?.data.plan as any;
      expect(plan.steps.find((s: any) => s.id === 'g-1')).toBeDefined();

      // then, disconnect the goal from the trigger.
      act(() => {
        useFlowStore.getState().onEdgesDelete([{ id: 'g-1-trigger-1', source: 'g-1', target: 'trigger-1' } as any]);
      });

      // finally, expect the goal id to NOT be part of the goal step of the plan.
      updatedTrigger = useFlowStore.getState().nodes.find((n) => n.id === 'trigger-1');
      const planAfter = updatedTrigger?.data.plan as any;
      const stillHas = planAfter?.steps?.find((s: any) => s.id === 'g-1');
      expect(stillHas).toBeUndefined();
    });
  });
 });
