"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "YamlService", {
    enumerable: true,
    get: function() {
        return YamlService;
    }
});
const _validators = require("../../handlers/validators");
const _yamlservicevalidation = require("../validators/yaml-service.validation");
const _jsyaml = require("js-yaml");
const _printerapiinterface = require("../printer-api.interface");
class YamlService {
    printerTagService;
    printerService;
    printerCache;
    floorStore;
    floorService;
    userService;
    roleService;
    settingsStore;
    logger;
    constructor(loggerFactory, printerTagService, printerService, printerCache, floorStore, floorService, userService, roleService, settingsStore){
        this.printerTagService = printerTagService;
        this.printerService = printerService;
        this.printerCache = printerCache;
        this.floorStore = floorStore;
        this.floorService = floorService;
        this.userService = userService;
        this.roleService = roleService;
        this.settingsStore = settingsStore;
        this.logger = loggerFactory(YamlService.name);
    }
    async importYaml(yamlBuffer) {
        const importSpec = await (0, _jsyaml.load)(yamlBuffer);
        const databaseTypeSqlite = importSpec.databaseType === "sqlite";
        const { exportPrinters, exportFloorGrid, exportSettings, exportUsers } = importSpec.config;
        this.normalizeYamlData(importSpec, databaseTypeSqlite);
        const importData = await (0, _validators.validateInput)(importSpec, _yamlservicevalidation.importPrintersFloorsYamlSchema);
        const hasSystemData = exportSettings || exportUsers;
        if (hasSystemData) {
            await this.validateSystemTablesEmpty(importSpec);
            if (exportSettings && importSpec.settings) {
                this.logger.log("Importing settings");
                await this.importSettings(importSpec.settings);
            }
            if (exportUsers && importSpec.users && importSpec.users.length > 0) {
                this.logger.log(`Importing users (${importSpec.users.length} users)`);
                await this.importUsers(importSpec.users, databaseTypeSqlite);
            }
        }
        if (exportFloorGrid && importData.floors?.length) {
            for (const floor of importData.floors){
                await (0, _validators.validateInput)(floor, _yamlservicevalidation.importPrinterPositionsSchema);
            }
        }
        this.logger.log("Analysing printers for import");
        const { updateByPropertyPrinters, insertPrinters } = await this.analysePrintersUpsert(importData.printers ?? [], importData.config.printerComparisonStrategiesByPriority);
        this.logger.log("Analysing floors for import");
        const { updateByPropertyFloors, insertFloors } = await this.analyseFloorsUpsert(importData.floors ?? [], importData.config.floorComparisonStrategiesByPriority);
        this.logger.log("Analysing tags for import");
        const { updateByNameTags, insertTags } = await this.analyseUpsertTags(importData.tags ?? []);
        this.logger.log(`Performing pure insert printers (${insertPrinters.length} printers)`);
        const printerIdMap = {};
        for (const newPrinter of insertPrinters){
            try {
                const state = await this.printerService.create({
                    ...newPrinter
                });
                if (!newPrinter.id) {
                    throw new Error(`Saved ID was empty ${JSON.stringify(newPrinter)}`);
                }
                printerIdMap[newPrinter.id] = state.id;
            } catch (error) {
                this.logger.error(`Failed to create printer ${newPrinter.name}:`, error);
            }
        }
        this.logger.log(`Performing update import printers (${updateByPropertyPrinters.length} printers)`);
        for (const updatePrinterSpec of updateByPropertyPrinters){
            try {
                const updateId = updatePrinterSpec.printerId;
                const updatedPrinter = updatePrinterSpec.value;
                if (typeof updateId === "string") {
                    throw new Error("Cannot update a printer by string id in sqlite mode");
                }
                const originalPrinterId = updatedPrinter.id;
                delete updatePrinterSpec.value.id;
                updatedPrinter.id = updateId;
                const state = await this.printerService.update(updateId, updatedPrinter);
                if (!updatePrinterSpec.printerId) {
                    throw new Error("Saved ID was empty");
                }
                printerIdMap[originalPrinterId] = state.id;
            } catch (error) {
                this.logger.error(`Failed to update printer ${updatePrinterSpec.value.name}:`, error);
            }
        }
        this.logger.log(`Performing pure create floors (${insertFloors.length} floors)`);
        const floorIdMap = {};
        for (const newFloor of insertFloors){
            try {
                const originalFloorId = newFloor.id;
                delete newFloor.id;
                const knownPrinterPositions = [];
                if (exportFloorGrid && exportPrinters) {
                    for (const floorPosition of newFloor.printers){
                        const knownPrinterId = printerIdMap[floorPosition.printerId];
                        if (!knownPrinterId) {
                            continue;
                        }
                        delete floorPosition.id;
                        delete floorPosition.floorId;
                        floorPosition.printerId = knownPrinterId;
                        knownPrinterPositions.push(floorPosition);
                    }
                    newFloor.printers = knownPrinterPositions;
                }
                const createdFloor = await this.floorStore.create({
                    ...newFloor
                });
                floorIdMap[originalFloorId] = createdFloor.id;
            } catch (error) {
                this.logger.error(`Failed to create floor ${newFloor.name}:`, error);
            }
        }
        this.logger.log(`Performing update of floors (${updateByPropertyFloors.length} floors)`);
        for (const updateFloorSpec of updateByPropertyFloors){
            try {
                const updateId = updateFloorSpec.floorId;
                if (typeof updateId === "string") {
                    throw new Error("Cannot update a floor by string id in sqlite mode");
                }
                const updatedFloor = updateFloorSpec.value;
                const originalFloorId = updatedFloor.id;
                delete updatedFloor.id;
                const knownPrinters = [];
                if (exportFloorGrid && exportPrinters) {
                    for (const floorPosition of updatedFloor?.printers){
                        const knownPrinterId = printerIdMap[floorPosition.printerId];
                        if (!knownPrinterId) {
                            continue;
                        }
                        delete floorPosition.id;
                        delete floorPosition.floorId;
                        floorPosition.printerId = knownPrinterId;
                        floorPosition.floorId = updateId;
                        knownPrinters.push(floorPosition);
                    }
                    updatedFloor.id = updateId;
                    updatedFloor.printers = knownPrinters;
                }
                const newFloor = await this.floorStore.update(updateId, updatedFloor);
                floorIdMap[originalFloorId] = newFloor.id;
            } catch (error) {
                this.logger.error(`Failed to update floor ${updateFloorSpec.value.name}:`, error);
            }
        }
        await this.floorStore.loadStore();
        this.logger.log(`Performing pure create tags (${insertTags.length} tags)`);
        for (const tag of insertTags){
            try {
                const createdTag = await this.printerTagService.createTag({
                    name: tag.name,
                    color: tag.color
                });
                for (const printer of tag.printers){
                    const knownPrinterId = printerIdMap[printer.printerId];
                    if (!knownPrinterId) continue;
                    try {
                        await this.printerTagService.addPrinterToTag(createdTag.id, knownPrinterId);
                    } catch (error) {
                        this.logger.error(`Failed to add printer ${knownPrinterId} to tag ${tag.name}:`, error);
                    }
                }
            } catch (error) {
                this.logger.error(`Failed to create tag ${tag.name}:`, error);
            }
        }
        this.logger.log(`Performing update of tag printer links (${updateByNameTags.length} tags)`);
        for (const updateTagSpec of updateByNameTags){
            try {
                const existingTag = await this.printerTagService.getPrintersByTag(updateTagSpec.tagId);
                const existingPrinterIds = existingTag.printers.map((p)=>p.printerId);
                const wantedTargetPrinterIds = updateTagSpec.value.printers.filter((p)=>!!printerIdMap[p.printerId]).map((p)=>printerIdMap[p.printerId]);
                for (const unwantedId of existingPrinterIds.filter((eid)=>!wantedTargetPrinterIds.includes(eid))){
                    try {
                        await this.printerTagService.removePrinterFromTag(existingTag.id, unwantedId);
                    } catch (error) {
                        this.logger.error(`Failed to remove printer ${unwantedId} from tag ${existingTag.name}:`, error);
                    }
                }
                for (const nonExistingNewId of wantedTargetPrinterIds.filter((eid)=>!existingPrinterIds.includes(eid))){
                    try {
                        await this.printerTagService.addPrinterToTag(existingTag.id, nonExistingNewId);
                    } catch (error) {
                        this.logger.error(`Failed to add printer ${nonExistingNewId} to tag ${existingTag.name}:`, error);
                    }
                }
            } catch (error) {
                this.logger.error(`Failed to update tag ${updateTagSpec.value.name}:`, error);
            }
        }
        return {
            updateByPropertyPrinters,
            updateByPropertyFloors,
            insertPrinters,
            insertFloors,
            printerIdMap,
            floorIdMap
        };
    }
    async importSettings(settings) {
        if (settings.server) {
            await this.settingsStore.updateServerSettings(settings.server);
        }
        if (settings.timeout) {
            await this.settingsStore.updateTimeoutSettings(settings.timeout);
        }
        if (settings.frontend) {
            await this.settingsStore.updateFrontendSettings(settings.frontend);
        }
        if (settings.wizard?.wizardCompleted) {
            const importedWizardVersion = settings.wizard.wizardVersion;
            this.logger.log(`Marking wizard as completed with version: ${importedWizardVersion}`);
            await this.settingsStore.setWizardCompleted(importedWizardVersion);
        }
        if (settings.credential) {
            const { jwtExpiresIn, refreshTokenAttempts, refreshTokenExpiry, slicerApiKey } = settings.credential;
            if (jwtExpiresIn || refreshTokenAttempts || refreshTokenExpiry) {
                await this.settingsStore.updateCoreCredentialSettings({
                    jwtExpiresIn: jwtExpiresIn ?? (await this.settingsStore.getCredentialSettings()).jwtExpiresIn,
                    refreshTokenAttempts: refreshTokenAttempts ?? (await this.settingsStore.getCredentialSettings()).refreshTokenAttempts,
                    refreshTokenExpiry: refreshTokenExpiry ?? (await this.settingsStore.getCredentialSettings()).refreshTokenExpiry
                });
                this.logger.log("Imported credential settings");
            }
            if (slicerApiKey) {
                await this.settingsStore.setSlicerApiKey(slicerApiKey);
                this.logger.log("Imported slicer API key");
            }
        }
        await this.settingsStore.loadSettings();
        this.logger.log("Settings imported successfully");
    }
    async importUsers(users, databaseTypeSqlite) {
        const allRoles = this.roleService.roles;
        for (const user of users){
            if (databaseTypeSqlite && typeof user.id === "string") {
                user.id = Number.parseInt(user.id);
            }
            const roleNames = (user.roles ?? []).filter((roleName)=>allRoles.some((r)=>r.name === roleName));
            await this.userService.register({
                username: user.username,
                password: "temporary-password-to-be-replaced",
                roles: roleNames,
                isRootUser: user.isRootUser ?? false,
                isDemoUser: user.isDemoUser ?? false,
                isVerified: user.isVerified ?? false,
                needsPasswordChange: user.needsPasswordChange ?? true
            });
            await this.userService.updatePasswordHashUnsafeByUsername(user.username, user.passwordHash);
        }
        this.logger.log(`Imported ${users.length} users`);
    }
    async validateSystemTablesEmpty(importSpec) {
        const errors = [];
        const wizardCompleted = this.settingsStore.getWizardSettings()?.wizardCompleted;
        if (!wizardCompleted) {
            return;
        }
        if (importSpec.settings && importSpec.config.exportSettings) {
            const existingSettings = this.settingsStore.getSettings();
            if (existingSettings && Object.keys(existingSettings).length > 0) {
                errors.push("Settings table is not empty. Cannot import settings when existing settings are present.");
            }
        }
        if (importSpec.users && importSpec.users.length > 0 && importSpec.config.exportUsers) {
            const existingUsers = await this.userService.listUsers(1);
            if (existingUsers.length > 0) {
                errors.push("Users table is not empty. Cannot import users when existing users are present.");
            }
        }
        if (errors.length > 0) {
            throw new Error(`Import validation failed:\n${errors.join("\n")}`);
        }
    }
    async analysePrintersUpsert(upsertPrinters, comparisonStrategies) {
        const existingPrinters = await this.printerService.list();
        const names = existingPrinters.map((p)=>p.name.toLowerCase());
        const urls = existingPrinters.map((p)=>p.printerURL);
        const ids = existingPrinters.map((p)=>p.id.toString());
        const insertPrinters = [];
        const updateByPropertyPrinters = [];
        for (const printer of upsertPrinters){
            for (const strategy of [
                ...comparisonStrategies,
                "new"
            ]){
                if (strategy === "name") {
                    const comparedName = printer.name.toLowerCase();
                    const foundIndex = names.findIndex((n)=>n === comparedName);
                    if (foundIndex !== -1) {
                        if (!ids[foundIndex]) {
                            throw new Error("Update ID is undefined");
                        }
                        updateByPropertyPrinters.push({
                            strategy: "name",
                            printerId: Number.parseInt(ids[foundIndex]),
                            value: printer
                        });
                        break;
                    }
                } else if (strategy === "url") {
                    const comparedName = printer.printerURL.toLowerCase();
                    const foundIndex = urls.findIndex((n)=>n === comparedName);
                    if (foundIndex !== -1) {
                        if (!ids[foundIndex]) {
                            throw new Error("Update ID is undefined");
                        }
                        updateByPropertyPrinters.push({
                            strategy: "url",
                            printerId: Number.parseInt(ids[foundIndex]),
                            value: printer
                        });
                        break;
                    }
                } else if (strategy === "new") {
                    if (!printer.id) {
                        throw new Error(JSON.stringify(printer));
                    }
                    insertPrinters.push(printer);
                    break;
                }
            }
        }
        return {
            updateByPropertyPrinters,
            insertPrinters
        };
    }
    async analyseFloorsUpsert(upsertFloors, comparisonStrategy) {
        const existingFloors = await this.floorService.list();
        const names = existingFloors.map((p)=>p.name.toLowerCase());
        const floorLevels = existingFloors.map((p)=>p.order);
        const ids = existingFloors.map((p)=>p.id.toString());
        const insertFloors = [];
        const updateByPropertyFloors = [];
        for (const floor of upsertFloors){
            for (const strategy of [
                comparisonStrategy,
                "new"
            ]){
                if (strategy === "name") {
                    const comparedProperty = floor.name.toLowerCase();
                    const foundIndex = names.findIndex((n)=>n === comparedProperty);
                    if (foundIndex !== -1) {
                        if (!ids[foundIndex]) {
                            throw new Error("IDS not found, floor name");
                        }
                        updateByPropertyFloors.push({
                            strategy: "name",
                            floorId: Number.parseInt(ids[foundIndex]),
                            value: floor
                        });
                        break;
                    }
                } else if (strategy === "floor") {
                    const comparedProperty = floor.order;
                    const foundIndex = floorLevels.findIndex((n)=>n === comparedProperty);
                    if (foundIndex !== -1) {
                        if (!ids[foundIndex]) {
                            throw new Error("IDS not found, floor level");
                        }
                        updateByPropertyFloors.push({
                            strategy: "floor",
                            floorId: Number.parseInt(ids[foundIndex]),
                            value: floor
                        });
                        break;
                    }
                } else if (strategy === "new") {
                    insertFloors.push(floor);
                    break;
                }
            }
        }
        return {
            updateByPropertyFloors,
            insertFloors
        };
    }
    async analyseUpsertTags(upsertTags) {
        if (!upsertTags?.length) {
            return {
                updateByNameTags: [],
                insertTags: []
            };
        }
        const existingTags = await this.printerTagService.listTags();
        const names = existingTags.map((p)=>p.name.toLowerCase());
        const ids = existingTags.map((p)=>p.id.toString());
        const insertTags = [];
        const updateByNameTags = [];
        for (const tag of upsertTags){
            const comparedProperty = tag.name.toLowerCase();
            const foundIndex = names.indexOf(comparedProperty);
            if (foundIndex === -1) {
                insertTags.push(tag);
            } else {
                if (!ids[foundIndex]) {
                    throw new Error("IDS not found, tag name");
                }
                updateByNameTags.push({
                    strategy: "name",
                    tagId: Number.parseInt(ids[foundIndex]),
                    value: tag
                });
                break;
            }
        }
        return {
            insertTags,
            updateByNameTags
        };
    }
    async exportYaml(options) {
        const input = await (0, _validators.validateInput)(options, _yamlservicevalidation.exportPrintersFloorsYamlSchema);
        const { exportFloors, exportPrinters, exportFloorGrid, exportTags, exportSettings, exportUsers } = input;
        const dumpedObject = {
            version: process.env.npm_package_version,
            exportedAt: new Date(),
            databaseType: "sqlite",
            config: input,
            printers: undefined,
            floors: undefined,
            tags: undefined,
            settings: undefined,
            users: undefined,
            user_roles: undefined
        };
        if (exportPrinters) {
            const printers = await this.printerService.list();
            dumpedObject.printers = printers.map((p)=>{
                const printerId = p.id;
                const { apiKey, username, password } = this.printerCache.getLoginDto(printerId);
                return {
                    id: printerId,
                    disabledReason: p.disabledReason,
                    enabled: p.enabled,
                    printerType: p.printerType,
                    dateAdded: p.dateAdded,
                    name: p.name,
                    printerURL: p.printerURL,
                    apiKey,
                    username,
                    password,
                    assignee: p.assignee,
                    flowRate: p.flowRate,
                    feedRate: p.feedRate
                };
            });
        }
        if (exportFloors) {
            const floors = await this.floorStore.listCache();
            dumpedObject.floors = floors.map((f)=>{
                const dumpedFloor = {
                    id: f.id,
                    order: f.order,
                    name: f.name,
                    printers: undefined
                };
                if (exportFloorGrid) {
                    dumpedFloor.printers = f.printers.map((p)=>{
                        const fPrinterId = p.printerId;
                        return {
                            printerId: fPrinterId,
                            x: p.x,
                            y: p.y
                        };
                    });
                }
                return dumpedFloor;
            });
        }
        if (exportTags) {
            const tags = await this.printerTagService.listTags();
            dumpedObject.tags = tags.map((t)=>{
                return {
                    name: t.name,
                    id: t.id,
                    printers: t.printers.map((p)=>{
                        return {
                            printerId: p.printerId
                        };
                    })
                };
            });
        }
        if (exportSettings) {
            dumpedObject.settings = {
                ...this.settingsStore.getSettings(),
                ...this.settingsStore.getSettingsSensitive()
            };
        }
        if (exportUsers) {
            const users = await this.userService.listUsers(1000);
            dumpedObject.users = users.map((u)=>{
                const userDto = this.userService.toDto(u);
                return {
                    id: userDto.id,
                    username: userDto.username,
                    isDemoUser: userDto.isDemoUser,
                    isRootUser: userDto.isRootUser,
                    isVerified: userDto.isVerified,
                    needsPasswordChange: userDto.needsPasswordChange,
                    passwordHash: u.passwordHash,
                    createdAt: userDto.createdAt,
                    roles: userDto.roles
                };
            });
        }
        return (0, _jsyaml.dump)(dumpedObject, {});
    }
    normalizeYamlData(importSpec, databaseTypeSqlite) {
        for (const printer of importSpec.printers){
            if (!printer.name && printer.printerName) {
                printer.name = printer.printerName;
                delete printer.printerName;
            }
            if (printer.settingsAppearance?.name) {
                printer.name = printer.settingsAppearance?.name;
                delete printer.settingsAppearance?.name;
            }
            if (databaseTypeSqlite && typeof printer.id === "string") {
                printer.id = Number.parseInt(printer.id);
            }
            if (![
                _printerapiinterface.OctoprintType,
                _printerapiinterface.MoonrakerType,
                _printerapiinterface.PrusaLinkType,
                _printerapiinterface.BambuType
            ].includes(printer.printerType)) {
                printer.printerType = _printerapiinterface.OctoprintType;
            }
        }
        for (const floor of importSpec.floors){
            if (floor.floor !== undefined && floor.order === undefined) {
                floor.order = floor.floor;
                delete floor.floor;
            }
            if (databaseTypeSqlite) {
                if (typeof floor.id === "string") {
                    floor.id = Number.parseInt(floor.id);
                }
                for (const printer of floor.printers){
                    if (typeof printer.printerId === "string") {
                        printer.printerId = Number.parseInt(printer.printerId);
                    }
                }
            }
        }
        if (importSpec.groups && !importSpec.tags) {
            importSpec.tags = importSpec.groups;
            delete importSpec.groups;
        }
        if (importSpec.config.exportGroups !== undefined && importSpec.config.exportTags === undefined) {
            importSpec.config.exportTags = importSpec.config.exportGroups;
            delete importSpec.config.exportGroups;
        }
    }
}

//# sourceMappingURL=yaml.service.js.map