// gif player
document.querySelectorAll('.paused-animation').forEach(el => {
	el.addEventListener('click', () => {
		el.classList.toggle('active');
	});
});

// track players
const players = document.querySelectorAll('.track-player');

document.addEventListener('DOMContentLoaded', updateAllWaveformWidths);
window.addEventListener('resize', updateAllWaveformWidths);

players.forEach(player => {
	const trackName = player.getAttribute('data-track');

	const button = document.createElement('div');
	button.className = 'blurgreen play-button play';
	player.appendChild(button);

	const wrapper = document.createElement('div');
	wrapper.className = 'track';
	wrapper.innerHTML = `
	<div class="blurgreen track-container">
	  <div class="track-played"></div>
	  <div class="track-unplayed"></div>
	</div>
  `;
	player.appendChild(wrapper);

	const playercontainer = player.querySelector('.track-container');
	const played = player.querySelector('.track-played');
	const unplayed = player.querySelector('.track-unplayed');
	unplayed.style.backgroundImage = `url('tracks/${trackName}.webp')`;

	const audio = document.createElement('audio');
	audio.style.display = 'none';
	player.appendChild(audio);

	button.addEventListener('click', () => {
		// pause other players immediately
		players.forEach(p => {
			const otherAudio = p.querySelector('audio');
			const otherBtn = p.querySelector('.play-button');
			if (otherAudio !== audio) {
				if (!otherAudio.paused) otherAudio.pause();
				if (otherBtn) {
					otherBtn.classList.add('play');
					otherBtn.classList.remove('pause');
				}
			}
		});

		// player needs mp3 url (and played waveform image to lazyload)
		if (!audio.src) {
			played.style.backgroundImage = `url('tracks/${trackName}-played.webp')`;
			audio.src = 'tracks/' + trackName + '.mp3';
			audio.currentTime = 0;
			audio.load();

			audio.addEventListener('canplay', () => {
				audio.play().then(() => {
					button.classList.remove('play');
					button.classList.add('pause');
				}).catch(() => {
					button.classList.add('play');
					button.classList.remove('pause');
				});
			}, { once: true });

			return;
		}

		if (audio.paused) {
			audio.play().then(() => {
				button.classList.remove('play');
				button.classList.add('pause');
			}).catch(() => {
				button.classList.add('play');
				button.classList.remove('pause');
			});
		} else {
			audio.pause();
			button.classList.add('play');
			button.classList.remove('pause');
		}
	});

	audio.addEventListener('ended', () => {
		button.classList.add('play');
		button.classList.remove('pause');
	});

	// skip to playback position on waveform interaction
	playercontainer.addEventListener('click', e => {
		const rect = playercontainer.getBoundingClientRect();
		const clickX = e.clientX - rect.left;
		const width = rect.width;

		const portion = Math.min(Math.max(clickX / width, 0), 1);

		audio.currentTime = portion * audio.duration;
	});

	audio.addEventListener('timeupdate', () => {
		const portion = audio.duration ? audio.currentTime / audio.duration : 0;
		updatePlayPosition(played, unplayed, portion);
	});
});

function updatePlayPosition(played_element, unplayed_element, portion) {
	const playedPercent = portion * 100;
	const unplayedPercent = 100 - playedPercent;
	played_element.style.width = playedPercent + "%";
	unplayed_element.style.width = unplayedPercent + "%";
}

function updatePlayerWaveformWidths(player_container_element) {
	const trackWidth = player_container_element.offsetWidth;
	const played_element = player_container_element.querySelector('.track-played');
	const unplayed_element = player_container_element.querySelector('.track-unplayed');
	played_element.style.backgroundSize = trackWidth + "px 80px";
	unplayed_element.style.backgroundSize = trackWidth + "px 80px";
}

function updateAllWaveformWidths() { // required for proper display of played/unplayed waveforms
	players.forEach(player => {
		const container = player.querySelector('.track-container');
		if (container) updatePlayerWaveformWidths(container);
	});
}


// icosahedron
window.onload = function() {
	// Make sure three.js is available
	if (typeof THREE === 'undefined') {
		console.error('THREE.js is not loaded from CDN');
		return;
	}

	// init scene
	const container = document.getElementById('icosahedron-container');
	const containerWidth = container.clientWidth;
	const containerHeight = container.clientHeight;

	// setup renderer
	const renderer = new THREE.WebGLRenderer({
		antialias: true,
		alpha: true
	});
	renderer.setSize(containerWidth, containerHeight);
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setClearColor(0x000000, 0); // transparent
	container.appendChild(renderer.domElement);

	// setup scene and camera
	const scene = new THREE.Scene();
	const camera = new THREE.PerspectiveCamera(26, containerWidth / containerHeight, 0.1, 100);
	camera.position.z = 4;

	// icosahedron with manually defined vertices
	function createIcosahedron(radius) {
		// golden ratio
		const t = (1 + Math.sqrt(5)) / 2;
		// normalize radius
		const normRadius = radius / Math.sqrt(1 + t * t);

		const vertices = [
			[-1, t, 0],
			[1, t, 0],
			[-1, -t, 0],
			[1, -t, 0],
			[0, -1, t],
			[0, 1, t],
			[0, -1, -t],
			[0, 1, -t],
			[t, 0, -1],
			[t, 0, 1],
			[-t, 0, -1],
			[-t, 0, 1]
		].map(v => new THREE.Vector3(v[0] * normRadius, v[1] * normRadius, v[2] * normRadius));

		// edges (pairs of vertex indices)
		const edges = [
			[0, 11],
			[0, 5],
			[0, 1],
			[0, 7],
			[0, 10],
			[1, 5],
			[1, 7],
			[1, 8],
			[1, 9],
			[2, 3],
			[2, 4],
			[2, 6],
			[2, 10],
			[2, 11],
			[3, 4],
			[3, 6],
			[3, 8],
			[3, 9],
			[4, 5],
			[4, 9],
			[4, 11],
			[5, 9],
			[5, 11],
			[6, 7],
			[6, 8],
			[6, 10],
			[7, 8],
			[7, 10],
			[8, 9],
			[10, 11]
		];

		return {
			vertices: vertices,
			edges: edges
		};
	}

	// convert vertex indices to actual vertices and create a line
	function createLine(icosa, edgeIndex, material) {
		const startIndex = icosa.edges[edgeIndex][0];
		const endIndex = icosa.edges[edgeIndex][1];

		const geometry = new THREE.BufferGeometry();
		const positions = new Float32Array([
			icosa.vertices[startIndex].x, icosa.vertices[startIndex].y, icosa.vertices[startIndex].z,
			icosa.vertices[endIndex].x, icosa.vertices[endIndex].y, icosa.vertices[endIndex].z
		]);

		geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

		return new THREE.Line(geometry, material);
	}

	// create thin back lines, thick front lines
	function createDualLineRendering(radius) {
		const group = new THREE.Group();
		const icosa = createIcosahedron(radius);

		const backMaterial = new THREE.MeshBasicMaterial({
			color: 0x63a8b8,
			depthTest: false, // don't test depth for back lines
			transparent: true,
			opacity: 0.4,
			side: THREE.DoubleSide
		});

		const frontMaterial = new THREE.MeshBasicMaterial({
			color: 0xffffff,
			depthTest: true, // depth test for front lines
			transparent: true,
			side: THREE.DoubleSide
		});

		// cylinders for each edge
		const backRadius = 0.025;
		const frontRadius = 0.035;

		icosa.edges.forEach((edge, index) => {
			const start = icosa.vertices[edge[0]];
			const end = icosa.vertices[edge[1]];

			const backLine = createCylinderBetweenPoints(start, end, backRadius, 1, backMaterial);
			backLine.renderOrder = 1;
			group.add(backLine);

			const frontLine = createCylinderBetweenPoints(start, end, frontRadius, 0.95, frontMaterial);
			frontLine.renderOrder = 3;
			group.add(frontLine);
		});

		// invisible face material for depth testing
		const faceMaterial = new THREE.MeshBasicMaterial({
			color: 0xffffff,
			transparent: true,
			opacity: 0,
			depthWrite: true,
			side: THREE.DoubleSide
		});

		// internal icosahedra material
		const internalMaterial = new THREE.MeshBasicMaterial({
			color: 0x63a8b8,
			depthTest: false,
			transparent: true,
			opacity: 0.2,
			side: THREE.DoubleSide
		});

		// triangles of vertex indices
		const faces = [
			[0, 11, 5],
			[0, 5, 1],
			[0, 1, 7],
			[0, 7, 10],
			[0, 10, 11],
			[1, 5, 9],
			[5, 11, 4],
			[11, 10, 2],
			[10, 7, 6],
			[7, 1, 8],
			[3, 9, 4],
			[3, 4, 2],
			[3, 2, 6],
			[3, 6, 8],
			[3, 8, 9],
			[4, 9, 5],
			[2, 4, 11],
			[6, 2, 10],
			[8, 6, 7],
			[9, 8, 1]
		];

		// create depth testing and internal icosahedra
		faces.forEach(face => {
			const geometry = new THREE.BufferGeometry();
			const positions = new Float32Array([
				icosa.vertices[face[0]].x, icosa.vertices[face[0]].y, icosa.vertices[face[0]].z,
				icosa.vertices[face[1]].x, icosa.vertices[face[1]].y, icosa.vertices[face[1]].z,
				icosa.vertices[face[2]].x, icosa.vertices[face[2]].y, icosa.vertices[face[2]].z
			]);

			geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

			const faceMesh = new THREE.Mesh(geometry, faceMaterial);
			faceMesh.scale.set(0.975, 0.975, 0.975);
			faceMesh.renderOrder = 0;
			group.add(faceMesh);

			// stack internal icosahedra for volumetric effect
			const internalMesh1 = new THREE.Mesh(geometry, internalMaterial);
			const internalMesh2 = new THREE.Mesh(geometry, internalMaterial);
			const internalMesh3 = new THREE.Mesh(geometry, internalMaterial);
			const internalMesh4 = new THREE.Mesh(geometry, internalMaterial);
			const internalMesh5 = new THREE.Mesh(geometry, internalMaterial);
			internalMesh1.scale.set(0.85, 0.85, 0.85);
			internalMesh1.renderOrder = 1;
			group.add(internalMesh1);
			internalMesh2.scale.set(0.65, 0.65, 0.65);
			internalMesh2.renderOrder = 1;
			group.add(internalMesh2);
			internalMesh3.scale.set(0.5, 0.5, 0.5);
			internalMesh3.renderOrder = 1;
			group.add(internalMesh3);
			internalMesh4.scale.set(0.4, 0.4, 0.4);
			internalMesh4.renderOrder = 1;
			group.add(internalMesh4);
			internalMesh5.scale.set(0.3, 0.3, 0.3);
			internalMesh5.renderOrder = 1;
			group.add(internalMesh5);
		});

		return group;
	}

	function createCylinderBetweenPoints(pointX, pointY, radius, lengthmultiplier, material) {
		const direction = new THREE.Vector3().subVectors(pointY, pointX);
		const length = direction.length();
		const geometry = new THREE.CylinderGeometry(radius, radius, length * lengthmultiplier, 4, 1);

		// default cylinder is along y-axis, rotate it
		geometry.rotateX(Math.PI / 2);

		const cylinder = new THREE.Mesh(geometry, material);

		// position and orient cylinder
		const midpoint = new THREE.Vector3().addVectors(pointX, pointY).multiplyScalar(0.5);
		cylinder.position.copy(midpoint);
		cylinder.lookAt(pointY);

		return cylinder;
	}

	// create icosahedron
	const icosahedronGroup = createDualLineRendering(0.85);
	scene.add(icosahedronGroup);

	// animation state
	let animating = true;

	let lastFrameTime = 0;
	const targetFPS = 20;
	const animMultiplier = 30 / targetFPS;
	const frameDuration = 1000 / targetFPS;

	function animate(now) {
		requestAnimationFrame(animate);

		const delta = now - lastFrameTime;
		if (delta < frameDuration) return;

		lastFrameTime = now;

		if (animating) {
			icosahedronGroup.rotation.x -= 0.002 * animMultiplier;
			icosahedronGroup.rotation.y += 0.004 * animMultiplier;
			icosahedronGroup.rotation.z -= 0.001 * animMultiplier;
		}

		renderer.render(scene, camera);
	}

	animate();

	window.addEventListener('resize', function() {
		const newWidth = container.clientWidth;
		const newHeight = container.clientHeight;

		if (newWidth !== containerWidth || newHeight !== containerHeight) {
			camera.aspect = newWidth / newHeight;
			camera.updateProjectionMatrix();
			renderer.setSize(newWidth, newHeight);
		}
	});

	// console.log('icosahedron wireframe created successfully');
};
