(function (root, factory) {
	if (typeof module === 'object' && typeof module.exports === 'object') {
		module.exports = factory();
	} else {
		root.CardOracleShuffle = factory();
	}
})(typeof self !== 'undefined' ? self : this, function () {
	'use strict';

	var DEFAULT_POSITIONS = 0;

	function isFiniteNumber(value) {
		if (typeof Number.isFinite === 'function') {
			return Number.isFinite(value);
		}
		return isFinite(value);
	}

	function normalisePositions(value) {
		var parsed = parseInt(value, 10);
		return isFiniteNumber(parsed) && parsed > 0 ? parsed : DEFAULT_POSITIONS;
	}

	function createState(totalPositions) {
		return {
			positions: normalisePositions(totalPositions),
			count: 0,
			clicked: [],
			reverse: [],
		};
	}

	function buildResult(state, wasRecorded, hideShuffle) {
		var isComplete = state.positions > 0 && state.count >= state.positions;
		return {
			wasRecorded: wasRecorded,
			hideShuffleButton: hideShuffle,
			isComplete: isComplete,
			clicked: state.clicked.slice(),
			reverse: state.reverse.slice(),
		};
	}

	function recordSelection(state, cardId, options) {
		if (!state || typeof state !== 'object') {
			throw new Error('Card Oracle shuffle state is required');
		}

		if (cardId === undefined || cardId === null || cardId === '') {
			return buildResult(state, false, false);
		}

		if (state.clicked.includes(cardId) || (state.positions > 0 && state.count >= state.positions)) {
			return buildResult(state, false, false);
		}

		state.clicked.push(cardId);
		if (options && options.reversed) {
			state.reverse.push(cardId);
		}

		state.count += 1;
		var hideShuffle = state.count === 1;

		return buildResult(state, true, hideShuffle);
	}

	function resetState(state) {
		if (!state || typeof state !== 'object') {
			return;
		}

		state.count = 0;
		state.clicked = [];
		state.reverse = [];
	}

	function resolveRng(rng) {
		return typeof rng === 'function' ? rng : Math.random;
	}

	function clampIndex(value, max) {
		var index = Math.floor(value);
		if (index < 0) {
			return 0;
		}
		if (index > max) {
			return max;
		}
		return index;
	}

	function shuffleIndices(length, rng) {
		var size = isFiniteNumber(length) && length > 0 ? Math.floor(length) : 0;
		var maxIndex = size > 0 ? size - 1 : 0;
		var random = resolveRng(rng);
		var indices = Array.from({ length: size }, function (_, index) {
			return index;
		});

		if (size <= 1) {
			return indices;
		}

		var performSwapPass = function () {
			for (var i = 0; i < size; i += 1) {
				var a = clampIndex(random() * (maxIndex + 1), maxIndex);
				var b = clampIndex(random() * (maxIndex + 1), maxIndex);
				if (a === b) {
					continue;
				}
				var temp = indices[a];
				indices[a] = indices[b];
				indices[b] = temp;
			}
		};

		performSwapPass();
		performSwapPass();

		return indices;
	}

	function asArray(elements) {
		if (!elements) {
			return [];
		}

		if (Array.isArray(elements)) {
			return elements.slice();
		}

		if (typeof elements.length === 'number') {
			return Array.prototype.slice.call(elements);
		}

		return [elements];
	}

	function reorderByIndices(elements, order) {
		var nodes = asArray(elements);
		if (!Array.isArray(order) || order.length === 0) {
			return nodes.slice();
		}

		return order
			.map(function (index) {
				return nodes[index];
			})
			.filter(function (node) {
				return Boolean(node);
			});
	}

	function toDomNode(container) {
		if (!container) {
			return null;
		}

		if (container.nodeType === 1) {
			return container;
		}

		if (typeof container.appendChild === 'function') {
			return container;
		}

		if (container.jquery && container.length) {
			return container[0];
		}

		if (Array.isArray(container) && container.length) {
			return container[0];
		}

		return null;
	}

	function applyOrder(container, orderedElements) {
		var target = toDomNode(container);
		if (!target || !orderedElements || orderedElements.length === 0) {
			return;
		}

		orderedElements.forEach(function (element) {
			if (element) {
				target.appendChild(element);
			}
		});
	}

	return {
		createState: createState,
		recordSelection: recordSelection,
		resetState: resetState,
		shuffleIndices: shuffleIndices,
		reorderByIndices: reorderByIndices,
		applyOrder: applyOrder,
	};
});
