"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
const csv = require("csvtojson");
const { FieldTypes } = require("../constants");
const VALIDATORS = {
    [FieldTypes.STRING]: () => true,
    [FieldTypes.OPTIONS]: () => true,
    [FieldTypes.NUMBER]: attribute => {
        // allow not to be present
        if (!attribute) {
            return true;
        }
        return !isNaN(Number(attribute));
    },
    [FieldTypes.DATETIME]: attribute => {
        // allow not to be present
        if (!attribute) {
            return true;
        }
        return !isNaN(new Date(attribute).getTime());
    },
};
const PARSERS = {
    [FieldTypes.NUMBER]: attribute => {
        if (!attribute) {
            return attribute;
        }
        return Number(attribute);
    },
    [FieldTypes.DATETIME]: attribute => {
        if (!attribute) {
            return attribute;
        }
        return new Date(attribute).toISOString();
    },
};
function parse(csvString, parsers) {
    const result = csv().fromString(csvString);
    const schema = {};
    return new Promise((resolve, reject) => {
        result.on("header", headers => {
            for (let header of headers) {
                schema[header] = {
                    type: parsers[header] ? parsers[header].type : "string",
                    success: true,
                };
            }
        });
        result.subscribe(row => {
            // For each CSV row parse all the columns that need parsed
            for (let key of Object.keys(parsers)) {
                if (!schema[key] || schema[key].success) {
                    // get the validator for the column type
                    const validator = VALIDATORS[parsers[key].type];
                    try {
                        // allow null/undefined values
                        schema[key].success = !row[key] || validator(row[key]);
                    }
                    catch (err) {
                        schema[key].success = false;
                    }
                }
            }
        });
        result.on("done", error => {
            if (error) {
                console.error(error);
                reject(error);
            }
            resolve(schema);
        });
    });
}
function updateSchema({ schema, existingTable }) {
    if (!schema) {
        return schema;
    }
    const finalSchema = {};
    const schemaKeyMap = {};
    Object.keys(schema).forEach(key => (schemaKeyMap[key.toLowerCase()] = key));
    for (let [key, field] of Object.entries(existingTable.schema)) {
        const lcKey = key.toLowerCase();
        const foundKey = schemaKeyMap[lcKey];
        if (foundKey) {
            finalSchema[key] = schema[foundKey];
            finalSchema[key].type = field.type;
        }
    }
    return finalSchema;
}
function transform({ schema, csvString, existingTable }) {
    return __awaiter(this, void 0, void 0, function* () {
        const colParser = {};
        // make sure the table has all the columns required for import
        if (existingTable) {
            schema = updateSchema({ schema, existingTable });
        }
        for (let [key, field] of Object.entries(schema)) {
            // don't import data to auto columns
            if (!field.autocolumn) {
                colParser[key] = PARSERS[field.type] || field.type;
            }
        }
        try {
            const data = yield csv({ colParser }).fromString(csvString);
            const schemaKeyMap = {};
            Object.keys(schema).forEach(key => (schemaKeyMap[key.toLowerCase()] = key));
            for (let element of data) {
                if (!data) {
                    continue;
                }
                for (let key of Object.keys(element)) {
                    const mappedKey = schemaKeyMap[key.toLowerCase()];
                    // isn't a column in the table, remove it
                    if (mappedKey == null) {
                        delete element[key];
                    }
                    // casing is different, fix it in row
                    else if (key !== mappedKey) {
                        element[mappedKey] = element[key];
                        delete element[key];
                    }
                }
            }
            return data;
        }
        catch (err) {
            console.error(`Error transforming CSV to JSON for data import`, err);
            throw err;
        }
    });
}
module.exports = {
    parse,
    transform,
    updateSchema,
};
