Source: arr.js

'use strict';

/**
 * Array utilities for immutable operations
 * @module arr
 */

/**
 * Immutably adds an item to an array (returns a new array).
 * @memberof module:arr
 * @param {Array} arr - The source array
 * @param {*} item - The item to add
 * @returns {Array} A new array with the item added at the end
 * @example
 * add([1, 2, 3], 4) // => [1, 2, 3, 4]
 */
const add = (arr, item) => [].concat(arr, [item]);

/**
 * Immutably removes the first occurrence of an item from an array.
 * If the item is not found, returns the original array.
 * @memberof module:arr
 * @param {Array} arr - The source array
 * @param {*} item - The item to remove
 * @returns {Array} A new array with the item removed, or the original array if not found
 * @example
 * remove([1, 2, 3, 2], 2) // => [1, 3, 2]
 * remove([1, 2, 3], 4) // => [1, 2, 3]
 */
const remove = (arr, item) => arr.indexOf(item) > -1 ? [].concat(
	arr.slice(0, arr.indexOf(item)),
	arr.slice(arr.indexOf(item) + 1)
) : arr;

/**
 * Immutably toggles an item in an array (adds if not present, removes if present).
 * @memberof module:arr
 * @param {Array} arr - The source array
 * @param {*} item - The item to toggle
 * @returns {Array} A new array with the item toggled
 * @example
 * toggle([1, 2, 3], 2) // => [1, 3]
 * toggle([1, 2, 3], 4) // => [1, 2, 3, 4]
 */
const toggle = (arr, item) => arr.indexOf(item) > -1
	? remove(arr, item)
	: add(arr, item);

/**
 * Checks if an array or comma-separated string contains an element.
 * @memberof module:arr
 * @param {Array|string} a - The array or comma-separated string to check
 * @param {*} el - The element to look for
 * @returns {boolean} True if the element is found, false otherwise
 * @example
 * contains([1, 2, 3], 2) // => true
 * contains('a,b,c', 'b') // => true
 * contains([1, 2, 3], 4) // => false
 */
const contains = (a, el) => [].concat(
	typeof a === 'string' && a.split(',')
	|| a instanceof Array && a
	|| []
).indexOf(el) > -1;

/**
 * Immutably reorders an array by moving an item from one index to another.
 * @memberof module:arr
 * @param {Array} arr - The source array
 * @param {number} from - The index of the item to move
 * @param {number} to - The index to move the item to
 * @returns {Array} A new array with the item moved
 * @example
 * reorder(['a', 'b', 'c', 'd', 'e'], 2, 1) // => ['a', 'c', 'b', 'd', 'e']
 * reorder(['a', 'b', 'c', 'd', 'e'], 3, 1) // => ['a', 'd', 'b', 'c', 'e']
 * reorder(['a', 'b', 'c', 'd', 'e'], 1, 3) // => ['a', 'c', 'd', 'b', 'e']
 * reorder(['a', 'b', 'c', 'd', 'e'], 3, 3) // => ['a', 'b', 'c', 'd', 'e'] // no change
 */
const reorder = (arr, from, to) => from === to ? arr : from > to
	? [].concat(
		arr.slice(0, to),
		[arr[from]],
		arr.slice(to, from),
		arr.slice(from + 1)
	)
	: [].concat(
		arr.slice(0, from),
		arr.slice(from + 1, to + 1),
		[arr[from]],
		arr.slice(to + 1)
	);

/**
 * Immutably patches an array at a given index.
 * @memberof module:arr
 * @param {Array} arr - The source array
 * @param {number} index - The index to patch
 * @param {Object|Function} v - The patch value to apply
 * @returns {Array} A new array with the patch applied
 * @example
 * // Patch with object (merges with existing object)
 * patch([{a: 1}, {b: 2}], 0, {c: 3}) // => [{a: 1, c: 3}, {b: 2}]
 *
 * // Patch with function updater
 * patch([{count: 1}, {count: 2}], 0, (item) => ({...item, count: item.count + 1}))
 * // => [{count: 2}, {count: 2}]
 *
 * // Patch at index beyond array length (extends array with undefined)
 * patch([1, 2], 5, 3) // => [1, 2, undefined, undefined, undefined, 3]
 */
const patch = (arr = [], index, v) => [].concat(
	// if index is greater than 0, slice the array up to the index
	index > 0 ? arr.slice(0, index) : [],
	// if index is greater than the array length, fill the array with undefined up to the index
	arr.length - 1 < index ? new Array(index - arr.length).fill(undefined) : [],
	// if the patch (v) is a function, call it with the array at the index, otherwise apply the patch directly
	v instanceof Function ? v(arr[index] != null ? arr[index] : {})
		: arr[index] instanceof Object && v instanceof Object
			? Object.assign({}, arr[index], v)
			: v,
	// if index is less than the array length - 1, slice the array from the index + 1
	index < (arr.length - 1) ? arr.slice(index + 1) : []
);

module.exports = {
	add,
	remove,
	toggle,
	contains,
	reorder,
	patch
};