function createBatchFindWork({ capacity, claim, queue, timeBudget = () => ({ capacity_minutes: null, planned_minutes: 0 }), persistEstimate = () => {}, onProgress = () => {}, }) { let request = null; function result(status, selected, available, queued = [], failed = []) { return { status, selected, available, queued, failed }; } function key(item) { return String(item.repository || '') + '#' + String(item.number || ''); } function run(items, estimates = {}) { if (request) return request; const selected = Array.isArray(items) ? items.slice() : []; const available = Math.max(0, Number(capacity()) || 0); if (selected.length > available) { const outcome = result('full', selected.length, available); onProgress(outcome); return Promise.resolve(outcome); } const budget = timeBudget() || {}; if (Number.isInteger(budget.capacity_minutes) && budget.capacity_minutes > 0) { const remaining = Math.max(0, budget.capacity_minutes - (Number(budget.planned_minutes) || 0)); const invalid = selected.map(key).filter(id => !Number.isInteger(estimates[id]) || estimates[id] <= 0 ); const requested = selected.reduce((sum, item) => sum + (Number.isInteger(estimates[key(item)]) && estimates[key(item)] > 0 ? estimates[key(item)] : 0), 0); if (invalid.length) { const outcome = { ...result('estimates-required', selected.length, available), remaining_minutes: remaining, requested_minutes: requested, invalid }; onProgress(outcome); return Promise.resolve(outcome); } if (requested > remaining) { const outcome = { ...result('over-budget', selected.length, available), remaining_minutes: remaining, requested_minutes: requested, over_minutes: requested - remaining }; onProgress(outcome); return Promise.resolve(outcome); } } request = (async () => { const queued = []; const failed = []; for (let index = 0; index < selected.length; index += 1) { const item = selected[index]; try { const confirmed = await claim(item); const queueResult = await queue(confirmed); if (queueResult === 'queued' || queueResult === 'exists') { queued.push(key(item)); if (Number.isInteger(estimates[key(item)]) && estimates[key(item)] > 0) { persistEstimate(confirmed, estimates[key(item)]); } } else { failed.push({ key: key(item), reason: 'assigned but Today sync is unavailable', assigned: true, }); } } catch (error) { failed.push({ key: key(item), reason: error?.message || 'assignment failed' }); } onProgress({ status: 'running', processed: index + 1, selected: selected.length }); } const outcome = result('complete', selected.length, available, queued, failed); onProgress({ ...outcome, processed: selected.length }); return outcome; })().finally(() => { request = null; }); return request; } return { run }; } if (typeof module !== 'undefined' && module.exports) module.exports = createBatchFindWork;