"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = __importDefault(require("lodash"));
const papaparse_1 = __importDefault(require("papaparse"));
var ErrorCodes;
(function (ErrorCodes) {
    ErrorCodes["missingRequiredColumns"] = "missingRequiredColumns";
    ErrorCodes["extraColumns"] = "extraColumns";
    ErrorCodes["rowsExceedMaxLimit"] = "rowsExceedMaxLimit";
    ErrorCodes["customValidationFailed"] = "customValidationFailed";
})(ErrorCodes = exports.ErrorCodes || (exports.ErrorCodes = {}));
// export interface ResultRow {
//   data: {
//     [key: string]: {
//       isColumnValid: boolean;
//       value: any;
//     };
//   };
//   isRowValid: boolean;
// }
/**
 * Parses a CSV file or string and returns array of parsed objects
 * @example
 * parseCsv(
 *  {
 *    data: `Column 1,Column 2,Column 3
 *      1,White,1/1/2019`,
 *    columns: [
 *        {
 *          name: 'Column 1',
 *          validationRules: [validators.isInt, validators.isNotEmpty]
 *        },
 *        {
 *          name: 'Column 2',
 *          validationRules: [validators.isString, validators.isNotEmpty]
 *        },
 *        {
 *          name: 'Column 3',
 *          validationRules: [validators.isDate, validators.isNotEmpty]
 *        },
 *      ],
 *    header: true,
 *    maxRows: 2,
 *    strict: true,
 *  }
 * )
 * .then((parseResult => {
 *   // Results are returned as ResultProps object
 * }));
 *
 * @param props Object of type Props containing the conversion details
 * @param props.data Can be a string containing CSV data of a file input value
 * @param props.columns Definition of the columns with their validation rules
 * @param props.header Specifies wether the CSV data has header or not
 * @param props.maxRows Maximum number of rows to parse from the file, any rows beyond that will be ignored
 * @param props.validateRow custom function to perform custom validation on each row
 * @param props.strict Strict mode: if set to true, file will be marked as invalid if the CSV data contain any extra column(s)
 */
exports.parseCsv = (props) => __awaiter(this, void 0, void 0, function* () {
    return new Promise(resolve => {
        // Prepare the data and errors arrays
        const result = [];
        const errors = [];
        let indexedColumns;
        let totalRows = 0;
        let validRows = 0;
        let errorRows = 0;
        let ignoredRows = 0;
        let headerRead = false;
        let isFileValid = true;
        let hasExtraRows = false;
        let hasExtraColumns = false;
        let isCustomValidationFailed = false;
        const { header, data, columns, maxRows, validateRow, validateFile, strict, } = props;
        if (!header) {
            indexedColumns = columns.map((column, index) => (Object.assign({}, column, { index })));
        }
        const step = (results) => {
            const { data: resultsData } = results;
            if (resultsData.length === 1) {
                if (header && !headerRead) {
                    indexedColumns = lodash_1.default.map(columns, col => (Object.assign({}, col, { index: -1 })));
                    // Map the indexes of the header with the file
                    for (const headerIndex of Object.keys(resultsData[0])) {
                        const columnIndex = lodash_1.default.findIndex(columns, col => {
                            return col.name === resultsData[0][headerIndex];
                        });
                        if (columnIndex >= 0) {
                            const index = parseInt(headerIndex, 10);
                            indexedColumns[columnIndex].index = index;
                        }
                        else {
                            // The file has a column that is not defined by the columns definition
                            if (strict) {
                                isFileValid = false;
                                if (!hasExtraColumns) {
                                    hasExtraColumns = true;
                                    errors.push(ErrorCodes.extraColumns);
                                }
                            }
                        }
                    }
                    headerRead = true;
                    if (lodash_1.default.find(indexedColumns, col => {
                        return col.index === -1 && col.required;
                    })) {
                        isFileValid = false;
                        errors.push(ErrorCodes.missingRequiredColumns);
                    }
                    return;
                }
                totalRows++;
                if (!header && indexedColumns.length !== resultsData[0].length) {
                    isFileValid = false;
                }
                if (maxRows !== undefined && maxRows > 0 && totalRows > maxRows) {
                    ignoredRows++;
                    if (!hasExtraRows) {
                        hasExtraRows = true;
                        errors.push(ErrorCodes.rowsExceedMaxLimit);
                    }
                    return;
                }
                const resultRow = {
                    isRowValid: true,
                    data: {},
                };
                for (const column of indexedColumns) {
                    const { index, name } = column;
                    // No header, results are passed as array
                    const isValid = lodash_1.default.every(column.validators, (validator) => validator(resultsData[0][index]));
                    // Papa Parser handles all data types except date, hadle date here
                    let columnValue = resultsData[0][index];
                    if (columnValue !== '' &&
                        columnValue !== null &&
                        typeof columnValue !== undefined &&
                        /(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d/.test(columnValue) &&
                        new Date(columnValue).toString() !== 'Invalid Date') {
                        columnValue = new Date(columnValue);
                    }
                    resultRow.data[name] = {
                        value: columnValue,
                        isColumnValid: isValid,
                    };
                    resultRow.isRowValid = resultRow.isRowValid && isValid;
                }
                if (typeof validateRow === 'function') {
                    resultRow.isRowValid =
                        resultRow.isRowValid && validateRow(resultsData[0]);
                }
                result.push(resultRow);
                if (resultRow.isRowValid) {
                    validRows++;
                }
                else {
                    errorRows++;
                }
            }
        };
        const complete = () => {
            // Validate the file
            if (validateFile && !validateFile(result)) {
                errors.push(ErrorCodes.customValidationFailed);
                isCustomValidationFailed = true;
            }
            resolve({
                result,
                totalRows,
                validRows,
                errorRows,
                ignoredRows,
                isFileValid,
                isCustomValidationFailed,
                errors,
            });
        };
        const config = {
            dynamicTyping: true,
            skipEmptyLines: true,
            header: false,
            step,
            complete,
        };
        // NOTE @monitz87: this might seem unintuitive but it's necessary for type safety in the props
        if (typeof data === 'string') {
            papaparse_1.default.parse(data, config);
        }
        else {
            papaparse_1.default.parse(data, config);
        }
    });
});
//# sourceMappingURL=csvParser.js.map