1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package task
  6
  7import (
  8	"encoding/hex"
  9	"errors"
 10	"strings"
 11	"time"
 12
 13	"github.com/zeebo/blake3"
 14)
 15
 16// ErrNotFound indicates that a task with the given ID is absent.
 17var ErrNotFound = errors.New("task: not found")
 18
 19// ErrExists indicates that a task already exists when attempting to create it.
 20var ErrExists = errors.New("task: already exists")
 21
 22// ErrEmptyTitle indicates that a title was missing when required.
 23var ErrEmptyTitle = errors.New("task: title is required")
 24
 25// ErrInvalidStatus is returned when a status string is not recognised.
 26var ErrInvalidStatus = errors.New("task: invalid status")
 27
 28// Status captures the lifecycle of a task.
 29type Status string
 30
 31const (
 32	StatusPending    Status = "pending"
 33	StatusInProgress Status = "in_progress"
 34	StatusCompleted  Status = "completed"
 35	StatusFailed     Status = "failed"
 36	StatusCancelled  Status = "cancelled"
 37)
 38
 39// AllStatuses returns the known task statuses.
 40func AllStatuses() []Status {
 41	return []Status{
 42		StatusPending,
 43		StatusInProgress,
 44		StatusCompleted,
 45		StatusFailed,
 46		StatusCancelled,
 47	}
 48}
 49
 50// Valid reports whether s matches a known status.
 51func (s Status) Valid() bool {
 52	switch s {
 53	case StatusPending,
 54		StatusInProgress,
 55		StatusCompleted,
 56		StatusFailed,
 57		StatusCancelled:
 58		return true
 59	default:
 60		return false
 61	}
 62}
 63
 64func (s Status) String() string {
 65	return string(s)
 66}
 67
 68// ParseStatus converts a string to a Status value.
 69func ParseStatus(v string) (Status, error) {
 70	status := Status(v)
 71	if !status.Valid() {
 72		return "", ErrInvalidStatus
 73	}
 74	return status, nil
 75}
 76
 77// Task represents an individual unit of work tracked within a session.
 78type Task struct {
 79	ID          string    `json:"id"`
 80	Title       string    `json:"title"`
 81	Description string    `json:"description"`
 82	Status      Status    `json:"status"`
 83	CreatedAt   time.Time `json:"created_at"`
 84	UpdatedAt   time.Time `json:"updated_at"`
 85	CreatedSeq  uint64    `json:"created_seq"`
 86}
 87
 88// GenerateID deterministically produces a task identifier for the given
 89// session, title, and description.
 90func GenerateID(sessionID, title, description string) string {
 91	normalized := normalizeForHash(title) + "|" + normalizeForHash(description) + "|" + sessionID
 92
 93	sum := blake3.Sum256([]byte(normalized))
 94	hexed := hex.EncodeToString(sum[:])
 95	return hexed[:6]
 96}
 97
 98func normalizeForHash(s string) string {
 99	trimmed := strings.TrimSpace(s)
100	if trimmed == "" {
101		return ""
102	}
103	return strings.Join(strings.Fields(trimmed), " ")
104}