package engine import ( "fmt" ) // DisplayFormat defines how tasks should be displayed type DisplayFormat string const ( DisplayFormatTable DisplayFormat = "table" DisplayFormatMinimal DisplayFormat = "minimal" ) // Report defines a named task report with filters and display options type Report struct { Name string Description string BaseFilter *Filter // Base filter that cannot be overridden DisplayFormat DisplayFormat // How to display results SortFunc func([]*Task) []*Task LimitFunc func([]*Task) []*Task } // AllReports returns all predefined reports func AllReports() map[string]*Report { return map[string]*Report{ "active": ActiveReport(), "all": AllReport(), "completed": CompletedReport(), "list": ListReport(), "minimal": MinimalReport(), "newest": NewestReport(), "next": NextReport(), "oldest": OldestReport(), "overdue": OverdueReport(), "ready": ReadyReport(), "recurring": RecurringReport(), "template": TemplateReport(), "waiting": WaitingReport(), } } // GetReport retrieves a report by name func GetReport(name string) (*Report, error) { reports := AllReports() report, exists := reports[name] if !exists { return nil, fmt.Errorf("unknown report: %s", name) } return report, nil } // ActiveReport shows started tasks func ActiveReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" filter.Attributes["_started"] = "true" // Special marker for tasks with start time return &Report{ Name: "active", Description: "Started tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: sortByUrgency, } } // AllReport shows all tasks (no status filter) func AllReport() *Report { filter := NewFilter() filter.Attributes["_all"] = "true" // Special marker to include templates return &Report{ Name: "all", Description: "All tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, } } // CompletedReport shows completed tasks func CompletedReport() *Report { filter := NewFilter() filter.Attributes["status"] = "completed" return &Report{ Name: "completed", Description: "Completed tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, } } // ListReport shows pending tasks (default report) func ListReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" return &Report{ Name: "list", Description: "Pending tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: sortByUrgency, } } // MinimalReport shows pending tasks in minimal format func MinimalReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" return &Report{ Name: "minimal", Description: "Pending tasks (minimal format)", BaseFilter: filter, DisplayFormat: DisplayFormatMinimal, SortFunc: sortByUrgency, } } // NewestReport shows most recent pending tasks func NewestReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" return &Report{ Name: "newest", Description: "Most recent pending tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: func(tasks []*Task) []*Task { // Sort by created descending sorted := make([]*Task, len(tasks)) copy(sorted, tasks) for i := 0; i < len(sorted)-1; i++ { for j := i + 1; j < len(sorted); j++ { if sorted[i].Created.Before(sorted[j].Created) { sorted[i], sorted[j] = sorted[j], sorted[i] } } } return sorted }, LimitFunc: func(tasks []*Task) []*Task { if len(tasks) > 10 { return tasks[:10] } return tasks }, } } // NextReport shows most urgent tasks ready to work on func NextReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" filter.Attributes["_ready"] = "true" return &Report{ Name: "next", Description: "Most urgent tasks ready to work on", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: func(tasks []*Task) []*Task { // Sort by urgency descending cfg, _ := GetConfig() coeffs := BuildUrgencyCoefficients(cfg) sorted := make([]*Task, len(tasks)) copy(sorted, tasks) for i := 0; i < len(sorted)-1; i++ { for j := i + 1; j < len(sorted); j++ { urgI := sorted[i].CalculateUrgency(coeffs) urgJ := sorted[j].CalculateUrgency(coeffs) if urgI < urgJ { sorted[i], sorted[j] = sorted[j], sorted[i] } } } return sorted }, LimitFunc: func(tasks []*Task) []*Task { cfg, _ := GetConfig() limit := cfg.NextLimit if limit <= 0 { limit = 5 } if len(tasks) > limit { return tasks[:limit] } return tasks }, } } // OldestReport shows oldest pending tasks func OldestReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" return &Report{ Name: "oldest", Description: "Oldest pending tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: func(tasks []*Task) []*Task { // Sort by created ascending (already default, but explicit) sorted := make([]*Task, len(tasks)) copy(sorted, tasks) for i := 0; i < len(sorted)-1; i++ { for j := i + 1; j < len(sorted); j++ { if sorted[i].Created.After(sorted[j].Created) { sorted[i], sorted[j] = sorted[j], sorted[i] } } } return sorted }, } } // OverdueReport shows overdue tasks func OverdueReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" filter.Attributes["_overdue"] = "true" // Special marker for overdue tasks return &Report{ Name: "overdue", Description: "Overdue tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: sortByUrgency, } } // ReadyReport shows tasks ready to work on func ReadyReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" filter.Attributes["_ready"] = "true" // Special marker for ready tasks return &Report{ Name: "ready", Description: "Tasks ready to work on", BaseFilter: filter, DisplayFormat: DisplayFormatTable, SortFunc: sortByUrgency, } } // RecurringReport shows pending recurring instances func RecurringReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" filter.Attributes["_recurring_instance"] = "true" // Special marker for recurring instances return &Report{ Name: "recurring", Description: "Pending recurring task instances", BaseFilter: filter, DisplayFormat: DisplayFormatTable, } } // TemplateReport shows recurring template tasks func TemplateReport() *Report { filter := NewFilter() filter.Attributes["status"] = "template" // Special status for templates return &Report{ Name: "template", Description: "Recurring template tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, } } // WaitingReport shows waiting/hidden tasks func WaitingReport() *Report { filter := NewFilter() filter.Attributes["status"] = "pending" filter.Attributes["_waiting"] = "true" // Special marker for waiting tasks return &Report{ Name: "waiting", Description: "Hidden/waiting tasks", BaseFilter: filter, DisplayFormat: DisplayFormatTable, } } // Execute runs the report with optional additional filters func (r *Report) Execute(additionalFilters []string) ([]*Task, error) { // Start with base filter filter := r.BaseFilter // Merge additional filters if provided if len(additionalFilters) > 0 { userFilter, err := ParseFilter(additionalFilters) if err != nil { return nil, fmt.Errorf("failed to parse additional filters: %w", err) } // Merge filters (user filters add to base, but cannot override status) filter = mergeFilters(r.BaseFilter, userFilter) } // Get tasks tasks, err := GetTasks(filter) if err != nil { return nil, err } // Apply post-filter for special markers tasks = r.applyPostFilters(tasks) // Apply sorting if defined if r.SortFunc != nil { tasks = r.SortFunc(tasks) } // Apply limit if defined if r.LimitFunc != nil { tasks = r.LimitFunc(tasks) } return tasks, nil } // applyPostFilters applies special filters that can't be done in SQL func (r *Report) applyPostFilters(tasks []*Task) []*Task { now := timeNow() filtered := []*Task{} for _, task := range tasks { include := true // Check for _started marker if r.BaseFilter.Attributes["_started"] == "true" { if task.Start == nil { include = false } } // Check for _overdue marker if r.BaseFilter.Attributes["_overdue"] == "true" { if task.Due == nil || !task.Due.Before(now) { include = false } } // Check for _ready marker if r.BaseFilter.Attributes["_ready"] == "true" { // Task is ready if scheduled and wait are either null or in the past if task.Scheduled != nil && task.Scheduled.After(now) { include = false } if task.Wait != nil && task.Wait.After(now) { include = false } } // Check for _waiting marker if r.BaseFilter.Attributes["_waiting"] == "true" { if task.Wait == nil || !task.Wait.After(now) { include = false } } // Check for _recurring_instance marker if r.BaseFilter.Attributes["_recurring_instance"] == "true" { if task.ParentUUID == nil { include = false } } if include { filtered = append(filtered, task) } } return filtered } // mergeFilters combines base filter with user filter func mergeFilters(base, user *Filter) *Filter { merged := NewFilter() // Copy base attributes (these take precedence) for k, v := range base.Attributes { merged.Attributes[k] = v } // Add user attributes (but don't override status or special markers) for k, v := range user.Attributes { if k != "status" && k[0] != '_' { merged.Attributes[k] = v } } // Merge tags merged.IncludeTags = append(merged.IncludeTags, base.IncludeTags...) merged.IncludeTags = append(merged.IncludeTags, user.IncludeTags...) merged.ExcludeTags = append(merged.ExcludeTags, base.ExcludeTags...) merged.ExcludeTags = append(merged.ExcludeTags, user.ExcludeTags...) // Merge IDs and UUIDs merged.IDs = append(merged.IDs, base.IDs...) merged.IDs = append(merged.IDs, user.IDs...) merged.UUIDs = append(merged.UUIDs, base.UUIDs...) merged.UUIDs = append(merged.UUIDs, user.UUIDs...) return merged } // sortByUrgency is a helper function to sort tasks by urgency (descending). // It also populates the Urgency field on each task so the score is available in responses. func sortByUrgency(tasks []*Task) []*Task { cfg, _ := GetConfig() coeffs := BuildUrgencyCoefficients(cfg) sorted := make([]*Task, len(tasks)) copy(sorted, tasks) // Calculate and store urgency on each task for _, t := range sorted { t.Urgency = t.CalculateUrgency(coeffs) } for i := 0; i < len(sorted)-1; i++ { for j := i + 1; j < len(sorted); j++ { if sorted[i].Urgency < sorted[j].Urgency { sorted[i], sorted[j] = sorted[j], sorted[i] } } } return sorted }