/**
 * Jobs CRUD — enqueue, progress, complete, block, cancel.
 *
 * Claim/release lives in claim-lock.ts (atomic path).
 * DAG promotion lives in dag.ts (runs after status changes).
 */
import { randomUUID } from 'crypto';
import { claimJob, releaseClaim } from './claim-lock.js';
import { reconcileDag, wouldCreateCycle } from './dag.js';
function rowToJob(r) {
    return {
        id: r.id,
        title: r.title,
        tier: r.tier,
        status: r.status,
        priority: r.priority,
        workerId: r.worker_id,
        parentId: r.parent_id,
        dependsOn: (() => { try {
            return JSON.parse(r.depends_on);
        }
        catch {
            return [];
        } })(),
        budgetCapUsd: r.budget_cap_usd,
        tokensUsed: r.tokens_used,
        payload: (() => { try {
            return JSON.parse(r.payload);
        }
        catch {
            return {};
        } })(),
        client: r.client,
        claimedAt: r.claimed_at,
        completedAt: r.completed_at,
        createdAt: r.created_at,
    };
}
// ─── Enqueue ──────────────────────────────────────────────────────────────────
export function enqueueJob(db, input) {
    const id = input.id ?? randomUUID();
    const deps = input.dependsOn ?? [];
    const hasDeps = deps.length > 0;
    const status = hasDeps ? 'backlog' : 'ready';
    if (hasDeps && wouldCreateCycle(db, id, deps)) {
        throw new Error(`Cycle detected: job "${input.title}" (${id}) depends on path that leads back to itself`);
    }
    db.prepare(`
        INSERT INTO workforce_jobs
          (id, title, tier, status, priority, parent_id, depends_on, budget_cap_usd, payload, client, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(id) DO NOTHING
    `).run(id, input.title, input.tier ?? 'tactical', status, input.priority ?? 5, input.parentId ?? null, JSON.stringify(deps), input.budgetCapUsd ?? null, JSON.stringify(input.payload ?? {}), input.client ?? 'unknown', Date.now());
    if (!hasDeps)
        reconcileDag(db);
    return getJob(db, id);
}
export function patchJobPayload(db, jobId, patch) {
    const job = getJob(db, jobId);
    if (!job)
        return null;
    db.prepare(`UPDATE workforce_jobs SET payload = ? WHERE id = ?`).run(JSON.stringify({ ...(job.payload ?? {}), ...patch }), jobId);
    return getJob(db, jobId);
}
// ─── Read ─────────────────────────────────────────────────────────────────────
export function getJob(db, id) {
    const row = db.prepare('SELECT * FROM workforce_jobs WHERE id = ?').get(id);
    return row ? rowToJob(row) : null;
}
export function listJobs(db, status, limit = 200, companyId) {
    if (status && companyId) {
        return db.prepare('SELECT * FROM workforce_jobs WHERE status = ? AND company_id = ? ORDER BY priority, created_at LIMIT ?').all(status, companyId, limit).map(rowToJob);
    }
    if (status) {
        return db.prepare('SELECT * FROM workforce_jobs WHERE status = ? ORDER BY priority, created_at LIMIT ?').all(status, limit).map(rowToJob);
    }
    if (companyId) {
        return db.prepare('SELECT * FROM workforce_jobs WHERE company_id = ? ORDER BY priority, created_at LIMIT ?').all(companyId, limit).map(rowToJob);
    }
    return db.prepare('SELECT * FROM workforce_jobs ORDER BY priority, created_at LIMIT ?').all(limit).map(rowToJob);
}
// ─── Claim proxy (also in claim-lock.ts, re-exported for convenience) ─────────
export function claimNextReady(db, workerId, leaseMs) {
    const next = db.prepare(`SELECT id FROM workforce_jobs WHERE status = 'ready' ORDER BY priority, created_at LIMIT 1`).get();
    if (!next)
        return null;
    return claimJob(db, next.id, workerId, leaseMs);
}
// ─── Progress ─────────────────────────────────────────────────────────────────
export function advanceToWip(db, jobId, workerId) {
    db.prepare(`
        UPDATE workforce_jobs SET status = 'wip'
        WHERE id = ? AND worker_id = ? AND status = 'claimed'
    `).run(jobId, workerId);
}
export function recordTokens(db, jobId, workerId, tokens) {
    db.prepare(`UPDATE workforce_jobs SET tokens_used = tokens_used + ? WHERE id = ? AND worker_id = ?`).run(tokens, jobId, workerId);
}
export function advanceToReview(db, jobId, workerId) {
    db.prepare(`
        UPDATE workforce_jobs SET status = 'review'
        WHERE id = ? AND worker_id = ? AND status = 'wip'
    `).run(jobId, workerId);
}
// ─── Terminal states ──────────────────────────────────────────────────────────
export function completeJob(db, jobId, workerId) {
    const now = Date.now();
    db.prepare(`
        UPDATE workforce_jobs SET status = 'done', completed_at = ?
        WHERE id = ? AND worker_id = ? AND status IN ('claimed','wip','review')
    `).run(now, jobId, workerId);
    releaseClaim(db, jobId, workerId);
    reconcileDag(db);
    return getJob(db, jobId);
}
export function failJob(db, jobId, workerId) {
    db.prepare(`
        UPDATE workforce_jobs SET status = 'failed', completed_at = ?
        WHERE id = ? AND worker_id = ? AND status IN ('claimed','wip','review')
    `).run(Date.now(), jobId, workerId);
    releaseClaim(db, jobId, workerId);
    reconcileDag(db); // block dependents
    return getJob(db, jobId);
}
export function blockJob(db, jobId, workerId) {
    db.prepare(`
        UPDATE workforce_jobs SET status = 'blocked'
        WHERE id = ? AND worker_id = ? AND status IN ('claimed','wip','review')
    `).run(jobId, workerId);
    releaseClaim(db, jobId, workerId);
    return getJob(db, jobId);
}
export function cancelJob(db, jobId) {
    db.prepare(`UPDATE workforce_jobs SET status = 'cancelled' WHERE id = ? AND status NOT IN ('done','failed')`).run(jobId);
}
