const selectTransformTool = () =>
	toolbar.registerTool(
		"res/icons/box-select.svg",
		"Select Image",
		(state, opt) => {
			// Draw new cursor immediately
			ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
			state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});

			// Canvas left mouse handlers
			mouse.listen.canvas.onmousemove.on(state.movecb);
			mouse.listen.canvas.left.onclick.on(state.clickcb);
			mouse.listen.canvas.left.ondragstart.on(state.dragstartcb);
			mouse.listen.canvas.left.ondragend.on(state.dragendcb);

			// Canvas right mouse handler
			mouse.listen.canvas.right.onclick.on(state.cancelcb);

			// Keyboard click handlers
			keyboard.listen.onkeyclick.on(state.keyclickcb);
			keyboard.listen.onkeydown.on(state.keydowncb);

			// Registers keyboard shortcuts
			keyboard.onShortcut({ctrl: true, key: "KeyC"}, state.ctrlccb);
			keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
			keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);

			state.selected = null;
		},
		(state, opt) => {
			// Clear all those listeners and shortcuts we set up
			mouse.listen.canvas.onmousemove.clear(state.movecb);
			mouse.listen.canvas.left.onclick.clear(state.clickcb);
			mouse.listen.canvas.left.ondragstart.clear(state.dragstartcb);
			mouse.listen.canvas.left.ondragend.clear(state.dragendcb);

			mouse.listen.canvas.right.onclick.clear(state.cancelcb);

			keyboard.listen.onkeyclick.clear(state.keyclickcb);
			keyboard.listen.onkeydown.clear(state.keydowncb);
			keyboard.deleteShortcut(state.ctrlccb, "KeyC");
			keyboard.deleteShortcut(state.ctrlvcb, "KeyV");
			keyboard.deleteShortcut(state.ctrlxcb, "KeyX");

			// Clear any selections
			state.reset();

			// Resets cursor
			ovCanvas.style.cursor = "auto";
		},
		{
			init: (state) => {
				state.clipboard = {};

				state.snapToGrid = true;
				state.keepAspectRatio = true;
				state.useClipboard = !!navigator.clipboard.write; // Use it by default if supported

				state.original = null;
				state.dragging = null;
				state._selected = null;
				Object.defineProperty(state, "selected", {
					get: () => state._selected,
					set: (v) => {
						if (v) state.ctxmenu.enableButtons();
						else state.ctxmenu.disableButtons();

						return (state._selected = v);
					},
				});
				state.moving = null;

				// Some things to easy request for a redraw
				state.lastMouseTarget = null;
				state.lastMouseMove = null;

				const redraw = () => {
					ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
					state.movecb(state.lastMouseMove);
				};

				// Clears selection and make things right
				state.reset = () => {
					if (state.selected)
						imgCtx.drawImage(
							state.original.image,
							state.original.x,
							state.original.y
						);

					if (state.dragging) state.dragging = null;
					else state.selected = null;

					redraw();
				};

				// Selection bounding box object. Has some witchery to deal with handles.
				const selectionBB = (x1, y1, x2, y2) => {
					return {
						original: {
							x: Math.min(x1, x2),
							y: Math.min(y1, y2),
							w: Math.abs(x1 - x2),
							h: Math.abs(y1 - y2),
						},
						x: Math.min(x1, x2),
						y: Math.min(y1, y2),
						w: Math.abs(x1 - x2),
						h: Math.abs(y1 - y2),
						updateOriginal() {
							this.original.x = this.x;
							this.original.y = this.y;
							this.original.w = this.w;
							this.original.h = this.h;
						},
						contains(x, y) {
							return (
								this.x <= x &&
								x <= this.x + this.w &&
								this.y <= y &&
								y <= this.y + this.h
							);
						},
						handles() {
							const _createHandle = (x, y, originOffset = null, size = 10) => {
								return {
									x: x - size / 2,
									y: y - size / 2,
									w: size,
									h: size,
									contains(x, y) {
										return (
											this.x <= x &&
											x <= this.x + this.w &&
											this.y <= y &&
											y <= this.y + this.h
										);
									},
									scaleTo: (tx, ty, keepAspectRatio = true) => {
										const origin = {
											x: this.original.x + this.original.w / 2,
											y: this.original.y + this.original.h / 2,
										};
										let nx = tx;
										let ny = ty;

										let xRatio = (nx - origin.x) / (x - origin.x);
										let yRatio = (ny - origin.y) / (y - origin.y);
										if (keepAspectRatio)
											xRatio = yRatio = Math.min(xRatio, yRatio);

										if (Number.isFinite(xRatio)) {
											let left = this.original.x;
											let right = this.original.x + this.original.w;

											left = (left - origin.x) * xRatio + origin.x;
											right = (right - origin.x) * xRatio + origin.x;

											this.x = left;
											this.w = right - left;
										}

										if (Number.isFinite(yRatio)) {
											let top = this.original.y;
											let bottom = this.original.y + this.original.h;

											top = (top - origin.y) * yRatio + origin.y;
											bottom = (bottom - origin.y) * yRatio + origin.y;

											this.y = top;
											this.h = bottom - top;
										}
									},
								};
							};
							return [
								_createHandle(this.x, this.y),
								_createHandle(this.x + this.w, this.y),
								_createHandle(this.x, this.y + this.h),
								_createHandle(this.x + this.w, this.y + this.h),
							];
						},
					};
				};

				// Mouse move handelr. As always, also renders cursor
				state.movecb = (evn) => {
					ovCanvas.style.cursor = "auto";
					state.lastMouseTarget = evn.target;
					state.lastMouseMove = evn;
					if (evn.target.id === "overlayCanvas") {
						let x = evn.x;
						let y = evn.y;
						if (state.snapToGrid) {
							x += snap(evn.x, true, 64);
							y += snap(evn.y, true, 64);
						}

						// Update scale
						if (state.scaling) {
							state.scaling.scaleTo(x, y, state.keepAspectRatio);
						}

						// Update position
						if (state.moving) {
							state.selected.x = x - state.moving.offset.x;
							state.selected.y = y - state.moving.offset.y;
							state.selected.updateOriginal();
						}

						// Draw dragging box
						if (state.dragging) {
							ovCtx.setLineDash([2, 2]);
							ovCtx.lineWidth = 1;
							ovCtx.strokeStyle = "#FFF";

							const ix = state.dragging.ix;
							const iy = state.dragging.iy;

							const bb = selectionBB(ix, iy, x, y);

							ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h);
							ovCtx.setLineDash([]);
						}

						if (state.selected) {
							ovCtx.lineWidth = 1;
							ovCtx.strokeStyle = "#FFF";

							const bb = {
								x: state.selected.x,
								y: state.selected.y,
								w: state.selected.w,
								h: state.selected.h,
							};

							// Draw Image
							ovCtx.drawImage(
								state.selected.image,
								0,
								0,
								state.selected.image.width,
								state.selected.image.height,
								state.selected.x,
								state.selected.y,
								state.selected.w,
								state.selected.h
							);

							// Draw selection box
							ovCtx.setLineDash([4, 2]);
							ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h);
							ovCtx.setLineDash([]);

							// Draw Scaling/Rotation Origin
							ovCtx.beginPath();
							ovCtx.arc(
								state.selected.x + state.selected.w / 2,
								state.selected.y + state.selected.h / 2,
								5,
								0,
								2 * Math.PI
							);
							ovCtx.stroke();

							// Draw Scaling Handles
							let cursorInHandle = false;
							state.selected.handles().forEach((handle) => {
								if (handle.contains(evn.x, evn.y)) {
									cursorInHandle = true;
									ovCtx.strokeRect(
										handle.x - 1,
										handle.y - 1,
										handle.w + 2,
										handle.h + 2
									);
								} else {
									ovCtx.strokeRect(handle.x, handle.y, handle.w, handle.h);
								}
							});

							// Change cursor
							if (cursorInHandle || state.selected.contains(evn.x, evn.y))
								ovCanvas.style.cursor = "pointer";
						}

						// Draw current cursor location
						ovCtx.lineWidth = 3;
						ovCtx.strokeStyle = "#FFF";

						ovCtx.beginPath();
						ovCtx.moveTo(x, y + 10);
						ovCtx.lineTo(x, y - 10);
						ovCtx.moveTo(x + 10, y);
						ovCtx.lineTo(x - 10, y);
						ovCtx.stroke();
					}
				};

				// Handles left mouse clicks
				state.clickcb = (evn) => {
					if (evn.target.id === "overlayCanvas") {
						// If something is selected, commit changes to the canvas
						if (state.selected) {
							imgCtx.drawImage(
								state.selected.image,
								state.original.x,
								state.original.y
							);
							commands.runCommand(
								"eraseImage",
								"Image Transform Erase",
								state.original
							);
							commands.runCommand(
								"drawImage",
								"Image Transform Draw",
								state.selected
							);
							state.original = null;
							state.selected = null;

							redraw();
						}
					}
				};

				// Handles left mouse drag events
				state.dragstartcb = (evn) => {
					if (evn.target.id === "overlayCanvas") {
						let ix = evn.ix;
						let iy = evn.iy;
						if (state.snapToGrid) {
							ix += snap(evn.ix, true, 64);
							iy += snap(evn.iy, true, 64);
						}

						// If is selected, check if drag is in handles/body and act accordingly
						if (state.selected) {
							const handles = state.selected.handles();

							const activeHandle = handles.find((v) =>
								v.contains(evn.ix, evn.iy)
							);
							if (activeHandle) {
								state.scaling = activeHandle;
								return;
							} else if (state.selected.contains(ix, iy)) {
								state.moving = {
									offset: {x: ix - state.selected.x, y: iy - state.selected.y},
								};
								return;
							}
						}
						// If it is not, just create new selection
						state.reset();
						state.dragging = {ix, iy};
					}
				};

				// Handles left mouse drag end events
				state.dragendcb = (evn) => {
					if (evn.target.id === "overlayCanvas") {
						let x = evn.x;
						let y = evn.y;
						if (state.snapToGrid) {
							x += snap(evn.x, true, 64);
							y += snap(evn.y, true, 64);
						}

						// If we are scaling, stop scaling and do some handler magic
						if (state.scaling) {
							state.selected.updateOriginal();
							state.scaling = null;
							// If we are moving the selection, just... stop
						} else if (state.moving) {
							state.moving = null;
							/**
							 * If we are dragging, create a cutout selection area and save to an auxiliar image
							 * We will be rendering the image to the overlay, so it will not be noticeable
							 */
						} else if (state.dragging) {
							state.original = selectionBB(
								state.dragging.ix,
								state.dragging.iy,
								x,
								y
							);
							state.selected = selectionBB(
								state.dragging.ix,
								state.dragging.iy,
								x,
								y
							);

							// Cut out selected portion of the image for manipulation
							const cvs = document.createElement("canvas");
							cvs.width = state.selected.w;
							cvs.height = state.selected.h;
							const ctx = cvs.getContext("2d");

							ctx.drawImage(
								imgCanvas,
								state.selected.x,
								state.selected.y,
								state.selected.w,
								state.selected.h,
								0,
								0,
								state.selected.w,
								state.selected.h
							);

							imgCtx.clearRect(
								state.selected.x,
								state.selected.y,
								state.selected.w,
								state.selected.h
							);
							state.selected.image = cvs;
							state.original.image = cvs;

							if (state.selected.w === 0 || state.selected.h === 0)
								state.selected = null;

							state.dragging = null;
						}
						redraw();
					}
				};

				// Handler for right clicks. Basically resets everything
				state.cancelcb = (evn) => {
					if (evn.target.id === "overlayCanvas") {
						state.reset();
					}
				};

				// Keyboard callbacks (For now, they just handle the "delete" key)
				state.keydowncb = (evn) => {};

				state.keyclickcb = (evn) => {
					if (state.lastMouseTarget.id === "overlayCanvas") {
						switch (evn.code) {
							case "Delete":
								// Deletes selected area
								state.selected &&
									commands.runCommand(
										"eraseImage",
										"Erase Area",
										state.selected
									);
								state.selected = null;
								redraw();
						}
					}
				};

				// Register Ctrl-C/V Shortcut

				// Handles copying
				state.ctrlccb = (evn, cut = false) => {
					if (state.selected && state.lastMouseTarget.id === "overlayCanvas") {
						// We create a new canvas to store the data
						state.clipboard.copy = document.createElement("canvas");

						state.clipboard.copy.width = state.selected.w;
						state.clipboard.copy.height = state.selected.h;

						const ctx = state.clipboard.copy.getContext("2d");

						ctx.clearRect(0, 0, state.selected.w, state.selected.h);
						ctx.drawImage(
							state.selected.image,
							0,
							0,
							state.selected.image.width,
							state.selected.image.height,
							0,
							0,
							state.selected.w,
							state.selected.h
						);

						// If cutting, we reverse the selection and erase the selection area
						if (cut) {
							const aux = state.original;
							state.reset();

							commands.runCommand("eraseImage", "Cut Image", aux);
						}

						// Because firefox needs manual activation of the feature
						if (state.useClipboard) {
							// Send to clipboard
							state.clipboard.copy.toBlob((blob) => {
								const item = new ClipboardItem({"image/png": blob});
								navigator.clipboard.write([item]).catch((e) => {
									console.warn("Error sending to clipboard");
									console.warn(e);
								});
							});
						}
					}
				};

				// Handles pasting
				state.ctrlvcb = (evn) => {
					if (state.useClipboard) {
						// If we use the clipboard, do some proccessing of clipboard data (ugly but kind of minimum required)
						navigator.clipboard.read().then((items) => {
							console.info(items[0]);
							for (const item of items) {
								for (const type of item.types) {
									if (type.startsWith("image/")) {
										item.getType(type).then((blob) => {
											// Converts blob to image
											const url = window.URL || window.webkitURL;
											const image = document.createElement("img");
											image.src = url.createObjectURL(file);
											tools.stamp.enable({
												image,
												back: tools.selecttransform.enable,
											});
										});
									}
								}
							}
						});
					} else if (state.clipboard.copy) {
						// Use internal clipboard
						const image = document.createElement("img");
						image.src = state.clipboard.copy.toDataURL();

						// Send to stamp, as clipboard temporary data
						tools.stamp.enable({
							image,
							back: tools.selecttransform.enable,
						});
					}
				};

				// Cut shortcut. Basically, send to copy handler
				state.ctrlxcb = (evn) => {
					state.ctrlccb(evn, true);
				};
			},
			populateContextMenu: (menu, state) => {
				if (!state.ctxmenu) {
					state.ctxmenu = {};

					// Snap To Grid Checkbox
					state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
						state,
						"snapToGrid",
						"Snap To Grid"
					).label;

					// Keep Aspect Ratio
					state.ctxmenu.keepAspectRatioLabel = _toolbar_input.checkbox(
						state,
						"keepAspectRatio",
						"Keep Aspect Ratio"
					).label;

					// Use Clipboard
					const clipboardCheckbox = _toolbar_input.checkbox(
						state,
						"useClipboard",
						"Use clipboard"
					);
					state.ctxmenu.useClipboardLabel = clipboardCheckbox.label;
					if (!navigator.clipboard.write)
						clipboardCheckbox.checkbox.disabled = true; // Disable if not available

					// Some useful actions to do with selection
					const actionArray = document.createElement("div");
					actionArray.classList.add("button-array");

					// Save button
					const saveSelectionButton = document.createElement("button");
					saveSelectionButton.classList.add("button", "tool");
					saveSelectionButton.textContent = "Save";
					saveSelectionButton.title = "Saves Selection";
					saveSelectionButton.onclick = () => {
						downloadCanvas({
							cropToContent: false,
							canvas: state.selected.image,
						});
					};

					// Save as Resource Button
					const createResourceButton = document.createElement("button");
					createResourceButton.classList.add("button", "tool");
					createResourceButton.textContent = "Resource";
					createResourceButton.title = "Saves Selection as a Resource";
					createResourceButton.onclick = () => {
						const image = document.createElement("img");
						image.src = state.selected.image.toDataURL();
						tools.stamp.state.addResource("Selection Resource", image);
						tools.stamp.enable();
					};

					actionArray.appendChild(saveSelectionButton);
					actionArray.appendChild(createResourceButton);

					// Disable buttons (if nothing is selected)
					state.ctxmenu.disableButtons = () => {
						saveSelectionButton.disabled = true;
						createResourceButton.disabled = true;
					};

					// Disable buttons (if something is selected)
					state.ctxmenu.enableButtons = () => {
						saveSelectionButton.disabled = "";
						createResourceButton.disabled = "";
					};
					state.ctxmenu.actionArray = actionArray;
				}
				menu.appendChild(state.ctxmenu.snapToGridLabel);
				menu.appendChild(document.createElement("br"));
				menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
				menu.appendChild(document.createElement("br"));
				menu.appendChild(state.ctxmenu.useClipboardLabel);
				menu.appendChild(state.ctxmenu.actionArray);
			},
			shortcut: "S",
		}
	);