import type { CandidateStatus, GenerationState, ProjectHeadline, RunCounts, RunState } from './types.js'; /** A candidate is "done" only once it has a downloaded video; otherwise it is still in flight. */ export function candidateState(status: CandidateStatus, hasVideo: boolean): RunState { if (status === 'failed' || status === 'cancelled') return 'failed'; if (status === 'completed') return hasVideo ? 'done' : 'rendering'; return 'rendering'; // 'pending' } /** Tally scene states against the storyboard's total scene count (queued = scenes not yet submitted). */ export function rollupCounts(scenes: GenerationState[], total: number): RunCounts { const done = scenes.filter((s) => s.state === 'done').length; const rendering = scenes.filter((s) => s.state === 'rendering').length; const failed = scenes.filter((s) => s.state === 'failed').length; const submitted = scenes.length; const queued = Math.max(0, total - submitted); return { total: Math.max(total, submitted), done, rendering, failed, queued }; } export function deriveHeadline(input: { counts: RunCounts; blockers: string[]; reviewState?: 'missing' | 'current' | 'stale'; hasFinal: boolean; }): ProjectHeadline { if (input.blockers.length > 0) return 'blocked'; if (input.counts.rendering > 0) return 'rendering'; if (input.hasFinal) return 'delivered'; const reviewPending = input.reviewState === 'missing' || input.reviewState === 'stale'; const doneUnreviewed = input.counts.total > 0 && input.counts.done === input.counts.total && input.reviewState !== 'current'; if (reviewPending || doneUnreviewed) return 'awaiting-review'; return 'idle'; }