1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package stats provides helpers for computing task statistics.
6package stats
7
8import (
9 "context"
10 "fmt"
11
12 "git.secluded.site/go-lunatask"
13)
14
15// TaskCounter provides methods to count tasks by various criteria.
16type TaskCounter struct {
17 client *lunatask.Client
18 tasks []lunatask.Task
19}
20
21// NewTaskCounter creates a counter that fetches tasks on first use.
22func NewTaskCounter(client *lunatask.Client) *TaskCounter {
23 return &TaskCounter{client: client}
24}
25
26// UncompletedInArea counts uncompleted tasks in the specified area.
27func (tc *TaskCounter) UncompletedInArea(ctx context.Context, areaID string) (int, error) {
28 if err := tc.ensureTasks(ctx); err != nil {
29 return 0, err
30 }
31
32 count := 0
33
34 for _, task := range tc.tasks {
35 if !isUncompleted(task) {
36 continue
37 }
38
39 if task.AreaID != nil && *task.AreaID == areaID {
40 count++
41 }
42 }
43
44 return count, nil
45}
46
47// UncompletedInGoal counts uncompleted tasks in the specified goal.
48func (tc *TaskCounter) UncompletedInGoal(ctx context.Context, goalID string) (int, error) {
49 if err := tc.ensureTasks(ctx); err != nil {
50 return 0, err
51 }
52
53 count := 0
54
55 for _, task := range tc.tasks {
56 if !isUncompleted(task) {
57 continue
58 }
59
60 if task.GoalID != nil && *task.GoalID == goalID {
61 count++
62 }
63 }
64
65 return count, nil
66}
67
68// ensureTasks fetches tasks if not already loaded.
69func (tc *TaskCounter) ensureTasks(ctx context.Context) error {
70 if tc.tasks != nil {
71 return nil
72 }
73
74 tasks, err := tc.client.ListTasks(ctx, nil)
75 if err != nil {
76 return fmt.Errorf("listing tasks: %w", err)
77 }
78
79 tc.tasks = tasks
80
81 return nil
82}
83
84func isUncompleted(task lunatask.Task) bool {
85 if task.Status == nil {
86 return true
87 }
88
89 return *task.Status != lunatask.StatusCompleted
90}