window.onload = function () {
	class Particle {
		public x: number;
		public y: number;
		public radius: number;
		public color: string;
		public vx: number = 0;
		public vy: number = 0;

		public static DEFAULT_RADIUS = 2.5;

		public constructor(x: number, y: number, color: string, radius: number) {
			this.x = x;
			this.y = y;
			this.color = color;
			this.radius = radius;
		}

		public draw() {
			ctx.beginPath();
			ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
			ctx.fillStyle = this.color;
			ctx.fill();
		}
	}

	const canvas = document.querySelector("canvas") as HTMLCanvasElement;
	const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;

	canvas.width = window.innerWidth;
	canvas.height = window.innerHeight;

	window.addEventListener("resize", () => {
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;
	});

	const particles: Particle[] = [];

	function randomInt(min: number, max: number) {
		return Math.floor(Math.random() * (max - min + 1) + min);
	}

	function makeGroup(
		number: number,
		color: string,
		options: { radius?: number }
	) {
		const group: Particle[] = [];

		for (let i = 0; i < number; i++) {
			const particle = new Particle(
				randomInt(0, canvas.width),
				randomInt(0, canvas.height),
				color,
				options.radius ?? Particle.DEFAULT_RADIUS
			);

			group.push(particle);
			particles.push(particle);
		}

		return group;
	}

	type Options = {
		minDistance?: number;
		maxDistance?: number;
		g?: number;
	};

	function interaction(
		group1: Particle[],
		group2: Particle[],
		options: Options = {}
	) {
		options.g = options.g ?? 0.1;
		options.maxDistance = options.maxDistance ?? 100;
		options.minDistance = options.minDistance ?? 0;

		for (let i = 0; i < group1.length; i++) {
			let fx = 0;
			let fy = 0;

			for (let j = 0; j < group2.length; j++) {
				let a = group1[i];
				let b = group2[j];

				const dx = a.x - b.x;
				const dy = a.y - b.y;
				const d = Math.sqrt(dx * dx + dy * dy);

				if (d > 0 && d < options.maxDistance && d > options.minDistance) {
					const F = options.g / d;
					fx += F * dx;
					fy += F * dy;
				}

				a.vx = (a.vx + fx) * 0.5;
				a.vy = (a.vy + fy) * 0.5;
				a.x += a.vx * 0.005;
				a.y += a.vy * 0.005;
			}
		}
	}

	const blue = makeGroup(5000, "#0000ff", { radius: 1 });
	const red = makeGroup(1000, "#ff0000", { radius: 2.5 });
	const green = makeGroup(50, "#00ff00", { radius: 4 });

	function animate() {
		interaction(red, red, { g: -0.05, maxDistance: 250 });
		interaction(green, red, { g: -0.1, maxDistance: 500 });
		interaction(red, green, { g: -0.05, maxDistance: 250 });
		interaction(blue, red, { g: -0.4, maxDistance: 250 });

		ctx.clearRect(0, 0, canvas.width, canvas.height);

		ctx.fillStyle = "#000000";
		ctx.fillRect(0, 0, canvas.width, canvas.height);

		particles.forEach((particle) => {
			particle.draw();
		});

		requestAnimationFrame(animate);
	}

	animate();
};