// CONSTANTS {{{

const commandBarElement = document.getElementById('menu-bar');
const toolBarElement = document.getElementById('tool-bar');
const layerControllersElement = document.getElementById('layer-controllers');
const studioElement = document.getElementById('studio');
const infoBarElement = document.getElementById('info-bar');
const easelElement = document.getElementById('easel');
const brushPreviewElement = document.getElementById('brush-preview');

const dZoom = 0.001;
const dBrushSize = 0.5;
const initialWidth = 800;
const initialHeight = 600;
const maxBrushSize = 500;
const tolerance = 1;
const shapes = ['circle', 'square'];

// }}}

// VARS {{{

let brushShape = 'circle'
let brushSize = 15;
let zoom = 1;

let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
let dX = 0;
let dY = 0;
let canvasStartX = 0;
let canvasStartY = 0;
let canvasEndX = 0;
let canvasEndY = 0;
let canvasDX = 0;
let canvasDY = 0;

let isKeyDown = false;
let isMouseDown = false;

// }}}

// HELPERS {{{

function disableImageSmoothing(ctx) {
	ctx.imageSmoothingEnabled = false;
	if (ctx.imageSmoothingEnabled !== false) {
		ctx.mozImageSmoothingEnabled = false;
		ctx.webkitImageSmoothingEnabled = false;
		ctx.msImageSmoothingEnabled = false;
	}
};

function hexToRgbArray(hex) {
    hex = hex.replace(/^#/, '');
    const bigint = parseInt(hex, 16);
    const r = (bigint >> 16) & 255;
    const g = (bigint >> 8) & 255;
    const b = bigint & 255;
    return [r, g, b, 255]; // Add 255 for full opacity
}

function colorsMatch(color1, color2, tolerance = 0) {
    return Math.abs(color1[0] - color2[0]) <= tolerance &&
           Math.abs(color1[1] - color2[1]) <= tolerance &&
           Math.abs(color1[2] - color2[2]) <= tolerance &&
           Math.abs(color1[3] - color2[3]) <= tolerance; // Include alpha comparison
}

function makeIconElement(htmlString) {
	const parentElement = document.createElement('div');
	parentElement.innerHTML = htmlString;
	const iconElement = parentElement.firstChild;
	return iconElement;
}

function makeButtonElement({icon, name, func, key}) {
	if (!icon) throw new Error('No icon provided');
	if (!name) throw new Error('No name provided');
	if (!func) throw new Error('No click function provided');
	if (!key) throw new Error('No key provided');

	const button = {};
	button.name = name;
	button.key = key;
	button.icon = icon;
	button.element = document.createElement('div');
	button.element.className = 'button';

	button.element.addEventListener('click', func);

	button.refresh = function() {
		button.element.innerHTML = '';
		const iconElement = makeIconElement(button.icon);
		button.element.appendChild(iconElement);
		button.element.title = button.name;
		if (button.key) {
			const keyHint = document.createElement('span');
			keyHint.className = 'key-hint';
			keyHint.innerHTML = key;
			button.element.appendChild(keyHint);
		}
	}

	button.refresh();

	return button;
}

// }}}

// COLOR {{{

function makeColor(rgb) {
	const color = {};

	color.color = rgb;

	color.toRgb = function() {
		return color.rgb;
	}

	color.toHex = function() {
		color.r = parseInt(rgb[0]);
		color.g = parseInt(rgb[1]);
		color.b = parseInt(rgb[2]);
		return `#${color.r.toString(16)}${color.g.toString(16)}${color.b.toString(16)}`;
	}

	color.mix = function(color2, t) {
		const color1 = color.color;
		const newColor = mixbox.lerp(color1, color2, t);
		color.color = newColor;
		console.log(color.color);
		colorPreview.update();
	}

	return color;
}

const color = makeColor({r: 0, g: 0, b: 0});

// }}}

// LAYERS {{{

// FACTORY {{{

function makeCanvas({height=600, width=800}) { // {{{
	const canvas = document.createElement('canvas');
	canvas.style.imageRendering = 'pixelated';
	canvas.ctx = canvas.getContext('2d');

	canvas.tempCanvas = document.createElement('canvas');
	canvas.tempCtx = canvas.tempCanvas.getContext('2d');

	canvas.saveCanvas = function() {
		canvas.ctx.save();
		canvas.tempCanvas.width = canvas.width;
		canvas.tempCanvas.height = canvas.height;
		canvas.tempCtx.clearRect(0, 0, canvas.width, canvas.height);
		disableImageSmoothing(canvas.tempCtx);
		canvas.tempCtx.drawImage(canvas, 0, 0);
	}

	canvas.clearCanvas = function() {
		canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
	}

	canvas.restoreCanvas = function(x=0, y=0) {
		canvas.ctx.drawImage(canvas.tempCanvas, x, y);
	}

	canvas.setHeight = function(height) {
		canvas.height = height;
		disableImageSmoothing(canvas.ctx);
	};

	canvas.setWidth = function(width) {
		canvas.width = width;
		disableImageSmoothing(canvas.ctx);
	};

	canvas.resize = function(width, height) {
		canvas.width = width;
		canvas.height = height;
		disableImageSmoothing(canvas.ctx);
	}

	canvas.getPositionOnCanvas = function(e) {
		const rect = canvas.getBoundingClientRect();
		return {
			x: Math.round((e.clientX - rect.left) / zoom),
			y: Math.round((e.clientY - rect.top) / zoom),
		};
	}

	canvas.drawPixel = function(x, y, color) {
		canvas.ctx.fillStyle = color;
		canvas.ctx.fillRect(x, y, 1, 1);
	}

	canvas.drawLineWithPixels = function(x1, y1, x2, y2, color) {
		const dx = Math.abs(x2 - x1);
		const dy = Math.abs(y2 - y1);
		const sx = x1 < x2 ? 1 : -1;
		const sy = y1 < y2 ? 1 : -1;
		let err = dx - dy;
		while (true) {
			canvas.drawPixel(x1, y1, color); // Draw each pixel along the line
			if (x1 === x2 && y1 === y2) break;
			const e2 = err * 2;
			if (e2 > -dy) { err -= dy; x1 += sx; }
			if (e2 < dx) { err += dx; y1 += sy; }
		}
	}

	canvas.drawShape = function(x, y, shape, size, color) {
		x = Math.round(x);
		y = Math.round(y);

		if (size === 1) {
			canvas.drawPixel(x, y, color);
			return;
		}
		canvas.ctx.fillStyle = color;

		if (shape === 'square') {
			canvas.ctx.fillRect(x - Math.floor(size / 2), y - Math.floor(size / 2), size, size);
		} else if (shape === 'circle') {
			let radius = Math.floor(size / 1);
			let radiusSquared = radius * radius;

			for (let y1 = -radius; y1 <= radius; y1++) {
				for (let x1 = -radius; x1 <= radius; x1++) {
					// Adjust the condition to avoid the outcrop
					if ((x1 * x1 + y1 * y1) <= radiusSquared - radius) {
						canvas.ctx.fillRect(x + x1, y + y1, 1, 1);
					}
				}
			}
		} else if (shape === 'empty-circle') {
			let radius = Math.floor(size / 2);
			let x1 = radius;
			let y1 = 0;
			let radiusError = 1 - x1;

			while (x1 >= y1) {
				// Draw the 8 octants of the circle
				canvas.ctx.fillRect(x + x1, y + y1, 1, 1);
				canvas.ctx.fillRect(x + y1, y + x1, 1, 1);
				canvas.ctx.fillRect(x - y1, y + x1, 1, 1);
				canvas.ctx.fillRect(x - x1, y + y1, 1, 1);
				canvas.ctx.fillRect(x - x1, y - y1, 1, 1);
				canvas.ctx.fillRect(x - y1, y - x1, 1, 1);
				canvas.ctx.fillRect(x + y1, y - x1, 1, 1);
				canvas.ctx.fillRect(x + x1, y - y1, 1, 1);

				y1++;
				if (radiusError < 0) {
					radiusError += 2 * y1 + 1;
				} else {
					x1--;
					radiusError += 2 * (y1 - x1 + 1);
				}
			}
		}
	}

	canvas.drawLineWithShape = function(x1, y1, x2, y2, shape, size, color) {
		const dx = x2 - x1;
		const dy = y2 - y1;
		const distance = Math.sqrt(dx * dx + dy * dy);
		const steps = Math.ceil(distance / (size / 2));

		for (let i = 0; i <= steps; i++) {
			const x = Math.round(x1 + (dx * i) / steps);
			const y = Math.round(y1 + (dy * i) / steps);
			canvas.drawShape(x, y, shape, size, color);
		}
	}

	canvas.fill = function(color) {
		canvas.ctx.fillStyle = color;
		canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
	}

	canvas.getColorAtPixelData = function(x, y, data) {
		const index = (y * canvas.width + x) * 4;
		const color = [data[index], data[index + 1], data[index + 2], data[index + 3]];
		return color;
	}

	canvas.setColorAtPixelData = function(x, y, color, data) {
		const index = (y * canvas.width + x) * 4;
		data[index] = color[0];
		data[index + 1] = color[1];
		data[index + 2] = color[2];
		data[index + 3] = color[3];
	}

	canvas.floodFill = function(x, y, color) {
		console.log('flood fill');
		const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height);
		const data = imageData.data;

		const targetColor = canvas.getColorAtPixelData(x, y, data);
		const fillColorArray = hexToRgbArray(color);

		if (colorsMatch(targetColor, fillColorArray, tolerance)) {
			return;
		}

		const stack = [{x, y}];

		while (stack.length > 0) {
			const {x, y} = stack.pop();
			const currentColor = canvas.getColorAtPixelData(x, y, data);

			if (colorsMatch(currentColor, targetColor, tolerance)) {
				canvas.setColorAtPixelData(x, y, fillColorArray, data);

				if (x > 0) stack.push({x: x - 1, y});
				if (x < canvas.width - 1) stack.push({x: x + 1, y});
				if (y > 0) stack.push({x, y: y - 1});
				if (y < canvas.height - 1) stack.push({x, y: y + 1});
			}
		}

		canvas.ctx.putImageData(imageData, 0, 0);
	}


	canvas.getColorAtPixel = function(x, y) {
		const data = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height).data;
		return canvas.getColorAtPixelData(x, y, data);
	}

	canvas.setColorAtPixel = function(x, y, color) {
		const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height);
		const data = imageData.data;
		canvas.setColorAtPixelData(x, y, color, data);
		canvas.ctx.putImageData(new ImageData(data, canvas.width, canvas.height), 0, 0);
	}

	canvas.toDataUrl = function() {
		const dataURL = canvas.toDataURL();
		const dimensions = `${canvas.width}x${canvas.height}`;
		return {dataURL, dimensions};
	}

	canvas.fromDataUrl = function(dataURL, dimensions) {
		const img = new Image();
		img.src = dataURL;
		img.onload = function() {
			canvas.width = dimensions.split('x')[0];
			canvas.height = dimensions.split('x')[1];
			canvas.style.width = canvas.width * zoom + 'px';
			canvas.style.height = canvas.height * zoom + 'px';
			canvas.ctx.drawImage(img, 0, 0);
		}
	}

	canvas.deleteCanvas = function() {
		canvas.remove();
	}

	canvas.setWidth(width);
	canvas.setHeight(height);

	return canvas;

} // }}}

function makeLayer({height=600, width=800}) { // {{{
	const layer = {}
	layer.canvas = makeCanvas({height, width});
	layer.active = false;
	layer.opacity = 1;

	layer.controllerElement = document.createElement('div');
	layer.controllerElement.className = 'layer-controller';
	layer.controllerElement.innerHTML = '<i class="fa-solid fa-circle-check"></i>';

	const moveUpHandle = document.createElement('div');
	moveUpHandle.classList.add('handle');
	moveUpHandle.classList.add('top-right');
	moveUpHandle.innerHTML = '<i class="fa-solid fa-arrow-up"></i>';
	moveUpHandle.addEventListener('click', () => {
		const index = layers.indexOf(layer);
		if (index > 0) {
			layers.move(layer, index - 1);
		}
	});
	layer.controllerElement.appendChild(moveUpHandle);

	const moveDownHandle = document.createElement('div');
	moveDownHandle.classList.add('handle');
	moveDownHandle.classList.add('bottom-right');
	moveDownHandle.innerHTML = '<i class="fa-solid fa-arrow-down"></i>';
	moveDownHandle.addEventListener('click', () => {
		const index = layers.indexOf(layer);
		if (index < layers.length - 1) {
			layers.move(layer, index + 1);
		}
	});
	layer.controllerElement.appendChild(moveDownHandle);

	layer.controllerElement.addEventListener('click', () => {
		layers.setActive(layer);
	});

	layer.activate = function() {
		layer.active = true;
		layer.controllerElement.classList.add('active');
	}

	layer.deactivate = function() {
		layer.active = false;
		layer.controllerElement.classList.remove('active');
	}

	return layer;
} // }}}

function makeLayers({height=600, width=800}) { // {{{
	const layers = [];
	layers.height = height;
	layers.width = width;

	layers.addButton = document.createElement('div');
	layers.addButton.className = 'layer-add-button';
	layers.addButton.innerHTML = '<i class="fa-solid fa-plus"></i>';
	layers.addButton.addEventListener('click', () => {
		layers.add();
	});

	layers.setHeight = function(height) {
		layers.height = height;
		easelElement.style.height = height + 2 + 'px';
	}

	layers.setHeight(height);

	layers.setWidth = function(width) {
		layers.width = width;
		easelElement.style.width = width + 2 + 'px';
	}

	layers.setWidth(width);

	layers.resetPosition = function() {
		const studioRect = studioElement.getBoundingClientRect();
		easelElement.style.left = `${studioRect.left}px`;
		easelElement.style.top = `${studioRect.top}px`;
	}

	layers.refreshControllers = function() {
		layerControllersElement.innerHTML = '';
		layers.forEach(layer => {
			layerControllersElement.appendChild(layer.controllerElement);
		});
		layerControllersElement.appendChild(layers.addButton);
	}

	layers.refreshLayers = function() {
		easelElement.innerHTML = '';
		layers.forEach(layer => {
			easelElement.appendChild(layer.canvas);
		});
	}

	layers.refresh = function() {
		layers.refreshControllers();
		layers.refreshLayers();
	}

	layers.add = function() {
		const layer = makeLayer({
			height: layers.height,
			width: layers.width,
		});
		layers.push(layer);
		layer.activate();
		layers.refresh();
	}

	layers.delete = function(layer) {
		layer.canvas.deleteCanvas();
		layers.splice(layers.indexOf(layer), 1);
		layers.refresh();
	}

	layers.deleteAll = function() {
		layers.forEach(layer => layer.deleteCanvas());
		// TODO
	}

	layers.move = function(layer, index) {
		layers.splice(layers.indexOf(layer), 1);
		layers.splice(index, 0, layer);
	}

	layers.setActive = function(layer) {
		layers.forEach(layer => layer.deactivate());
		layer.activate();
	}

	layers.getActive = function() {
		return layers.find(layer => layer.active);
	}

	return layers;
} // }}}

// }}}

const layers = makeLayers({height: initialHeight, width: initialWidth});
layers.add();
layers.add();
layers[0].canvas.fill('rgb(255, 255, 255)');
layers.setActive(layers[1]);

// }}}

// COLOR PREVIEW {{{

function makeColorPreview() {
	const colorPreview = {}
	colorPreview.element = document.createElement('div');
	colorPreview.element.id = 'color-preview';
	colorPreview.element.className = 'puck';
	colorPreview.element.style.backgroundColor = color.color;
	commandBarElement.appendChild(colorPreview.element);
	colorPreview.update = function() {
		colorPreview.element.style.backgroundColor = color.color;
	}

	return colorPreview;
}

const colorPreview = makeColorPreview();

// }}}

// COMMANDS {{{

// FACTORY {{{

function makeCommand({name, key, icon, func}) {
	if (!name) throw new Error('No name provided');
	if (!icon) throw new Error('No icon provided');
	if (!func) throw new Error('No click function provided');
	if (!key) throw new Error('No key provided');

	const command = {};
	command.name = name;
	command.key = key;
	command.func = function() {
		func();
		infos.update();
	}
	command.button = makeButtonElement({
		icon: icon,
		name: name,
		key: key,
		func: command.func,
	});
	commandBarElement.appendChild(command.button.element);

	return command
}

function makeCommands() {
	const commands = [];

	commands.add = function({name, key, icon, func}) {
		const command = makeCommand({name, key, icon, func});
		commands.push(command);
	}

	commands.get = function(name) {
		return commands.find(command => command.name === name);
	}

	commands.click = function(name) {
		const command = commands.get(name);
		command.func();
	}

	return commands;
}


// }}}

const commands = makeCommands();

commands.add({ // flip-horizontally {{{
	name: 'flip-horizontally',
	key: 'f',
	icon: '<i class="fa-solid fa-left-right"></i>',
	func: function flipCanvasHorizontally() {
		const canvas = layers.getActive().canvas;
		const ctx = canvas.ctx;
		ctx.save();
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.scale(-1, 1);
		ctx.translate(-canvas.width, 0);
		canvas.restoreCanvas();
		ctx.restore();
	}
}); // }}}

commands.add({ // flip-vertically {{{
	name: 'flip-vertically',
	key: 'v',
	icon: '<i class="fa-solid fa-up-down"></i>',
	func: function flipCanvasVertically() {
		const canvas = layers.getActive().canvas;
		const ctx = canvas.ctx;
		ctx.save();
		canvas.saveCanvas();
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.scale(1, -1);
		ctx.translate(0, -canvas.height);
		canvas.restoreCanvas();
		ctx.restore();
	}
}); // }}}

commands.add({ // export {{{
	name: 'export',
	key: 'e',
	icon: '<i class="fa-solid fa-floppy-disk"></i>',
	func: function exportCanvas() {
		const canvas = layers.getActive().canvas;
		const link = document.createElement('a');
		link.download = 'canvas.png';
		link.href = canvas.toDataURL();
		link.click();
	}
}); // }}}

commands.add({ // import {{{
	name: 'import',
	key: 'i',
	icon: '<i class="fa-regular fa-folder-open"></i>',
	func: function importCanvas() {
		const canvas = layers.getActive().canvas;
		const ctx = canvas.ctx;
		const input = document.createElement('input');
		input.type = 'file';
		input.accept = 'image/*';
		input.onchange = (e) => {
			const file = e.target.files[0];
			const reader = new FileReader();
			reader.onload = (e) => {
				const img = new Image();
				img.onload = () => {
					canvas.width = img.width;
					canvas.height = img.height;
					ctx.drawImage(img, 0, 0);
				}
				img.src = e.target.result;
			}
			reader.readAsDataURL(file);
		}
		input.click();
	}
}); // }}}

commands.add({ // clear {{{
	name: 'clear',
	key: 'c',
	icon: '<i class="fa-solid fa-trash-can"></i>',
	func: function clearCanvas() {
		const canvas = layers.getActive().canvas;
		const ctx = canvas.ctx;
		canvas.saveCanvas();
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'white';
		ctx.fillRect(0, 0, canvas.width, canvas.height);
	}
}); // }}}

commands.add({ // change-shape {{{
	name: 'change-shape',
	key: 's',
	icon: `<i class="fa-solid fa-shapes"></i>`,
	func: function changeShape() {
		const currentIndex = shapes.indexOf(brushShape);
		brushShape = shapes[(currentIndex + 1) % shapes.length];
	}
}); // }}}

// }}}

// TOOLS {{{

// FACTORY {{{

function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}) {
	if (!name) throw new Error('No name provided');
	if (!key) throw new Error('No key provided');
	if (!icon) throw new Error('No icon provided');

	const tool = {};
	tool.name = name;
	tool.key = key;
	tool.icon = icon;
	tool.mouseDown = mouseDown;
	tool.mouseMove = mouseMove;
	tool.mouseUp = mouseUp;
	tool.active = false;

	tool.activate = function() {
		tool.active = true;
		tool.button.element.classList.add('active');
	}

	tool.deactivate = function() {
		tool.active = false;
		tool.button.element.classList.remove('active');
	}

	tool.button = makeButtonElement({
		icon: tool.icon,
		name: tool.name,
		key: tool.key,
		func: function() {
			tools.activate(tool.name);
		}
	});

	toolBarElement.appendChild(tool.button.element);

	return tool;
}

function makeTools() {
	const tools = [];

	tools.prevToolName = 'na';

	tools.add = function({name, key, icon, mouseDown, mouseMove, mouseUp}) {
		const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp});
		tools.push(tool);
	}

	tools.get = function(name) {
		return tools.find(tool => tool.name === name);
	}

	tools.getActive = function() {
		return tools.find(tool => tool.active);
	}

	tools.activate = function(name) {
		const tool = tools.get(name);
		if (tool.active) return;
		if (tools.getActive()) {
			tools.prevToolName = tools.getActive().name;
			tools.forEach(tool => tool.deactivate());
		}
		tool.activate();
	}

	tools.restore = function() {
		const tool = tools.get(tools.prevToolName);
		tools.forEach(tool => tool.deactivate());
		tool.activate();
	}

	return tools;
}

// }}}

const tools = makeTools();

tools.add({ // brush {{{
	name: 'brush',
	key: 'b',
	icon: '<i class="fa-solid fa-paintbrush"></i>',
	mouseDown: function(e) {
		const canvas = layers.getActive().canvas;
		if (brushSize === 1) {
			canvas.drawPixel(canvasStartX, canvasStartY, color.color);
		} else {
			canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, color.color);
		}
	},
	mouseMove: function(e) {
		const canvas = layers.getActive().canvas;
		if (brushSize === 1) {
			canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, color.color);
		} else {
			canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, color.color);
		}
		canvasStartX = canvasEndX;
		canvasStartY = canvasEndY;
	},
}); // }}}

tools.add({ // content-move {{{
	name: 'content-move',
	key: 'h',
	icon: '<i class="fa-regular fa-hand"></i>',
	mouseDown: function(e) {
		const canvas = layers.getActive().canvas;
		canvas.saveCanvas();
	},
	mouseMove: function(e) {
		const canvas = layers.getActive().canvas;
		canvas.clearCanvas();
		canvas.restoreCanvas(dX, dY);
	},
}); // }}}

tools.add({ // move {{{
	name: 'move',
	key: 'm',
	icon: '<i class="fa-solid fa-arrows-up-down-left-right"></i>',
	mouseDown: function(e) {
		startX = e.clientX - easelElement.offsetLeft;
		startY = e.clientY - easelElement.offsetTop;
	},
	mouseMove: function(e) {
		easelElement.style.left = dX + 'px';
		easelElement.style.top = dY + 'px';
	},
}); // }}}

tools.add({ // zoom {{{
	name: 'zoom',
	key: 'z',
	icon: '<i class="fa-solid fa-magnifying-glass"></i>',
	mouseMove: function(e) {
		// TODO all canvases
		// const canvas = layers.getActive().canvas;
		zoom += dX * dZoom;
		if (zoom < 0.1) zoom = 0.1;
		// canvas.style.height = canvasHeight * zoom + 'px';
		// canvas.style.width = canvasWidth * zoom + 'px';
		startX = endX;
	}
}); // }}}

tools.add({ // bucket-fill {{{
	name: 'bucket-fill',
	key: 'k',
	icon: '<i class="fa-solid fa-fill"></i>',
	mouseDown: function(e) {
		const canvas = layers.getActive().canvas;
		canvas.floodFill(canvasStartX, canvasStartY, color.color);
	}
}); // }}}

tools.add({ // color-picker {{{
	name: 'color-picker',
	key: 'a',
	icon: '<i class="fa-solid fa-eye-dropper"></i>',
	mouseDown: function(e) {
		const canvas = layers.getActive().canvas;
		const imageData = canvas.ctx.getImageData(canvasStartX, canvasStartY, 1, 1).data;
		const pickedColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`;
		color.color = pickedColor;
		colorPreview.update();
	}
}); // }}}

tools.add({ // brush-size {{{
	name: 'brush-size',
	key: 'd',
	icon: '<i class="fa-regular fa-circle-dot"></i>',
	mouseMove: function(e) {
		brushSize += dX * dBrushSize;
		if (brushSize < 1) brushSize = 1;
		if (brushSize > maxBrushSize) brushSize = maxBrushSize;
		startX = endX;
	}
}); // }}}

tools.add({ // resize {{{
	name: 'resize',
	key: 'r',
	icon: '<i class="fa-solid fa-ruler-combined"></i>',
	mouseMove: function(e) {
		// const canvas = layers.getActive().canvas;
		// let newWidth = canvasWidth + dX / zoom;
		// let newHeight = canvasHeight + dY / zoom;
		// if (newWidth > 0 && newHeight > 0) {
		// 	canvas.setWidth(newWidth);
		// 	canvas.setHeight(newHeight);
		// 	canvas.style.width = newWidth * zoom + 'px';
		// 	canvas.style.height = newHeight * zoom + 'px';
		// 	canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
		// 	canvas.ctx.fillStyle = backgroundColor;
		// 	canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
		// 	canvas.ctx.drawImage(tempCanvas, 0, 0);
		// }
	}
}); // }}}

tools.add({ // color-mix {{{
	name: 'color-mix',
	key: 'x',
	icon: '<i class="fa-solid fa-mortar-pestle"></i>',
	mouseMove: function(e) {
		const canvas = layers.getActive().canvas;
		const canvasColor = canvas.getColorAtPixel(canvasStartX, canvasStartY);
		const distance = Math.sqrt(Math.pow(e.clientX - startX, 2) + Math.pow(e.clientY - startY, 2));
		const t = Math.min(1, distance / 300);
		color.mix(canvasColor, t);
		startX = endX;
		startY = endY;
	}	
}); // }}}

// }}}

// PUCKS {{{

// FACTORY {{{

function makePuck({puckColor, key, editable=true}) {
	if (!puckColor) throw new Error('No puck color provided');


	const puck = {}
	puck.element = document.createElement('div');
	puck.element.style.backgroundColor = puckColor;
	puck.element.className = 'puck';

	if (editable) {
		const deleteHandle = document.createElement('div');
		deleteHandle.className = 'delete-handle';
		deleteHandle.innerHTML = '<i class="fa-solid fa-trash-can"></i>';
		puck.element.appendChild(deleteHandle);
		deleteHandle.addEventListener('click', () => {
			puck.element.remove();
		});
	}

	if (key) {
		puck.key = key;
		const keyHint = document.createElement('div');
		keyHint.className = 'key-hint';
		keyHint.innerHTML = key;
		puck.element.appendChild(keyHint);
	}

	// function mixx(startTime) {
	// 	var interval = setInterval(() => {
	// 		const elapsedTime = Date.now() - startTime;
	// 		const t = Math.min(1, elapsedTime / 10000);
	// 		const puckColor = puck.element.style.backgroundColor;
	// 		const mixedColor = mixbox.lerp(color.color, puck.element.style.backgroundColor, t);
	// 		color.color = mixedColor;
	// 		colorPreview.update();
	// 		infos.update();
	// 	}, 50);
	// 	return interval;
	// }

	puck.element.addEventListener('mousedown', () => {
		const startTime = Date.now();
		var interval = mixx(startTime);
		function onMouseUp() {
			clearInterval(interval);
			document.removeEventListener('mouseup', onMouseUp);
		}
		document.addEventListener('mouseup', onMouseUp);
	});

	puck.keydown = function(e) {
		if (e.key == key) {
			const startTime = Date.now();
			var interval = mixx(startTime);
			function onKeyUp() {
				clearInterval(interval);
				document.removeEventListener('keyup', onKeyUp);
			}
			document.addEventListener('keyup', onKeyUp);
		}
	}

	commandBarElement.appendChild(puck.element);

	return puck;
}

function makePucks() {
	const pucks = [];

	pucks.add = function({puckColor, key, editable}) {
		const puck = makePuck({puckColor, key, editable});
		pucks.push(puck);
	}

	return pucks;
}

// }}}

const pucks = makePucks();


pucks.add({
	puckColor: 'rgb(0, 0, 0)',
	key: '1',
	editable: false,
});

pucks.add({
	puckColor: 'rgb(255, 255, 255)',
	key: '2',
	editable: false,
});

pucks.add({
	puckColor: 'rgb(255, 0, 0)',
	key: '3',
	editable: false,
});

pucks.add({
	puckColor: 'rgb(0, 255, 0)',
	key: '4',
	editable: false,
});

pucks.add({
	puckColor: 'rgb(0, 0, 255)',
	key: '5',
	editable: false,
});


// }}}

// INFO {{{

// FACTORY {{{

function makeInfo({name, updateFunction}) {
	if (!name) throw new Error('No name provided');
	if (!updateFunction) throw new Error('No update function provided');

	const info = {};
	info.name = name;
	info.updateFunction = updateFunction;

	info.element = document.createElement('span');
	info.element.className = 'info';

	const key = document.createElement('span');
	key.className = 'key';
	key.innerHTML = info.name + ':';

	const value = document.createElement('span');
	value.className = 'value';
	value.innerHTML = '0';

	info.element.appendChild(key);
	info.element.appendChild(value);

	infoBarElement.appendChild(info.element);

	info.update = function() {
		let v = updateFunction();
		if (v === undefined) v = '?';
		value.innerHTML = v;
	}

	return info;
}

function makeInfos() {
	const infos = []
	infos.add = function({name, updateFunction}) {
		const info = makeInfo({name, updateFunction});
		infos.push(info);
	}
	infos.update = function() {
		infos.forEach(function(info){
			info.update();
		});
	}
	return infos;
}

// }}}

const infos = makeInfos();

infos.add({
	name: 'zoom',
	updateFunction: function() {
		var percent = zoom * 100;
		return percent.toFixed(0) + '%';
	}
});

infos.add({
	name: 'brush',
	updateFunction: function() {
		return brushSize;
	}
});


infos.add({
	name: 'x',
	updateFunction: function() {
		return canvasEndX;
	}
});


infos.add({
	name: 'y',
	updateFunction: function() {
		return canvasEndY;
	}
});

infos.add({
	name: 'color',
	updateFunction: function() {
		return color.color;
	}
});

infos.add({
	name: 'width',
	updateFunction: function() {
		return "width";
	}
});

infos.add({
	name: 'height',
	updateFunction: function() {
		return "height";
	}
});

infos.add({
	name: 'shape',
	updateFunction: function() {
		return brushShape;
	}
});

infos.add({
	name: 'tool',
	updateFunction: function() {
		return tools.getActive().name;
	}
});

// }}}

// MOUSE EVENT LISTENERS {{{

studioElement.addEventListener('mousedown', (e) => {
	const canvas = layers.getActive().canvas;
	isMouseDown = true;
	startX = e.clientX;
	startY = e.clientY;
	canvasStartX = canvas.getPositionOnCanvas(e).x;
	canvasStartY = canvas.getPositionOnCanvas(e).y;

	for (var i = 0; i < tools.length; i++) {
		var tool = tools[i];
		if (tool.active) {
			if (tool.mouseDown) {
				tool.mouseDown(e);
				break;
			}
		}
	}

	infos.update();

});

studioElement.addEventListener('mousemove', (e) => {
	const canvas = layers.getActive().canvas;
	endX = e.clientX;
	endY = e.clientY;
	dX = endX - startX;
	dY = endY - startY;
	canvasEndX = canvas.getPositionOnCanvas(e).x;
	canvasEndY = canvas.getPositionOnCanvas(e).y;
	canvasDX = canvasEndX - canvasStartX;
	canvasDY = canvasEndY - canvasStartY;

	if (tools.getActive().name === 'brush-size') {
		brushPreviewElement.style.display = 'block';
		brushPreviewElement.style.width = brushSize + 'px';
		brushPreviewElement.style.height = brushSize + 'px';
		brushPreviewElement.style.left = e.clientX - brushSize / 2 + 'px';
		brushPreviewElement.style.top = e.clientY - brushSize / 2 + 'px';
	}

	if (isMouseDown) {
		for (var i = 0; i < tools.length; i++) {
			var tool = tools[i];
			if (tool.active) {
				if (tool.mouseMove) {
					tool.mouseMove(e);
					break;
				}
			}
		}
	}

	infos.update();

});

studioElement.addEventListener('mouseup', () => {
	isMouseDown = false;
	infos.update();
});

studioElement.addEventListener('mouseleave', () => {
	isMouseDown = false;
	brushPreviewElement.style.display = 'none';
	infos.update();
});

// }}}

// KEYBINDINGS {{{

document.addEventListener('keydown', (e) => {
	if (isKeyDown) return;
	console.log(e.key);

	tools.forEach(tool => {
		if (tool.key.toLowerCase() === e.key.toLowerCase()) {
			tools.activate(tool.name);
		}
	});

	commands.forEach(command => {
		if (command.key.toLowerCase() === e.key.toLowerCase()) {
			command.func();
		}
	});

	pucks.filter(puck => puck.key !== undefined).forEach(puck => {
		if (puck.key.toLowerCase() === e.key.toLowerCase()) {
			puck.keydown(e);
		}
	});

	isKeyDown = true;

});

document.addEventListener('keyup', (e) => {
	tools.forEach(tool => {
		if (tool.key.toLowerCase() === e.key) {
			tools.restore();
		}
	});

	isKeyDown = false;

});

// }}}

layers.resetPosition();
tools.activate('brush');