/*!
 * jquery.fancytree.gridnav.js
 *
 * Support keyboard navigation for trees with embedded input controls.
 * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
 *
 * Copyright (c) 2008-2019, Martin Wendt (https://wwWendt.de)
 *
 * Released under the MIT license
 * https://github.com/mar10/fancytree/wiki/LicenseInfo
 *
 * @version @VERSION
 * @date @DATE
 */

(function(factory) {
	if (typeof define === "function" && define.amd) {
		// AMD. Register as an anonymous module.
		define([
			"jquery",
			"./jquery.fancytree",
			"./jquery.fancytree.table",
		], factory);
	} else if (typeof module === "object" && module.exports) {
		// Node/CommonJS
		require("./jquery.fancytree.table"); // core + table
		module.exports = factory(require("jquery"));
	} else {
		// Browser globals
		factory(jQuery);
	}
})(function($) {
	"use strict";

	/*******************************************************************************
	 * Private functions and variables
	 */

	// Allow these navigation keys even when input controls are focused

	var KC = $.ui.keyCode,
		// which keys are *not* handled by embedded control, but passed to tree
		// navigation handler:
		NAV_KEYS = {
			text: [KC.UP, KC.DOWN],
			checkbox: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
			link: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
			radiobutton: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
			"select-one": [KC.LEFT, KC.RIGHT],
			"select-multiple": [KC.LEFT, KC.RIGHT],
		};

	/* Calculate TD column index (considering colspans).*/
	function getColIdx($tr, $td) {
		var colspan,
			td = $td.get(0),
			idx = 0;

		$tr.children().each(function() {
			if (this === td) {
				return false;
			}
			colspan = $(this).prop("colspan");
			idx += colspan ? colspan : 1;
		});
		return idx;
	}

	/* Find TD at given column index (considering colspans).*/
	function findTdAtColIdx($tr, colIdx) {
		var colspan,
			res = null,
			idx = 0;

		$tr.children().each(function() {
			if (idx >= colIdx) {
				res = $(this);
				return false;
			}
			colspan = $(this).prop("colspan");
			idx += colspan ? colspan : 1;
		});
		return res;
	}

	/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
	function findNeighbourTd($target, keyCode) {
		var $tr,
			colIdx,
			$td = $target.closest("td"),
			$tdNext = null;

		switch (keyCode) {
			case KC.LEFT:
				$tdNext = $td.prev();
				break;
			case KC.RIGHT:
				$tdNext = $td.next();
				break;
			case KC.UP:
			case KC.DOWN:
				$tr = $td.parent();
				colIdx = getColIdx($tr, $td);
				while (true) {
					$tr = keyCode === KC.UP ? $tr.prev() : $tr.next();
					if (!$tr.length) {
						break;
					}
					// Skip hidden rows
					if ($tr.is(":hidden")) {
						continue;
					}
					// Find adjacent cell in the same column
					$tdNext = findTdAtColIdx($tr, colIdx);
					// Skip cells that don't conatain a focusable element
					if ($tdNext && $tdNext.find(":input,a").length) {
						break;
					}
				}
				break;
		}
		return $tdNext;
	}

	/*******************************************************************************
	 * Extension code
	 */
	$.ui.fancytree.registerExtension({
		name: "gridnav",
		version: "@VERSION",
		// Default options for this extension.
		options: {
			autofocusInput: false, // Focus first embedded input if node gets activated
			handleCursorKeys: true, // Allow UP/DOWN in inputs to move to prev/next node
		},

		treeInit: function(ctx) {
			// gridnav requires the table extension to be loaded before itself
			this._requireExtension("table", true, true);
			this._superApply(arguments);

			this.$container.addClass("fancytree-ext-gridnav");

			// Activate node if embedded input gets focus (due to a click)
			this.$container.on("focusin", function(event) {
				var ctx2,
					node = $.ui.fancytree.getNode(event.target);

				if (node && !node.isActive()) {
					// Call node.setActive(), but also pass the event
					ctx2 = ctx.tree._makeHookContext(node, event);
					ctx.tree._callHook("nodeSetActive", ctx2, true);
				}
			});
		},
		nodeSetActive: function(ctx, flag, callOpts) {
			var $outer,
				opts = ctx.options.gridnav,
				node = ctx.node,
				event = ctx.originalEvent || {},
				triggeredByInput = $(event.target).is(":input");

			flag = flag !== false;

			this._superApply(arguments);

			if (flag) {
				if (ctx.options.titlesTabbable) {
					if (!triggeredByInput) {
						$(node.span)
							.find("span.fancytree-title")
							.focus();
						node.setFocus();
					}
					// If one node is tabbable, the container no longer needs to be
					ctx.tree.$container.attr("tabindex", "-1");
					// ctx.tree.$container.removeAttr("tabindex");
				} else if (opts.autofocusInput && !triggeredByInput) {
					// Set focus to input sub input (if node was clicked, but not
					// when TAB was pressed )
					$outer = $(node.tr || node.span);
					$outer
						.find(":input:enabled")
						.first()
						.focus();
				}
			}
		},
		nodeKeydown: function(ctx) {
			var inputType,
				handleKeys,
				$td,
				opts = ctx.options.gridnav,
				event = ctx.originalEvent,
				$target = $(event.target);

			if ($target.is(":input:enabled")) {
				inputType = $target.prop("type");
			} else if ($target.is("a")) {
				inputType = "link";
			}
			// ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);

			if (inputType && opts.handleCursorKeys) {
				handleKeys = NAV_KEYS[inputType];
				if (handleKeys && $.inArray(event.which, handleKeys) >= 0) {
					$td = findNeighbourTd($target, event.which);
					if ($td && $td.length) {
						// ctx.node.debug("ignore keydown in input", event.which, handleKeys);
						$td.find(":input:enabled,a").focus();
						// Prevent Fancytree default navigation
						return false;
					}
				}
				return true;
			}
			// ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
			return this._superApply(arguments);
		},
	});
	// Value returned by `require('jquery.fancytree..')`
	return $.ui.fancytree;
}); // End of closure
