/**
 * Atomic claim lock — exactly one worker claims a ready job.
 *
 * Uses SQLite's serialised writes (single-writer model) to guarantee
 * atomicity without explicit transactions: the UPDATE ... RETURNING
 * pattern fails silently for any second caller.
 */
import { randomUUID } from 'crypto';
function rowToClaim(r) {
    return {
        id: r.id,
        jobId: r.job_id,
        workerId: r.worker_id,
        acquiredAt: r.acquired_at,
        releasedAt: r.released_at,
        leaseMs: r.lease_ms,
        expiresAt: r.expires_at,
    };
}
/** Attempt atomic claim. Returns Claim on success, null if already claimed. */
export function claimJob(db, jobId, workerId, leaseMs = 300_000) {
    const now = Date.now();
    const claimId = randomUUID();
    const expiresAt = now + leaseMs;
    const claim = db.transaction(() => {
        // Atomic: update only if status is ready AND no active un-released claim
        const updated = db.prepare(`
            UPDATE workforce_jobs
            SET status = 'claimed', worker_id = ?, claimed_at = ?
            WHERE id = ? AND status = 'ready'
        `).run(workerId, now, jobId);
        if (updated.changes === 0)
            return null;
        db.prepare(`
            INSERT INTO workforce_claims (id, job_id, worker_id, acquired_at, lease_ms, expires_at)
            VALUES (?, ?, ?, ?, ?, ?)
        `).run(claimId, jobId, workerId, now, leaseMs, expiresAt);
        return db.prepare('SELECT * FROM workforce_claims WHERE id = ?').get(claimId);
    })();
    return claim ? rowToClaim(claim) : null;
}
/** Renew lease. Returns updated claim or null if worker no longer holds it. */
export function renewClaim(db, jobId, workerId, leaseMs = 300_000) {
    const expiresAt = Date.now() + leaseMs;
    db.prepare(`
        UPDATE workforce_claims
        SET lease_ms = ?, expires_at = ?
        WHERE job_id = ? AND worker_id = ? AND released_at IS NULL
    `).run(leaseMs, expiresAt, jobId, workerId);
    const row = db.prepare(`
        SELECT * FROM workforce_claims
        WHERE job_id = ? AND worker_id = ? AND released_at IS NULL
        ORDER BY acquired_at DESC LIMIT 1
    `).get(jobId, workerId);
    return row ? rowToClaim(row) : null;
}
/** Release claim (job moves to done/failed/blocked by caller). */
export function releaseClaim(db, jobId, workerId) {
    db.prepare(`
        UPDATE workforce_claims
        SET released_at = ?
        WHERE job_id = ? AND worker_id = ? AND released_at IS NULL
    `).run(Date.now(), jobId, workerId);
}
/** Reclaim expired locks — return job to 'ready', release claim. */
export function reclaimExpiredLocks(db) {
    const now = Date.now();
    const expired = db.prepare(`
        SELECT c.job_id, c.worker_id FROM workforce_claims c
        WHERE c.released_at IS NULL AND c.expires_at < ?
    `).all(now);
    if (expired.length === 0)
        return 0;
    const count = db.transaction(() => {
        let n = 0;
        for (const { job_id, worker_id } of expired) {
            db.prepare(`UPDATE workforce_claims SET released_at = ? WHERE job_id = ? AND worker_id = ? AND released_at IS NULL`)
                .run(now, job_id, worker_id);
            db.prepare(`UPDATE workforce_jobs SET status = 'ready', worker_id = NULL, claimed_at = NULL WHERE id = ? AND status IN ('claimed','wip')`)
                .run(job_id);
            n++;
        }
        return n;
    })();
    return count;
}
