import { Nullable } from 'tsdef';

import { FileAction } from '../types/action.types';
import { FileArray } from '../types/file.types';
import { Logger } from '../util/logger';

interface SanitizeFiles {
    (mode: 'files', rawArray: FileArray | any): {
        sanitizedArray: FileArray;
        errorMessages: string[];
    };
    (mode: 'folderChain', rawArray: Nullable<FileArray> | any): {
        sanitizedArray: FileArray;
        errorMessages: string[];
    };
    (mode: 'fileActions', rawArray: FileAction[] | any): {
        sanitizedArray: FileAction[];
        errorMessages: string[];
    };
}

export const sanitizeInputArray: SanitizeFiles = (mode: string, rawArray: any[]) => {
    const sanitizedFiles = [];
    const errorMessages: string[] = [];

    if ((mode === 'folderChain' || mode === 'fileActions') && !rawArray) {
        // Do nothing, we allow folder chain to be null.
    } else if (!Array.isArray(rawArray)) {
        errorMessages.push(
            `Expected "${mode}" prop to be an array, got "${typeof rawArray}" instead.`
        );
    } else {
        let nonObjectFileCount = 0;
        let missingFieldFileCount = 0;
        const seenIds = new Set<string>();
        const duplicateIds = new Set<string>();

        for (let i = 0; i < rawArray.length; ++i) {
            const item = rawArray[i];
            if (!item) {
                if (mode === 'fileActions') nonObjectFileCount++;
                else sanitizedFiles.push(null);
            } else if (typeof item !== 'object') {
                nonObjectFileCount++;
            } else {
                if (!item.id || (mode !== 'fileActions' && !item.name)) {
                    missingFieldFileCount++;
                } else if (seenIds.has(item.id)) {
                    duplicateIds.add(item.id);
                } else {
                    seenIds.add(item.id);
                    sanitizedFiles.push(item);
                }
            }
        }

        if (nonObjectFileCount) {
            errorMessages.push(
                `Detected ${nonObjectFileCount} file(s) of invalid type. Remember ` +
                    `that "files" array should contain either objects or nulls.`
            );
        }
        if (missingFieldFileCount) {
            errorMessages.push(
                `Detected ${missingFieldFileCount} file(s) that are missing the ` +
                    `required fields. Remember that file object should define an ` +
                    `"id" and a "name".`
            );
        }
        if (duplicateIds.size > 0) {
            const repeatedIdsString = '"' + Array.from(duplicateIds).join('", "') + '"';
            errorMessages.push(
                `Detected ${duplicateIds.size} file IDs that are used multiple ` +
                    `times. Remember that each file should have a unique IDs. The ` +
                    `following IDs were seen multiple times: ${repeatedIdsString}`
            );
        }
    }

    if (errorMessages.length > 0) {
        const errorMessageString = '\n- ' + errorMessages.join('\n- ');
        let arrayString: string;
        let itemString: string;
        if (mode === 'folderChain') {
            arrayString = 'folder chain';
            itemString = 'files';
        } else if (mode === 'fileActions') {
            arrayString = 'file actions';
            itemString = 'file actions';
        } else {
            // mode === 'files'
            arrayString = 'files';
            itemString = 'files';
        }

        Logger.error(
            `Errors were detected when sanitizing the ${arrayString} array. ` +
                `Offending ${itemString} were removed from the array. Summary of ` +
                `validation errors: ${errorMessageString}`
        );
    }

    return {
        sanitizedArray: sanitizedFiles,
        errorMessages,
    };
};
