// src/vitest.setup.ts or where you define your custom matchers
import {expect} from 'vitest';

expect.extend({
    toEqualUnordered(received, expected) {
        const {isNot, promise, utils} = this;

        if (!Array.isArray(received)) {
            return {
                pass: false,
                message: () => `Received should be array, but received ${utils.printReceived(received)}`,
            }
        }
        if (!Array.isArray(expected)) {
            return {
                pass: false,
                message: () => `Expected should be array, but received ${utils.printReceived(expected)}`,
            }
        }

        // Create a clone of received array to check against
        const remainingReceived = [...received];

        // Check if each element in expected exists in received
        for (const expectedItem of expected) {
            const index = remainingReceived.findIndex(receivedItem =>
                this.equals(receivedItem, expectedItem)
            );

            if (index === -1) {
                const similarItemWithSameid = ('id' in expectedItem) && remainingReceived.find(item => {
                    if (!('id' in item)) {
                        return false;
                    }

                    return item.id === expectedItem.id
                })

                const receivedToPrint = similarItemWithSameid
                return {
                    pass: false,
                    message: () =>
                        `Expected array to contain: ${utils.printExpected(expectedItem)}\n` +
                        `Number of items of both arrays: Received ${utils.printReceived(received.length)} | Expected ${utils.printExpected(expected.length)}` +
                        `Received (${similarItemWithSameid ? 'similar item with id' : 'array'}): ${similarItemWithSameid ? utils.printDiffOrStringify(similarItemWithSameid, expectedItem) : utils.printReceived(received)}`,
                };
            }

            // Remove the matched item to prevent duplicate matches
            remainingReceived.splice(index, 1);
        }

        // First check if arrays have the same length
        if (received.length > expected.length) {
            return {
                pass: false,
                message: () => `Received array has more elements than expected` +
                    `\nExpected: ${utils.printExpected(expected.length)} elements, Received: ${utils.printExpected(received.length)}` +
                    `\nExtra received not in expected: ${utils.printReceived(remainingReceived)}`,
            }
            return {
                pass: false,
                message: () => `Arrays have different lengths. Expected ${expected.length}, received ${received.length}`,
            };
        } else if (received.length < expected.length) {
            return {
                pass: false,
                message: () => `Expected array has more elements than received` +
                    `\nExpected: ${utils.printExpected(expected.length)} elements, Received: ${utils.printReceived(received.length)}` +
                    `\nExtra expected not in received: ${utils.printReceived(expected.filter(item => !remainingReceived.includes(item)))}`,
            }
            return {
                pass: false,
                message: () => `Arrays have different lengths. Expected ${expected.length}, received ${received.length}`,
            };
        }

        return {
            pass: true,
            message: () =>
                `Expected arrays not to match regardless of order:\n` +
                `Expected: ${utils.printExpected(expected)}\n` +
                `Received: ${utils.printReceived(received)}`,
        };
    },
});

// TypeScript declarations
declare module 'vitest' {
    interface Assertion {
        toEqualUnordered(expected: any[]): void;
    }

    interface AsymmetricMatchersContaining {
        toEqualUnordered(expected: any[]): void;
    }
}