firecloud-cli 0.2.0

Command-line interface for FireCloud P2P messaging and file sharing
use anyhow::{Context, Result};
use colored::Colorize;
use dialoguer::Select;
use std::path::PathBuf;
use firecloud_storage::ManifestStore;
use comfy_table::{Table, presets::UTF8_FULL};

pub async fn run_interactive(data_dir: PathBuf, file_id: Option<String>) -> Result<()> {
    let manifest_store_path = data_dir.join("manifests");
    let manifest_store = ManifestStore::open(&manifest_store_path)
        .context("Failed to open manifest store")?;
    
    // If file_id is provided directly, download it
    if let Some(id) = file_id {
        println!("\n{} Downloading file: {}", "📥".cyan(), id.yellow());
        
        // Use existing download logic
        super::download::run(data_dir, None, Some(id), None).await?;
        return Ok(());
    }
    
    // Otherwise, show interactive file selection
    println!("\n{}", "📥 Available Files".bold().cyan());
    println!("{}", "─".repeat(50).dimmed());
    
    let manifests = manifest_store.list()
        .context("Failed to list manifests")?;
    
    if manifests.is_empty() {
        println!("\n{} No files available for download", "ℹ".blue());
        println!("  Upload files using: {}\n", "firecloud upload <file>".cyan());
        return Ok(());
    }
    
    // Create a table for display
    let mut table = Table::new();
    table.load_preset(UTF8_FULL);
    table.set_header(vec!["#", "Filename", "Size", "Chunks", "Encrypted"]);
    
    let mut items = Vec::new();
    for (idx, summary) in manifests.iter().enumerate() {
        let size = format_size(summary.size);
        let encrypted = "  ?"; // Don't show encrypted status in summary (not available)
        
        table.add_row(vec![
            (idx + 1).to_string(),
            summary.name.clone(),
            size.clone(),
            summary.chunk_count.to_string(),
            encrypted.to_string(),
        ]);
        
        items.push(format!(
            "{:2}. {:<30} {:>10}",
            idx + 1,
            truncate_filename(&summary.name, 30),
            size
        ));
    }
    
    println!("{}\n", table);
    
    // Interactive selection
    let selection = Select::new()
        .with_prompt("Select file to download")
        .items(&items)
        .default(0)
        .interact_opt()?;
    
    match selection {
        Some(idx) => {
            let summary = &manifests[idx];
            println!("\n{} Downloading: {}", 
                     "📥".cyan(), 
                     summary.name.yellow());
            
            // Use existing download logic with the file_id
            super::download::run(
                data_dir, 
                None, 
                Some(summary.file_id.to_string()), 
                None
            ).await?;
        }
        None => {
            println!("\n{} Download cancelled", "✓".yellow());
        }
    }
    
    Ok(())
}

/// Truncate filename to max length with ellipsis
fn truncate_filename(filename: &str, max_len: usize) -> String {
    if filename.len() <= max_len {
        filename.to_string()
    } else {
        format!("{}...", &filename[..max_len-3])
    }
}

/// Format bytes to human-readable string
fn format_size(bytes: u64) -> String {
    const KB: u64 = 1_024;
    const MB: u64 = 1_024 * 1_024;
    const GB: u64 = 1_024 * 1_024 * 1_024;
    const TB: u64 = 1_024 * 1_024 * 1_024 * 1_024;
    
    if bytes >= TB {
        format!("{:.2} TB", bytes as f64 / TB as f64)
    } else if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} B", bytes)
    }
}