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