import { describe, it, expect } from 'vitest';
import { VelocitySampler, PanInertia, ZoomInertia, PAN_FRICTION, ZOOM_FRICTION, MIN_VELOCITY } from '../gestures/inertia';

describe('gesture inertia', () => {
	describe('VelocitySampler', () => {
		it('returns zero for empty sampler', () => {
			const s = new VelocitySampler();
			expect(s.average()).toEqual({ x: 0, y: 0 });
		});

		it('averages samples', () => {
			const s = new VelocitySampler();
			s.sample(10, 20, 0);
			s.sample(30, 40, 1000);
			const avg = s.average();
			expect(avg.x).toBe(20);
			expect(avg.y).toBe(30);
		});

		it('ring buffer evicts oldest', () => {
			const s = new VelocitySampler(2);
			s.sample(10, 0, 0);
			s.sample(20, 0, 1);
			s.sample(30, 0, 2); // evicts 10
			expect(s.average().x).toBe(25); // (20+30)/2
		});

		it('reset clears samples', () => {
			const s = new VelocitySampler();
			s.sample(100, 200, 0);
			s.reset();
			expect(s.average()).toEqual({ x: 0, y: 0 });
		});
	});

	describe('PanInertia', () => {
		it('decays velocity each tick', () => {
			const pan = new PanInertia({ x: 100, y: 100 });
			const t1 = pan.tick();
			expect(t1).not.toBeNull();
			expect(Math.abs(t1!.x)).toBeLessThan(100);
		});

		it('returns null when velocity drops below threshold', () => {
			const pan = new PanInertia({ x: 1, y: 1 });
			let result = pan.tick();
			// Small initial velocity should stop quickly
			let iterations = 0;
			while (result !== null && iterations < 100) {
				result = pan.tick();
				iterations++;
			}
			expect(result).toBeNull();
		});

		it('applies friction correctly', () => {
			const pan = new PanInertia({ x: 100, y: 0 }, 0.5);
			const t1 = pan.tick();
			expect(t1!.x).toBe(50); // 100 * 0.5
			const t2 = pan.tick();
			expect(t2!.x).toBe(25); // 50 * 0.5
		});
	});

	describe('ZoomInertia', () => {
		it('decays zoom velocity', () => {
			const zoom = new ZoomInertia(5, { x: 400, y: 300 });
			const t1 = zoom.tick(1.5);
			expect(t1).not.toBeNull();
			expect(Math.abs(t1!.delta)).toBeLessThan(5);
		});

		it('snaps to 100% when close', () => {
			const zoom = new ZoomInertia(0.001, { x: 400, y: 300 }, 0.5, 0.01, 0.05);
			const result = zoom.tick(0.99); // close to 1.0
			// Should either snap or return null
			if (result) {
				expect(result.delta).toBeCloseTo(0.01, 2);
			}
		});

		it('returns null when velocity is exhausted', () => {
			const zoom = new ZoomInertia(0.001, { x: 0, y: 0 }, 0.5, 0.01);
			const result = zoom.tick(2.0);
			expect(result).toBeNull();
		});
	});

	describe('constants', () => {
		it('friction values are between 0 and 1', () => {
			expect(PAN_FRICTION).toBeGreaterThan(0);
			expect(PAN_FRICTION).toBeLessThan(1);
			expect(ZOOM_FRICTION).toBeGreaterThan(0);
			expect(ZOOM_FRICTION).toBeLessThan(1);
		});

		it('MIN_VELOCITY is positive', () => {
			expect(MIN_VELOCITY).toBeGreaterThan(0);
		});
	});
});
