import type { ReviewResult, TaskAttempt, WorkerResult } from "./types.ts"; export type HistorySection = | { kind: "worker"; attempt: TaskAttempt; result?: WorkerResult } | { kind: "reviewer"; attempt: TaskAttempt; result: WorkerResult } | { kind: "review"; review: ReviewResult }; export interface ScrollbarGeometry { trackSize: number; thumbStart: number; thumbSize: number; } export type HistoryNavigationDirection = "next" | "previous"; export function historyNavigationTarget( workerOffsets: readonly number[], scrollOffset: number, currentIndex: number | undefined, direction: HistoryNavigationDirection, ): number | undefined { if (workerOffsets.length === 0) return undefined; if (currentIndex !== undefined) { const target = currentIndex + (direction === "next" ? 1 : -1); return target >= 0 && target < workerOffsets.length ? target : undefined; } if (direction === "next") { const target = workerOffsets.findIndex((offset) => offset > scrollOffset); return target >= 0 ? target : undefined; } for (let index = workerOffsets.length - 1; index >= 0; index--) { if (workerOffsets[index] < scrollOffset) return index; } return undefined; } export function historySections(attempts: readonly TaskAttempt[]): HistorySection[] { const sections: HistorySection[] = []; for (const attempt of attempts) { sections.push({ kind: "worker", attempt, result: attempt.worker }); if (attempt.reviewer) sections.push({ kind: "reviewer", attempt, result: attempt.reviewer }); if (attempt.review) sections.push({ kind: "review", review: attempt.review }); } return sections; } export function fitContentLines(content: readonly string[], maxLines: number, preservedTailLines = 1): string[] { if (content.length <= maxLines) return [...content]; if (maxLines <= 0) return []; const footer = content.at(-1) ?? ""; const availableForContent = Math.max(0, maxLines - 1); const tailLines = Math.min(Math.max(0, preservedTailLines), availableForContent); const headLines = availableForContent - tailLines; const tailStart = Math.max(0, content.length - 1 - tailLines); return [ ...content.slice(0, headLines), ...content.slice(tailStart, -1), footer, ]; } export function scrollbarGeometry(contentSize: number, viewportSize: number, scrollOffset: number): ScrollbarGeometry | undefined { const content = Math.max(0, Math.floor(contentSize)); const viewport = Math.max(0, Math.floor(viewportSize)); if (viewport === 0 || content <= viewport) return undefined; const maxScroll = content - viewport; const scroll = Math.max(0, Math.min(maxScroll, Math.floor(scrollOffset))); const thumbSize = Math.max(1, Math.min(viewport, Math.round((viewport * viewport) / content))); const maxThumbStart = viewport - thumbSize; const thumbStart = Math.round((scroll / maxScroll) * maxThumbStart); return { trackSize: viewport, thumbStart, thumbSize }; }