// #region DebounceTest [C:2] [TYPE Module] [SEMANTICS test, debounce, utility, timer]
// @BRIEF Unit tests for the debounce utility — timing behavior, argument forwarding, and edge cases.
// @LAYER Tests
// @RELATION BINDS_TO -> [DebounceModule]
// @TEST_CONTRACT: debounce -> Delays function execution until wait period elapses
// @TEST_CONTRACT: debounce -> Cancels previous pending invocation on new call
// @TEST_CONTRACT: debounce -> Forwards arguments to the original function

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

describe('debounce', () => {
	beforeEach(() => {
		vi.useFakeTimers();
	});

	afterEach(() => {
		vi.useRealTimers();
	});

	it('delays function execution by the specified wait time', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn = vi.fn();
		const debounced = debounce(fn, 500);

		debounced();
		expect(fn).not.toHaveBeenCalled();

		vi.advanceTimersByTime(499);
		expect(fn).not.toHaveBeenCalled();

		vi.advanceTimersByTime(1);
		expect(fn).toHaveBeenCalledTimes(1);
	});

	it('cancels previous invocation when called again within wait period', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn = vi.fn();
		const debounced = debounce(fn, 300);

		debounced();
		vi.advanceTimersByTime(200);

		debounced();
		vi.advanceTimersByTime(200);
		expect(fn).not.toHaveBeenCalled();

		vi.advanceTimersByTime(100);
		expect(fn).toHaveBeenCalledTimes(1);
	});

	it('forwards arguments to the original function', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn = vi.fn();
		const debounced = debounce(fn, 100);

		debounced('arg1', 42);
		vi.advanceTimersByTime(100);

		expect(fn).toHaveBeenCalledWith('arg1', 42);
	});

	it('executes the latest invocation with most recent args', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn = vi.fn();
		const debounced = debounce(fn, 200);

		debounced('first');
		debounced('second');
		debounced('third');
		vi.advanceTimersByTime(200);

		expect(fn).toHaveBeenCalledTimes(1);
		expect(fn).toHaveBeenCalledWith('third');
	});

	it('handles zero wait time', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn = vi.fn();
		const debounced = debounce(fn, 0);

		debounced();
		vi.advanceTimersByTime(0);

		expect(fn).toHaveBeenCalledTimes(1);
	});

	it('handles multiple independent debounced functions', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn1 = vi.fn();
		const fn2 = vi.fn();
		const debounced1 = debounce(fn1, 100);
		const debounced2 = debounce(fn2, 200);

		debounced1();
		debounced2();
		vi.advanceTimersByTime(100);

		expect(fn1).toHaveBeenCalledTimes(1);
		expect(fn2).not.toHaveBeenCalled();

		vi.advanceTimersByTime(100);
		expect(fn2).toHaveBeenCalledTimes(1);
	});

	it('executes immediately when wait is very large and timer advances far', async () => {
		const { debounce } = await import('$lib/utils/debounce.js');
		const fn = vi.fn();
		const debounced = debounce(fn, 10000);

		debounced();
		vi.advanceTimersByTime(10000);

		expect(fn).toHaveBeenCalledTimes(1);
	});
});
// #endregion DebounceTest
