Angular-RU/angular-ru-sdk

View on GitHub
libs/cdk/virtual-table/src/services/sortable/sort.worker.ts

Summary

Maintainability
C
1 day
Test Coverage
import { Nullable, PlainObject, PlainObjectOf } from '@angular-ru/cdk/typings';

import { SortableMessage } from './sortable-message';

// TODO: should be refactor for decomposition web workers
// eslint-disable-next-line max-lines-per-function
export function sortWorker<T>(message: SortableMessage<T>): T[] {
    // eslint-disable-next-line
    enum OrderType {
        DESC = 'desc',
        SKIP = 'skip'
    }

    // eslint-disable-next-line unicorn/consistent-function-scoping
    function getValueByPath(object: PlainObject, path: string): Nullable<PlainObject> {
        return path
            ? path
                  .split('.')
                  // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
                  .reduce((value: Nullable<PlainObject>, key: string): any => value && value[key], object)
            : object;
    }

    // eslint-disable-next-line unicorn/consistent-function-scoping
    function checkValueIsEmpty(value: any): boolean {
        // note: don't use isString here
        // noinspection SuspiciousTypeOfGuard
        const nextValue: string = typeof value === 'string' ? value.trim() : value;

        return [undefined, null, NaN, '', Infinity].includes(nextValue);
    }

    class Sortable {
        public static sortByKeys(data: T[], keys: PlainObjectOf<OrderType>): any[] {
            const countKeys: number = Object.keys(keys).length;

            if (!countKeys) {
                return data.sort(Sortable.shallowSort);
            }

            const matches: PlainObjectOf<number> = Sortable.getMatchesKeys(keys);

            return data.sort((a: unknown, b: unknown): any => Sortable.multiSort(a, b, matches));
        }

        private static multiSort(a: unknown, b: unknown, matches: PlainObjectOf<number>): any {
            const countKeys: number = Object.keys(matches).length;
            let sorted: number = 0;
            let ix: number = 0;

            while (sorted === 0 && ix < countKeys) {
                const key: Nullable<string> = Sortable.observeKey(matches, ix);

                // note: don't use isString here
                if (typeof key === 'string') {
                    const depth: Nullable<number> = matches[key];

                    sorted = Sortable.deepSort(key, a, b, depth);
                    ix++;
                }
            }

            return sorted;
        }

        // eslint-disable-next-line complexity
        private static getMatchesKeys(keys: PlainObjectOf<OrderType | number>): PlainObjectOf<number> {
            const matches: PlainObjectOf<number> = {};

            for (const key in keys) {
                if (keys.hasOwnProperty(key)) {
                    matches[key] =
                        // eslint-disable-next-line no-nested-ternary
                        keys[key] === OrderType.DESC || keys[key] === -1
                            ? -1
                            : // eslint-disable-next-line unicorn/no-nested-ternary
                            keys[key] === OrderType.SKIP || keys[key] === 0
                            ? 0
                            : 1;
                }
            }

            return matches;
        }

        // eslint-disable-next-line max-params-no-constructor/max-params-no-constructor
        private static deepSort(key: string, leftHand: any, rightHand: any, depth: Nullable<number>): number {
            const a: any = getValueByPath(leftHand, key);
            const b: any = getValueByPath(rightHand, key);

            return Sortable.shallowSort(a, b, depth);
        }

        private static shallowSort(a: any, b: any, depth?: Nullable<number>): number {
            let newB: any = b;
            // eslint-disable-next-line no-negated-condition
            const currentDepth: Nullable<number> = depth !== null ? depth : 1;

            newB = checkValueIsEmpty(newB) ? '' : newB;

            if (a === newB) {
                return 0;
            }

            // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
            return a! > newB! ? currentDepth! : -1 * currentDepth!;
        }

        private static observeKey(keys: PlainObjectOf<number>, count: number): Nullable<string> {
            let key: string;
            let size: number = 0;

            for (key in keys) {
                if (keys.hasOwnProperty(key)) {
                    if (size === count) {
                        return key;
                    }

                    size++;
                }
            }

            return null;
        }
    }

    return Sortable.sortByKeys(message.source, message.definition as unknown as PlainObjectOf<OrderType>);
}