"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "PrintJobService", {
    enumerable: true,
    get: function() {
        return PrintJobService;
    }
});
const _printjobentity = require("../../entities/print-job.entity");
const _runtimeexceptions = require("../../exceptions/runtime.exceptions");
const _jobstatsutil = require("../../utils/job-stats.util");
class PrintJobService {
    printJobRepository;
    eventEmitter2;
    logger;
    constructor(loggerFactory, typeormService, eventEmitter2){
        this.printJobRepository = typeormService.getDataSource().getRepository(_printjobentity.PrintJob);
        this.eventEmitter2 = eventEmitter2;
        this.logger = loggerFactory(PrintJobService.name);
    }
    async getJobByIdOrFail(id, relations) {
        const job = await this.printJobRepository.findOne({
            where: {
                id
            },
            relations
        });
        if (!job) {
            throw new _runtimeexceptions.NotFoundException(`Job ${id} not found`);
        }
        return job;
    }
    async handleFileAnalyzed(jobId, metadata, thumbnails) {
        const job = await this.printJobRepository.findOne({
            where: {
                id: jobId
            }
        });
        if (!job) {
            throw new Error(`Print job ${jobId} not found`);
        }
        job.metadata = metadata;
        job.analysisState = "ANALYZED";
        job.analyzedAt = new Date();
        job.fileFormat = metadata.fileFormat;
        await this.printJobRepository.save(job);
        this.eventEmitter2.emit("printJob.analyzed", {
            jobId: job.id,
            printerId: job.printerId,
            metadata
        });
        this.logger.log(`Print job ${jobId} analyzed: ${metadata.fileName}`);
        return job;
    }
    async handlePrintStarted(printerId, fileName, jobId, printerName) {
        const existingJob = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (existingJob?.fileName === fileName && !jobId) {
            return existingJob;
        }
        if (existingJob && existingJob.fileName !== fileName && !jobId) {
            existingJob.status = "UNKNOWN";
            existingJob.statusReason = "Print state unknown - printer started new job while previous job was still marked as printing. " + "This may indicate a disconnect, server restart, or manual printer control.";
            existingJob.endedAt = new Date();
            await this.printJobRepository.save(existingJob);
            this.logger.warn(`Printer ${printerId} started new print "${fileName}" while job ${existingJob.id} was PRINTING "${existingJob.fileName}". ` + `Marked job ${existingJob.id} as UNKNOWN.`);
        }
        let job;
        if (jobId) {
            job = await this.printJobRepository.findOne({
                where: {
                    id: jobId
                }
            });
            if (!job) {
                throw new Error(`Print job ${jobId} not found`);
            }
        } else {
            job = await this.printJobRepository.findOne({
                where: {
                    printerId,
                    fileName,
                    status: "PENDING"
                },
                order: {
                    createdAt: "DESC"
                }
            });
            if (!job) {
                job = await this.printJobRepository.findOne({
                    where: {
                        printerId,
                        fileName
                    },
                    order: {
                        createdAt: "DESC"
                    }
                });
            }
            if (!job) {
                this.logger.log(`Creating new job for ${fileName} - no pending or existing job found`);
                job = this.printJobRepository.create({
                    printerId,
                    printerName: printerName || null,
                    fileName,
                    status: "PRINTING",
                    analysisState: "NOT_ANALYZED"
                });
            } else if (job.status === "PENDING") {
                this.logger.log(`Promoting pending job ${job.id} to PRINTING`);
            } else if (job.status === "COMPLETED" || job.status === "FAILED" || job.status === "CANCELLED") {
                this.logger.log(`Creating new job for re-print of ${fileName} (previous job ${job.id} was ${job.status})`);
                job = this.printJobRepository.create({
                    printerId,
                    printerName: printerName || job.printerName || null,
                    fileName,
                    status: "PRINTING",
                    analysisState: job.analysisState,
                    metadata: job.metadata,
                    fileFormat: job.fileFormat
                });
            }
        }
        job.status = "PRINTING";
        job.startedAt = new Date();
        job.progress = 0;
        if (printerName && !job.printerName) {
            job.printerName = printerName;
        }
        if (!job.statistics) {
            job.statistics = {
                startedAt: new Date(),
                endedAt: null,
                actualPrintTimeSeconds: null,
                progress: 0
            };
        } else {
            job.statistics.startedAt = new Date();
            job.statistics.progress = 0;
        }
        await this.printJobRepository.save(job);
        this.eventEmitter2.emit("printJob.started", {
            jobId: job.id,
            printerId,
            fileName,
            startedAt: job.startedAt
        });
        this.logger.log(`Print job ${job.id} started on printer ${printerId}: ${fileName}`);
        return job;
    }
    async handlePrintProgress(printerId, progress, currentLayer, totalLayers) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.warn(`No active print job found for printer ${printerId} during progress update`);
            return null;
        }
        job.progress = Math.min(100, Math.max(0, progress));
        if (!job.statistics) {
            job.statistics = {
                startedAt: job.startedAt,
                endedAt: null,
                actualPrintTimeSeconds: null,
                progress: job.progress
            };
        } else {
            job.statistics.progress = job.progress;
        }
        if (currentLayer !== undefined) {
            job.statistics.currentLayer = currentLayer;
        }
        if (totalLayers !== undefined) {
            job.statistics.totalLayers = totalLayers;
        }
        await this.printJobRepository.save(job);
        this.eventEmitter2.emit("printJob.progress", {
            jobId: job.id,
            printerId,
            progress: job.progress,
            currentLayer,
            totalLayers
        });
        return job;
    }
    async handlePrintCompleted(printerId, fileName) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.warn(`No active print job found for printer ${printerId} on completion`);
            return null;
        }
        if (fileName && job.fileName !== fileName) {
            this.logger.warn(`Filename mismatch on completion: expected "${job.fileName}", got "${fileName}"`);
        }
        const endedAt = new Date();
        const actualTimeSeconds = (0, _jobstatsutil.calculateJobDuration)(job.startedAt, endedAt);
        job.status = "COMPLETED";
        (0, _jobstatsutil.updateStatisticsForCompletion)(job, endedAt);
        await this.printJobRepository.save(job);
        this.eventEmitter2.emit("printJob.completed", {
            jobId: job.id,
            printerId,
            fileName: job.fileName,
            actualTimeSeconds,
            estimatedTimeSeconds: job.metadata?.gcodePrintTimeSeconds
        });
        this.logger.log(`Print job ${job.id} completed on printer ${printerId}: ${job.fileName} ` + `(${actualTimeSeconds?.toFixed(0)}s actual, ${job.metadata?.gcodePrintTimeSeconds}s estimated)`);
        return job;
    }
    async handlePrintFailed(printerId, reason, fileName) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.warn(`No active print job found for printer ${printerId} on failure`);
            return null;
        }
        const endedAt = new Date();
        job.status = "FAILED";
        (0, _jobstatsutil.updateStatisticsForFailure)(job, reason, endedAt);
        await this.printJobRepository.save(job);
        this.eventEmitter2.emit("printJob.failed", {
            jobId: job.id,
            printerId,
            fileName: job.fileName,
            reason,
            failedAt: endedAt
        });
        this.logger.log(`Print job ${job.id} failed on printer ${printerId}: ${job.fileName} - ${reason}`);
        return job;
    }
    async handlePrintCancelled(printerId, reason) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.warn(`No active print job found for printer ${printerId} on cancellation`);
            return null;
        }
        const endedAt = new Date();
        job.status = "CANCELLED";
        (0, _jobstatsutil.updateStatisticsForCancellation)(job, reason, endedAt);
        await this.printJobRepository.save(job);
        this.eventEmitter2.emit("printJob.cancelled", {
            jobId: job.id,
            printerId,
            fileName: job.fileName,
            cancelledAt: endedAt
        });
        this.logger.log(`Print job ${job.id} cancelled on printer ${printerId}: ${job.fileName}`);
        return job;
    }
    async handlePrintPaused(printerId) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.warn(`No active print job found for printer ${printerId} on pause`);
            return null;
        }
        job.status = "PAUSED";
        await this.printJobRepository.save(job);
        this.logger.log(`Print job ${job.id} paused on printer ${printerId}`);
        return job;
    }
    async handlePrintResumed(printerId) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PAUSED"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.warn(`No paused print job found for printer ${printerId} on resume`);
            return null;
        }
        job.status = "PRINTING";
        await this.printJobRepository.save(job);
        this.logger.log(`Print job ${job.id} resumed on printer ${printerId}`);
        return job;
    }
    async cleanupStaleJobs() {
        const staleJobs = await this.printJobRepository.find({
            where: {
                status: "PRINTING"
            }
        });
        for (const job of staleJobs){
            job.status = "UNKNOWN";
            job.statusReason = "Print state unknown after server restart. The print may have completed, " + "failed, or still be running. Check your printer for current status.";
            await this.printJobRepository.save(job);
            this.logger.warn(`Marked job ${job.id} (printer ${job.printerId}) as UNKNOWN after startup - ` + `was PRINTING before server stopped`);
        }
        if (staleJobs.length > 0) {
            this.logger.log(`Cleaned up ${staleJobs.length} stale print job(s) on startup`);
        }
    }
    async getActivePrintJob(printerId) {
        return this.printJobRepository.findOne({
            where: {
                printerId,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
    }
    async getPrintJobHistory(printerId, limit = 50) {
        return this.printJobRepository.find({
            where: {
                printerId
            },
            order: {
                createdAt: "DESC"
            },
            take: limit
        });
    }
    async markStarted(printerId, fileName, printerName) {
        return await this.handlePrintStarted(printerId, fileName, undefined, printerName);
    }
    async markProgress(printerId, fileName, progress, currentLayer, totalLayers) {
        return await this.handlePrintProgress(printerId, progress, currentLayer, totalLayers);
    }
    async markFinished(printerId, fileName) {
        return await this.handlePrintCompleted(printerId, fileName);
    }
    async markFailed(printerId, fileName, reason) {
        return await this.handlePrintFailed(printerId, reason, fileName);
    }
    async updateJobMetadata(printerId, fileName, partialMetadata) {
        const job = await this.printJobRepository.findOne({
            where: {
                printerId,
                fileName,
                status: "PRINTING"
            },
            order: {
                startedAt: "DESC"
            }
        });
        if (!job) {
            this.logger.debug(`No active job found for printer ${printerId}, file ${fileName} - skipping metadata update`);
            return;
        }
        if (job.analysisState === "ANALYZED" && job.metadata) {
            this.logger.debug(`Job ${job.id} already has analyzed metadata, merging only missing fields`);
            const updatedMetadata = {
                ...job.metadata
            };
            for (const [key, value] of Object.entries(partialMetadata)){
                if (value != null && (updatedMetadata[key] == null || updatedMetadata[key] === null)) {
                    updatedMetadata[key] = value;
                }
            }
            job.metadata = updatedMetadata;
        } else if (job.metadata) {
            job.metadata = {
                ...job.metadata,
                ...partialMetadata
            };
        } else {
            const hasData = Object.values(partialMetadata).some((v)=>v != null && v !== null);
            if (!hasData) {
                this.logger.debug(`Skipping metadata creation for job ${job.id} - no meaningful data provided`);
                return;
            }
            job.metadata = {
                fileName,
                fileFormat: job.fileFormat || "gcode",
                ...partialMetadata
            };
        }
        await this.printJobRepository.save(job);
        this.logger.debug(`Updated metadata for job ${job.id}`);
    }
    async searchPrintJobs(searchPrinter, searchFile, startDate, endDate) {
        const query = this.printJobRepository.createQueryBuilder("job");
        if (searchPrinter) {
            query.andWhere("job.printerId = :printerId", {
                printerId: Number.parseInt(searchPrinter, 10)
            });
        }
        if (searchFile) {
            query.andWhere("job.fileName LIKE :fileName", {
                fileName: `%${searchFile}%`
            });
        }
        if (startDate) {
            query.andWhere("job.startedAt >= :startDate", {
                startDate
            });
        }
        if (endDate) {
            query.andWhere("job.startedAt <= :endDate", {
                endDate
            });
        }
        return await query.orderBy("job.startedAt", "DESC").getMany();
    }
    async searchPrintJobsPaged(searchPrinter, searchFile, startDate, endDate, page = 1, pageSize = 50) {
        const query = this.printJobRepository.createQueryBuilder("job");
        if (searchPrinter) {
            query.andWhere("job.printerId = :printerId", {
                printerId: Number.parseInt(searchPrinter, 10)
            });
        }
        if (searchFile) {
            query.andWhere("job.fileName LIKE :fileName", {
                fileName: `%${searchFile}%`
            });
        }
        if (startDate) {
            query.andWhere("job.startedAt >= :startDate", {
                startDate
            });
        }
        if (endDate) {
            query.andWhere("job.startedAt <= :endDate", {
                endDate
            });
        }
        return await query.orderBy("job.startedAt", "DESC").skip((page - 1) * pageSize).take(pageSize).getManyAndCount();
    }
    async createPendingJob(printerId, fileName, metadata, printerName) {
        const hasAnalysisData = metadata.gcodePrintTimeSeconds !== null || metadata.filamentUsedGrams !== null || metadata.totalFilamentUsedGrams !== null || metadata.layerHeight !== null || metadata.totalLayers !== null;
        const analysisState = hasAnalysisData ? "ANALYZED" : "NOT_ANALYZED";
        const status = "PENDING";
        const job = this.printJobRepository.create({
            printerId,
            printerName: printerName || null,
            fileName,
            status,
            analysisState,
            metadata,
            fileFormat: metadata.fileFormat,
            fileSize: metadata.fileSize,
            analyzedAt: hasAnalysisData ? new Date() : null
        });
        await this.printJobRepository.save(job);
        this.logger.log(`Created ${analysisState.toLowerCase()} print job ${job.id} for printer ${printerId}: ${fileName} (format: ${metadata.fileFormat})`);
        return job;
    }
    async triggerFileAnalysis(jobId) {
        this.eventEmitter2.emit("printJob.needsFileDownload", {
            jobId
        });
        this.logger.log(`Triggered file download and analysis for job ${jobId}`);
    }
    async markAsCompleted(jobId, reason) {
        const job = await this.getJobByIdOrFail(jobId);
        job.status = "COMPLETED";
        (0, _jobstatsutil.updateStatisticsForCompletion)(job);
        if (reason) {
            job.statusReason = reason;
        } else {
            job.statusReason = "Manually marked as completed by user";
        }
        await this.printJobRepository.save(job);
        this.logger.log(`Job ${jobId} manually marked as COMPLETED`);
        return job;
    }
    async markAsFailed(jobId, reason) {
        const job = await this.getJobByIdOrFail(jobId);
        job.status = "FAILED";
        (0, _jobstatsutil.updateStatisticsForFailure)(job, reason);
        await this.printJobRepository.save(job);
        this.logger.log(`Job ${jobId} manually marked as FAILED: ${reason}`);
        return job;
    }
    async markAsCancelled(jobId, reason) {
        const job = await this.getJobByIdOrFail(jobId);
        job.status = "CANCELLED";
        (0, _jobstatsutil.updateStatisticsForCancellation)(job, reason);
        await this.printJobRepository.save(job);
        this.logger.log(`Job ${jobId} manually marked as CANCELLED`);
        return job;
    }
    async markAsUnknown(jobId, reason) {
        const job = await this.getJobByIdOrFail(jobId);
        job.status = "UNKNOWN";
        job.statusReason = reason || "Manually marked as unknown by user (state uncertain)";
        await this.printJobRepository.save(job);
        this.logger.log(`Job ${jobId} manually marked as UNKNOWN`);
        return job;
    }
    async countJobsReferencingFile(fileStorageId) {
        return await this.printJobRepository.count({
            where: {
                fileStorageId
            }
        });
    }
    async updateJob(job) {
        return await this.printJobRepository.save(job);
    }
    async deleteJob(job) {
        await this.printJobRepository.remove(job);
    }
}

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