// 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 useProgramStore, {type ReducedProgram} from "../../src/utils/programStore.ts";


describe('useProgramStore', () => {
  beforeEach(() => {
    // Reset store before each test
    useProgramStore.setState({
      currentProgram: { phases: [] },
    });
  });

  const mockProgram: ReducedProgram = {
    phases: [
      {
        id: 'phase-1',
        norms: [{ id: 'norm-1' }],
        goals: [{ id: 'goal-1' }],
        triggers: [{ id: 'trigger-1' }],
      },
      {
        id: 'phase-2',
        norms: [{ id: 'norm-2' }],
        goals: [{ id: 'goal-2' }],
        triggers: [{ id: 'trigger-2' }],
      },
    ],
  };

  it('should set and get the program state', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    const program = useProgramStore.getState().getProgramState();
    expect(program).toEqual(mockProgram);
  });

  it('should return the ids of all phases in the program', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    const phaseIds = useProgramStore.getState().getPhaseIds();
    expect(phaseIds).toEqual(['phase-1', 'phase-2']);
  });

  it('should return all norms for a given phase', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    const norms = useProgramStore.getState().getNormsInPhase('phase-1');
    expect(norms).toEqual([{ id: 'norm-1' }]);
  });

  it('should return all goals for a given phase', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    const goals = useProgramStore.getState().getGoalsInPhase('phase-2');
    expect(goals).toEqual([{ id: 'goal-2' }]);
  });

  it('should return all triggers for a given phase', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    const triggers = useProgramStore.getState().getTriggersInPhase('phase-1');
    expect(triggers).toEqual([{ id: 'trigger-1' }]);
  });

  it('throws if phase does not exist when getting norms', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    expect(() =>
      useProgramStore.getState().getNormsInPhase('missing-phase')
    ).toThrow('phase with id:"missing-phase" not found');
  });

  it('throws if phase does not exist when getting goals', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    expect(() =>
      useProgramStore.getState().getGoalsInPhase('missing-phase')
    ).toThrow('phase with id:"missing-phase" not found');
  });

  it('throws if phase does not exist when getting triggers', () => {
    useProgramStore.getState().setProgramState(mockProgram);

    expect(() =>
      useProgramStore.getState().getTriggersInPhase('missing-phase')
    ).toThrow('phase with id:"missing-phase" not found');
  });

  it('should clone program state when setting it (no shared references should exist)', () => {
    const changeableMockProgram: ReducedProgram = {
      phases: [
        {
          id: 'phase-1',
          norms: [{ id: 'norm-1' }],
          goals: [{ id: 'goal-1' }],
          triggers: [{ id: 'trigger-1' }],
        },
        {
          id: 'phase-2',
          norms: [{ id: 'norm-2' }],
          goals: [{ id: 'goal-2' }],
          triggers: [{ id: 'trigger-2' }],
        },
      ],
    };

    useProgramStore.getState().setProgramState(changeableMockProgram);

    const storedProgram = useProgramStore.getState().getProgramState();

    // mutate original
    (changeableMockProgram.phases[0].norms as any[]).push({ id: 'norm-mutated' });

    // store should NOT change
    expect(storedProgram.phases[0]['norms']).toHaveLength(1);
  });
});

describe('getGoalsWithDepth', () => {
    const complexProgram: ReducedProgram = {
      phases: [
        {
          id: 'phase-nested',
          goals: [
            // Level 0: Root Goal 1
            {
              id: 'root-1',
              name: 'Root Goal 1',
              plan: {
                steps: [
                  // This is an ACTION (no plan), should be ignored
                  { id: 'action-1', type: 'speech' },
                  
                  // Level 1: Child Goal
                  {
                    id: 'child-1',
                    name: 'Child Goal',
                    plan: {
                      steps: [
                        // Level 2: Grandchild Goal
                        { 
                          id: 'grandchild-1', 
                          name: 'Grandchild', 
                          plan: { steps: [] } // Empty plan is still a plan
                        }
                      ]
                    }
                  }
                ]
              }
            },
            // Level 0: Root Goal 2 (Sibling)
            {
              id: 'root-2',
              name: 'Root Goal 2',
              plan: { steps: [] }
            }
          ]
        }
      ]
    };

    it('should flatten nested goals and assign correct depth levels', () => {
      useProgramStore.getState().setProgramState(complexProgram);

      const goals = useProgramStore.getState().getGoalsWithDepth('phase-nested');

      // logic: Root 1 -> Child 1 -> Grandchild 1 -> Root 2
      expect(goals).toHaveLength(4);

      // Check Root 1
      expect(goals[0]).toEqual(expect.objectContaining({ id: 'root-1', level: 0 }));
      
      // Check Child 1
      expect(goals[1]).toEqual(expect.objectContaining({ id: 'child-1', level: 1 }));

      // Check Grandchild 1
      expect(goals[2]).toEqual(expect.objectContaining({ id: 'grandchild-1', level: 2 }));

      // Check Root 2
      expect(goals[3]).toEqual(expect.objectContaining({ id: 'root-2', level: 0 }));
    });

    it('should ignore steps that are not goals (missing "plan" property)', () => {
      useProgramStore.getState().setProgramState(complexProgram);
      const goals = useProgramStore.getState().getGoalsWithDepth('phase-nested');

      // The 'action-1' object should NOT be in the list
      const action = goals.find(g => g.id === 'action-1');
      expect(action).toBeUndefined();
    });

    it('throws if phase does not exist', () => {
      useProgramStore.getState().setProgramState(complexProgram);

      expect(() =>
        useProgramStore.getState().getGoalsWithDepth('missing-phase')
      ).toThrow('phase with id:"missing-phase" not found');
    });
  });

it('should return the names of all phases in the program', () => {
    // Define a program specifically with names for this test
    const programWithNames: ReducedProgram = {
      phases: [
        {
          id: 'phase-1',
          name: 'Introduction Phase', // Assuming the property is 'name'
          norms: [],
          goals: [],
          triggers: [],
        },
        {
          id: 'phase-2',
          name: 'Execution Phase',
          norms: [],
          goals: [],
          triggers: [],
        },
      ],
    };

    useProgramStore.getState().setProgramState(programWithNames);

    const phaseNames = useProgramStore.getState().getPhaseNames();
    expect(phaseNames).toEqual(['Introduction Phase', 'Execution Phase']);
  });