/**
 * DAG dependency resolver — jobs only become 'ready' when all deps are 'done'.
 *
 * Called after any job status change that could unblock dependents.
 */
/**
 * Returns true if adding `newJobId` with `deps` would create a cycle.
 * Uses iterative DFS from each dep, looking for newJobId.
 */
export function wouldCreateCycle(db, newJobId, deps) {
    if (deps.length === 0)
        return false;
    const visited = new Set();
    const stack = [...deps];
    while (stack.length > 0) {
        const cur = stack.pop();
        if (cur === newJobId)
            return true;
        if (visited.has(cur))
            continue;
        visited.add(cur);
        const row = db.prepare('SELECT depends_on FROM workforce_jobs WHERE id = ?').get(cur);
        if (row) {
            try {
                const nested = JSON.parse(row.depends_on);
                for (const n of nested)
                    stack.push(n);
            }
            catch { /* ignore parse error */ }
        }
    }
    return false;
}
/** Resolve a job's depends_on JSON. */
function getDeps(row) {
    try {
        const parsed = JSON.parse(row.depends_on);
        return Array.isArray(parsed) ? parsed : [];
    }
    catch {
        return [];
    }
}
/** Promote backlog jobs whose all deps are done. Returns count promoted. */
export function promoteReady(db) {
    const backlog = db.prepare(`SELECT id, status, depends_on FROM workforce_jobs WHERE status = 'backlog'`).all();
    if (backlog.length === 0)
        return 0;
    let promoted = 0;
    for (const job of backlog) {
        const deps = getDeps(job);
        if (deps.length === 0) {
            // No deps — promote immediately
            db.prepare(`UPDATE workforce_jobs SET status = 'ready' WHERE id = ? AND status = 'backlog'`).run(job.id);
            promoted++;
            continue;
        }
        // Check all deps are done
        const placeholders = deps.map(() => '?').join(',');
        const doneCount = db.prepare(`SELECT COUNT(*) AS n FROM workforce_jobs WHERE id IN (${placeholders}) AND status = 'done'`).get(...deps).n;
        if (doneCount === deps.length) {
            db.prepare(`UPDATE workforce_jobs SET status = 'ready' WHERE id = ? AND status = 'backlog'`).run(job.id);
            promoted++;
        }
    }
    return promoted;
}
/** Mark jobs whose deps have a failed job as 'blocked'. */
export function blockOnFailedDeps(db) {
    const backlog = db.prepare(`SELECT id, depends_on FROM workforce_jobs WHERE status IN ('backlog','ready')`).all();
    let blocked = 0;
    for (const job of backlog) {
        const deps = getDeps(job);
        if (deps.length === 0)
            continue;
        const placeholders = deps.map(() => '?').join(',');
        const failedCount = db.prepare(`SELECT COUNT(*) AS n FROM workforce_jobs WHERE id IN (${placeholders}) AND status = 'failed'`).get(...deps).n;
        if (failedCount > 0) {
            db.prepare(`UPDATE workforce_jobs SET status = 'blocked' WHERE id = ? AND status IN ('backlog','ready')`).run(job.id);
            blocked++;
        }
    }
    return blocked;
}
/** Run full DAG reconciliation: promote ready + block on failures. */
export function reconcileDag(db) {
    const promoted = promoteReady(db);
    const blocked = blockOnFailedDeps(db);
    return { promoted, blocked };
}
