"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "PrintQueueService", {
    enumerable: true,
    get: function() {
        return PrintQueueService;
    }
});
const _typeorm = require("typeorm");
const _printjobentity = require("../entities/print-job.entity");
const _printerentity = require("../entities/printer.entity");
const _socketstatetype = require("../shared/dtos/socket-state.type");
const _apistatetype = require("../shared/dtos/api-state.type");
const _node = require("@sentry/node");
class PrintQueueService {
    printerApiFactory;
    fileStorageService;
    printerSocketStore;
    printJobRepository;
    printerRepository;
    eventEmitter2;
    logger;
    constructor(loggerFactory, typeormService, eventEmitter2, printerApiFactory, fileStorageService, printerSocketStore){
        this.printerApiFactory = printerApiFactory;
        this.fileStorageService = fileStorageService;
        this.printerSocketStore = printerSocketStore;
        this.printJobRepository = typeormService.getDataSource().getRepository(_printjobentity.PrintJob);
        this.printerRepository = typeormService.getDataSource().getRepository(_printerentity.Printer);
        this.eventEmitter2 = eventEmitter2;
        this.logger = loggerFactory(PrintQueueService.name);
        this.eventEmitter2.on("printQueue.jobSubmitted", (event)=>{
            this.handleJobSubmission(event.printerId, event.jobId, event.fileName, event.fileStorageId, event.queuePosition).catch((error)=>{
                this.logger.error(`Failed to handle job submission for job ${event.jobId}`, error);
                (0, _node.captureException)(error);
            });
        });
    }
    isPrinterConnected(printerId) {
        const socket = this.printerSocketStore.getPrinterSocket(printerId);
        if (!socket) {
            return {
                connected: false,
                reason: "No socket connection found"
            };
        }
        const socketState = socket.socketState;
        const apiState = socket.apiState;
        if (socketState !== _socketstatetype.SOCKET_STATE.opened && socketState !== _socketstatetype.SOCKET_STATE.authenticated) {
            return {
                connected: false,
                reason: `Socket not connected (state: ${socketState})`
            };
        }
        if (apiState !== _apistatetype.API_STATE.responding) {
            return {
                connected: false,
                reason: `Printer not responding (API state: ${apiState})`
            };
        }
        return {
            connected: true
        };
    }
    async addToQueue(printerId, jobId, position) {
        const job = await this.printJobRepository.findOne({
            where: {
                id: jobId
            }
        });
        if (!job) {
            throw new Error(`Print job ${jobId} not found`);
        }
        this.ensurePrinterAssignment(job, printerId);
        if (position === undefined || position === null) {
            const maxPosition = await this.getMaxQueuePosition(printerId);
            job.queuePosition = (maxPosition ?? -1) + 1;
        } else {
            await this.shiftQueuePositions(printerId, position);
            job.queuePosition = position;
        }
        job.status = "QUEUED";
        await this.printJobRepository.save(job);
        this.logger.log(`Added job ${jobId} to printer ${printerId} queue at position ${job.queuePosition}`);
        this.eventEmitter2.emit("printQueue.jobAdded", {
            printerId,
            jobId,
            position: job.queuePosition
        });
    }
    async removeFromQueue(jobId) {
        const job = await this.printJobRepository.findOne({
            where: {
                id: jobId
            }
        });
        if (!job) {
            throw new Error(`Print job ${jobId} not found`);
        }
        const printerId = job.printerId;
        const oldPosition = job.queuePosition;
        job.queuePosition = null;
        if (job.status === "QUEUED") {
            job.status = "PENDING";
        }
        await this.printJobRepository.save(job);
        if (oldPosition !== null && printerId) {
            await this.compactQueuePositions(printerId, oldPosition);
        }
        this.logger.log(`Removed job ${jobId} from queue`);
        this.eventEmitter2.emit("printQueue.jobRemoved", {
            printerId,
            jobId
        });
    }
    async getQueue(printerId) {
        const jobs = await this.printJobRepository.find({
            where: {
                printerId,
                status: "QUEUED",
                queuePosition: (0, _typeorm.Not)((0, _typeorm.IsNull)())
            },
            order: {
                queuePosition: "ASC"
            }
        });
        return jobs.map((j)=>({
                id: j.id,
                fileName: j.fileName,
                queuePosition: j.queuePosition,
                status: j.status,
                estimatedTimeSeconds: j.metadata?.gcodePrintTimeSeconds,
                filamentGrams: j.metadata?.filamentUsedGrams,
                createdAt: j.createdAt
            }));
    }
    async getGlobalQueuePaged(page, pageSize) {
        const skip = (page - 1) * pageSize;
        return await this.printJobRepository.findAndCount({
            where: {
                status: "QUEUED"
            },
            order: {
                printerId: "ASC",
                queuePosition: "ASC"
            },
            relations: [
                'printer'
            ],
            take: pageSize,
            skip: skip
        });
    }
    async getNextInQueue(printerId) {
        return this.printJobRepository.findOne({
            where: {
                printerId,
                status: "QUEUED",
                queuePosition: (0, _typeorm.Not)((0, _typeorm.IsNull)())
            },
            order: {
                queuePosition: "ASC"
            }
        });
    }
    async reorderQueue(printerId, jobIds) {
        for(let i = 0; i < jobIds.length; i++){
            const job = await this.printJobRepository.findOne({
                where: {
                    id: jobIds[i]
                }
            });
            if (job?.printerId === printerId) {
                job.queuePosition = i;
                await this.printJobRepository.save(job);
            }
        }
        this.logger.log(`Reordered queue for printer ${printerId}`);
        this.eventEmitter2.emit("printQueue.reordered", {
            printerId
        });
    }
    async clearQueue(printerId) {
        const jobs = await this.printJobRepository.find({
            where: {
                printerId,
                status: "QUEUED"
            }
        });
        for (const job of jobs){
            job.status = "PENDING";
            job.queuePosition = null;
            await this.printJobRepository.save(job);
        }
        this.logger.log(`Cleared queue for printer ${printerId} (${jobs.length} jobs)`);
        this.eventEmitter2.emit("printQueue.cleared", {
            printerId
        });
    }
    async processQueue(printerId) {
        const nextJob = await this.getNextInQueue(printerId);
        if (!nextJob) {
            this.logger.log(`No jobs in queue for printer ${printerId}`);
            return null;
        }
        this.logger.log(`Processing queue: next job is ${nextJob.id} (${nextJob.fileName})`);
        this.eventEmitter2.emit("printQueue.processNext", {
            printerId,
            jobId: nextJob.id,
            fileName: nextJob.fileName,
            fileStorageId: nextJob.fileStorageId
        });
        return nextJob;
    }
    ensurePrinterAssignment(job, printerId) {
        if (!job.printerId) {
            job.printerId = printerId;
        } else if (job.printerId !== printerId) {
            throw new Error(`Job ${job.id} belongs to printer ${job.printerId}, cannot submit to printer ${printerId}`);
        }
    }
    async getMaxQueuePosition(printerId) {
        const result = await this.printJobRepository.createQueryBuilder("job").select("MAX(job.queuePosition)", "max").where("job.printerId = :printerId", {
            printerId
        }).getRawOne();
        return result?.max ?? null;
    }
    async shiftQueuePositions(printerId, fromPosition) {
        await this.printJobRepository.createQueryBuilder().update(_printjobentity.PrintJob).set({
            queuePosition: ()=>"queuePosition + 1"
        }).where("printerId = :printerId", {
            printerId
        }).andWhere("queuePosition >= :fromPosition", {
            fromPosition
        }).execute();
    }
    async compactQueuePositions(printerId, removedPosition) {
        await this.printJobRepository.createQueryBuilder().update(_printjobentity.PrintJob).set({
            queuePosition: ()=>"queuePosition - 1"
        }).where("printerId = :printerId", {
            printerId
        }).andWhere("queuePosition > :removedPosition", {
            removedPosition
        }).execute();
    }
    async submitToPrinter(printerId, jobId) {
        const job = await this.printJobRepository.findOne({
            where: {
                id: jobId
            }
        });
        if (!job) {
            throw new Error(`Print job ${jobId} not found`);
        }
        this.ensurePrinterAssignment(job, printerId);
        const queuePosition = job.queuePosition;
        if (job.queuePosition !== null) {
            const oldPosition = job.queuePosition;
            job.queuePosition = null;
            await this.compactQueuePositions(printerId, oldPosition);
        }
        job.status = "PRINTING";
        job.startedAt = new Date();
        await this.printJobRepository.save(job);
        this.logger.log(`Submitting job ${jobId} (${job.fileName}) to printer ${printerId}`);
        this.eventEmitter2.emit("printQueue.jobSubmitted", {
            printerId,
            jobId: job.id,
            fileName: job.fileName,
            fileStorageId: job.fileStorageId,
            queuePosition
        });
    }
    async handleJobSubmission(printerId, jobId, fileName, fileStorageId, queuePosition) {
        this.logger.log(`Handling job submission for job ${jobId} on printer ${printerId}`);
        try {
            if (!fileStorageId) {
                throw new Error(`Job ${jobId} has no fileStorageId - cannot submit to printer`);
            }
            const printerApi = this.printerApiFactory.getById(printerId);
            const fileSize = this.fileStorageService.getFileSize(fileStorageId);
            const fileStream = this.fileStorageService.readFileStream(fileStorageId);
            this.logger.log(`Uploading file ${fileName} to printer ${printerId} and starting print`);
            await printerApi.uploadFile({
                stream: fileStream,
                fileName,
                contentLength: fileSize,
                startPrint: true
            });
            this.logger.log(`Successfully submitted job ${jobId} to printer ${printerId}`);
            if (queuePosition !== null && queuePosition !== undefined) {
                const job = await this.printJobRepository.findOne({
                    where: {
                        id: jobId
                    }
                });
                if (job?.queuePosition === queuePosition) {
                    job.queuePosition = null;
                    await this.printJobRepository.save(job);
                    await this.compactQueuePositions(printerId, queuePosition);
                    this.logger.log(`Removed job ${jobId} from queue after successful submission`);
                }
            }
        } catch (error) {
            this.logger.error(`Failed to submit job ${jobId} to printer ${printerId}`, error);
            try {
                const job = await this.printJobRepository.findOne({
                    where: {
                        id: jobId
                    }
                });
                if (job) {
                    job.status = "FAILED";
                    job.statusReason = `Print submission failed: ${error instanceof Error ? error.message : "Unknown error"}`;
                    job.endedAt = new Date();
                    await this.printJobRepository.save(job);
                    this.logger.log(`Updated job ${jobId} status to FAILED (still in queue for retry)`);
                }
            } catch (updateError) {
                this.logger.error(`Failed to update job ${jobId} status after submission error`, updateError);
            }
            throw error;
        }
    }
}

//# sourceMappingURL=print-queue.service.js.map