import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";

let double_click_policy = "copy-all";

api.fetchApi('/manager/dbl_click/policy')
	.then(response => response.text())
	.then(data => set_double_click_policy(data));

export function set_double_click_policy(mode) {
	double_click_policy = mode;
}

function addMenuHandler(nodeType, cb) {
	const getOpts = nodeType.prototype.getExtraMenuOptions;
	nodeType.prototype.getExtraMenuOptions = function () {
		const r = getOpts.apply(this, arguments);
		cb.apply(this, arguments);
		return r;
	};
}

function distance(node1, node2) {
	let dx = (node1.pos[0] + node1.size[0]/2) - (node2.pos[0] + node2.size[0]/2);
	let dy = (node1.pos[1] + node1.size[1]/2) - (node2.pos[1] + node2.size[1]/2);
	return Math.sqrt(dx * dx + dy * dy);
}

function lookup_nearest_nodes(node) {
	let nearest_distance = Infinity;
	let nearest_node = null;
	for(let other of app.graph._nodes) {
		if(other === node)
			continue;

		let dist = distance(node, other);
		if (dist < nearest_distance && dist < 1000) {
			nearest_distance = dist;
			nearest_node = other;
		}
	}

	return nearest_node;
}

function lookup_nearest_inputs(node) {
	let input_map = {};

	for(let i in node.inputs) {
		let input = node.inputs[i];

		if(input.link || input_map[input.type])
			continue;

		input_map[input.type] = {distance: Infinity, input_name: input.name, node: null, slot: null};
	}

	let x = node.pos[0];
	let y = node.pos[1] + node.size[1]/2;

	for(let other of app.graph._nodes) {
		if(other === node || !other.outputs)
			continue;

		let dx = x - (other.pos[0] + other.size[0]);
		let dy = y - (other.pos[1] + other.size[1]/2);

		if(dx < 0)
			continue;

		let dist = Math.sqrt(dx * dx + dy * dy);

		for(let input_type in input_map) {
			for(let j in other.outputs) {
				let output = other.outputs[j];
				if(output.type == input_type) {
					if(input_map[input_type].distance > dist) {
						input_map[input_type].distance = dist;
						input_map[input_type].node = other;
						input_map[input_type].slot = parseInt(j);
					}
				}
			}
		}
	}

	let res = {};
	for (let i in input_map) {
		if (input_map[i].node) {
			res[i] = input_map[i];
		}
	}

	return res;
}

function connect_inputs(nearest_inputs, node) {
	for(let i in nearest_inputs) {
		let info = nearest_inputs[i];
		info.node.connect(info.slot, node.id, info.input_name);
	}
}

function node_info_copy(src, dest, connect_both, copy_shape) {
	// copy input connections
	for(let i in src.inputs) {
		let input = src.inputs[i];
		if (input.widget !== undefined) {
			const destWidget = dest.widgets.find(x => x.name === input.widget.name);
			dest.convertWidgetToInput(destWidget);
		}
		if(input.link) {
			let link = app.graph.links[input.link];
			let src_node = app.graph.getNodeById(link.origin_id);
			src_node.connect(link.origin_slot, dest.id, input.name);
		}
	}

	// copy output connections
	if(connect_both) {
		let output_links = {};
		for(let i in src.outputs) {
			let output = src.outputs[i];
			if(output.links) {
				let links = [];
				for(let j in output.links) {
					links.push(app.graph.links[output.links[j]]);
				}
				output_links[output.name] = links;
			}
		}

		for(let i in dest.outputs) {
			let links = output_links[dest.outputs[i].name];
			if(links) {
				for(let j in links) {
					let link = links[j];
					let target_node = app.graph.getNodeById(link.target_id);
					dest.connect(parseInt(i), target_node, link.target_slot);
				}
			}
		}
	}

	if(copy_shape) {
		dest.color = src.color;
		dest.bgcolor = src.bgcolor;
		dest.size = max(src.size, dest.size);
	}

	app.graph.afterChange();
}

app.registerExtension({
	name: "Comfy.Manager.NodeFixer",

	async nodeCreated(node, app) {
		let orig_dblClick = node.onDblClick;
		node.onDblClick = function (e, pos, self) {
			orig_dblClick?.apply?.(this, arguments);

			if((!node.inputs && !node.outputs) || pos[1] > 0)
				return;

			switch(double_click_policy) {
				case "copy-all":
				case "copy-full":
				case "copy-input":
					{
						if(node.inputs?.some(x => x.link != null) || node.outputs?.some(x => x.links != null && x.links.length > 0) )
							return;

						let src_node = lookup_nearest_nodes(node);
						if(src_node)
						{
							let both_connection = double_click_policy != "copy-input";
							let copy_shape = double_click_policy == "copy-full";
							node_info_copy(src_node, node, both_connection, copy_shape);
						}
					}
					break;
				case "possible-input":
					{
						let nearest_inputs = lookup_nearest_inputs(node);
						if(nearest_inputs)
							connect_inputs(nearest_inputs, node);
					}
					break;
				case "dual":
					{
						if(pos[0] < node.size[0]/2) {
							// left: possible-input
							let nearest_inputs = lookup_nearest_inputs(node);
							if(nearest_inputs)
								connect_inputs(nearest_inputs, node);
						}
						else {
							// right: copy-all
							if(node.inputs?.some(x => x.link != null) || node.outputs?.some(x => x.links != null && x.links.length > 0) )
								return;

							let src_node = lookup_nearest_nodes(node);
							if(src_node)
								node_info_copy(src_node, node, true);
						}
					}
					break;
			}
		}
	},

	beforeRegisterNodeDef(nodeType, nodeData, app) {
		addMenuHandler(nodeType, function (_, options) {
			options.push({
				content: "Fix node (recreate)",
				callback: () => {
					let new_node = LiteGraph.createNode(nodeType.comfyClass);
					new_node.pos = [this.pos[0], this.pos[1]];
					app.canvas.graph.add(new_node, false);
					node_info_copy(this, new_node, true);
					app.canvas.graph.remove(this);
				},
			});
		});
	}
});
