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)
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}