"use strict";
// Worker Queue — local model background task processing.
//
// Local models handle cheap background tasks: classify new tasks,
// extract structured tasks from notes, summarise daily notes,
// compress memory, scan logs, detect repeated failures, generate
// reminder drafts, refresh project summaries, validate JSON outputs.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkerQueue = void 0;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
// ─── Queue ──────────────────────────────────────────────────────────
class WorkerQueue {
    queue = [];
    completed = [];
    executors = new Map();
    maxCompleted;
    constructor(maxCompleted = 100) {
        this.maxCompleted = maxCompleted;
    }
    /** Register an executor for a job type. */
    registerExecutor(jobType, executor) {
        this.executors.set(jobType, executor);
    }
    /** Enqueue a new job. */
    enqueue(jobType, input, options) {
        const job = {
            job_id: crypto.randomUUID(),
            job_type: jobType,
            service_id: options?.service_id,
            model_id: options?.model_id,
            input,
            status: 'pending',
            created_at: new Date().toISOString(),
        };
        this.queue.push(job);
        return job;
    }
    /** Process the next pending job. Returns result or null if queue is empty. */
    async processNext() {
        const job = this.queue.find((j) => j.status === 'pending');
        if (!job)
            return null;
        const executor = this.executors.get(job.job_type);
        if (!executor) {
            job.status = 'failed';
            job.error = `No executor registered for job type: ${job.job_type}`;
            job.completed_at = new Date().toISOString();
            this.moveToCompleted(job);
            return { job_id: job.job_id, status: 'failed', error: job.error, duration_ms: 0 };
        }
        job.status = 'running';
        job.started_at = new Date().toISOString();
        const startMs = Date.now();
        try {
            const output = await executor(job);
            job.status = 'completed';
            job.output = output;
            job.completed_at = new Date().toISOString();
            const duration_ms = Date.now() - startMs;
            this.moveToCompleted(job);
            return { job_id: job.job_id, status: 'completed', output, duration_ms };
        }
        catch (err) {
            job.status = 'failed';
            job.error = err instanceof Error ? err.message : String(err);
            job.completed_at = new Date().toISOString();
            const duration_ms = Date.now() - startMs;
            this.moveToCompleted(job);
            return { job_id: job.job_id, status: 'failed', error: job.error, duration_ms };
        }
    }
    /** Process all pending jobs. */
    async processAll() {
        const results = [];
        while (this.pendingCount() > 0) {
            const result = await this.processNext();
            if (result)
                results.push(result);
        }
        return results;
    }
    /** Get the number of pending jobs. */
    pendingCount() {
        return this.queue.filter((j) => j.status === 'pending').length;
    }
    /** List pending jobs. */
    pending() {
        return this.queue.filter((j) => j.status === 'pending');
    }
    /** List completed jobs (recent history). */
    history() {
        return [...this.completed];
    }
    /** Get a job by ID. */
    getJob(jobId) {
        return this.queue.find((j) => j.job_id === jobId)
            ?? this.completed.find((j) => j.job_id === jobId);
    }
    /** Clear all pending jobs. */
    clear() {
        const count = this.pendingCount();
        this.queue = this.queue.filter((j) => j.status !== 'pending');
        return count;
    }
    moveToCompleted(job) {
        this.queue = this.queue.filter((j) => j.job_id !== job.job_id);
        this.completed.push(job);
        while (this.completed.length > this.maxCompleted) {
            this.completed.shift();
        }
    }
    // ─── Disk persistence ────────────────────────────────────────
    /** Save queue state to a JSON file. */
    async saveToDisk(filePath) {
        const data = {
            version: 1,
            saved_at: new Date().toISOString(),
            pending: this.queue.filter((j) => j.status === 'pending'),
            completed: this.completed.slice(-this.maxCompleted),
        };
        await fs.mkdir(path.dirname(filePath), { recursive: true });
        const tmpPath = `${filePath}.tmp`;
        await fs.writeFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
        await fs.rename(tmpPath, filePath);
    }
    /** Load queue state from a JSON file. Does not overwrite registered executors. */
    async loadFromDisk(filePath) {
        const errors = [];
        try {
            const raw = await fs.readFile(filePath, 'utf-8');
            const data = JSON.parse(raw);
            if (data.version !== 1) {
                return { loaded: 0, errors: ['Unsupported snapshot version.'] };
            }
            let loaded = 0;
            if (Array.isArray(data.pending)) {
                for (const job of data.pending) {
                    if (isWorkerJob(job) && !this.getJob(job.job_id)) {
                        job.status = 'pending'; // Reset in case it was running when saved
                        this.queue.push(job);
                        loaded++;
                    }
                }
            }
            if (Array.isArray(data.completed)) {
                for (const job of data.completed) {
                    if (isWorkerJob(job) && !this.getJob(job.job_id)) {
                        this.completed.push(job);
                    }
                }
            }
            return { loaded, errors };
        }
        catch (err) {
            if (err.code === 'ENOENT') {
                return { loaded: 0, errors: [] }; // No file yet, not an error
            }
            return { loaded: 0, errors: [`Failed to load: ${err instanceof Error ? err.message : String(err)}`] };
        }
    }
    /** Append a completed job to a JSONL log file for auditability. */
    async appendToLog(logPath, job) {
        await fs.mkdir(path.dirname(logPath), { recursive: true });
        await fs.appendFile(logPath, JSON.stringify(job) + '\n', 'utf-8');
    }
}
exports.WorkerQueue = WorkerQueue;
function isWorkerJob(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const v = value;
    return typeof v.job_id === 'string' && typeof v.job_type === 'string' && typeof v.status === 'string';
}
//# sourceMappingURL=workerQueue.js.map