stats.go

 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	"git.secluded.site/lune/internal/ui"
14)
15
16// TaskCounter provides methods to count tasks by various criteria.
17type TaskCounter struct {
18	client *lunatask.Client
19	tasks  []lunatask.Task
20}
21
22// NewTaskCounter creates a counter that fetches tasks on first use.
23func NewTaskCounter(client *lunatask.Client) *TaskCounter {
24	return &TaskCounter{client: client}
25}
26
27// UncompletedInArea counts uncompleted tasks in the specified area.
28func (tc *TaskCounter) UncompletedInArea(ctx context.Context, areaID string) (int, error) {
29	if err := tc.ensureTasks(ctx); err != nil {
30		return 0, err
31	}
32
33	count := 0
34
35	for _, task := range tc.tasks {
36		if !isUncompleted(task) {
37			continue
38		}
39
40		if task.AreaID != nil && *task.AreaID == areaID {
41			count++
42		}
43	}
44
45	return count, nil
46}
47
48// UncompletedInGoal counts uncompleted tasks in the specified goal.
49func (tc *TaskCounter) UncompletedInGoal(ctx context.Context, goalID string) (int, error) {
50	if err := tc.ensureTasks(ctx); err != nil {
51		return 0, err
52	}
53
54	count := 0
55
56	for _, task := range tc.tasks {
57		if !isUncompleted(task) {
58			continue
59		}
60
61		if task.GoalID != nil && *task.GoalID == goalID {
62			count++
63		}
64	}
65
66	return count, nil
67}
68
69// ensureTasks fetches tasks if not already loaded.
70func (tc *TaskCounter) ensureTasks(ctx context.Context) error {
71	if tc.tasks != nil {
72		return nil
73	}
74
75	tasks, err := ui.Spin("Fetching tasks…", func() ([]lunatask.Task, error) {
76		return tc.client.ListTasks(ctx, nil)
77	})
78	if err != nil {
79		return fmt.Errorf("listing tasks: %w", err)
80	}
81
82	tc.tasks = tasks
83
84	return nil
85}
86
87func isUncompleted(task lunatask.Task) bool {
88	if task.Status == nil {
89		return true
90	}
91
92	return *task.Status != lunatask.StatusCompleted
93}