{"version":3,"file":"selectors-BaS6dObz.mjs","names":[],"sources":["../src/extensions/workspaces/sessions/(frontend)/overlay/_lib/selectors.ts"],"sourcesContent":["import type { Task } from '../../../../../../models/task';\nimport { canonicalizeTasks } from '../../../../../../services/invariants';\n\nexport interface TaskCounts {\n  total: number;\n  completed: number;\n  inProgress: number;\n  pending: number;\n  failed: number;\n  blocked: number;\n}\n\nexport interface TaskGroups {\n  inProgress: Task[];\n  blocked: Task[];\n  pending: Task[];\n  completed: Task[];\n  failed: Task[];\n}\n\nexport interface TaskProjection {\n  visible: Task[];\n  counts: TaskCounts;\n  groups: TaskGroups;\n  showIds: boolean;\n  hasActiveWork: boolean;\n}\n\nexport function visibleTasks(tasks: readonly Task[]): Task[] {\n  return canonicalizeTasks(tasks).filter((task) => task.status !== 'deleted');\n}\n\nfunction indexStatuses(tasks: readonly Task[]): Map<number, Task['status']> {\n  return new Map(tasks.map((task) => [task.id, task.status]));\n}\n\nfunction hasUnresolvedBlocker(task: Task, statuses: ReadonlyMap<number, Task['status']>): boolean {\n  return (\n    task.blockedBy?.some((id) => {\n      const status = statuses.get(id);\n      return status !== undefined && status !== 'completed' && status !== 'deleted';\n    }) ?? false\n  );\n}\n\n/**\n * Derive all overlay-facing state in O(tasks + dependency edges).\n *\n * The id index replaces one full task-list scan per dependency, while the\n * ordered pass keeps group ordering identical to the source document.\n */\nexport function deriveTaskProjection(tasks: readonly Task[]): TaskProjection {\n  const canonical = canonicalizeTasks(tasks);\n  const statuses = indexStatuses(canonical);\n  const visible: Task[] = [];\n  const counts: TaskCounts = { total: 0, completed: 0, inProgress: 0, pending: 0, failed: 0, blocked: 0 };\n  const groups: TaskGroups = { inProgress: [], blocked: [], pending: [], completed: [], failed: [] };\n  let showIds = false;\n\n  for (const task of canonical) {\n    if (task.status === 'deleted') continue;\n\n    visible.push(task);\n    counts.total += 1;\n    showIds ||= Boolean(task.blockedBy?.length);\n\n    switch (task.status) {\n      case 'in_progress':\n        counts.inProgress += 1;\n        groups.inProgress.push(task);\n        break;\n      case 'pending': {\n        counts.pending += 1;\n        if (hasUnresolvedBlocker(task, statuses)) {\n          counts.blocked += 1;\n          groups.blocked.push(task);\n        } else {\n          groups.pending.push(task);\n        }\n        break;\n      }\n      case 'failed':\n        counts.failed += 1;\n        groups.failed.push(task);\n        break;\n      case 'completed':\n        counts.completed += 1;\n        groups.completed.push(task);\n        break;\n    }\n  }\n\n  return { visible, counts, groups, showIds, hasActiveWork: counts.inProgress > 0 };\n}\n\nexport function countTasks(tasks: readonly Task[]): TaskCounts {\n  return deriveTaskProjection(tasks).counts;\n}\n\n/**\n * Group tasks for display.\n *\n * Blocked tasks split out of `pending` because a reader scanning the overlay\n * needs to know at a glance what is actually actionable right now.\n */\nexport function groupTasks(tasks: readonly Task[]): TaskGroups {\n  return deriveTaskProjection(tasks).groups;\n}\n\n/** Ids are only worth the visual noise once dependencies are in play. */\nexport function shouldShowIds(tasks: readonly Task[]): boolean {\n  return tasks.some((task) => task.status !== 'deleted' && Boolean(task.blockedBy?.length));\n}\n\nexport function hasActiveWork(tasks: readonly Task[]): boolean {\n  return tasks.some((task) => task.status === 'in_progress');\n}\n\nexport interface OverlayLayout {\n  visible: Task[];\n  hiddenCompleted: number;\n  truncatedTail: number;\n}\n\n/**\n * Choose which rows fit the overlay budget.\n *\n * Active work wins limited space: in-progress first, then failed, blocked, and\n * pending. Selected rows are always returned in source order, however, so a\n * status update does not reshuffle an otherwise unchanged overlay.\n */\nexport function selectOverlayLayoutFromProjection(projection: TaskProjection, budget: number): OverlayLayout {\n  const { groups, visible: sourceOrder } = projection;\n  const active = [...groups.inProgress, ...groups.failed, ...groups.blocked, ...groups.pending];\n  if (budget <= 0) {\n    return {\n      visible: [],\n      hiddenCompleted: groups.completed.length,\n      truncatedTail: active.length,\n    };\n  }\n  if (sourceOrder.length <= budget) {\n    return { visible: sourceOrder, hiddenCompleted: 0, truncatedTail: 0 };\n  }\n\n  if (active.length <= budget) {\n    const remaining = budget - active.length;\n    const shownCompleted = groups.completed.slice(0, remaining);\n    const selected = new Set([...active, ...shownCompleted]);\n    return {\n      visible: sourceOrder.filter((task) => selected.has(task)),\n      hiddenCompleted: groups.completed.length - shownCompleted.length,\n      truncatedTail: 0,\n    };\n  }\n\n  const selected = new Set(active.slice(0, budget));\n  return {\n    visible: sourceOrder.filter((task) => selected.has(task)),\n    hiddenCompleted: groups.completed.length,\n    truncatedTail: active.length - budget,\n  };\n}\n\nexport function selectOverlayLayout(tasks: readonly Task[], budget: number): OverlayLayout {\n  return selectOverlayLayoutFromProjection(deriveTaskProjection(tasks), budget);\n}\n"],"mappings":"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"}