tempo_cli/cli/
commands.rs

1use super::{Cli, Commands, ProjectAction, SessionAction, TagAction, ConfigAction, GoalAction, EstimateAction, BranchAction, TemplateAction, WorkspaceAction, CalendarAction, IssueAction, ClientAction};
2use crate::utils::ipc::{IpcClient, IpcMessage, IpcResponse, get_socket_path, is_daemon_running};
3use crate::db::queries::{ProjectQueries, SessionQueries, TagQueries, SessionEditQueries};
4use crate::db::advanced_queries::{GoalQueries, GitBranchQueries, TimeEstimateQueries, InsightQueries, TemplateQueries, WorkspaceQueries};
5use crate::db::{Database, get_database_path};
6use crate::models::{Project, Tag, Goal, TimeEstimate, ProjectTemplate, Workspace};
7use crate::utils::paths::{canonicalize_path, detect_project_name, get_git_hash, is_git_repository};
8use crate::utils::config::{load_config, save_config};
9use crate::cli::reports::ReportGenerator;
10use anyhow::Result;
11use std::env;
12use std::path::PathBuf;
13use std::process::{Command, Stdio};
14use chrono::{Utc, TimeZone};
15
16use crate::ui::dashboard::Dashboard;
17use crate::ui::timer::InteractiveTimer;
18use crate::ui::history::SessionHistoryBrowser;
19use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}};
20use ratatui::{backend::CrosstermBackend, Terminal};
21use std::io;
22use tokio::runtime::Handle;
23
24pub async fn handle_command(cli: Cli) -> Result<()> {
25    match cli.command {
26        Commands::Start => {
27            start_daemon().await
28        }
29        
30        Commands::Stop => {
31            stop_daemon().await
32        }
33        
34        Commands::Restart => {
35            restart_daemon().await
36        }
37        
38        Commands::Status => {
39            status_daemon().await
40        }
41        
42        Commands::Init { name, path, description } => {
43            init_project(name, path, description).await
44        }
45        
46        Commands::List { all, tag } => {
47            list_projects(all, tag).await
48        }
49        
50        Commands::Report { project, from, to, format, group } => {
51            generate_report(project, from, to, format, group).await
52        }
53        
54        Commands::Project { action } => {
55            handle_project_action(action).await
56        }
57        
58        Commands::Session { action } => {
59            handle_session_action(action).await
60        }
61        
62        Commands::Tag { action } => {
63            handle_tag_action(action).await
64        }
65        
66        Commands::Config { action } => {
67            handle_config_action(action).await
68        }
69        
70        Commands::Dashboard => {
71            launch_dashboard().await
72        }
73        
74        Commands::Tui => {
75            launch_dashboard().await
76        }
77        
78        Commands::Timer => {
79            launch_timer().await
80        }
81
82        Commands::History => {
83            launch_history().await
84        }
85
86        Commands::Goal { action } => {
87            handle_goal_action(action).await
88        }
89
90        Commands::Insights { period, project } => {
91            show_insights(period, project).await
92        }
93
94        Commands::Summary { period, from } => {
95            show_summary(period, from).await
96        }
97
98        Commands::Compare { projects, from, to } => {
99            compare_projects(projects, from, to).await
100        }
101
102        Commands::Estimate { action } => {
103            handle_estimate_action(action).await
104        }
105
106        Commands::Branch { action } => {
107            handle_branch_action(action).await
108        }
109
110        Commands::Template { action } => {
111            handle_template_action(action).await
112        }
113
114        Commands::Workspace { action } => {
115            handle_workspace_action(action).await
116        }
117
118        Commands::Calendar { action } => {
119            handle_calendar_action(action).await
120        }
121
122        Commands::Issue { action } => {
123            handle_issue_action(action).await
124        }
125
126        Commands::Client { action } => {
127            handle_client_action(action).await
128        }
129
130        Commands::Completions { shell } => {
131            Cli::generate_completions(shell);
132            Ok(())
133        }
134    }
135}
136
137async fn handle_project_action(action: ProjectAction) -> Result<()> {
138    match action {
139        ProjectAction::Archive { project } => {
140            archive_project(project).await
141        }
142        
143        ProjectAction::Unarchive { project } => {
144            unarchive_project(project).await
145        }
146        
147        ProjectAction::UpdatePath { project, path } => {
148            update_project_path(project, path).await
149        }
150        
151        ProjectAction::AddTag { project, tag } => {
152            add_tag_to_project(project, tag).await
153        }
154        
155        ProjectAction::RemoveTag { project, tag } => {
156            remove_tag_from_project(project, tag).await
157        }
158    }
159}
160
161async fn handle_session_action(action: SessionAction) -> Result<()> {
162    match action {
163        SessionAction::Start { project, context } => {
164            start_session(project, context).await
165        }
166        
167        SessionAction::Stop => {
168            stop_session().await
169        }
170        
171        SessionAction::Pause => {
172            pause_session().await
173        }
174        
175        SessionAction::Resume => {
176            resume_session().await
177        }
178        
179        SessionAction::Current => {
180            current_session().await
181        }
182        
183        SessionAction::List { limit, project } => {
184            list_sessions(limit, project).await
185        }
186        
187        SessionAction::Edit { id, start, end, project, reason } => {
188            edit_session(id, start, end, project, reason).await
189        }
190        
191        SessionAction::Delete { id, force } => {
192            delete_session(id, force).await
193        }
194        
195        SessionAction::Merge { session_ids, project, notes } => {
196            merge_sessions(session_ids, project, notes).await
197        }
198        
199        SessionAction::Split { session_id, split_times, notes } => {
200            split_session(session_id, split_times, notes).await
201        }
202    }
203}
204
205async fn handle_tag_action(action: TagAction) -> Result<()> {
206    match action {
207        TagAction::Create { name, color, description } => {
208            create_tag(name, color, description).await
209        }
210        
211        TagAction::List => {
212            list_tags().await
213        }
214        
215        TagAction::Delete { name } => {
216            delete_tag(name).await
217        }
218    }
219}
220
221async fn handle_config_action(action: ConfigAction) -> Result<()> {
222    match action {
223        ConfigAction::Show => {
224            show_config().await
225        }
226        
227        ConfigAction::Set { key, value } => {
228            set_config(key, value).await
229        }
230        
231        ConfigAction::Get { key } => {
232            get_config(key).await
233        }
234        
235        ConfigAction::Reset => {
236            reset_config().await
237        }
238    }
239}
240
241// Daemon control functions
242async fn start_daemon() -> Result<()> {
243    if is_daemon_running() {
244        println!("Daemon is already running");
245        return Ok(());
246    }
247
248    println!("Starting tempo daemon...");
249    
250    let current_exe = std::env::current_exe()?;
251    let daemon_exe = current_exe.with_file_name("tempo-daemon");
252    
253    if !daemon_exe.exists() {
254        return Err(anyhow::anyhow!("tempo-daemon executable not found at {:?}", daemon_exe));
255    }
256
257    let mut cmd = Command::new(&daemon_exe);
258    cmd.stdout(Stdio::null())
259       .stderr(Stdio::null())
260       .stdin(Stdio::null());
261
262    #[cfg(unix)]
263    {
264        use std::os::unix::process::CommandExt;
265        cmd.process_group(0);
266    }
267
268    let child = cmd.spawn()?;
269    
270    // Wait a moment for daemon to start
271    tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
272    
273    if is_daemon_running() {
274        println!("Daemon started successfully (PID: {})", child.id());
275        Ok(())
276    } else {
277        Err(anyhow::anyhow!("Failed to start daemon"))
278    }
279}
280
281async fn stop_daemon() -> Result<()> {
282    if !is_daemon_running() {
283        println!("Daemon is not running");
284        return Ok(());
285    }
286
287    println!("Stopping tempo daemon...");
288    
289    // Try to connect and send shutdown message
290    if let Ok(socket_path) = get_socket_path() {
291        if let Ok(mut client) = IpcClient::connect(&socket_path).await {
292            match client.send_message(&IpcMessage::Shutdown).await {
293                Ok(_) => {
294                    println!("Daemon stopped successfully");
295                    return Ok(());
296                }
297                Err(e) => {
298                    eprintln!("Failed to send shutdown message: {}", e);
299                }
300            }
301        }
302    }
303
304    // Fallback: kill via PID file
305    if let Ok(Some(pid)) = crate::utils::ipc::read_pid_file() {
306        #[cfg(unix)]
307        {
308            let result = Command::new("kill")
309                .arg(pid.to_string())
310                .output();
311            
312            match result {
313                Ok(_) => println!("Daemon stopped via kill signal"),
314                Err(e) => eprintln!("Failed to kill daemon: {}", e),
315            }
316        }
317        
318        #[cfg(windows)]
319        {
320            let result = Command::new("taskkill")
321                .args(&["/PID", &pid.to_string(), "/F"])
322                .output();
323            
324            match result {
325                Ok(_) => println!("Daemon stopped via taskkill"),
326                Err(e) => eprintln!("Failed to kill daemon: {}", e),
327            }
328        }
329    }
330
331    Ok(())
332}
333
334async fn restart_daemon() -> Result<()> {
335    println!("Restarting tempo daemon...");
336    stop_daemon().await?;
337    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
338    start_daemon().await
339}
340
341async fn status_daemon() -> Result<()> {
342    if !is_daemon_running() {
343        print_daemon_not_running();
344        return Ok(());
345    }
346
347    if let Ok(socket_path) = get_socket_path() {
348        match IpcClient::connect(&socket_path).await {
349            Ok(mut client) => {
350                match client.send_message(&IpcMessage::GetStatus).await {
351                    Ok(IpcResponse::Status { daemon_running: _, active_session, uptime }) => {
352                        print_daemon_status(uptime, active_session.as_ref());
353                    }
354                    Ok(IpcResponse::Pong) => {
355                        print_daemon_status(0, None); // Minimal response
356                    }
357                    Ok(other) => {
358                        println!("Daemon is running (unexpected response: {:?})", other);
359                    }
360                    Err(e) => {
361                        println!("Daemon is running but not responding: {}", e);
362                    }
363                }
364            }
365            Err(e) => {
366                println!("Daemon appears to be running but cannot connect: {}", e);
367            }
368        }
369    } else {
370        println!("Cannot determine socket path");
371    }
372
373    Ok(())
374}
375
376// Session control functions
377async fn start_session(project: Option<String>, context: Option<String>) -> Result<()> {
378    if !is_daemon_running() {
379        println!("Daemon is not running. Start it with 'tempo start'");
380        return Ok(());
381    }
382
383    let project_path = if let Some(proj) = project {
384        PathBuf::from(proj)
385    } else {
386        env::current_dir()?
387    };
388
389    let context = context.unwrap_or_else(|| "manual".to_string());
390
391    let socket_path = get_socket_path()?;
392    let mut client = IpcClient::connect(&socket_path).await?;
393    
394    let message = IpcMessage::StartSession { 
395        project_path: Some(project_path.clone()), 
396        context 
397    };
398    
399    match client.send_message(&message).await {
400        Ok(IpcResponse::Ok) => {
401            println!("Session started for project at {:?}", project_path);
402        }
403        Ok(IpcResponse::Error(message)) => {
404            println!("Failed to start session: {}", message);
405        }
406        Ok(other) => {
407            println!("Unexpected response: {:?}", other);
408        }
409        Err(e) => {
410            println!("Failed to communicate with daemon: {}", e);
411        }
412    }
413
414    Ok(())
415}
416
417async fn stop_session() -> Result<()> {
418    if !is_daemon_running() {
419        println!("Daemon is not running");
420        return Ok(());
421    }
422
423    let socket_path = get_socket_path()?;
424    let mut client = IpcClient::connect(&socket_path).await?;
425    
426    match client.send_message(&IpcMessage::StopSession).await {
427        Ok(IpcResponse::Ok) => {
428            println!("Session stopped");
429        }
430        Ok(IpcResponse::Error(message)) => {
431            println!("Failed to stop session: {}", message);
432        }
433        Ok(other) => {
434            println!("Unexpected response: {:?}", other);
435        }
436        Err(e) => {
437            println!("Failed to communicate with daemon: {}", e);
438        }
439    }
440
441    Ok(())
442}
443
444async fn pause_session() -> Result<()> {
445    if !is_daemon_running() {
446        println!("Daemon is not running");
447        return Ok(());
448    }
449
450    let socket_path = get_socket_path()?;
451    let mut client = IpcClient::connect(&socket_path).await?;
452    
453    match client.send_message(&IpcMessage::PauseSession).await {
454        Ok(IpcResponse::Ok) => {
455            println!("Session paused");
456        }
457        Ok(IpcResponse::Error(message)) => {
458            println!("Failed to pause session: {}", message);
459        }
460        Ok(other) => {
461            println!("Unexpected response: {:?}", other);
462        }
463        Err(e) => {
464            println!("Failed to communicate with daemon: {}", e);
465        }
466    }
467
468    Ok(())
469}
470
471async fn resume_session() -> Result<()> {
472    if !is_daemon_running() {
473        println!("Daemon is not running");
474        return Ok(());
475    }
476
477    let socket_path = get_socket_path()?;
478    let mut client = IpcClient::connect(&socket_path).await?;
479    
480    match client.send_message(&IpcMessage::ResumeSession).await {
481        Ok(IpcResponse::Ok) => {
482            println!("Session resumed");
483        }
484        Ok(IpcResponse::Error(message)) => {
485            println!("Failed to resume session: {}", message);
486        }
487        Ok(other) => {
488            println!("Unexpected response: {:?}", other);
489        }
490        Err(e) => {
491            println!("Failed to communicate with daemon: {}", e);
492        }
493    }
494
495    Ok(())
496}
497
498async fn current_session() -> Result<()> {
499    if !is_daemon_running() {
500        print_daemon_not_running();
501        return Ok(());
502    }
503
504    let socket_path = get_socket_path()?;
505    let mut client = IpcClient::connect(&socket_path).await?;
506    
507    match client.send_message(&IpcMessage::GetActiveSession).await {
508        Ok(IpcResponse::SessionInfo(session)) => {
509            print_formatted_session(&session)?;
510        }
511        Ok(IpcResponse::Error(message)) => {
512            print_no_active_session(&message);
513        }
514        Ok(other) => {
515            println!("Unexpected response: {:?}", other);
516        }
517        Err(e) => {
518            println!("Failed to communicate with daemon: {}", e);
519        }
520    }
521
522    Ok(())
523}
524
525// Report generation function
526async fn generate_report(
527    project: Option<String>,
528    from: Option<String>,
529    to: Option<String>,
530    format: Option<String>,
531    group: Option<String>,
532) -> Result<()> {
533    println!("Generating time report...");
534    
535    let generator = ReportGenerator::new()?;
536    let report = generator.generate_report(project, from, to, group)?;
537    
538    match format.as_deref() {
539        Some("csv") => {
540            let output_path = PathBuf::from("tempo-report.csv");
541            generator.export_csv(&report, &output_path)?;
542            println!("Report exported to: {:?}", output_path);
543        }
544        Some("json") => {
545            let output_path = PathBuf::from("tempo-report.json");
546            generator.export_json(&report, &output_path)?;
547            println!("Report exported to: {:?}", output_path);
548        }
549        _ => {
550            // Print to console with formatted output
551            print_formatted_report(&report)?;
552        }
553    }
554    
555    Ok(())
556}
557
558// Formatted output functions
559fn print_formatted_session(session: &crate::utils::ipc::SessionInfo) -> Result<()> {
560    // Color scheme definitions
561    let context_color = match session.context.as_str() {
562        "terminal" => "\x1b[96m",    // Bright cyan
563        "ide" => "\x1b[95m",        // Bright magenta
564        "linked" => "\x1b[93m",     // Bright yellow
565        "manual" => "\x1b[94m",     // Bright blue
566        _ => "\x1b[97m",            // Bright white (default)
567    };
568    
569    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
570    println!("\x1b[36m│\x1b[0m           \x1b[1;37mCurrent Session\x1b[0m               \x1b[36m│\x1b[0m");
571    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
572    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;32m●\x1b[0m \x1b[32mActive\x1b[0m                     \x1b[36m│\x1b[0m");
573    println!("\x1b[36m│\x1b[0m Project:  \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&session.project_name, 25));
574    println!("\x1b[36m│\x1b[0m Duration: \x1b[1;32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(session.duration));
575    println!("\x1b[36m│\x1b[0m Started:  \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", session.start_time.format("%H:%M:%S").to_string());
576    println!("\x1b[36m│\x1b[0m Context:  {}{:<25}\x1b[0m \x1b[36m│\x1b[0m", context_color, truncate_string(&session.context, 25));
577    println!("\x1b[36m│\x1b[0m Path:     \x1b[2;37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&session.project_path.to_string_lossy(), 25));
578    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
579    Ok(())
580}
581
582fn print_formatted_report(report: &crate::cli::reports::TimeReport) -> Result<()> {
583    // Helper function to get context color
584    let get_context_color = |context: &str| -> &str {
585        match context {
586            "terminal" => "\x1b[96m",    // Bright cyan
587            "ide" => "\x1b[95m",        // Bright magenta
588            "linked" => "\x1b[93m",     // Bright yellow
589            "manual" => "\x1b[94m",     // Bright blue
590            _ => "\x1b[97m",            // Bright white (default)
591        }
592    };
593
594    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
595    println!("\x1b[36m│\x1b[0m            \x1b[1;37mTime Report\x1b[0m                  \x1b[36m│\x1b[0m");
596    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
597    
598    for (project_name, project_summary) in &report.projects {
599        println!("\x1b[36m│\x1b[0m \x1b[1;33m{:<20}\x1b[0m \x1b[1;32m{:>15}\x1b[0m \x1b[36m│\x1b[0m", 
600            truncate_string(project_name, 20),
601            format_duration_fancy(project_summary.total_duration)
602        );
603        
604        for (context, duration) in &project_summary.contexts {
605            let context_color = get_context_color(context);
606            println!("\x1b[36m│\x1b[0m   {}{:<15}\x1b[0m \x1b[32m{:>20}\x1b[0m \x1b[36m│\x1b[0m", 
607                context_color,
608                truncate_string(context, 15),
609                format_duration_fancy(*duration)
610            );
611        }
612        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
613    }
614    
615    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
616    println!("\x1b[36m│\x1b[0m \x1b[1;37mTotal Time:\x1b[0m \x1b[1;32m{:>26}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(report.total_duration));
617    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
618    Ok(())
619}
620
621fn format_duration_fancy(seconds: i64) -> String {
622    let hours = seconds / 3600;
623    let minutes = (seconds % 3600) / 60;
624    let secs = seconds % 60;
625
626    if hours > 0 {
627        format!("{}h {}m {}s", hours, minutes, secs)
628    } else if minutes > 0 {
629        format!("{}m {}s", minutes, secs)
630    } else {
631        format!("{}s", secs)
632    }
633}
634
635fn truncate_string(s: &str, max_len: usize) -> String {
636    if s.len() <= max_len {
637        format!("{:<width$}", s, width = max_len)
638    } else {
639        format!("{:.width$}...", s, width = max_len.saturating_sub(3))
640    }
641}
642
643// Helper functions for consistent messaging
644fn print_daemon_not_running() {
645    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
646    println!("\x1b[36m│\x1b[0m               \x1b[1;37mDaemon Status\x1b[0m               \x1b[36m│\x1b[0m");
647    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
648    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;31m●\x1b[0m \x1b[31mOffline\x1b[0m                   \x1b[36m│\x1b[0m");
649    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
650    println!("\x1b[36m│\x1b[0m \x1b[33mDaemon is not running.\x1b[0m                 \x1b[36m│\x1b[0m");
651    println!("\x1b[36m│\x1b[0m \x1b[37mStart it with:\x1b[0m \x1b[96mtempo start\x1b[0m         \x1b[36m│\x1b[0m");
652    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
653}
654
655fn print_no_active_session(message: &str) {
656    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
657    println!("\x1b[36m│\x1b[0m           \x1b[1;37mCurrent Session\x1b[0m               \x1b[36m│\x1b[0m");
658    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
659    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;33m○\x1b[0m \x1b[33mIdle\x1b[0m                      \x1b[36m│\x1b[0m");
660    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
661    println!("\x1b[36m│\x1b[0m \x1b[90m{:<37}\x1b[0m \x1b[36m│\x1b[0m", message);
662    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
663    println!("\x1b[36m│\x1b[0m \x1b[37mStart tracking:\x1b[0m                       \x1b[36m│\x1b[0m");
664    println!("\x1b[36m│\x1b[0m   \x1b[96mtempo session start\x1b[0m                \x1b[36m│\x1b[0m");
665    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
666}
667
668fn print_daemon_status(uptime: u64, active_session: Option<&crate::utils::ipc::SessionInfo>) {
669    let uptime_formatted = format_duration_fancy(uptime as i64);
670    
671    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
672    println!("\x1b[36m│\x1b[0m               \x1b[1;37mDaemon Status\x1b[0m               \x1b[36m│\x1b[0m");
673    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
674    println!("\x1b[36m│\x1b[0m Status:   \x1b[1;32m●\x1b[0m \x1b[32mOnline\x1b[0m                    \x1b[36m│\x1b[0m");
675    println!("\x1b[36m│\x1b[0m Uptime:   \x1b[37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", uptime_formatted);
676    
677    if let Some(session) = active_session {
678        let context_color = match session.context.as_str() {
679            "terminal" => "\x1b[96m", "ide" => "\x1b[95m", "linked" => "\x1b[93m", 
680            "manual" => "\x1b[94m", _ => "\x1b[97m",
681        };
682        
683        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
684        println!("\x1b[36m│\x1b[0m \x1b[1;37mActive Session:\x1b[0m                      \x1b[36m│\x1b[0m");
685        println!("\x1b[36m│\x1b[0m   Project: \x1b[1;33m{:<23}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&session.project_name, 23));
686        println!("\x1b[36m│\x1b[0m   Duration: \x1b[1;32m{:<22}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(session.duration));
687        println!("\x1b[36m│\x1b[0m   Context: {}{:<23}\x1b[0m \x1b[36m│\x1b[0m", context_color, session.context);
688    } else {
689        println!("\x1b[36m│\x1b[0m Session:  \x1b[33mNo active session\x1b[0m             \x1b[36m│\x1b[0m");
690    }
691    
692    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
693}
694
695// Project management functions
696async fn init_project(name: Option<String>, path: Option<PathBuf>, description: Option<String>) -> Result<()> {
697    let project_path = path.unwrap_or_else(|| env::current_dir().unwrap());
698    let canonical_path = canonicalize_path(&project_path)?;
699    
700    let project_name = name.unwrap_or_else(|| detect_project_name(&canonical_path));
701    
702    // Initialize database
703    let db_path = get_database_path()?;
704    let db = Database::new(&db_path)?;
705    
706    // Check if project already exists
707    if let Some(existing) = ProjectQueries::find_by_path(&db.connection, &canonical_path)? {
708        println!("\x1b[33m⚠  Project already exists:\x1b[0m {}", existing.name);
709        return Ok(());
710    }
711    
712    // Get git hash if it's a git repository
713    let git_hash = if is_git_repository(&canonical_path) {
714        get_git_hash(&canonical_path)
715    } else {
716        None
717    };
718    
719    // Create project
720    let mut project = Project::new(project_name.clone(), canonical_path.clone())
721        .with_git_hash(git_hash.clone())
722        .with_description(description.clone());
723    
724    // Save to database
725    let project_id = ProjectQueries::create(&db.connection, &project)?;
726    project.id = Some(project_id);
727    
728    // Create .tempo marker file
729    let marker_path = canonical_path.join(".tempo");
730    if !marker_path.exists() {
731        std::fs::write(&marker_path, format!("# Tempo time tracking project\nname: {}\n", project_name))?;
732    }
733    
734    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
735    println!("\x1b[36m│\x1b[0m         \x1b[1;37mProject Initialized\x1b[0m               \x1b[36m│\x1b[0m");
736    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
737    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
738    println!("\x1b[36m│\x1b[0m Path:     \x1b[37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&canonical_path.to_string_lossy(), 27));
739    if let Some(desc) = &description {
740        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
741    }
742    if git_hash.is_some() {
743        println!("\x1b[36m│\x1b[0m Type:     \x1b[32mGit Repository\x1b[0m              \x1b[36m│\x1b[0m");
744    } else {
745        println!("\x1b[36m│\x1b[0m Type:     \x1b[37mStandard Project\x1b[0m             \x1b[36m│\x1b[0m");
746    }
747    println!("\x1b[36m│\x1b[0m ID:       \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", project_id);
748    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
749    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Project created successfully\x1b[0m          \x1b[36m│\x1b[0m");
750    println!("\x1b[36m│\x1b[0m \x1b[32m✓ .tempo marker file added\x1b[0m             \x1b[36m│\x1b[0m");
751    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
752    println!("\x1b[36m│\x1b[0m \x1b[37mStart tracking:\x1b[0m                       \x1b[36m│\x1b[0m");
753    println!("\x1b[36m│\x1b[0m   \x1b[96mtempo session start\x1b[0m                 \x1b[36m│\x1b[0m");
754    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
755    
756    Ok(())
757}
758
759async fn list_projects(include_archived: bool, tag_filter: Option<String>) -> Result<()> {
760    // Initialize database
761    let db_path = get_database_path()?;
762    let db = Database::new(&db_path)?;
763    
764    // Get projects
765    let projects = ProjectQueries::list_all(&db.connection, include_archived)?;
766    
767    if projects.is_empty() {
768        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
769        println!("\x1b[36m│\x1b[0m              \x1b[1;37mNo Projects\x1b[0m                 \x1b[36m│\x1b[0m");
770        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
771        println!("\x1b[36m│\x1b[0m No projects found.                      \x1b[36m│\x1b[0m");
772        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
773        println!("\x1b[36m│\x1b[0m \x1b[37mCreate a project:\x1b[0m                      \x1b[36m│\x1b[0m");
774        println!("\x1b[36m│\x1b[0m   \x1b[96mtempo init [project-name]\x1b[0m           \x1b[36m│\x1b[0m");
775        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
776        return Ok(());
777    }
778    
779    // Filter by tag if specified
780    let filtered_projects = if let Some(_tag) = tag_filter {
781        // TODO: Implement tag filtering when project-tag associations are implemented
782        projects
783    } else {
784        projects
785    };
786    
787    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
788    println!("\x1b[36m│\x1b[0m              \x1b[1;37mProjects\x1b[0m                    \x1b[36m│\x1b[0m");
789    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
790    
791    for project in &filtered_projects {
792        let status_icon = if project.is_archived { "📦" } else { "📁" };
793        let status_color = if project.is_archived { "\x1b[90m" } else { "\x1b[37m" };
794        let git_indicator = if project.git_hash.is_some() { " (git)" } else { "" };
795        
796        println!("\x1b[36m│\x1b[0m {} {}{:<25}\x1b[0m \x1b[36m│\x1b[0m", 
797            status_icon,
798            status_color,
799            format!("{}{}", truncate_string(&project.name, 20), git_indicator)
800        );
801        
802        if let Some(description) = &project.description {
803            println!("\x1b[36m│\x1b[0m   \x1b[2;37m{:<35}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(description, 35));
804        }
805        
806        let path_display = project.path.to_string_lossy();
807        if path_display.len() > 35 {
808            let home_dir = dirs::home_dir();
809            let display_path = if let Some(home) = home_dir {
810                if let Ok(stripped) = project.path.strip_prefix(&home) {
811                    format!("~/{}", stripped.display())
812                } else {
813                    path_display.to_string()
814                }
815            } else {
816                path_display.to_string()
817            };
818            println!("\x1b[36m│\x1b[0m   \x1b[90m{:<35}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&display_path, 35));
819        } else {
820            println!("\x1b[36m│\x1b[0m   \x1b[90m{:<35}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&path_display, 35));
821        }
822        
823        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
824    }
825    
826    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
827    println!("\x1b[36m│\x1b[0m \x1b[1;37mTotal:\x1b[0m {:<30} \x1b[36m│\x1b[0m", 
828        format!("{} projects", filtered_projects.len())
829    );
830    if include_archived {
831        let active_count = filtered_projects.iter().filter(|p| !p.is_archived).count();
832        let archived_count = filtered_projects.iter().filter(|p| p.is_archived).count();
833        println!("\x1b[36m│\x1b[0m \x1b[37mActive:\x1b[0m {:<15} \x1b[90mArchived:\x1b[0m {:<8} \x1b[36m│\x1b[0m", 
834            active_count, archived_count
835        );
836    }
837    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
838    
839    Ok(())
840}
841
842// Tag management functions
843async fn create_tag(name: String, color: Option<String>, description: Option<String>) -> Result<()> {
844    // Initialize database
845    let db_path = get_database_path()?;
846    let db = Database::new(&db_path)?;
847    
848    // Create tag
849    let mut tag = Tag::new(name.clone());
850    if let Some(c) = color {
851        tag = tag.with_color(c);
852    }
853    if let Some(d) = description {
854        tag = tag.with_description(d);
855    }
856    
857    // Validate tag
858    tag.validate()?;
859    
860    // Check if tag already exists
861    let existing_tags = TagQueries::list_all(&db.connection)?;
862    if existing_tags.iter().any(|t| t.name == tag.name) {
863        println!("\x1b[33m⚠  Tag already exists:\x1b[0m {}", tag.name);
864        return Ok(());
865    }
866    
867    // Save to database
868    let tag_id = TagQueries::create(&db.connection, &tag)?;
869    
870    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
871    println!("\x1b[36m│\x1b[0m           \x1b[1;37mTag Created\x1b[0m                   \x1b[36m│\x1b[0m");
872    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
873    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&tag.name, 27));
874    if let Some(color_val) = &tag.color {
875        println!("\x1b[36m│\x1b[0m Color:    \x1b[37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(color_val, 27));
876    }
877    if let Some(desc) = &tag.description {
878        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
879    }
880    println!("\x1b[36m│\x1b[0m ID:       \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", tag_id);
881    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
882    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Tag created successfully\x1b[0m             \x1b[36m│\x1b[0m");
883    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
884    
885    Ok(())
886}
887
888async fn list_tags() -> Result<()> {
889    // Initialize database
890    let db_path = get_database_path()?;
891    let db = Database::new(&db_path)?;
892    
893    // Get tags
894    let tags = TagQueries::list_all(&db.connection)?;
895    
896    if tags.is_empty() {
897        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
898        println!("\x1b[36m│\x1b[0m               \x1b[1;37mNo Tags\x1b[0m                    \x1b[36m│\x1b[0m");
899        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
900        println!("\x1b[36m│\x1b[0m No tags found.                          \x1b[36m│\x1b[0m");
901        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
902        println!("\x1b[36m│\x1b[0m \x1b[37mCreate a tag:\x1b[0m                          \x1b[36m│\x1b[0m");
903        println!("\x1b[36m│\x1b[0m   \x1b[96mtempo tag create <name>\x1b[0m             \x1b[36m│\x1b[0m");
904        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
905        return Ok(());
906    }
907    
908    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
909    println!("\x1b[36m│\x1b[0m                \x1b[1;37mTags\x1b[0m                      \x1b[36m│\x1b[0m");
910    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
911    
912    for tag in &tags {
913        let color_indicator = if let Some(color) = &tag.color {
914            format!(" ({})", color)
915        } else {
916            "".to_string()
917        };
918        
919        println!("\x1b[36m│\x1b[0m 🏷️  \x1b[1;33m{:<30}\x1b[0m \x1b[36m│\x1b[0m", 
920            format!("{}{}", truncate_string(&tag.name, 25), color_indicator)
921        );
922        
923        if let Some(description) = &tag.description {
924            println!("\x1b[36m│\x1b[0m     \x1b[2;37m{:<33}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(description, 33));
925        }
926        
927        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
928    }
929    
930    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
931    println!("\x1b[36m│\x1b[0m \x1b[1;37mTotal:\x1b[0m {:<30} \x1b[36m│\x1b[0m", 
932        format!("{} tags", tags.len())
933    );
934    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
935    
936    Ok(())
937}
938
939async fn delete_tag(name: String) -> Result<()> {
940    // Initialize database
941    let db_path = get_database_path()?;
942    let db = Database::new(&db_path)?;
943    
944    // Check if tag exists
945    if TagQueries::find_by_name(&db.connection, &name)?.is_none() {
946        println!("\x1b[31m✗ Tag '{}' not found\x1b[0m", name);
947        return Ok(());
948    }
949    
950    // Delete the tag
951    let deleted = TagQueries::delete_by_name(&db.connection, &name)?;
952    
953    if deleted {
954        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
955        println!("\x1b[36m│\x1b[0m           \x1b[1;37mTag Deleted\x1b[0m                   \x1b[36m│\x1b[0m");
956        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
957        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
958        println!("\x1b[36m│\x1b[0m Status:   \x1b[32mDeleted\x1b[0m                   \x1b[36m│\x1b[0m");
959        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
960        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Tag deleted successfully\x1b[0m             \x1b[36m│\x1b[0m");
961        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
962    } else {
963        println!("\x1b[31m✗ Failed to delete tag '{}'\x1b[0m", name);
964    }
965    
966    Ok(())
967}
968
969// Configuration management functions
970async fn show_config() -> Result<()> {
971    let config = load_config()?;
972    
973    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
974    println!("\x1b[36m│\x1b[0m           \x1b[1;37mConfiguration\x1b[0m                  \x1b[36m│\x1b[0m");
975    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
976    println!("\x1b[36m│\x1b[0m idle_timeout_minutes:  \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.idle_timeout_minutes);
977    println!("\x1b[36m│\x1b[0m auto_pause_enabled:    \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.auto_pause_enabled);
978    println!("\x1b[36m│\x1b[0m default_context:       \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.default_context);
979    println!("\x1b[36m│\x1b[0m max_session_hours:     \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.max_session_hours);
980    println!("\x1b[36m│\x1b[0m backup_enabled:        \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.backup_enabled);
981    println!("\x1b[36m│\x1b[0m log_level:             \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", config.log_level);
982    
983    if !config.custom_settings.is_empty() {
984        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
985        println!("\x1b[36m│\x1b[0m \x1b[1;37mCustom Settings:\x1b[0m                      \x1b[36m│\x1b[0m");
986        for (key, value) in &config.custom_settings {
987            println!("\x1b[36m│\x1b[0m {:<20} \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", 
988                truncate_string(key, 20), 
989                truncate_string(value, 16)
990            );
991        }
992    }
993    
994    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
995    
996    Ok(())
997}
998
999async fn get_config(key: String) -> Result<()> {
1000    let config = load_config()?;
1001    
1002    let value = match key.as_str() {
1003        "idle_timeout_minutes" => Some(config.idle_timeout_minutes.to_string()),
1004        "auto_pause_enabled" => Some(config.auto_pause_enabled.to_string()),
1005        "default_context" => Some(config.default_context),
1006        "max_session_hours" => Some(config.max_session_hours.to_string()),
1007        "backup_enabled" => Some(config.backup_enabled.to_string()),
1008        "log_level" => Some(config.log_level),
1009        _ => config.custom_settings.get(&key).cloned(),
1010    };
1011    
1012    match value {
1013        Some(val) => {
1014            println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1015            println!("\x1b[36m│\x1b[0m          \x1b[1;37mConfiguration Value\x1b[0m             \x1b[36m│\x1b[0m");
1016            println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1017            println!("\x1b[36m│\x1b[0m {:<20} \x1b[33m{:<16}\x1b[0m \x1b[36m│\x1b[0m", 
1018                truncate_string(&key, 20), 
1019                truncate_string(&val, 16)
1020            );
1021            println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1022        }
1023        None => {
1024            println!("\x1b[31m✗ Configuration key not found:\x1b[0m {}", key);
1025        }
1026    }
1027    
1028    Ok(())
1029}
1030
1031async fn set_config(key: String, value: String) -> Result<()> {
1032    let mut config = load_config()?;
1033    
1034    let display_value = value.clone(); // Clone for display purposes
1035    
1036    match key.as_str() {
1037        "idle_timeout_minutes" => {
1038            config.idle_timeout_minutes = value.parse()?;
1039        }
1040        "auto_pause_enabled" => {
1041            config.auto_pause_enabled = value.parse()?;
1042        }
1043        "default_context" => {
1044            config.default_context = value;
1045        }
1046        "max_session_hours" => {
1047            config.max_session_hours = value.parse()?;
1048        }
1049        "backup_enabled" => {
1050            config.backup_enabled = value.parse()?;
1051        }
1052        "log_level" => {
1053            config.log_level = value;
1054        }
1055        _ => {
1056            config.set_custom(key.clone(), value);
1057        }
1058    }
1059    
1060    config.validate()?;
1061    save_config(&config)?;
1062    
1063    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1064    println!("\x1b[36m│\x1b[0m        \x1b[1;37mConfiguration Updated\x1b[0m             \x1b[36m│\x1b[0m");
1065    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1066    println!("\x1b[36m│\x1b[0m {:<20} \x1b[32m{:<16}\x1b[0m \x1b[36m│\x1b[0m", 
1067        truncate_string(&key, 20), 
1068        truncate_string(&display_value, 16)
1069    );
1070    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1071    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Configuration saved successfully\x1b[0m      \x1b[36m│\x1b[0m");
1072    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1073    
1074    Ok(())
1075}
1076
1077async fn reset_config() -> Result<()> {
1078    let default_config = crate::models::Config::default();
1079    save_config(&default_config)?;
1080    
1081    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1082    println!("\x1b[36m│\x1b[0m         \x1b[1;37mConfiguration Reset\x1b[0m              \x1b[36m│\x1b[0m");
1083    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1084    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Configuration reset to defaults\x1b[0m       \x1b[36m│\x1b[0m");
1085    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1086    println!("\x1b[36m│\x1b[0m \x1b[37mView current config:\x1b[0m                   \x1b[36m│\x1b[0m");
1087    println!("\x1b[36m│\x1b[0m   \x1b[96mtempo config show\x1b[0m                   \x1b[36m│\x1b[0m");
1088    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1089    
1090    Ok(())
1091}
1092
1093// Session management functions
1094async fn list_sessions(limit: Option<usize>, project_filter: Option<String>) -> Result<()> {
1095    // Initialize database
1096    let db_path = get_database_path()?;
1097    let db = Database::new(&db_path)?;
1098    
1099    let session_limit = limit.unwrap_or(10);
1100    
1101    // Handle project filtering
1102    let project_id = if let Some(project_name) = &project_filter {
1103        match ProjectQueries::find_by_name(&db.connection, project_name)? {
1104            Some(project) => Some(project.id.unwrap()),
1105            None => {
1106                println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1107                return Ok(());
1108            }
1109        }
1110    } else {
1111        None
1112    };
1113    
1114    let sessions = SessionQueries::list_with_filter(&db.connection, project_id, None, None, Some(session_limit))?;
1115    
1116    if sessions.is_empty() {
1117        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1118        println!("\x1b[36m│\x1b[0m             \x1b[1;37mNo Sessions\x1b[0m                  \x1b[36m│\x1b[0m");
1119        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1120        println!("\x1b[36m│\x1b[0m No sessions found.                      \x1b[36m│\x1b[0m");
1121        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1122        println!("\x1b[36m│\x1b[0m \x1b[37mStart a session:\x1b[0m                      \x1b[36m│\x1b[0m");
1123        println!("\x1b[36m│\x1b[0m   \x1b[96mtempo session start\x1b[0m                 \x1b[36m│\x1b[0m");
1124        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1125        return Ok(());
1126    }
1127    
1128    // Filter by project if specified
1129    let filtered_sessions = if let Some(_project) = project_filter {
1130        // TODO: Implement project filtering when we have project relationships
1131        sessions
1132    } else {
1133        sessions
1134    };
1135    
1136    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1137    println!("\x1b[36m│\x1b[0m         \x1b[1;37mRecent Sessions\x1b[0m                 \x1b[36m│\x1b[0m");
1138    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1139    
1140    for session in &filtered_sessions {
1141        let status_icon = if session.end_time.is_some() { "✅" } else { "🔄" };
1142        let duration = if let Some(end) = session.end_time {
1143            (end - session.start_time).num_seconds() - session.paused_duration.num_seconds()
1144        } else {
1145            (Utc::now() - session.start_time).num_seconds() - session.paused_duration.num_seconds()
1146        };
1147        
1148        let context_color = match session.context.to_string().as_str() {
1149            "terminal" => "\x1b[96m",
1150            "ide" => "\x1b[95m", 
1151            "linked" => "\x1b[93m",
1152            "manual" => "\x1b[94m",
1153            _ => "\x1b[97m",
1154        };
1155        
1156        println!("\x1b[36m│\x1b[0m {} \x1b[1;37m{:<32}\x1b[0m \x1b[36m│\x1b[0m", 
1157            status_icon,
1158            format!("Session {}", session.id.unwrap_or(0))
1159        );
1160        println!("\x1b[36m│\x1b[0m    Duration: \x1b[32m{:<24}\x1b[0m \x1b[36m│\x1b[0m", format_duration_fancy(duration));
1161        println!("\x1b[36m│\x1b[0m    Context:  {}{:<24}\x1b[0m \x1b[36m│\x1b[0m", 
1162            context_color, 
1163            session.context.to_string()
1164        );
1165        println!("\x1b[36m│\x1b[0m    Started:  \x1b[37m{:<24}\x1b[0m \x1b[36m│\x1b[0m", 
1166            session.start_time.format("%Y-%m-%d %H:%M:%S").to_string()
1167        );
1168        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1169    }
1170    
1171    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1172    println!("\x1b[36m│\x1b[0m \x1b[1;37mShowing:\x1b[0m {:<28} \x1b[36m│\x1b[0m", 
1173        format!("{} recent sessions", filtered_sessions.len())
1174    );
1175    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1176    
1177    Ok(())
1178}
1179
1180async fn edit_session(id: i64, start: Option<String>, end: Option<String>, project: Option<String>, reason: Option<String>) -> Result<()> {
1181    // Initialize database
1182    let db_path = get_database_path()?;
1183    let db = Database::new(&db_path)?;
1184    
1185    // Find the session
1186    let session = SessionQueries::find_by_id(&db.connection, id)?;
1187    let session = match session {
1188        Some(s) => s,
1189        None => {
1190            println!("\x1b[31m✗ Session {} not found\x1b[0m", id);
1191            return Ok(());
1192        }
1193    };
1194    
1195    let original_start = session.start_time;
1196    let original_end = session.end_time;
1197    
1198    // Parse new values
1199    let mut new_start = original_start;
1200    let mut new_end = original_end;
1201    let mut new_project_id = session.project_id;
1202    
1203    // Parse start time if provided
1204    if let Some(start_str) = &start {
1205        new_start = match chrono::DateTime::parse_from_rfc3339(start_str) {
1206            Ok(dt) => dt.with_timezone(&chrono::Utc),
1207            Err(_) => {
1208                match chrono::NaiveDateTime::parse_from_str(start_str, "%Y-%m-%d %H:%M:%S") {
1209                    Ok(dt) => chrono::Utc.from_utc_datetime(&dt),
1210                    Err(_) => return Err(anyhow::anyhow!("Invalid start time format. Use RFC3339 or 'YYYY-MM-DD HH:MM:SS'"))
1211                }
1212            }
1213        };
1214    }
1215    
1216    // Parse end time if provided
1217    if let Some(end_str) = &end {
1218        if end_str.to_lowercase() == "null" || end_str.to_lowercase() == "none" {
1219            new_end = None;
1220        } else {
1221            new_end = Some(match chrono::DateTime::parse_from_rfc3339(end_str) {
1222                Ok(dt) => dt.with_timezone(&chrono::Utc),
1223                Err(_) => {
1224                    match chrono::NaiveDateTime::parse_from_str(end_str, "%Y-%m-%d %H:%M:%S") {
1225                        Ok(dt) => chrono::Utc.from_utc_datetime(&dt),
1226                        Err(_) => return Err(anyhow::anyhow!("Invalid end time format. Use RFC3339 or 'YYYY-MM-DD HH:MM:SS'"))
1227                    }
1228                }
1229            });
1230        }
1231    }
1232    
1233    // Find project by name if provided
1234    if let Some(project_name) = &project {
1235        if let Some(proj) = ProjectQueries::find_by_name(&db.connection, project_name)? {
1236            new_project_id = proj.id.unwrap();
1237        } else {
1238            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1239            return Ok(());
1240        }
1241    }
1242    
1243    // Validate the edit
1244    if new_start >= new_end.unwrap_or(chrono::Utc::now()) {
1245        println!("\x1b[31m✗ Start time must be before end time\x1b[0m");
1246        return Ok(());
1247    }
1248    
1249    // Create audit trail record
1250    SessionEditQueries::create_edit_record(
1251        &db.connection,
1252        id,
1253        original_start,
1254        original_end,
1255        new_start,
1256        new_end,
1257        reason.clone()
1258    )?;
1259    
1260    // Update the session
1261    SessionQueries::update_session(
1262        &db.connection,
1263        id,
1264        if start.is_some() { Some(new_start) } else { None },
1265        if end.is_some() { Some(new_end) } else { None },
1266        if project.is_some() { Some(new_project_id) } else { None },
1267        None
1268    )?;
1269    
1270    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1271    println!("\x1b[36m│\x1b[0m         \x1b[1;37mSession Updated\x1b[0m                 \x1b[36m│\x1b[0m");
1272    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1273    println!("\x1b[36m│\x1b[0m Session:  \x1b[1;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", id);
1274    
1275    if start.is_some() {
1276        println!("\x1b[36m│\x1b[0m Start:    \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", 
1277            truncate_string(&new_start.format("%Y-%m-%d %H:%M:%S").to_string(), 27)
1278        );
1279    }
1280    
1281    if end.is_some() {
1282        let end_str = if let Some(e) = new_end {
1283            e.format("%Y-%m-%d %H:%M:%S").to_string()
1284        } else {
1285            "Ongoing".to_string()
1286        };
1287        println!("\x1b[36m│\x1b[0m End:      \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&end_str, 27));
1288    }
1289    
1290    if let Some(r) = &reason {
1291        println!("\x1b[36m│\x1b[0m Reason:   \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(r, 27));
1292    }
1293    
1294    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1295    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Session updated with audit trail\x1b[0m     \x1b[36m│\x1b[0m");
1296    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1297    
1298    Ok(())
1299}
1300
1301async fn delete_session(id: i64, force: bool) -> Result<()> {
1302    // Initialize database
1303    let db_path = get_database_path()?;
1304    let db = Database::new(&db_path)?;
1305    
1306    // Check if session exists
1307    let session = SessionQueries::find_by_id(&db.connection, id)?;
1308    let session = match session {
1309        Some(s) => s,
1310        None => {
1311            println!("\x1b[31m✗ Session {} not found\x1b[0m", id);
1312            return Ok(());
1313        }
1314    };
1315    
1316    // Check if it's an active session and require force flag
1317    if session.end_time.is_none() && !force {
1318        println!("\x1b[33m⚠  Cannot delete active session without --force flag\x1b[0m");
1319        println!("  Use: tempo session delete {} --force", id);
1320        return Ok(());
1321    }
1322    
1323    // Delete the session
1324    SessionQueries::delete_session(&db.connection, id)?;
1325    
1326    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1327    println!("\x1b[36m│\x1b[0m         \x1b[1;37mSession Deleted\x1b[0m                 \x1b[36m│\x1b[0m");
1328    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1329    println!("\x1b[36m│\x1b[0m Session:  \x1b[1;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", id);
1330    println!("\x1b[36m│\x1b[0m Status:   \x1b[32mDeleted\x1b[0m                   \x1b[36m│\x1b[0m");
1331    
1332    if session.end_time.is_none() {
1333        println!("\x1b[36m│\x1b[0m Type:     \x1b[33mActive session (forced)\x1b[0m      \x1b[36m│\x1b[0m");
1334    } else {
1335        println!("\x1b[36m│\x1b[0m Type:     \x1b[37mCompleted session\x1b[0m           \x1b[36m│\x1b[0m");
1336    }
1337    
1338    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1339    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Session and audit trail removed\x1b[0m      \x1b[36m│\x1b[0m");
1340    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1341    
1342    Ok(())
1343}
1344
1345// Project management functions
1346async fn archive_project(project_name: String) -> Result<()> {
1347    let db_path = get_database_path()?;
1348    let db = Database::new(&db_path)?;
1349    
1350    let project = match ProjectQueries::find_by_name(&db.connection, &project_name)? {
1351        Some(p) => p,
1352        None => {
1353            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1354            return Ok(());
1355        }
1356    };
1357    
1358    if project.is_archived {
1359        println!("\x1b[33m⚠  Project '{}' is already archived\x1b[0m", project_name);
1360        return Ok(());
1361    }
1362    
1363    let success = ProjectQueries::archive_project(&db.connection, project.id.unwrap())?;
1364    
1365    if success {
1366        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1367        println!("\x1b[36m│\x1b[0m        \x1b[1;37mProject Archived\x1b[0m                \x1b[36m│\x1b[0m");
1368        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1369        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
1370        println!("\x1b[36m│\x1b[0m Status:   \x1b[90mArchived\x1b[0m                  \x1b[36m│\x1b[0m");
1371        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1372        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Project archived successfully\x1b[0m        \x1b[36m│\x1b[0m");
1373        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1374    } else {
1375        println!("\x1b[31m✗ Failed to archive project '{}'\x1b[0m", project_name);
1376    }
1377    
1378    Ok(())
1379}
1380
1381async fn unarchive_project(project_name: String) -> Result<()> {
1382    let db_path = get_database_path()?;
1383    let db = Database::new(&db_path)?;
1384    
1385    let project = match ProjectQueries::find_by_name(&db.connection, &project_name)? {
1386        Some(p) => p,
1387        None => {
1388            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1389            return Ok(());
1390        }
1391    };
1392    
1393    if !project.is_archived {
1394        println!("\x1b[33m⚠  Project '{}' is not archived\x1b[0m", project_name);
1395        return Ok(());
1396    }
1397    
1398    let success = ProjectQueries::unarchive_project(&db.connection, project.id.unwrap())?;
1399    
1400    if success {
1401        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1402        println!("\x1b[36m│\x1b[0m       \x1b[1;37mProject Unarchived\x1b[0m               \x1b[36m│\x1b[0m");
1403        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1404        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
1405        println!("\x1b[36m│\x1b[0m Status:   \x1b[32mActive\x1b[0m                    \x1b[36m│\x1b[0m");
1406        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1407        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Project unarchived successfully\x1b[0m      \x1b[36m│\x1b[0m");
1408        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1409    } else {
1410        println!("\x1b[31m✗ Failed to unarchive project '{}'\x1b[0m", project_name);
1411    }
1412    
1413    Ok(())
1414}
1415
1416async fn update_project_path(project_name: String, new_path: PathBuf) -> Result<()> {
1417    let db_path = get_database_path()?;
1418    let db = Database::new(&db_path)?;
1419    
1420    let project = match ProjectQueries::find_by_name(&db.connection, &project_name)? {
1421        Some(p) => p,
1422        None => {
1423            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", project_name);
1424            return Ok(());
1425        }
1426    };
1427    
1428    let canonical_path = canonicalize_path(&new_path)?;
1429    let success = ProjectQueries::update_project_path(&db.connection, project.id.unwrap(), &canonical_path)?;
1430    
1431    if success {
1432        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1433        println!("\x1b[36m│\x1b[0m       \x1b[1;37mProject Path Updated\x1b[0m              \x1b[36m│\x1b[0m");
1434        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1435        println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
1436        println!("\x1b[36m│\x1b[0m Old Path: \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project.path.to_string_lossy(), 27));
1437        println!("\x1b[36m│\x1b[0m New Path: \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&canonical_path.to_string_lossy(), 27));
1438        println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1439        println!("\x1b[36m│\x1b[0m \x1b[32m✓ Path updated successfully\x1b[0m            \x1b[36m│\x1b[0m");
1440        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1441    } else {
1442        println!("\x1b[31m✗ Failed to update path for project '{}'\x1b[0m", project_name);
1443    }
1444    
1445    Ok(())
1446}
1447
1448async fn add_tag_to_project(project_name: String, tag_name: String) -> Result<()> {
1449    println!("\x1b[33m⚠  Project-tag associations not yet implemented\x1b[0m");
1450    println!("Would add tag '{}' to project '{}'", tag_name, project_name);
1451    println!("This requires implementing project_tags table operations.");
1452    Ok(())
1453}
1454
1455async fn remove_tag_from_project(project_name: String, tag_name: String) -> Result<()> {
1456    println!("\x1b[33m⚠  Project-tag associations not yet implemented\x1b[0m");
1457    println!("Would remove tag '{}' from project '{}'", tag_name, project_name);
1458    println!("This requires implementing project_tags table operations.");
1459    Ok(())
1460}
1461
1462// Bulk session operations
1463async fn bulk_update_sessions_project(session_ids: Vec<i64>, new_project_name: String) -> Result<()> {
1464    let db_path = get_database_path()?;
1465    let db = Database::new(&db_path)?;
1466    
1467    // Find the target project
1468    let project = match ProjectQueries::find_by_name(&db.connection, &new_project_name)? {
1469        Some(p) => p,
1470        None => {
1471            println!("\x1b[31m✗ Project '{}' not found\x1b[0m", new_project_name);
1472            return Ok(());
1473        }
1474    };
1475    
1476    let updated = SessionQueries::bulk_update_project(&db.connection, &session_ids, project.id.unwrap())?;
1477    
1478    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1479    println!("\x1b[36m│\x1b[0m      \x1b[1;37mBulk Session Update\x1b[0m               \x1b[36m│\x1b[0m");
1480    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1481    println!("\x1b[36m│\x1b[0m Sessions: \x1b[1;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", updated);
1482    println!("\x1b[36m│\x1b[0m Project:  \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&new_project_name, 27));
1483    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1484    println!("\x1b[36m│\x1b[0m \x1b[32m✓ {} sessions updated\x1b[0m {:<12} \x1b[36m│\x1b[0m", updated, "");
1485    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1486    
1487    Ok(())
1488}
1489
1490async fn bulk_delete_sessions(session_ids: Vec<i64>) -> Result<()> {
1491    let db_path = get_database_path()?;
1492    let db = Database::new(&db_path)?;
1493    
1494    let deleted = SessionQueries::bulk_delete(&db.connection, &session_ids)?;
1495    
1496    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1497    println!("\x1b[36m│\x1b[0m      \x1b[1;37mBulk Session Delete\x1b[0m               \x1b[36m│\x1b[0m");
1498    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1499    println!("\x1b[36m│\x1b[0m Requested: \x1b[1;37m{:<25}\x1b[0m \x1b[36m│\x1b[0m", session_ids.len());
1500    println!("\x1b[36m│\x1b[0m Deleted:   \x1b[32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", deleted);
1501    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1502    println!("\x1b[36m│\x1b[0m \x1b[32m✓ {} sessions deleted\x1b[0m {:<10} \x1b[36m│\x1b[0m", deleted, "");
1503    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1504    
1505    Ok(())
1506}
1507
1508async fn launch_dashboard() -> Result<()> {
1509    // Setup terminal
1510    enable_raw_mode()?;
1511    let mut stdout = io::stdout();
1512    execute!(stdout, EnterAlternateScreen)?;
1513    let backend = CrosstermBackend::new(stdout);
1514    let mut terminal = Terminal::new(backend)?;
1515
1516    // Create dashboard instance and run it
1517    let result = async {
1518        let mut dashboard = Dashboard::new().await?;
1519        dashboard.run(&mut terminal).await
1520    };
1521    
1522    let result = tokio::task::block_in_place(|| {
1523        Handle::current().block_on(result)
1524    });
1525
1526    // Restore terminal
1527    disable_raw_mode()?;
1528    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
1529    terminal.show_cursor()?;
1530
1531    result
1532}
1533
1534async fn launch_timer() -> Result<()> {
1535    // Setup terminal
1536    enable_raw_mode()?;
1537    let mut stdout = io::stdout();
1538    execute!(stdout, EnterAlternateScreen)?;
1539    let backend = CrosstermBackend::new(stdout);
1540    let mut terminal = Terminal::new(backend)?;
1541
1542    // Create timer instance and run it
1543    let result = async {
1544        let mut timer = InteractiveTimer::new().await?;
1545        timer.run(&mut terminal).await
1546    };
1547    
1548    let result = tokio::task::block_in_place(|| {
1549        Handle::current().block_on(result)
1550    });
1551
1552    // Restore terminal
1553    disable_raw_mode()?;
1554    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
1555    terminal.show_cursor()?;
1556
1557    result
1558}
1559
1560async fn merge_sessions(session_ids_str: String, project_name: Option<String>, notes: Option<String>) -> Result<()> {
1561    // Parse session IDs
1562    let session_ids: Result<Vec<i64>, _> = session_ids_str
1563        .split(',')
1564        .map(|s| s.trim().parse::<i64>())
1565        .collect();
1566    
1567    let session_ids = session_ids.map_err(|_| anyhow::anyhow!("Invalid session IDs format. Use comma-separated numbers like '1,2,3'"))?;
1568    
1569    if session_ids.len() < 2 {
1570        return Err(anyhow::anyhow!("At least 2 sessions are required for merging"));
1571    }
1572    
1573    // Get target project ID if specified
1574    let mut target_project_id = None;
1575    if let Some(project) = project_name {
1576        let db_path = get_database_path()?;
1577        let db = Database::new(&db_path)?;
1578        
1579        // Try to find project by name first, then by ID
1580        if let Ok(project_id) = project.parse::<i64>() {
1581            if ProjectQueries::find_by_id(&db.connection, project_id)?.is_some() {
1582                target_project_id = Some(project_id);
1583            }
1584        } else if let Some(proj) = ProjectQueries::find_by_name(&db.connection, &project)? {
1585            target_project_id = proj.id;
1586        }
1587        
1588        if target_project_id.is_none() {
1589            return Err(anyhow::anyhow!("Project '{}' not found", project));
1590        }
1591    }
1592    
1593    // Perform the merge
1594    let db_path = get_database_path()?;
1595    let db = Database::new(&db_path)?;
1596    
1597    let merged_id = SessionQueries::merge_sessions(&db.connection, &session_ids, target_project_id, notes)?;
1598    
1599    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1600    println!("\x1b[36m│\x1b[0m        \x1b[1;37mSession Merge Complete\x1b[0m            \x1b[36m│\x1b[0m");
1601    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1602    println!("\x1b[36m│\x1b[0m Merged sessions: \x1b[33m{:<22}\x1b[0m \x1b[36m│\x1b[0m", session_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(", "));
1603    println!("\x1b[36m│\x1b[0m New session ID:  \x1b[32m{:<22}\x1b[0m \x1b[36m│\x1b[0m", merged_id);
1604    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1605    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Sessions successfully merged\x1b[0m        \x1b[36m│\x1b[0m");
1606    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1607    
1608    Ok(())
1609}
1610
1611async fn split_session(session_id: i64, split_times_str: String, notes: Option<String>) -> Result<()> {
1612    // Parse split times
1613    let split_time_strings: Vec<&str> = split_times_str.split(',').map(|s| s.trim()).collect();
1614    let mut split_times = Vec::new();
1615    
1616    for time_str in split_time_strings {
1617        // Try to parse as time (HH:MM or HH:MM:SS)
1618        let datetime = if time_str.contains(':') {
1619            // Parse as time and combine with today's date
1620            let today = chrono::Local::now().date_naive();
1621            let time = chrono::NaiveTime::parse_from_str(time_str, "%H:%M")
1622                .or_else(|_| chrono::NaiveTime::parse_from_str(time_str, "%H:%M:%S"))
1623                .map_err(|_| anyhow::anyhow!("Invalid time format '{}'. Use HH:MM or HH:MM:SS", time_str))?;
1624            today.and_time(time).and_utc()
1625        } else {
1626            // Try to parse as full datetime
1627            chrono::DateTime::parse_from_rfc3339(time_str)
1628                .map_err(|_| anyhow::anyhow!("Invalid datetime format '{}'. Use HH:MM or RFC3339 format", time_str))?
1629                .to_utc()
1630        };
1631        
1632        split_times.push(datetime);
1633    }
1634    
1635    if split_times.is_empty() {
1636        return Err(anyhow::anyhow!("No valid split times provided"));
1637    }
1638    
1639    // Parse notes if provided
1640    let notes_list = notes.map(|n| {
1641        n.split(',').map(|s| s.trim().to_string()).collect::<Vec<String>>()
1642    });
1643    
1644    // Perform the split
1645    let db_path = get_database_path()?;
1646    let db = Database::new(&db_path)?;
1647    
1648    let new_session_ids = SessionQueries::split_session(&db.connection, session_id, &split_times, notes_list)?;
1649    
1650    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1651    println!("\x1b[36m│\x1b[0m        \x1b[1;37mSession Split Complete\x1b[0m            \x1b[36m│\x1b[0m");
1652    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1653    println!("\x1b[36m│\x1b[0m Original session: \x1b[33m{:<20}\x1b[0m \x1b[36m│\x1b[0m", session_id);
1654    println!("\x1b[36m│\x1b[0m Split points:     \x1b[90m{:<20}\x1b[0m \x1b[36m│\x1b[0m", split_times.len());
1655    println!("\x1b[36m│\x1b[0m New sessions:     \x1b[32m{:<20}\x1b[0m \x1b[36m│\x1b[0m", new_session_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(", "));
1656    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1657    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Session successfully split\x1b[0m          \x1b[36m│\x1b[0m");
1658    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1659    
1660    Ok(())
1661}
1662
1663async fn launch_history() -> Result<()> {
1664    enable_raw_mode()?;
1665    let mut stdout = io::stdout();
1666    execute!(stdout, EnterAlternateScreen)?;
1667    let backend = CrosstermBackend::new(stdout);
1668    let mut terminal = Terminal::new(backend)?;
1669
1670    let result = async {
1671        let mut browser = SessionHistoryBrowser::new().await?;
1672        browser.run(&mut terminal).await
1673    };
1674    
1675    let result = tokio::task::block_in_place(|| {
1676        Handle::current().block_on(result)
1677    });
1678
1679    disable_raw_mode()?;
1680    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
1681    terminal.show_cursor()?;
1682
1683    result
1684}
1685
1686async fn handle_goal_action(action: GoalAction) -> Result<()> {
1687    match action {
1688        GoalAction::Create { name, target_hours, project, description, start_date, end_date } => {
1689            create_goal(name, target_hours, project, description, start_date, end_date).await
1690        }
1691        GoalAction::List { project } => {
1692            list_goals(project).await
1693        }
1694        GoalAction::Update { id, hours } => {
1695            update_goal_progress(id, hours).await
1696        }
1697    }
1698}
1699
1700async fn create_goal(name: String, target_hours: f64, project: Option<String>, description: Option<String>, start_date: Option<String>, end_date: Option<String>) -> Result<()> {
1701    let db_path = get_database_path()?;
1702    let db = Database::new(&db_path)?;
1703    
1704    let project_id = if let Some(proj_name) = project {
1705        match ProjectQueries::find_by_name(&db.connection, &proj_name)? {
1706            Some(p) => p.id,
1707            None => {
1708                println!("\x1b[31m✗ Project '{}' not found\x1b[0m", proj_name);
1709                return Ok(());
1710            }
1711        }
1712    } else {
1713        None
1714    };
1715    
1716    let start = start_date.and_then(|d| chrono::NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok());
1717    let end = end_date.and_then(|d| chrono::NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok());
1718    
1719    let mut goal = Goal::new(name.clone(), target_hours);
1720    if let Some(pid) = project_id {
1721        goal = goal.with_project(pid);
1722    }
1723    if let Some(desc) = description {
1724        goal = goal.with_description(desc);
1725    }
1726    goal = goal.with_dates(start, end);
1727    
1728    goal.validate()?;
1729    let goal_id = GoalQueries::create(&db.connection, &goal)?;
1730    
1731    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1732    println!("\x1b[36m│\x1b[0m           \x1b[1;37mGoal Created\x1b[0m                   \x1b[36m│\x1b[0m");
1733    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1734    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
1735    println!("\x1b[36m│\x1b[0m Target:   \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", format!("{} hours", target_hours));
1736    println!("\x1b[36m│\x1b[0m ID:       \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", goal_id);
1737    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1738    println!("\x1b[36m│\x1b[0m \x1b[32m✓ Goal created successfully\x1b[0m             \x1b[36m│\x1b[0m");
1739    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1740    
1741    Ok(())
1742}
1743
1744async fn list_goals(project: Option<String>) -> Result<()> {
1745    let db_path = get_database_path()?;
1746    let db = Database::new(&db_path)?;
1747    
1748    let project_id = if let Some(proj_name) = &project {
1749        match ProjectQueries::find_by_name(&db.connection, proj_name)? {
1750            Some(p) => p.id,
1751            None => {
1752                println!("\x1b[31m✗ Project '{}' not found\x1b[0m", proj_name);
1753                return Ok(());
1754            }
1755        }
1756    } else {
1757        None
1758    };
1759    
1760    let goals = GoalQueries::list_by_project(&db.connection, project_id)?;
1761    
1762    if goals.is_empty() {
1763        println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1764        println!("\x1b[36m│\x1b[0m              \x1b[1;37mNo Goals\x1b[0m                    \x1b[36m│\x1b[0m");
1765        println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1766        return Ok(());
1767    }
1768    
1769    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1770    println!("\x1b[36m│\x1b[0m                \x1b[1;37mGoals\x1b[0m                      \x1b[36m│\x1b[0m");
1771    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1772    
1773    for goal in &goals {
1774        let progress_pct = goal.progress_percentage();
1775        println!("\x1b[36m│\x1b[0m 🎯 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&goal.name, 25));
1776        println!("\x1b[36m│\x1b[0m    Progress: \x1b[32m{:.1}%\x1b[0m ({:.1}h / {:.1}h)     \x1b[36m│\x1b[0m", 
1777            progress_pct, goal.current_progress, goal.target_hours);
1778        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1779    }
1780    
1781    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1782    Ok(())
1783}
1784
1785async fn update_goal_progress(id: i64, hours: f64) -> Result<()> {
1786    let db_path = get_database_path()?;
1787    let db = Database::new(&db_path)?;
1788    
1789    GoalQueries::update_progress(&db.connection, id, hours)?;
1790    println!("\x1b[32m✓ Updated goal {} progress by {} hours\x1b[0m", id, hours);
1791    Ok(())
1792}
1793
1794async fn show_insights(period: Option<String>, project: Option<String>) -> Result<()> {
1795    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1796    println!("\x1b[36m│\x1b[0m        \x1b[1;37mProductivity Insights\x1b[0m              \x1b[36m│\x1b[0m");
1797    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1798    println!("\x1b[36m│\x1b[0m Period:   \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", period.as_deref().unwrap_or("all"));
1799    if let Some(proj) = project {
1800        println!("\x1b[36m│\x1b[0m Project:  \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&proj, 27));
1801    }
1802    println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1803    println!("\x1b[36m│\x1b[0m \x1b[33m⚠  Insights calculation in progress...\x1b[0m  \x1b[36m│\x1b[0m");
1804    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1805    Ok(())
1806}
1807
1808async fn show_summary(period: String, from: Option<String>) -> Result<()> {
1809    let db_path = get_database_path()?;
1810    let db = Database::new(&db_path)?;
1811    
1812    let start_date = if let Some(from_str) = from {
1813        chrono::NaiveDate::parse_from_str(&from_str, "%Y-%m-%d")?
1814    } else {
1815        match period.as_str() {
1816            "week" => chrono::Local::now().date_naive() - chrono::Duration::days(7),
1817            "month" => chrono::Local::now().date_naive() - chrono::Duration::days(30),
1818            _ => chrono::Local::now().date_naive(),
1819        }
1820    };
1821    
1822    let insight_data = match period.as_str() {
1823        "week" => InsightQueries::calculate_weekly_summary(&db.connection, start_date)?,
1824        "month" => InsightQueries::calculate_monthly_summary(&db.connection, start_date)?,
1825        _ => return Err(anyhow::anyhow!("Invalid period. Use 'week' or 'month'")),
1826    };
1827    
1828    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1829    println!("\x1b[36m│\x1b[0m         \x1b[1;37m{} Summary\x1b[0m                  \x1b[36m│\x1b[0m", period);
1830    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1831    println!("\x1b[36m│\x1b[0m Total Hours:  \x1b[32m{:<23}\x1b[0m \x1b[36m│\x1b[0m", format!("{:.1}h", insight_data.total_hours));
1832    println!("\x1b[36m│\x1b[0m Sessions:     \x1b[33m{:<23}\x1b[0m \x1b[36m│\x1b[0m", insight_data.sessions_count);
1833    println!("\x1b[36m│\x1b[0m Avg Session:  \x1b[33m{:<23}\x1b[0m \x1b[36m│\x1b[0m", format!("{:.1}h", insight_data.avg_session_duration));
1834    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1835    Ok(())
1836}
1837
1838async fn compare_projects(projects: String, _from: Option<String>, _to: Option<String>) -> Result<()> {
1839    let _project_names: Vec<&str> = projects.split(',').map(|s| s.trim()).collect();
1840    
1841    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1842    println!("\x1b[36m│\x1b[0m        \x1b[1;37mProject Comparison\x1b[0m                \x1b[36m│\x1b[0m");
1843    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1844    println!("\x1b[36m│\x1b[0m Projects: \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&projects, 27));
1845    println!("\x1b[36m│\x1b[0m \x1b[33m⚠  Comparison feature in development\x1b[0m    \x1b[36m│\x1b[0m");
1846    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1847    Ok(())
1848}
1849
1850async fn handle_estimate_action(action: EstimateAction) -> Result<()> {
1851    match action {
1852        EstimateAction::Create { project, task, hours, due_date } => {
1853            create_estimate(project, task, hours, due_date).await
1854        }
1855        EstimateAction::Record { id, hours } => {
1856            record_actual_time(id, hours).await
1857        }
1858        EstimateAction::List { project } => {
1859            list_estimates(project).await
1860        }
1861    }
1862}
1863
1864async fn create_estimate(project: String, task: String, hours: f64, due_date: Option<String>) -> Result<()> {
1865    let db_path = get_database_path()?;
1866    let db = Database::new(&db_path)?;
1867    
1868    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
1869        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
1870    
1871    let due = due_date.and_then(|d| chrono::NaiveDate::parse_from_str(&d, "%Y-%m-%d").ok());
1872    
1873    let mut estimate = TimeEstimate::new(project_obj.id.unwrap(), task.clone(), hours);
1874    estimate.due_date = due;
1875    
1876    let estimate_id = TimeEstimateQueries::create(&db.connection, &estimate)?;
1877    
1878    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1879    println!("\x1b[36m│\x1b[0m      \x1b[1;37mTime Estimate Created\x1b[0m              \x1b[36m│\x1b[0m");
1880    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1881    println!("\x1b[36m│\x1b[0m Task:      \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&task, 27));
1882    println!("\x1b[36m│\x1b[0m Estimate:  \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", format!("{} hours", hours));
1883    println!("\x1b[36m│\x1b[0m ID:        \x1b[90m{:<27}\x1b[0m \x1b[36m│\x1b[0m", estimate_id);
1884    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1885    Ok(())
1886}
1887
1888async fn record_actual_time(id: i64, hours: f64) -> Result<()> {
1889    let db_path = get_database_path()?;
1890    let db = Database::new(&db_path)?;
1891    
1892    TimeEstimateQueries::record_actual(&db.connection, id, hours)?;
1893    println!("\x1b[32m✓ Recorded {} hours for estimate {}\x1b[0m", hours, id);
1894    Ok(())
1895}
1896
1897async fn list_estimates(project: String) -> Result<()> {
1898    let db_path = get_database_path()?;
1899    let db = Database::new(&db_path)?;
1900    
1901    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
1902        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
1903    
1904    let estimates = TimeEstimateQueries::list_by_project(&db.connection, project_obj.id.unwrap())?;
1905    
1906    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1907    println!("\x1b[36m│\x1b[0m          \x1b[1;37mTime Estimates\x1b[0m                  \x1b[36m│\x1b[0m");
1908    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1909    
1910    for est in &estimates {
1911        let variance = est.variance();
1912        let variance_str = if let Some(v) = variance {
1913            if v > 0.0 {
1914                format!("\x1b[31m+{:.1}h over\x1b[0m", v)
1915            } else {
1916                format!("\x1b[32m{:.1}h under\x1b[0m", v.abs())
1917            }
1918        } else {
1919            "N/A".to_string()
1920        };
1921        
1922        println!("\x1b[36m│\x1b[0m 📋 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&est.task_name, 25));
1923        let actual_str = est.actual_hours.map(|h| format!("{:.1}h", h)).unwrap_or_else(|| "N/A".to_string());
1924        println!("\x1b[36m│\x1b[0m    Est: {}h | Actual: {} | {}  \x1b[36m│\x1b[0m", 
1925            est.estimated_hours,
1926            actual_str,
1927            variance_str
1928        );
1929        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1930    }
1931    
1932    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1933    Ok(())
1934}
1935
1936async fn handle_branch_action(action: BranchAction) -> Result<()> {
1937    match action {
1938        BranchAction::List { project } => {
1939            list_branches(project).await
1940        }
1941        BranchAction::Stats { project, branch } => {
1942            show_branch_stats(project, branch).await
1943        }
1944    }
1945}
1946
1947async fn list_branches(project: String) -> Result<()> {
1948    let db_path = get_database_path()?;
1949    let db = Database::new(&db_path)?;
1950    
1951    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
1952        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
1953    
1954    let branches = GitBranchQueries::list_by_project(&db.connection, project_obj.id.unwrap())?;
1955    
1956    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1957    println!("\x1b[36m│\x1b[0m          \x1b[1;37mGit Branches\x1b[0m                   \x1b[36m│\x1b[0m");
1958    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1959    
1960    for branch in &branches {
1961        println!("\x1b[36m│\x1b[0m 🌿 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&branch.branch_name, 25));
1962        println!("\x1b[36m│\x1b[0m    Time: \x1b[32m{:<27}\x1b[0m \x1b[36m│\x1b[0m", format!("{:.1}h", branch.total_hours()));
1963        println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
1964    }
1965    
1966    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1967    Ok(())
1968}
1969
1970async fn show_branch_stats(project: String, branch: Option<String>) -> Result<()> {
1971    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
1972    println!("\x1b[36m│\x1b[0m        \x1b[1;37mBranch Statistics\x1b[0m                \x1b[36m│\x1b[0m");
1973    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
1974    println!("\x1b[36m│\x1b[0m Project:  \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project, 27));
1975    if let Some(b) = branch {
1976        println!("\x1b[36m│\x1b[0m Branch:   \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&b, 27));
1977    }
1978    println!("\x1b[36m│\x1b[0m \x1b[33m⚠  Branch stats in development\x1b[0m         \x1b[36m│\x1b[0m");
1979    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
1980    Ok(())
1981}
1982
1983// Template management functions
1984async fn handle_template_action(action: TemplateAction) -> Result<()> {
1985    match action {
1986        TemplateAction::Create { name, description, tags, workspace_path } => {
1987            create_template(name, description, tags, workspace_path).await
1988        }
1989        TemplateAction::List => {
1990            list_templates().await
1991        }
1992        TemplateAction::Delete { template } => {
1993            delete_template(template).await
1994        }
1995        TemplateAction::Use { template, project_name, path } => {
1996            use_template(template, project_name, path).await
1997        }
1998    }
1999}
2000
2001async fn create_template(name: String, description: Option<String>, tags: Option<String>, workspace_path: Option<PathBuf>) -> Result<()> {
2002    let db_path = get_database_path()?;
2003    let db = Database::new(&db_path)?;
2004    
2005    let default_tags = tags
2006        .map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
2007        .unwrap_or_default();
2008    
2009    let mut template = ProjectTemplate::new(name.clone())
2010        .with_tags(default_tags);
2011    
2012    let desc_clone = description.clone();
2013    if let Some(desc) = description {
2014        template = template.with_description(desc);
2015    }
2016    if let Some(path) = workspace_path {
2017        template = template.with_workspace_path(path);
2018    }
2019    
2020    let _template_id = TemplateQueries::create(&db.connection, &template)?;
2021    
2022    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2023    println!("\x1b[36m│\x1b[0m         \x1b[1;37mTemplate Created\x1b[0m                  \x1b[36m│\x1b[0m");
2024    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2025    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
2026    if let Some(desc) = &desc_clone {
2027        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2028    }
2029    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2030    Ok(())
2031}
2032
2033async fn list_templates() -> Result<()> {
2034    let db_path = get_database_path()?;
2035    let db = Database::new(&db_path)?;
2036    
2037    let templates = TemplateQueries::list_all(&db.connection)?;
2038    
2039    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2040    println!("\x1b[36m│\x1b[0m          \x1b[1;37mTemplates\x1b[0m                      \x1b[36m│\x1b[0m");
2041    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2042    
2043    if templates.is_empty() {
2044        println!("\x1b[36m│\x1b[0m No templates found.                      \x1b[36m│\x1b[0m");
2045    } else {
2046        for template in &templates {
2047            println!("\x1b[36m│\x1b[0m 📋 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&template.name, 25));
2048            if let Some(desc) = &template.description {
2049                println!("\x1b[36m│\x1b[0m    \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2050            }
2051            println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
2052        }
2053    }
2054    
2055    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2056    Ok(())
2057}
2058
2059async fn delete_template(_template: String) -> Result<()> {
2060    println!("\x1b[33m⚠  Template deletion not yet implemented\x1b[0m");
2061    Ok(())
2062}
2063
2064async fn use_template(template: String, project_name: String, path: Option<PathBuf>) -> Result<()> {
2065    let db_path = get_database_path()?;
2066    let db = Database::new(&db_path)?;
2067    
2068    let templates = TemplateQueries::list_all(&db.connection)?;
2069    let selected_template = templates.iter()
2070        .find(|t| t.name == template || t.id.map(|id| id.to_string()) == Some(template.clone()))
2071        .ok_or_else(|| anyhow::anyhow!("Template '{}' not found", template))?;
2072    
2073    // Initialize project with template
2074    let project_path = path.unwrap_or_else(|| env::current_dir().unwrap());
2075    let canonical_path = canonicalize_path(&project_path)?;
2076    
2077    // Check if project already exists
2078    if ProjectQueries::find_by_path(&db.connection, &canonical_path)?.is_some() {
2079        return Err(anyhow::anyhow!("Project already exists at this path"));
2080    }
2081    
2082    let git_hash = if is_git_repository(&canonical_path) {
2083        get_git_hash(&canonical_path)
2084    } else {
2085        None
2086    };
2087    
2088    let template_desc = selected_template.description.clone();
2089    let mut project = Project::new(project_name.clone(), canonical_path.clone())
2090        .with_git_hash(git_hash)
2091        .with_description(template_desc);
2092    
2093    let project_id = ProjectQueries::create(&db.connection, &project)?;
2094    project.id = Some(project_id);
2095    
2096    // Apply template tags (project-tag associations not yet implemented)
2097    // TODO: Implement project_tags table operations
2098    
2099    // Apply template goals
2100    for goal_def in &selected_template.default_goals {
2101        let mut goal = Goal::new(goal_def.name.clone(), goal_def.target_hours)
2102            .with_project(project_id);
2103        if let Some(desc) = &goal_def.description {
2104            goal = goal.with_description(desc.clone());
2105        }
2106        GoalQueries::create(&db.connection, &goal)?;
2107    }
2108    
2109    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2110    println!("\x1b[36m│\x1b[0m    \x1b[1;37mProject Created from Template\x1b[0m          \x1b[36m│\x1b[0m");
2111    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2112    println!("\x1b[36m│\x1b[0m Template: \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&selected_template.name, 27));
2113    println!("\x1b[36m│\x1b[0m Project:   \x1b[33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&project_name, 27));
2114    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2115    Ok(())
2116}
2117
2118// Workspace management functions
2119async fn handle_workspace_action(action: WorkspaceAction) -> Result<()> {
2120    match action {
2121        WorkspaceAction::Create { name, description, path } => {
2122            create_workspace(name, description, path).await
2123        }
2124        WorkspaceAction::List => {
2125            list_workspaces().await
2126        }
2127        WorkspaceAction::AddProject { workspace, project } => {
2128            add_project_to_workspace(workspace, project).await
2129        }
2130        WorkspaceAction::RemoveProject { workspace, project } => {
2131            remove_project_from_workspace(workspace, project).await
2132        }
2133        WorkspaceAction::Projects { workspace } => {
2134            list_workspace_projects(workspace).await
2135        }
2136        WorkspaceAction::Delete { workspace } => {
2137            delete_workspace(workspace).await
2138        }
2139    }
2140}
2141
2142async fn create_workspace(name: String, description: Option<String>, path: Option<PathBuf>) -> Result<()> {
2143    let db_path = get_database_path()?;
2144    let db = Database::new(&db_path)?;
2145    
2146    let mut workspace = Workspace::new(name.clone());
2147    let desc_clone = description.clone();
2148    if let Some(desc) = description {
2149        workspace = workspace.with_description(desc);
2150    }
2151    if let Some(p) = path {
2152        workspace = workspace.with_path(p);
2153    }
2154    
2155    let _workspace_id = WorkspaceQueries::create(&db.connection, &workspace)?;
2156    
2157    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2158    println!("\x1b[36m│\x1b[0m        \x1b[1;37mWorkspace Created\x1b[0m                  \x1b[36m│\x1b[0m");
2159    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2160    println!("\x1b[36m│\x1b[0m Name:     \x1b[1;33m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&name, 27));
2161    if let Some(desc) = &desc_clone {
2162        println!("\x1b[36m│\x1b[0m Desc:     \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2163    }
2164    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2165    Ok(())
2166}
2167
2168async fn list_workspaces() -> Result<()> {
2169    let db_path = get_database_path()?;
2170    let db = Database::new(&db_path)?;
2171    
2172    let workspaces = WorkspaceQueries::list_all(&db.connection)?;
2173    
2174    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2175    println!("\x1b[36m│\x1b[0m          \x1b[1;37mWorkspaces\x1b[0m                      \x1b[36m│\x1b[0m");
2176    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2177    
2178    if workspaces.is_empty() {
2179        println!("\x1b[36m│\x1b[0m No workspaces found.                     \x1b[36m│\x1b[0m");
2180    } else {
2181        for workspace in &workspaces {
2182            println!("\x1b[36m│\x1b[0m 📁 \x1b[1;33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&workspace.name, 25));
2183            if let Some(desc) = &workspace.description {
2184                println!("\x1b[36m│\x1b[0m    \x1b[2;37m{:<27}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(desc, 27));
2185            }
2186            println!("\x1b[36m│\x1b[0m                                         \x1b[36m│\x1b[0m");
2187        }
2188    }
2189    
2190    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2191    Ok(())
2192}
2193
2194async fn add_project_to_workspace(workspace: String, project: String) -> Result<()> {
2195    let db_path = get_database_path()?;
2196    let db = Database::new(&db_path)?;
2197    
2198    // Find workspace by name
2199    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2200        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2201    
2202    // Find project by name
2203    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
2204        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
2205    
2206    let workspace_id = workspace_obj.id.unwrap();
2207    let project_id = project_obj.id.unwrap();
2208    
2209    if WorkspaceQueries::add_project(&db.connection, workspace_id, project_id)? {
2210        println!("\x1b[32m✓\x1b[0m Added project '\x1b[33m{}\x1b[0m' to workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2211    } else {
2212        println!("\x1b[33m⚠\x1b[0m Project '\x1b[33m{}\x1b[0m' is already in workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2213    }
2214    
2215    Ok(())
2216}
2217
2218async fn remove_project_from_workspace(workspace: String, project: String) -> Result<()> {
2219    let db_path = get_database_path()?;
2220    let db = Database::new(&db_path)?;
2221    
2222    // Find workspace by name
2223    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2224        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2225    
2226    // Find project by name
2227    let project_obj = ProjectQueries::find_by_name(&db.connection, &project)?
2228        .ok_or_else(|| anyhow::anyhow!("Project '{}' not found", project))?;
2229    
2230    let workspace_id = workspace_obj.id.unwrap();
2231    let project_id = project_obj.id.unwrap();
2232    
2233    if WorkspaceQueries::remove_project(&db.connection, workspace_id, project_id)? {
2234        println!("\x1b[32m✓\x1b[0m Removed project '\x1b[33m{}\x1b[0m' from workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2235    } else {
2236        println!("\x1b[33m⚠\x1b[0m Project '\x1b[33m{}\x1b[0m' was not in workspace '\x1b[33m{}\x1b[0m'", project, workspace);
2237    }
2238    
2239    Ok(())
2240}
2241
2242async fn list_workspace_projects(workspace: String) -> Result<()> {
2243    let db_path = get_database_path()?;
2244    let db = Database::new(&db_path)?;
2245    
2246    // Find workspace by name
2247    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2248        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2249    
2250    let workspace_id = workspace_obj.id.unwrap();
2251    let projects = WorkspaceQueries::list_projects(&db.connection, workspace_id)?;
2252    
2253    if projects.is_empty() {
2254        println!("\x1b[33m⚠\x1b[0m No projects found in workspace '\x1b[33m{}\x1b[0m'", workspace);
2255        return Ok(());
2256    }
2257    
2258    println!("\x1b[36m┌─────────────────────────────────────────┐\x1b[0m");
2259    println!("\x1b[36m│\x1b[0m        \x1b[1;37mWorkspace Projects\x1b[0m               \x1b[36m│\x1b[0m");
2260    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2261    println!("\x1b[36m│\x1b[0m Workspace: \x1b[33m{:<25}\x1b[0m \x1b[36m│\x1b[0m", truncate_string(&workspace, 25));
2262    println!("\x1b[36m│\x1b[0m Projects:  \x1b[32m{:<25}\x1b[0m \x1b[36m│\x1b[0m", format!("{} projects", projects.len()));
2263    println!("\x1b[36m├─────────────────────────────────────────┤\x1b[0m");
2264    
2265    for project in &projects {
2266        let status_indicator = if !project.is_archived { "\x1b[32m●\x1b[0m" } else { "\x1b[31m○\x1b[0m" };
2267        println!("\x1b[36m│\x1b[0m {} \x1b[37m{:<33}\x1b[0m \x1b[36m│\x1b[0m", 
2268                status_indicator, 
2269                truncate_string(&project.name, 33));
2270    }
2271    
2272    println!("\x1b[36m└─────────────────────────────────────────┘\x1b[0m");
2273    Ok(())
2274}
2275
2276async fn delete_workspace(workspace: String) -> Result<()> {
2277    let db_path = get_database_path()?;
2278    let db = Database::new(&db_path)?;
2279    
2280    // Find workspace by name
2281    let workspace_obj = WorkspaceQueries::find_by_name(&db.connection, &workspace)?
2282        .ok_or_else(|| anyhow::anyhow!("Workspace '{}' not found", workspace))?;
2283    
2284    let workspace_id = workspace_obj.id.unwrap();
2285    
2286    // Check if workspace has projects
2287    let projects = WorkspaceQueries::list_projects(&db.connection, workspace_id)?;
2288    if !projects.is_empty() {
2289        println!("\x1b[33m⚠\x1b[0m Cannot delete workspace '\x1b[33m{}\x1b[0m' - it contains {} project(s). Remove projects first.", 
2290                workspace, projects.len());
2291        return Ok(());
2292    }
2293    
2294    if WorkspaceQueries::delete(&db.connection, workspace_id)? {
2295        println!("\x1b[32m✓\x1b[0m Deleted workspace '\x1b[33m{}\x1b[0m'", workspace);
2296    } else {
2297        println!("\x1b[31m✗\x1b[0m Failed to delete workspace '\x1b[33m{}\x1b[0m'", workspace);
2298    }
2299    
2300    Ok(())
2301}
2302
2303// Calendar integration functions
2304async fn handle_calendar_action(action: CalendarAction) -> Result<()> {
2305    match action {
2306        CalendarAction::Add { name, start, end, event_type, project, description } => {
2307            add_calendar_event(name, start, end, event_type, project, description).await
2308        }
2309        CalendarAction::List { from, to, project } => {
2310            list_calendar_events(from, to, project).await
2311        }
2312        CalendarAction::Delete { id } => {
2313            delete_calendar_event(id).await
2314        }
2315    }
2316}
2317
2318async fn add_calendar_event(_name: String, _start: String, _end: Option<String>, _event_type: Option<String>, _project: Option<String>, _description: Option<String>) -> Result<()> {
2319    println!("\x1b[33m⚠  Calendar integration in development\x1b[0m");
2320    Ok(())
2321}
2322
2323async fn list_calendar_events(_from: Option<String>, _to: Option<String>, _project: Option<String>) -> Result<()> {
2324    println!("\x1b[33m⚠  Calendar integration in development\x1b[0m");
2325    Ok(())
2326}
2327
2328async fn delete_calendar_event(_id: i64) -> Result<()> {
2329    println!("\x1b[33m⚠  Calendar integration in development\x1b[0m");
2330    Ok(())
2331}
2332
2333// Issue tracker integration functions
2334async fn handle_issue_action(action: IssueAction) -> Result<()> {
2335    match action {
2336        IssueAction::Sync { project, tracker_type } => {
2337            sync_issues(project, tracker_type).await
2338        }
2339        IssueAction::List { project, status } => {
2340            list_issues(project, status).await
2341        }
2342        IssueAction::Link { session_id, issue_id } => {
2343            link_session_to_issue(session_id, issue_id).await
2344        }
2345    }
2346}
2347
2348async fn sync_issues(_project: String, _tracker_type: Option<String>) -> Result<()> {
2349    println!("\x1b[33m⚠  Issue tracker integration in development\x1b[0m");
2350    Ok(())
2351}
2352
2353async fn list_issues(_project: String, _status: Option<String>) -> Result<()> {
2354    println!("\x1b[33m⚠  Issue tracker integration in development\x1b[0m");
2355    Ok(())
2356}
2357
2358async fn link_session_to_issue(_session_id: i64, _issue_id: String) -> Result<()> {
2359    println!("\x1b[33m⚠  Issue tracker integration in development\x1b[0m");
2360    Ok(())
2361}
2362
2363// Client reporting functions
2364async fn handle_client_action(action: ClientAction) -> Result<()> {
2365    match action {
2366        ClientAction::Generate { client, from, to, projects, format } => {
2367            generate_client_report(client, from, to, projects, format).await
2368        }
2369        ClientAction::List { client } => {
2370            list_client_reports(client).await
2371        }
2372        ClientAction::View { id } => {
2373            view_client_report(id).await
2374        }
2375    }
2376}
2377
2378async fn generate_client_report(_client: String, _from: String, _to: String, _projects: Option<String>, _format: Option<String>) -> Result<()> {
2379    println!("\x1b[33m⚠  Client reporting in development\x1b[0m");
2380    Ok(())
2381}
2382
2383async fn list_client_reports(_client: Option<String>) -> Result<()> {
2384    println!("\x1b[33m⚠  Client reporting in development\x1b[0m");
2385    Ok(())
2386}
2387
2388async fn view_client_report(_id: i64) -> Result<()> {
2389    println!("\x1b[33m⚠  Client reporting in development\x1b[0m");
2390    Ok(())
2391}
2392
2393fn should_quit(event: crossterm::event::Event) -> bool {
2394    match event {
2395        crossterm::event::Event::Key(key) if key.kind == crossterm::event::KeyEventKind::Press => {
2396            matches!(key.code, crossterm::event::KeyCode::Char('q') | crossterm::event::KeyCode::Esc)
2397        }
2398        _ => false,
2399    }
2400}