"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get PERMISSIONS () {
        return PERMISSIONS;
    },
    get referenceDataImporter () {
        return referenceDataImporter;
    }
});
const _lodash = require("lodash");
const _xlsx = require("xlsx");
const _logging = require("@tamanu/shared/services/logging");
const _constants = require("@tamanu/constants");
const _importerEndpoint = require("../importer/importerEndpoint");
const _loaders = require("./loaders");
const _sheet = require("./sheet");
const _dependencies = /*#__PURE__*/ _interop_require_default(require("./dependencies"));
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
const PERMISSIONS = [
    'Permission',
    'Role',
    'User',
    'ReferenceData'
];
async function referenceDataImporter({ errors, models, stats, file, data = null, workbook: providedWorkbook = null, includedDataTypes = [], checkPermission, skipExisting }) {
    let workbook;
    if (providedWorkbook) {
        workbook = providedWorkbook;
        _logging.log.info('Importing data definitions from workbook');
    } else if (data) {
        workbook = (0, _xlsx.read)(data, {
            type: 'buffer'
        });
        _logging.log.info('Importing data definitions from data');
    } else if (file) {
        workbook = (0, _xlsx.readFile)(file);
        _logging.log.info('Importing data definitions from file', {
            file
        });
    } else {
        throw new Error('Must provide either workbook, data, or file');
    }
    if (checkPermission) {
        for (const dataType of includedDataTypes){
            if (_constants.REFERENCE_TYPE_VALUES.includes(dataType)) {
                checkPermission('create', 'ReferenceData');
                checkPermission('write', 'ReferenceData');
                continue;
            }
            const nonReferenceDataModalName = (0, _lodash.upperFirst)(dataType);
            checkPermission('create', nonReferenceDataModalName);
            checkPermission('write', nonReferenceDataModalName);
        }
    }
    _logging.log.debug('Normalise all sheet names for lookup');
    const sheets = new Map();
    for (const [sheetName, sheet] of Object.entries(workbook.Sheets)){
        const name = (0, _importerEndpoint.normaliseSheetName)(sheetName);
        if (!includedDataTypes.includes(name)) {
            _logging.log.debug('Sheet has been manually excluded', {
                name
            });
            continue;
        }
        _logging.log.debug('Found and normalised sheet', {
            name
        });
        sheets.set(name, sheet);
    }
    // general idea is there are a number of phases, and during each we iterate
    // through the entire set of remaining rows. any errors are caught and stored,
    // and the erroring rows are omitted from the set that goes on to the next bit
    //
    // if there are any errors at the end of the process, we throw to rollback the
    // transaction.
    //
    // pushing on like this means that there may be some false-positive errors in
    // later steps that actually wouldn't be errors if the right rows had made it
    // through without erroring previously. overall, though, it should provide a
    // lot more feedback than erroring early.
    const context = (sheetName, dataType = sheetName)=>({
            errors,
            log: _logging.log.child({
                file,
                dataType,
                sheetName
            }),
            models
        });
    _logging.log.debug('Import all reference data', {
        types: _constants.REFERENCE_TYPE_VALUES
    });
    const importedRef = [];
    for (const refType of _constants.REFERENCE_TYPE_VALUES){
        _logging.log.debug('Look for reference data in sheets', {
            refType
        });
        const sheet = sheets.get(refType);
        if (!sheet) continue;
        _logging.log.debug('Found a sheet for the reference data', {
            refType
        });
        stats.push(await (0, _sheet.importSheet)(context(refType, 'referenceData'), {
            loader: (0, _loaders.referenceDataLoaderFactory)(refType),
            sheetName: refType,
            sheet,
            skipExisting
        }));
        importedRef.push(refType);
    }
    _logging.log.debug('Done importing reference data', {
        imported: importedRef
    });
    // sort by length of needs, so that stuff that doesn't depend on anything else gets done first
    // (as an optimisation, the algorithm doesn't need this, but it saves a few cycles)
    const nonRefDataTypes = Object.entries(_dependencies.default).map(([k, v])=>[
            (0, _importerEndpoint.normaliseSheetName)(k),
            v
        ]);
    // eslint-disable-next-line no-unused-vars
    nonRefDataTypes.sort(([_ka, a], [_kb, b])=>(a.needs?.length ?? 0) - (b.needs?.length ?? 0));
    _logging.log.debug('Importing other data types', {
        nonRefDataTypes
    });
    const importedData = [];
    const droppedData = [];
    let loopProtection = 100;
    while(nonRefDataTypes.length > 0 && loopProtection > 0){
        loopProtection -= 1;
        const [dataType, { model = (0, _lodash.upperFirst)(dataType), loader = (0, _loaders.loaderFactory)(model), needs = [] }] = nonRefDataTypes.shift();
        _logging.log.debug('Look for data type in sheets', {
            dataType
        });
        const sheet = sheets.get(dataType);
        if (!sheet) {
            _logging.log.debug('No sheet for it, drop that data type', {
                dataType
            });
            droppedData.push(dataType);
            continue;
        }
        _logging.log.debug('Found a sheet for the data', {
            dataType
        });
        if (needs) {
            _logging.log.debug('Resolve data type needs', {
                dataType,
                needs
            });
            if (!needs.every((need)=>importedData.includes(need) || droppedData.includes(need))) {
                _logging.log.debug('Some needs are missing, deferring');
                nonRefDataTypes.push([
                    dataType,
                    {
                        loader,
                        model,
                        needs
                    }
                ]);
                continue;
            }
        }
        stats.push(await (0, _sheet.importSheet)(context(dataType), {
            loader,
            sheetName: dataType,
            sheet,
            skipExisting
        }));
        importedData.push(dataType);
    }
    if (!loopProtection) throw new Error('Loop, cycle, or unresolvable import dependencies');
    _logging.log.debug('Done importing data', {
        importedData,
        droppedData
    });
}

//# sourceMappingURL=referenceDataImporter.js.map