// 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 {applyPriorityPredicates, type PriorityFilterPredicate} from "../../src/utils/priorityFiltering";

const makePred = <T>(priority: number, fn: (el: T) => boolean | null): PriorityFilterPredicate<T> => ({
  priority,
  predicate: jest.fn(fn),
});

describe("applyPriorityPredicates (unit)", () => {
  beforeEach(() => jest.clearAllMocks());

  it("returns true when there are no predicates", () => {
    expect(applyPriorityPredicates(123, [])).toBe(true);
  });

  it("behaves like a normal predicate with only one predicate", () => {
    const even = makePred<number>(1, (n) => n % 2 === 0);
    expect(applyPriorityPredicates(2, [even])).toBe(true);
    expect(applyPriorityPredicates(3, [even])).toBe(false);
  });

  it("determines the result only listening to the highest priority predicates", () => {
    const lowFail = makePred<number>(1, (_) => false);
    const lowPass = makePred<number>(1, (_) => true);
    const highPass = makePred<number>(10, (n) => n > 0);
    const highFail = makePred<number>(10, (n) => n < 0);

    expect(applyPriorityPredicates(5, [lowFail, highPass])).toBe(true);
    expect(applyPriorityPredicates(5, [lowPass, highFail])).toBe(false);
  });

  it("uses all predicates at the highest priority", () => {
    const high1 = makePred<number>(5, (n) => n % 2 === 0);
    const high2 = makePred<number>(5, (n) => n > 2);
    expect(applyPriorityPredicates(4, [high1, high2])).toBe(true);
    expect(applyPriorityPredicates(2, [high1, high2])).toBe(false);
  });

  it("is order independent (later higher positive clears earlier lower negative)", () => {
    const lowFalse = makePred<number>(1, (_) => false);
    const highTrue = makePred<number>(9, (n) => n === 7);

    // Higher priority appears later → should reset and decide by highest only
    expect(applyPriorityPredicates(7, [lowFalse, highTrue])).toBe(true);

    // Same set, different order → same result
    expect(applyPriorityPredicates(7, [highTrue, lowFalse])).toBe(true);
  });

  it("handles many priorities: only max matters", () => {
    const p1 = makePred<number>(1, (_) => false);
    const p3 = makePred<number>(3, (_) => false);
    const p5 = makePred<number>(5, (n) => n > 0);
    expect(applyPriorityPredicates(1, [p1, p3, p5])).toBe(true);
  });

  it("skips predicates that return null", () => {
    const high = makePred<number>(10, (n) => n === 0 ? true : null);
    const low = makePred<number>(1, (_) => false);
    expect(applyPriorityPredicates(0, [high, low])).toBe(true);
    expect(applyPriorityPredicates(1, [high, low])).toBe(false);
  });
});

describe("(integration) filter with applyPriorityPredicates", () => {
  it("filters an array using only highest-priority predicates", () => {
    const elems = [1, 2, 3, 4, 5];
    const low = makePred<number>(0, (_) => false);
    const high1 = makePred<number>(5, (n) => n % 2 === 0);
    const high2 = makePred<number>(5, (n) => n > 2);
    const result = elems.filter((e) => applyPriorityPredicates(e, [low, high1, high2]));
    expect(result).toEqual([4]);
  });

  it("filters an array using only highest-priority predicates", () => {
    const elems = [1, 2, 3, 4, 5];
    const low = makePred<number>(0, (_) => false);
    const high = makePred<number>(5, (n) => n === 3 ? true : null);
    const result = elems.filter((e) => applyPriorityPredicates(e, [low, high]));
    expect(result).toEqual([3]);
  });
});
