import type { PhpAssoc, PhpRuntimeValue } from '../_helpers/_phpTypes.ts'
type SortValue = PhpRuntimeValue type SortableObject = PhpAssoc<SortValue> type SortComparator = (a: SortValue, b: SortValue) => number type ComparablePrimitive = string | number | bigint | boolean type SortArg = SortValue[] | SortableObject | SortFlag
const flags: Readonly<{ SORT_REGULAR: number SORT_NUMERIC: number SORT_STRING: number SORT_ASC: number SORT_DESC: number }> = { SORT_REGULAR: 16, SORT_NUMERIC: 17, SORT_STRING: 18, SORT_ASC: 32, SORT_DESC: 40, }
type SortFlag = keyof typeof flags
const isSortableObject = (value: SortValue): value is SortableObject => value !== null && typeof value === 'object' && !Array.isArray(value)
const isSortFlag = (value: SortValue): value is SortFlag => typeof value === 'string' && value in flags
const isComparablePrimitive = (value: SortValue): value is ComparablePrimitive => typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean' || value instanceof Date
const copyBackToObject = (target: SortableObject, values: SortValue[], keys: string[]): void => { for (const key in target) { if (Object.hasOwn(target, key)) { delete target[key] } } for (let index = 0; index < values.length; index++) { const key = keys[index] if (key === undefined) { continue } target[key] = values[index] } }
const compareRegular = (leftValue: SortValue, rightValue: SortValue): number => { if (leftValue === rightValue) { return 0 } if (leftValue === null || leftValue === undefined) { return -1 } if (rightValue === null || rightValue === undefined) { return 1 }
if (isComparablePrimitive(leftValue) && isComparablePrimitive(rightValue)) { return leftValue > rightValue ? 1 : -1 }
const left = String(leftValue) const right = String(rightValue) return left > right ? 1 : left < right ? -1 : 0 }
export function array_multisort(arr: SortValue[] | SortableObject, ...rest: SortArg[]): boolean {
let i = 0 let j = 0 let l = 0 let sal = 0 let vkey = '' let elIndex = 0 let zlast: SortValue
const sortFlag: number[] = [0] const thingsToSort: boolean[] = [] let nLastSort: number[] = [] let lastSort: number[] = [] const args: SortArg[] = [arr, ...rest]
const sortDuplicator: SortComparator = function () { return nLastSort.shift() ?? 0 }
const sortFunctions: SortComparator[][] = [ [ function (a: SortValue, b: SortValue) { const result = compareRegular(a, b) lastSort.push(result) return result }, function (a: SortValue, b: SortValue) { const result = compareRegular(b, a) lastSort.push(result) return result }, ], [ function (a: SortValue, b: SortValue) { const result = Number(a) - Number(b) lastSort.push(result) return result }, function (a: SortValue, b: SortValue) { const result = Number(b) - Number(a) lastSort.push(result) return result }, ], [ function (a: SortValue, b: SortValue) { const left = String(a) const right = String(b) const result = left > right ? 1 : left < right ? -1 : 0 lastSort.push(result) return result }, function (a: SortValue, b: SortValue) { const left = String(b) const right = String(a) const result = left > right ? 1 : left < right ? -1 : 0 lastSort.push(result) return result }, ], ]
const sortArrs: SortValue[][] = [[]]
const sortKeys: string[][] = [[]]
if (Array.isArray(arr)) { sortArrs[0] = arr } else if (isSortableObject(arr)) { const firstSortKeys = sortKeys[0] const firstSortValues = sortArrs[0] if (!firstSortKeys || !firstSortValues) { return false } for (const key in arr) { if (Object.hasOwn(arr, key)) { firstSortKeys.push(key) firstSortValues.push(arr[key]) } } } else { return false }
const primarySortArray = sortArrs[0] if (!primarySortArray) { return false } const arrMainLength = primarySortArray.length let sortComponents = [0, arrMainLength]
const argl = args.length for (j = 1; j < argl; j++) { const arg = args[j] if (Array.isArray(arg)) { sortArrs[j] = arg sortFlag[j] = 0 if (arg.length !== arrMainLength) { return false } } else if (isSortableObject(arg)) { const currentSortKeys: string[] = (sortKeys[j] = []) const currentSortValues: SortValue[] = (sortArrs[j] = []) sortFlag[j] = 0 for (const key in arg) { if (Object.hasOwn(arg, key)) { currentSortKeys.push(key) currentSortValues.push(arg[key]) } } if (currentSortValues.length !== arrMainLength) { return false } } else if (typeof arg === 'string') { if (!isSortFlag(arg)) { return false } const lFlag = sortFlag.pop() ?? 0 if (((flags[arg] >>> 4) & (lFlag >>> 4)) > 0) { return false } sortFlag.push(lFlag + flags[arg]) } else { return false } }
for (i = 0; i !== arrMainLength; i++) { thingsToSort.push(true) }
for (const iKey in sortArrs) { if (Object.hasOwn(sortArrs, iKey)) { const iNum = Number(iKey) const lastSorts: number[][] = [] let tmpArray: SortValue[] = [] elIndex = 0 nLastSort = [] lastSort = [] const currentSortArr = sortArrs[iNum] if (!currentSortArr) { continue }
if (sortComponents.length === 0) { const arg = args[iNum] if (Array.isArray(arg)) { args[iNum] = currentSortArr } else if (isSortableObject(arg)) { const currentSortKeySet = sortKeys[iNum] ?? [] sal = currentSortArr.length for (j = 0, vkey = ''; j < sal; j++) { vkey = currentSortKeySet[j] ?? '' if (vkey !== '') { arg[vkey] = currentSortArr[j] } } } sortArrs.splice(iNum, 1) sortKeys.splice(iNum, 1) continue }
const currentSortFlag = sortFlag[iNum] ?? 0 const functionGroup = sortFunctions[currentSortFlag & 3] if (!functionGroup) { return false } let sFunction = functionGroup[(currentSortFlag & 8) > 0 ? 1 : 0] ?? functionGroup[0]
for (l = 0; l !== sortComponents.length; l += 2) { const componentStart = sortComponents[l] const componentEnd = sortComponents[l + 1] if (componentStart === undefined || componentEnd === undefined) { continue } tmpArray = currentSortArr.slice(componentStart, componentEnd + 1) tmpArray.sort(sFunction) lastSorts[l] = [...lastSort] elIndex = componentStart for (const value of tmpArray) { currentSortArr[elIndex] = value elIndex++ } }
sFunction = sortDuplicator for (const jKey in sortArrs) { if (Object.hasOwn(sortArrs, jKey)) { const jNum = Number(jKey) const targetSortArr = sortArrs[jNum] if (!targetSortArr || targetSortArr === currentSortArr) { continue } for (l = 0; l !== sortComponents.length; l += 2) { const componentStart = sortComponents[l] const componentEnd = sortComponents[l + 1] if (componentStart === undefined || componentEnd === undefined) { continue } tmpArray = targetSortArr.slice(componentStart, componentEnd + 1) nLastSort = [...(lastSorts[l] ?? [])] tmpArray.sort(sFunction) elIndex = componentStart for (const value of tmpArray) { targetSortArr[elIndex] = value elIndex++ } } } }
for (const jKey in sortKeys) { if (Object.hasOwn(sortKeys, jKey)) { const jNum = Number(jKey) const targetSortKeys = sortKeys[jNum] if (!targetSortKeys) { continue } for (l = 0; l !== sortComponents.length; l += 2) { const componentStart = sortComponents[l] const componentEnd = sortComponents[l + 1] if (componentStart === undefined || componentEnd === undefined) { continue } tmpArray = targetSortKeys.slice(componentStart, componentEnd + 1) nLastSort = [...(lastSorts[l] ?? [])] tmpArray.sort(sFunction) elIndex = componentStart for (const value of tmpArray) { targetSortKeys[elIndex] = String(value) elIndex++ } } } }
zlast = null sortComponents = [] let lastIndex = 0 for (let idx = 0; idx < currentSortArr.length; idx++) { lastIndex = idx if (!thingsToSort[idx]) { if (sortComponents.length & 1) { sortComponents.push(idx - 1) } zlast = null continue } if (!(sortComponents.length & 1)) { if (zlast !== null) { if (currentSortArr[idx] === zlast) { sortComponents.push(idx - 1) } else { thingsToSort[idx] = false } } zlast = currentSortArr[idx] } else if (currentSortArr[idx] !== zlast) { sortComponents.push(idx - 1) zlast = currentSortArr[idx] } }
if (sortComponents.length & 1) { sortComponents.push(lastIndex) } const arg = args[iNum] if (Array.isArray(arg)) { args[iNum] = currentSortArr } else if (isSortableObject(arg)) { copyBackToObject(arg, currentSortArr, sortKeys[iNum] ?? []) } sortArrs.splice(iNum, 1) sortKeys.splice(iNum, 1) } } return true }
|