/* 
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

THIS CODE MUST MATCH LIT ACTIONS CODE

CAUTION WHEN EDITING, YOU MUST REPLACE
THE LIT ACTIONS CODE IF DO SO.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
// Deterministic JSON
const jsonStringify = JSON.stringify

const isArray =
	Array.isArray ||
	function (x) {
		return {}.toString.call(x) === '[object Array]'
	}

const objectKeys =
	Object.keys ||
	function (obj) {
		var has =
			Object.prototype.hasOwnProperty ||
			function () {
				return true
			}
		var keys = [] as any[]
		for (var key in obj) {
			if (has.call(obj, key)) {
				keys.push(key)
			}
		}
		return keys
	}

export const deterministicStringify = function (obj: any, opts?: any) {
	if (!opts) {
		opts = {}
	}
	if (typeof opts === 'function') {
		opts = { cmp: opts }
	}
	var space = opts.space || ''
	if (typeof space === 'number') {
		space = Array(space + 1).join(' ')
	}
	var cycles = typeof opts.cycles === 'boolean' ? opts.cycles : false
	var replacer =
		opts.replacer ||
		function (key: any, value: any) {
			return value
		}

	var cmpOpt = opts.cmp
	var cmp =
		cmpOpt &&
		function (node: any) {
			return function (a: any, b: any) {
				return cmpOpt(
					{ key: a, value: node[a] },
					{ key: b, value: node[b] },
				)
			}
		}

	var seen: any = []
	return (function stringify(
		parent: any,
		key: any,
		node: any,
		level: any,
	): string | undefined {
		var indent = space ? '\n' + new Array(level + 1).join(space) : ''
		var colonSeparator = space ? ': ' : ':'

		if (node && node.toJSON && typeof node.toJSON === 'function') {
			node = node.toJSON()
		}

		node = replacer.call(parent, key, node)

		if (node === undefined) {
			return
		}
		if (typeof node !== 'object' || node === null) {
			return jsonStringify(node)
		}
		if (isArray(node)) {
			var out = [] as any[]
			for (var i = 0; i < node.length; i++) {
				var item =
					stringify(node, i, node[i], level + 1) ||
					jsonStringify(null)
				out.push(indent + space + item)
			}
			return '[' + out.join(',') + indent + ']'
		}

		if (seen.indexOf(node) !== -1) {
			if (cycles) {
				return jsonStringify('__cycle__')
			}
			throw new TypeError('Converting circular structure to JSON')
		} else {
			seen.push(node)
		}

		var keys = objectKeys(node).sort(cmp && cmp(node))
		var out = [] as any[]
		for (var i = 0; i < keys.length; i++) {
			var key: any = keys[i]
			var value = stringify(node, key, node[key], level + 1)

			if (!value) {
				continue
			}

			var keyValue = jsonStringify(key) + colonSeparator + value

			out.push(indent + space + keyValue)
		}
		seen.splice(seen.indexOf(node), 1)
		return '{' + out.join(',') + indent + '}'
	})({ '': obj }, '', obj, 0)
}

/**
 * Returns a hash code from a string
 * Pre signing hash applicable to json strings objects
 */
export const hashJsonStringPreSign = (str: string): string => {
	let hash = 0
	for (let i = 0, len = str.length; i < len; i++) {
		let chr = str.charCodeAt(i)
		hash = (hash << 5) - hash + chr
		hash |= 0 // Convert to 32bit integer
	}
	const strPreHash = '' + hash
	return strPreHash
}

/**
 *
 * Receives an amount of arrays with some length each
 * and returns an evenly mixed array with all the elements of the input arrays
 * with no repetition or missing element in a deterministic way
 */
export const deterministicEvenlyMixedArray = <T>(arrs: T[][]): T[] => {
	const resultEvenlyArray = [] as T[]
	const arrsLength = arrs.length
	const arrsLengths = arrs.map((arr) => arr.length)
	const maxArrLength = Math.max(...arrsLengths)
	for (let i = 0; i < maxArrLength; i++) {
		for (let j = 0; j < arrsLength; j++) {
			const arr = arrs[j]
			const element = arr[i]
			if (element !== undefined) {
				resultEvenlyArray.push(element)
			}
		}
	}
	return resultEvenlyArray
}
