import type { GroupSummary, RepoSummary, SessionSummary, WorktreeSummary } from '@arboretum/shared'; import type { FilterDef, SortDef } from './useListControls'; // ---- état canonique d'une session (réutilise la logique de SessionStateBadge) ---- type CanonState = 'waiting' | 'busy' | 'idle' | null; const canonState = (s: SessionSummary): CanonState => (s.activity ?? s.registryStatus ?? null); /** Rang d'actionnabilité : waiting(0) < busy(1) < idle(2) < live(3) < resumable(4) < exited(5). */ export function sessionStateRank(s: SessionSummary): number { if (s.live) { const st = canonState(s); return st === 'waiting' ? 0 : st === 'busy' ? 1 : st === 'idle' ? 2 : 3; } return s.resumable ? 4 : 5; } /** Une session correspond-elle à une valeur d'état de filtre ? */ function sessionMatchesState(s: SessionSummary, value: string): boolean { switch (value) { case 'live': return s.live; case 'waiting': case 'busy': case 'idle': return s.live && canonState(s) === value; case 'exited': return !s.live && !s.resumable; case 'resumable': return !s.live && s.resumable; default: return false; } } // ---- sessions ---- export function sessionSorts(): SortDef[] { return [ { key: 'state', labelKey: 'sort.session.state', compare: (a, b) => sessionStateRank(a) - sessionStateRank(b), defaultDir: 'asc' }, { key: 'recent', labelKey: 'sort.session.recent', compare: (a, b) => a.createdAt.localeCompare(b.createdAt), defaultDir: 'desc' }, { key: 'clients', labelKey: 'sort.session.clients', compare: (a, b) => a.clients - b.clients, defaultDir: 'desc' }, { key: 'cwd', labelKey: 'sort.session.cwd', compare: (a, b) => a.cwd.localeCompare(b.cwd), defaultDir: 'asc' }, { key: 'command', labelKey: 'sort.session.command', compare: (a, b) => a.command.localeCompare(b.command), defaultDir: 'asc' }, ]; } export function sessionFilters(repos: readonly RepoSummary[]): FilterDef[] { const STATES = ['live', 'waiting', 'busy', 'idle', 'exited', 'resumable']; // corrélation session→repo par préfixe de chemin (cwd sous repo.path). const repoPaths = repos.map((r) => ({ id: r.id, path: r.path })); return [ { key: 'state', labelKey: 'filter.state.label', options: STATES.map((v) => ({ value: v, labelKey: `filter.state.${v}` })), predicate: (s, active) => [...active].some((v) => sessionMatchesState(s, v)), }, { key: 'command', labelKey: 'filter.command.label', options: [ { value: 'claude', label: 'claude' }, { value: 'bash', label: 'bash' }, ], predicate: (s, active) => active.has(s.command), }, { key: 'source', labelKey: 'filter.source.label', options: [ { value: 'managed', labelKey: 'filter.source.managed' }, { value: 'discovered', labelKey: 'filter.source.discovered' }, ], predicate: (s, active) => active.has(s.source), }, { key: 'repo', labelKey: 'filter.repo.label', options: repos.map((r) => ({ value: r.id, label: r.label })), predicate: (s, active) => repoPaths.some((r) => active.has(r.id) && (s.cwd === r.path || s.cwd.startsWith(r.path + '/'))), }, ]; } // ---- worktrees ---- function worktreeActivityRank(w: WorktreeSummary): number { if (w.sessions.length === 0) return 99; return Math.min(...w.sessions.map(sessionStateRank)); } export function worktreeSorts(): SortDef[] { return [ { key: 'main', labelKey: 'sort.worktree.main', compare: (a, b) => Number(b.isMain) - Number(a.isMain), defaultDir: 'asc' }, { key: 'branch', labelKey: 'sort.worktree.branch', compare: (a, b) => (a.branch ?? '').localeCompare(b.branch ?? ''), defaultDir: 'asc' }, { key: 'dirty', labelKey: 'sort.worktree.dirty', compare: (a, b) => a.git.dirtyCount - b.git.dirtyCount, defaultDir: 'desc' }, { key: 'ahead', labelKey: 'sort.worktree.ahead', compare: (a, b) => a.git.ahead - b.git.ahead, defaultDir: 'desc' }, { key: 'behind', labelKey: 'sort.worktree.behind', compare: (a, b) => a.git.behind - b.git.behind, defaultDir: 'desc' }, { key: 'activity', labelKey: 'sort.worktree.activity', compare: (a, b) => worktreeActivityRank(a) - worktreeActivityRank(b), defaultDir: 'asc' }, ]; } export function worktreeFilters(): FilterDef[] { return [ { key: 'dirtiness', labelKey: 'filter.worktree.dirtiness', options: [ { value: 'dirty', labelKey: 'filter.worktree.dirty' }, { value: 'clean', labelKey: 'filter.worktree.clean' }, ], predicate: (w, active) => (active.has('dirty') && w.git.dirtyCount > 0) || (active.has('clean') && w.git.dirtyCount === 0), }, { key: 'session', labelKey: 'filter.worktree.session', options: [ { value: 'with', labelKey: 'filter.worktree.with' }, { value: 'without', labelKey: 'filter.worktree.without' }, ], predicate: (w, active) => (active.has('with') && w.sessions.length > 0) || (active.has('without') && w.sessions.length === 0), }, { key: 'flags', labelKey: 'filter.worktree.flags', options: [ { value: 'locked', labelKey: 'filter.worktree.locked' }, { value: 'prunable', labelKey: 'filter.worktree.prunable' }, ], predicate: (w, active) => (active.has('locked') && w.locked) || (active.has('prunable') && w.prunable), }, ]; } // ---- repos ---- export interface RepoSortCtx { worktreeCount: (repoId: string) => number; activeCount: (repoId: string) => number; } export function repoSorts(ctx: RepoSortCtx): SortDef[] { return [ { key: 'label', labelKey: 'sort.repo.label', compare: (a, b) => a.label.localeCompare(b.label), defaultDir: 'asc' }, { key: 'created', labelKey: 'sort.repo.created', compare: (a, b) => a.createdAt.localeCompare(b.createdAt), defaultDir: 'desc' }, { key: 'worktrees', labelKey: 'sort.repo.worktrees', compare: (a, b) => ctx.worktreeCount(a.id) - ctx.worktreeCount(b.id), defaultDir: 'desc' }, { key: 'activity', labelKey: 'sort.repo.activity', compare: (a, b) => ctx.activeCount(a.id) - ctx.activeCount(b.id), defaultDir: 'desc' }, ]; } // ---- groupes ---- export interface GroupSortCtx { activeCount: (groupId: string) => number; } export function groupSorts(ctx: GroupSortCtx): SortDef[] { return [ { key: 'label', labelKey: 'sort.group.label', compare: (a, b) => a.label.localeCompare(b.label), defaultDir: 'asc' }, { key: 'created', labelKey: 'sort.group.created', compare: (a, b) => a.createdAt.localeCompare(b.createdAt), defaultDir: 'desc' }, { key: 'repos', labelKey: 'sort.group.repos', compare: (a, b) => a.repoIds.length - b.repoIds.length, defaultDir: 'desc' }, { key: 'activeSessions', labelKey: 'sort.group.activeSessions', compare: (a, b) => ctx.activeCount(a.id) - ctx.activeCount(b.id), defaultDir: 'desc' }, ]; }