Skip to main content

kasl/libs/
task.rs

1//! Task management and manipulation functionality for productivity tracking.
2//!
3//! Provides comprehensive task management capabilities including task creation,
4//! modification, filtering, and formatting for organizing work items.
5//!
6//! ## Features
7//!
8//! - **Task Structure**: Identification, description, progress tracking, categorization
9//! - **Filtering System**: Date-based, completion status, ID-based, tag-based filtering
10//! - **Formatting**: Console table rendering, export formatting, template support
11//! - **Integration**: Jira issues, GitLab commits, manual entry, template instantiation
12//! - **Database Integration**: CRUD operations, transaction safety, relationship management
13//!
14//! ## Usage
15//!
16//! ```rust
17//! use kasl::libs::task::Task;
18//!
19//! let task = Task::new(
20//!     "Implement user authentication",
21//!     "Add OAuth2 integration with Google and GitHub",
22//!     Some(25)
23//! );
24//! ```
25
26use crate::db::tags::Tag;
27use chrono::NaiveDate;
28
29/// Represents a single task or work item in the productivity tracking system.
30///
31/// The Task struct encapsulates all information about a work item including
32/// its identification, description, progress status, and associated metadata.
33/// Tasks serve as the fundamental unit of work organization within kasl.
34///
35/// ## Field Descriptions
36///
37/// ### Identification Fields
38/// - `id`: Database primary key for persistence and relationships
39/// - `task_id`: Reference ID for linking to external systems or parent tasks
40/// - `timestamp`: Creation/modification time for audit trails
41///
42/// ### Content Fields
43/// - `name`: Brief, descriptive title for the task
44/// - `comment`: Detailed description, notes, or additional context
45/// - `completeness`: Progress percentage (0-100) indicating work completion
46///
47/// ### Configuration Fields
48/// - `excluded_from_search`: Flag to hide tasks from general searches
49/// - `tags`: Collection of categorization labels for organization
50///
51/// ## Usage Patterns
52///
53/// ### New Task Creation
54/// ```rust
55/// use kasl::libs::task::Task;
56///
57/// let task = Task::new(
58///     "Code review for PR #123",
59///     "Review authentication changes and security implications",
60///     Some(0) // Just started
61/// );
62/// ```
63///
64/// ### Task Updates
65/// ```rust
66/// use kasl::libs::task::Task;
67///
68/// let existing_task = Task::new("Existing task", "Details", Some(50));
69/// let mut task = existing_task;
70/// task.completeness = Some(75); // 75% complete
71/// task.comment = "Almost finished, testing remaining".to_string();
72/// ```
73///
74/// ### External Integration
75/// ```rust
76/// use kasl::libs::task::Task;
77///
78/// // Simulated Jira issue data used to populate a task.
79/// let jira_issue_id = 42;
80/// struct JiraIssue {
81///     summary: String,
82///     description: Option<String>,
83/// }
84/// let jira_issue = JiraIssue {
85///     summary: "Fix login bug".to_string(),
86///     description: Some("Users cannot log in with SSO".to_string()),
87/// };
88///
89/// let jira_task = Task {
90///     id: None, // Will be assigned by database
91///     task_id: Some(jira_issue_id),
92///     timestamp: None,
93///     name: jira_issue.summary,
94///     comment: jira_issue.description.unwrap_or_default(),
95///     completeness: Some(100), // Imported completed issues
96///     excluded_from_search: None,
97///     tags: vec![],
98/// };
99/// # let _ = jira_task;
100/// ```
101#[derive(Debug, Clone)]
102pub struct Task {
103    /// Database primary key for task identification and relationships.
104    ///
105    /// This field contains the unique identifier assigned by the database
106    /// when the task is first saved. It's used for:
107    /// - Database queries and updates
108    /// - Foreign key relationships (tags, time tracking)
109    /// - Cross-referencing with other application data
110    ///
111    /// **Values:**
112    /// - `Some(id)`: Task exists in database with assigned ID
113    /// - `None`: New task not yet saved to database
114    pub id: Option<i32>,
115
116    /// Reference identifier for external system integration or task linking.
117    ///
118    /// This field provides a way to link tasks to external systems or
119    /// create hierarchical relationships between tasks. Common uses include:
120    /// - Jira issue numbers for imported tasks
121    /// - GitLab merge request IDs for code review tasks
122    /// - Parent task IDs for subtask relationships
123    /// - External project management system references
124    ///
125    /// **Values:**
126    /// - `Some(id)`: Task is linked to external system or parent task
127    /// - `None`: Standalone task with no external references
128    pub task_id: Option<i32>,
129
130    /// ISO 8601 timestamp string indicating task creation or last modification.
131    ///
132    /// This field provides audit trail information for task management.
133    /// The timestamp is automatically managed by the database layer and
134    /// is primarily used for:
135    /// - Sorting tasks by creation or modification time
136    /// - Audit trails and change tracking
137    /// - Time-based filtering and reporting
138    /// - Synchronization with external systems
139    ///
140    /// **Format:** "YYYY-MM-DD HH:MM:SS" in local timezone
141    /// **Example:** "2025-01-15 14:30:45"
142    pub timestamp: Option<String>,
143
144    /// Brief, descriptive title summarizing the task's purpose or objective.
145    ///
146    /// The task name should be concise yet descriptive enough to understand
147    /// the work item at a glance. It appears in task lists, reports, and
148    /// notifications throughout the application.
149    ///
150    /// **Guidelines:**
151    /// - Keep under 100 characters for display compatibility
152    /// - Use action-oriented language ("Implement", "Review", "Fix")
153    /// - Include key context ("Fix login bug in mobile app")
154    /// - Avoid technical jargon when possible
155    ///
156    /// **Examples:**
157    /// - "Implement OAuth2 authentication"
158    /// - "Review security audit findings"
159    /// - "Update API documentation for v2.0"
160    pub name: String,
161
162    /// Detailed description, notes, or additional context for the task.
163    ///
164    /// The comment field provides space for detailed information that doesn't
165    /// fit in the task name. This might include:
166    /// - Technical requirements and specifications
167    /// - Links to related resources or documentation
168    /// - Progress notes and status updates
169    /// - Dependencies and prerequisites
170    /// - Testing criteria and acceptance conditions
171    ///
172    /// **Content Guidelines:**
173    /// - Use clear, structured formatting when helpful
174    /// - Include links to relevant resources
175    /// - Update with progress notes and findings
176    /// - Keep information current and relevant
177    pub comment: String,
178
179    /// Completion percentage indicating task progress from 0% to 100%.
180    ///
181    /// This field tracks the task's progress through its lifecycle,
182    /// providing quantitative measurement of work completion. The percentage
183    /// is used for:
184    /// - Progress reporting and analytics
185    /// - Filtering incomplete vs. complete tasks
186    /// - Productivity calculations and trends
187    /// - Project status tracking and reporting
188    ///
189    /// **Values:**
190    /// - `Some(0)`: Task not started
191    /// - `Some(1-99)`: Task in progress
192    /// - `Some(100)`: Task completed
193    /// - `None`: Progress not tracked or unknown
194    ///
195    /// **Default:** 100% for tasks imported from external systems
196    pub completeness: Option<i32>,
197
198    /// Flag indicating whether task should be hidden from general searches.
199    ///
200    /// This field allows tasks to be excluded from default search results
201    /// while remaining accessible through direct queries. Useful for:
202    /// - Administrative or system-generated tasks
203    /// - Deprecated or obsolete tasks that shouldn't appear in normal workflow
204    /// - Sensitive tasks that require explicit access
205    /// - Archived tasks that should remain searchable but not prominent
206    ///
207    /// **Values:**
208    /// - `Some(true)`: Task excluded from general searches
209    /// - `Some(false)` or `None`: Task included in normal search results
210    pub excluded_from_search: Option<bool>,
211
212    /// Collection of categorization tags associated with this task.
213    ///
214    /// Tags provide a flexible labeling system for task organization and
215    /// filtering. They enable:
216    /// - Project-based organization ("frontend", "backend", "mobile")
217    /// - Priority classification ("urgent", "low-priority", "nice-to-have")
218    /// - Status indicators ("blocked", "waiting-review", "approved")
219    /// - Skill-based categorization ("javascript", "database", "ui-design")
220    ///
221    /// **Management:**
222    /// - Tags are created automatically when first used
223    /// - Multiple tags can be associated with a single task
224    /// - Tag associations are maintained in separate database table
225    /// - Tags can be deleted if no longer associated with any tasks
226    pub tags: Vec<Tag>,
227}
228
229impl Task {
230    /// Creates a new task with the specified name, comment, and completion status.
231    ///
232    /// This constructor initializes a new task with the provided information
233    /// and sets appropriate defaults for other fields. The task is not
234    /// automatically saved to the database - use the database layer for persistence.
235    ///
236    /// ## Default Values
237    ///
238    /// New tasks are initialized with:
239    /// - `id`: None (assigned when saved to database)
240    /// - `task_id`: None (no external reference)
241    /// - `timestamp`: None (managed by database)
242    /// - `excluded_from_search`: None (included in searches)
243    /// - `tags`: Empty vector (no initial categorization)
244    ///
245    /// ## Parameter Guidelines
246    ///
247    /// ### Task Name
248    /// - Should be concise but descriptive
249    /// - Use action-oriented language
250    /// - Include key context for clarity
251    ///
252    /// ### Comment
253    /// - Provide detailed context and requirements
254    /// - Include links to related resources
255    /// - Can be updated as work progresses
256    ///
257    /// ### Completeness
258    /// - Use `Some(0)` for new tasks that haven't started
259    /// - Use `Some(100)` for already completed imported tasks
260    /// - Use `None` if progress tracking isn't needed
261    ///
262    /// # Arguments
263    ///
264    /// * `name` - Brief, descriptive task title
265    /// * `comment` - Detailed description or notes
266    /// * `completeness` - Optional completion percentage (0-100)
267    ///
268    /// # Returns
269    ///
270    /// A new Task instance ready for use or database persistence.
271    ///
272    /// # Examples
273    ///
274    /// ```rust
275    /// use kasl::libs::task::Task;
276    ///
277    /// // Create a new task that's just starting
278    /// let new_task = Task::new(
279    ///     "Implement user registration",
280    ///     "Add email verification and password validation",
281    ///     Some(0)
282    /// );
283    ///
284    /// // Create a completed task (e.g., imported from external system)
285    /// let completed_task = Task::new(
286    ///     "Fix login redirect bug",
287    ///     "Resolved issue with OAuth callback URL handling",
288    ///     Some(100)
289    /// );
290    ///
291    /// // Create a task without progress tracking
292    /// let planning_task = Task::new(
293    ///     "Research authentication libraries",
294    ///     "Evaluate OAuth2 libraries for Node.js backend",
295    ///     None
296    /// );
297    /// ```
298    pub fn new(name: &str, comment: &str, completeness: Option<i32>) -> Self {
299        Task {
300            id: None,
301            task_id: None,
302            timestamp: None,
303            name: collapse_whitespace(name),
304            comment: collapse_whitespace(comment),
305            completeness,
306            excluded_from_search: None,
307            tags: Vec::new(),
308        }
309    }
310
311    /// Updates task fields from another task while preserving identity fields.
312    ///
313    /// This method provides a convenient way to update task content while
314    /// maintaining database identity and relationships. It's particularly
315    /// useful for implementing task editing workflows where the user modifies
316    /// task details but the task's core identity remains unchanged.
317    ///
318    /// ## Preserved Fields
319    /// The following fields are **not** updated to maintain task identity:
320    /// - `id`: Database primary key remains unchanged
321    /// - `task_id`: External reference remains unchanged
322    /// - `timestamp`: Will be updated by database on save
323    /// - `excluded_from_search`: Search visibility remains unchanged
324    /// - `tags`: Tag associations require separate management
325    ///
326    /// ## Updated Fields
327    /// The following fields are copied from the source task:
328    /// - `name`: Task title is updated
329    /// - `comment`: Description and notes are updated
330    /// - `completeness`: Progress percentage is updated
331    ///
332    /// ## Use Cases
333    ///
334    /// ### Task Editing Workflow
335    /// ```rust,no_run
336    /// # fn f() -> anyhow::Result<()> {
337    /// use kasl::libs::task::Task;
338    /// use kasl::db::tasks::Tasks;
339    ///
340    /// let mut tasks_db = Tasks::new()?;
341    ///
342    /// // Load existing task from database
343    /// let mut existing_task = tasks_db.get_by_id(42)?.expect("task exists");
344    ///
345    /// // Create updated version with user modifications
346    /// let updated_task = Task::new(
347    ///     "Updated task name",
348    ///     "Updated description with new requirements",
349    ///     Some(75)
350    /// );
351    ///
352    /// // Apply updates while preserving identity
353    /// existing_task.update_from(&updated_task);
354    ///
355    /// // Save to database
356    /// tasks_db.update(&existing_task)?;
357    /// # Ok(())
358    /// # }
359    /// ```
360    ///
361    /// ### Bulk Task Updates
362    /// ```rust,no_run
363    /// # fn f() -> anyhow::Result<()> {
364    /// use kasl::libs::task::Task;
365    /// use kasl::db::tasks::Tasks;
366    ///
367    /// let mut tasks_db = Tasks::new()?;
368    /// let tasks_to_update: Vec<Task> = vec![];
369    /// let get_update_template = |_task: &Task| -> Option<Task> { None };
370    ///
371    /// for mut task in tasks_to_update {
372    ///     if let Some(template) = get_update_template(&task) {
373    ///         task.update_from(&template);
374    ///         tasks_db.update(&task)?;
375    ///     }
376    /// }
377    /// # Ok(())
378    /// # }
379    /// ```
380    ///
381    /// # Arguments
382    ///
383    /// * `other` - Source task containing the updated field values
384    ///
385    /// # Examples
386    ///
387    /// ```rust
388    /// use kasl::libs::task::Task;
389    ///
390    /// let mut original_task = Task::new(
391    ///     "Original task",
392    ///     "Original description",
393    ///     Some(25)
394    /// );
395    /// // Simulate database assignment
396    /// original_task.id = Some(42);
397    ///
398    /// let updated_task = Task::new(
399    ///     "Updated task name",
400    ///     "Updated description with more details",
401    ///     Some(75)
402    /// );
403    ///
404    /// // Apply updates while preserving ID
405    /// original_task.update_from(&updated_task);
406    ///
407    /// assert_eq!(original_task.id, Some(42)); // ID preserved
408    /// assert_eq!(original_task.name, "Updated task name"); // Content updated
409    /// assert_eq!(original_task.completeness, Some(75)); // Progress updated
410    /// ```
411    pub fn update_from(&mut self, other: &Task) {
412        // Update content fields while preserving identity
413        self.name = other.name.clone();
414        self.comment = other.comment.clone();
415        self.completeness = other.completeness;
416    }
417}
418
419/// Enumeration of available task filtering criteria for database queries.
420///
421/// This enum provides a type-safe way to specify different filtering options
422/// when querying tasks from the database. It supports simple filters as well
423/// as complex multi-criteria filtering for advanced task management workflows.
424///
425/// ## Filter Categories
426///
427/// ### Scope Filters
428/// - `All`: No filtering, returns all tasks
429/// - `Date`: Time-based filtering for specific dates
430///
431/// ### Status Filters  
432/// - `Incomplete`: Tasks with progress less than 100%
433///
434/// ### Identity Filters
435/// - `ByIds`: Specific tasks by their database IDs
436///
437/// ### Categorization Filters
438/// - `ByTag`: Tasks associated with a single tag
439/// - `ByTags`: Tasks associated with multiple tags (intersection)
440///
441/// ## Query Optimization
442///
443/// Different filter types have different performance characteristics:
444/// - `All`: Fastest, no WHERE clause needed
445/// - `ByIds`: Very fast with proper indexing
446/// - `Date`: Fast with timestamp indexing
447/// - `Incomplete`: Moderate speed, depends on data distribution
448/// - `ByTag`/`ByTags`: Moderate speed, requires JOIN operations
449///
450/// ## Examples Usage
451///
452/// ```rust
453/// use kasl::libs::task::TaskFilter;
454/// use chrono::Local;
455///
456/// // Get all tasks
457/// let all_tasks_filter = TaskFilter::All;
458///
459/// // Get today's tasks
460/// let today = Local::now().date_naive();
461/// let today_filter = TaskFilter::Date(today);
462///
463/// // Get incomplete tasks
464/// let incomplete_filter = TaskFilter::Incomplete;
465///
466/// // Get specific tasks
467/// let specific_filter = TaskFilter::ByIds(vec![1, 2, 3]);
468///
469/// // Get tagged tasks
470/// let tagged_filter = TaskFilter::ByTag("urgent".to_string());
471/// let multi_tagged_filter = TaskFilter::ByTags(vec![
472///     "frontend".to_string(),
473///     "javascript".to_string()
474/// ]);
475/// ```
476#[derive(Debug, Clone)]
477pub enum TaskFilter {
478    /// Returns all tasks without any filtering restrictions.
479    ///
480    /// This filter retrieves the complete task collection from the database.
481    /// It's the most efficient filter type since it doesn't require any
482    /// WHERE clauses or complex query conditions.
483    ///
484    /// **Use Cases:**
485    /// - Complete task listings for administrative purposes
486    /// - Full data exports and backups
487    /// - Global task analysis and reporting
488    /// - Initial load for client-side filtering
489    ///
490    /// **Performance:** Excellent - simple SELECT query
491    All,
492
493    /// Returns tasks created or modified on the specified date.
494    ///
495    /// This filter uses the task timestamp to find tasks associated with
496    /// a particular date. The filtering is typically done at the day level,
497    /// including all tasks from 00:00:00 to 23:59:59 on the specified date.
498    ///
499    /// **Use Cases:**
500    /// - Daily task reviews and reports
501    /// - Time-based productivity analysis
502    /// - Date-specific task exports
503    /// - Calendar integration and scheduling
504    ///
505    /// **Performance:** Good - benefits from timestamp indexing
506    ///
507    /// **Example:**
508    /// ```rust
509    /// use kasl::libs::task::TaskFilter;
510    /// use chrono::{Local, NaiveDate};
511    ///
512    /// let today = Local::now().date_naive();
513    /// let filter = TaskFilter::Date(today);
514    /// ```
515    Date(NaiveDate),
516
517    /// Returns tasks with completion percentage less than 100%.
518    ///
519    /// This filter identifies tasks that are still in progress or haven't
520    /// been started. It's useful for focusing on active work items and
521    /// identifying tasks that need attention.
522    ///
523    /// **Criteria:**
524    /// - Tasks with `completeness` < 100
525    /// - Tasks with `completeness` = None (treated as incomplete)
526    ///
527    /// **Use Cases:**
528    /// - Active work item management
529    /// - Progress tracking and follow-up
530    /// - Workload planning and estimation
531    /// - Focus mode filtering for current work
532    ///
533    /// **Performance:** Moderate - depends on completion data distribution
534    ///
535    /// **Example:**
536    /// ```rust
537    /// use kasl::libs::task::TaskFilter;
538    ///
539    /// let incomplete_filter = TaskFilter::Incomplete;
540    /// // Returns tasks with completeness: None, Some(0), Some(50), etc.
541    /// // Excludes tasks with completeness: Some(100)
542    /// ```
543    Incomplete,
544
545    /// Returns specific tasks identified by their database IDs.
546    ///
547    /// This filter provides precise task retrieval when the exact task
548    /// identifiers are known. It's the most efficient way to retrieve
549    /// a known set of tasks and is commonly used for bulk operations.
550    ///
551    /// **Use Cases:**
552    /// - Bulk task operations (update, delete, export)
553    /// - User-selected task collections
554    /// - Related task loading (parent/child relationships)
555    /// - API responses for specific task requests
556    ///
557    /// **Performance:** Excellent - uses primary key indexing
558    ///
559    /// **Example:**
560    /// ```rust
561    /// use kasl::libs::task::TaskFilter;
562    ///
563    /// let specific_tasks = TaskFilter::ByIds(vec![1, 5, 10, 15]);
564    /// // Returns only tasks with IDs 1, 5, 10, and 15
565    /// ```
566    ByIds(Vec<i32>),
567
568    /// Returns tasks associated with the specified tag.
569    ///
570    /// This filter finds all tasks that have been tagged with a particular
571    /// label. It's useful for category-based task management and organizing
572    /// work by project, priority, or skill area.
573    ///
574    /// **Query Method:**
575    /// - Performs JOIN with task_tags relationship table
576    /// - Matches tag name case-sensitively
577    /// - Returns tasks with at least one matching tag
578    ///
579    /// **Use Cases:**
580    /// - Project-specific task listings
581    /// - Priority-based filtering ("urgent", "low-priority")
582    /// - Skill-based work organization ("javascript", "database")
583    /// - Status-based filtering ("blocked", "waiting-review")
584    ///
585    /// **Performance:** Moderate - requires JOIN operation
586    ///
587    /// **Example:**
588    /// ```rust
589    /// use kasl::libs::task::TaskFilter;
590    ///
591    /// let urgent_filter = TaskFilter::ByTag("urgent".to_string());
592    /// // Returns all tasks tagged with "urgent"
593    /// ```
594    ByTag(String),
595
596    /// Returns tasks associated with all of the specified tags.
597    ///
598    /// This filter finds tasks that have been tagged with every tag in the
599    /// provided list (intersection, not union). It's useful for finding tasks
600    /// that meet multiple criteria simultaneously.
601    ///
602    /// **Query Method:**
603    /// - Performs multiple JOINs with task_tags table
604    /// - Requires ALL tags to be present on the task
605    /// - More restrictive than single tag filtering
606    ///
607    /// **Use Cases:**
608    /// - Complex filtering ("frontend" AND "urgent" AND "javascript")
609    /// - Multi-criteria task discovery
610    /// - Advanced search functionality
611    /// - Refined project management workflows
612    ///
613    /// **Performance:** Moderate to Slow - multiple JOINs required
614    ///
615    /// **Example:**
616    /// ```rust
617    /// use kasl::libs::task::TaskFilter;
618    ///
619    /// let complex_filter = TaskFilter::ByTags(vec![
620    ///     "frontend".to_string(),
621    ///     "urgent".to_string(),
622    ///     "javascript".to_string()
623    /// ]);
624    /// // Returns tasks that have ALL three tags
625    /// ```
626    ByTags(Vec<String>),
627}
628
629/// Trait providing formatting and manipulation operations for task collections.
630///
631/// This trait extends Vec<Task> with specialized methods for formatting tasks
632/// for display and dividing task collections for parallel processing or
633/// load balancing. It provides a clean interface for common task collection
634/// operations.
635///
636/// ## Design Philosophy
637///
638/// The trait follows Rust's iterator philosophy by providing chainable,
639/// efficient operations on task collections. Methods are designed to be:
640/// - **Composable**: Can be chained together for complex operations
641/// - **Efficient**: Minimize allocations and copying where possible
642/// - **Flexible**: Support various output formats and processing patterns
643/// - **Predictable**: Consistent behavior across different input sizes
644///
645/// ## Method Categories
646///
647/// ### Formatting Methods
648/// - `format()`: Convert tasks to human-readable string representation
649///
650/// ### Partitioning Methods
651/// - `divide()`: Split tasks into balanced groups for parallel processing
652///
653/// ## Performance Characteristics
654///
655/// - **Memory Usage**: Methods minimize unnecessary allocations
656/// - **Time Complexity**: Most operations are O(n) where n is task count
657/// - **Parallelization**: Partitioning methods support concurrent processing
658///
659/// ## Examples
660///
661/// ```rust
662/// use kasl::libs::task::{Task, FormatTasks};
663///
664/// let mut tasks = vec![
665///     Task::new("Task 1", "Description 1", Some(50)),
666///     Task::new("Task 2", "Description 2", Some(75)),
667///     Task::new("Task 3", "Description 3", Some(100)),
668/// ];
669///
670/// // Format for display
671/// let formatted = tasks.format();
672/// println!("{}", formatted);
673///
674/// // Divide for parallel processing
675/// let groups = tasks.divide(2);
676/// for (i, group) in groups.iter().enumerate() {
677///     println!("Group {}: {} tasks", i, group.len());
678/// }
679/// ```
680pub trait FormatTasks {
681    /// Formats the task collection into a human-readable string representation.
682    ///
683    /// This method converts a collection of tasks into a structured string
684    /// format suitable for console output, logging, or simple text-based
685    /// displays. The format includes key task information in a consistent,
686    /// scannable layout.
687    ///
688    /// ## Output Format
689    ///
690    /// The method produces a multi-line string with each task formatted as:
691    /// ```text
692    /// {name} ({completeness}%)
693    /// ```
694    ///
695    /// ## Field Handling
696    ///
697    /// - **ID**: Shows database ID or "New" for unsaved tasks
698    /// - **Name**: Task title, truncated if excessively long
699    /// - **Completeness**: Percentage or "Unknown" if not set
700    /// - **Comment**: Description, truncated if excessively long
701    ///
702    /// ## Use Cases
703    ///
704    /// - **Debug Output**: Quick task collection visualization
705    /// - **Log Messages**: Structured logging of task operations
706    /// - **Simple Reports**: Basic text-based task summaries
707    /// - **CLI Output**: Command-line interface task displays
708    ///
709    /// # Returns
710    ///
711    /// A formatted string representation of all tasks in the collection.
712    ///
713    /// # Examples
714    ///
715    /// ```rust
716    /// use kasl::libs::task::{Task, FormatTasks};
717    ///
718    /// let mut tasks = vec![
719    ///     Task::new("Review PR", "Code review for auth changes", Some(25)),
720    ///     Task::new("Write tests", "Unit tests for API endpoints", Some(75)),
721    /// ];
722    ///
723    /// let output = tasks.format();
724    /// // Output:
725    /// // Review PR (25%)
726    /// // Write tests (75%)
727    /// ```
728    fn format(&mut self) -> String;
729
730    /// Divides the task collection into the specified number of balanced groups.
731    ///
732    /// This method partitions tasks into multiple groups of approximately equal
733    /// size, which is useful for parallel processing, load balancing, or
734    /// organizing large task collections into manageable chunks.
735    ///
736    /// ## Partitioning Algorithm
737    ///
738    /// The method uses a round-robin distribution strategy:
739    /// 1. **Base Size Calculation**: Determines minimum tasks per group
740    /// 2. **Remainder Distribution**: Distributes extra tasks evenly
741    /// 3. **Sequential Assignment**: Assigns tasks to groups in order
742    /// 4. **Balance Optimization**: Ensures groups differ by at most 1 task
743    ///
744    /// ## Edge Case Handling
745    ///
746    /// ### Empty Collection
747    /// - Returns vector of empty groups
748    /// - Number of groups equals requested parts
749    ///
750    /// ### Single Task
751    /// - Duplicates the task across all groups
752    /// - Useful for broadcast scenarios
753    ///
754    /// ### Fewer Tasks Than Parts
755    /// - Creates groups with 0-1 tasks each
756    /// - Distributes tasks round-robin style
757    ///
758    /// ### More Tasks Than Parts
759    /// - Creates balanced groups with similar sizes
760    /// - Groups differ by at most 1 task
761    ///
762    /// ## Use Cases
763    ///
764    /// ### Parallel Processing
765    /// ```text
766    /// let task_groups = tasks.divide(cpu_count);
767    /// for group in task_groups {
768    ///     spawn_worker_thread(group);
769    /// }
770    /// ```
771    ///
772    /// ### Load Balancing
773    /// ```text
774    /// let worker_assignments = tasks.divide(worker_count);
775    /// for (worker_id, assignment) in worker_assignments.iter().enumerate() {
776    ///     assign_tasks_to_worker(worker_id, assignment);
777    /// }
778    /// ```
779    ///
780    /// ### UI Organization
781    /// ```text
782    /// let columns = tasks.divide(3); // Three-column layout
783    /// for (col_index, column_tasks) in columns.iter().enumerate() {
784    ///     render_task_column(col_index, column_tasks);
785    /// }
786    /// ```
787    ///
788    /// # Arguments
789    ///
790    /// * `parts` - Number of groups to create (must be > 0)
791    ///
792    /// # Returns
793    ///
794    /// A vector containing the requested number of task groups. Each group
795    /// is a Vec<Task> containing a portion of the original task collection.
796    ///
797    /// # Examples
798    ///
799    /// ```rust
800    /// use kasl::libs::task::{Task, FormatTasks};
801    ///
802    /// let mut tasks = vec![
803    ///     Task::new("Task 1", "", None),
804    ///     Task::new("Task 2", "", None),
805    ///     Task::new("Task 3", "", None),
806    ///     Task::new("Task 4", "", None),
807    ///     Task::new("Task 5", "", None),
808    /// ];
809    ///
810    /// // Divide into 3 groups
811    /// let groups = tasks.divide(3);
812    /// // groups[0]: [Task 1, Task 4] (2 tasks)
813    /// // groups[1]: [Task 2, Task 5] (2 tasks)  
814    /// // groups[2]: [Task 3]         (1 task)
815    ///
816    /// // Verify balanced distribution
817    /// assert_eq!(groups.len(), 3);
818    /// assert_eq!(groups[0].len(), 2);
819    /// assert_eq!(groups[1].len(), 2);
820    /// assert_eq!(groups[2].len(), 1);
821    /// ```
822    fn divide(&mut self, parts: usize) -> Vec<Vec<Task>>;
823}
824
825/// Implementation of FormatTasks trait for Vec<Task>.
826///
827/// This implementation provides concrete formatting and partitioning logic
828/// for task collections. It handles various edge cases and provides efficient
829/// algorithms for common task manipulation scenarios.
830impl FormatTasks for Vec<Task> {
831    /// Divides the task collection into balanced groups using round-robin distribution.
832    ///
833    /// This implementation uses an optimized algorithm that ensures balanced
834    /// distribution while handling edge cases gracefully. The algorithm
835    /// minimizes memory allocations and provides predictable results.
836    ///
837    /// ## Algorithm Details
838    ///
839    /// 1. **Input Validation**: Handle zero parts and empty collections
840    /// 2. **Special Cases**: Optimize for single task and small collections
841    /// 3. **Size Calculation**: Compute base size and remainder distribution
842    /// 4. **Group Assignment**: Distribute tasks using calculated sizes
843    ///
844    /// ## Performance Characteristics
845    ///
846    /// - **Time Complexity**: O(n) where n is the number of tasks
847    /// - **Space Complexity**: O(n) for the output groups
848    /// - **Memory Efficiency**: Minimal allocations during processing
849    ///
850    /// The implementation is optimized for common use cases while maintaining
851    /// correctness for edge cases.
852    fn divide(&mut self, parts: usize) -> Vec<Vec<Task>> {
853        // Initialize result vector with requested capacity
854        let mut result: Vec<Vec<Task>> = Vec::with_capacity(parts);
855        let len = self.len();
856
857        // Handle edge case: no parts requested
858        if len == 0 || parts == 0 {
859            return result;
860        }
861
862        // Handle edge case: single task
863        if len == 1 {
864            for _ in 0..parts {
865                result.push(self.to_vec());
866            }
867            return result;
868        }
869
870        // Handle edge case: fewer tasks than parts
871        if len < parts {
872            for i in 0..parts {
873                let mut part: Vec<Task> = Vec::with_capacity(len.div_ceil(parts));
874                for j in 0..len.div_ceil(parts) {
875                    part.push(self[(i + j * len / parts) % len].clone());
876                }
877                result.push(part);
878            }
879            return result;
880        }
881
882        // General case: distribute tasks across parts
883        let mut start = 0;
884        let mut end;
885        for i in 0..parts {
886            // Calculate group size with remainder distribution
887            end = start + len / parts + if i < len % parts { 1 } else { 0 };
888            result.push(self[start..end].to_vec());
889            start = end;
890        }
891
892        result
893    }
894
895    /// Formats the task collection into a structured string representation.
896    ///
897    /// This implementation creates a multi-line string with each task formatted
898    /// consistently. It handles missing fields gracefully and provides readable
899    /// output suitable for debugging and simple displays.
900    ///
901    /// ## Format Structure
902    ///
903    /// Each task is formatted on a separate line with pipe-separated fields:
904    /// ```text
905    /// {name} ({completeness}%)
906    /// ```
907    ///
908    /// ## Field Processing
909    ///
910    /// - **Name**: Used as-is from task struct
911    /// - **Completeness**: Shows percentage or "Unknown" for None values
912    ///
913    /// The method handles all field types gracefully and provides consistent
914    /// output regardless of which optional fields are present.
915    fn format(&mut self) -> String {
916        self.iter()
917            .map(|task| {
918                // Format completeness field
919                let completeness_display = task.completeness.map_or("Unknown".to_string(), |comp| format!("{}%", comp));
920
921                // Create formatted line for this task
922                format!("{} ({})", task.name, completeness_display)
923            })
924            .collect::<Vec<_>>()
925            .join("\n")
926    }
927}
928
929/// Replaces newlines and other whitespace runs with single spaces and trims.
930///
931/// Useful when pasting multi-line titles into `kasl task` prompts:
932/// ```text
933/// PROJ-42
934/// Fix login redirect
935/// ```
936/// becomes `PROJ-42 Fix login redirect`.
937pub fn collapse_whitespace(s: &str) -> String {
938    s.split_whitespace().collect::<Vec<_>>().join(" ")
939}
940
941/// Normalizes a task/commit name for near-duplicate and ignore-list comparison.
942///
943/// Trims whitespace, collapses internal spaces, lowercases, and strips
944/// trailing punctuation so variants like `"New commit"`, `"New commit."`,
945/// and `" New commit"` map to the same key.
946pub fn normalize_task_name(name: &str) -> String {
947    let mut s = collapse_whitespace(name).to_lowercase();
948
949    loop {
950        let trimmed = s.trim_end_matches(['.', ',', ';', '!', '?', ':', '…']).trim_end();
951        if trimmed.len() == s.len() {
952            break;
953        }
954        s = trimmed.to_string();
955    }
956
957    s
958}
959
960/// Returns true when `name` matches an ignore pattern exactly or by prefix
961/// (after normalization). Used for task discovery filtering.
962pub fn is_ignored_name(name: &str, ignore_names: &[String]) -> bool {
963    let n = normalize_task_name(name);
964    ignore_names.iter().any(|pat| {
965        let p = normalize_task_name(pat);
966        !p.is_empty() && (n == p || n.starts_with(&p))
967    })
968}