using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using DeepDrftContent.Data; using DeepDrftModels.Entities; using NetBlocks.Models; using DeepDrftCli.Utils; namespace DeepDrftCli.Services; /// /// Main CLI service for handling command-line operations /// public class CliService { private readonly ILogger _logger; private readonly DeepDrftData.ITrackService _webTrackService; private readonly DeepDrftContent.Data.TrackService _contentTrackService; public CliService( ILogger logger, DeepDrftData.ITrackService webTrackService, DeepDrftContent.Data.TrackService contentTrackService) { _logger = logger; _webTrackService = webTrackService; _contentTrackService = contentTrackService; } /// /// Main entry point for CLI operations /// public async Task RunAsync(string[] args) { try { if (args.Length == 0) { ShowHelp(); return; } var command = args[0].ToLowerInvariant(); switch (command) { case "add": await HandleAddCommand(args); break; case "list": await HandleListCommand(); break; case "gui": case "--gui": Console.WriteLine("Error: GUI mode should be launched directly. Use: DeepDrftCli gui"); break; case "help": case "--help": case "-h": ShowHelp(); break; default: Console.WriteLine($"Unknown command: {command}"); ShowHelp(); break; } } catch (Exception ex) { _logger.LogError(ex, "CLI operation failed"); Console.WriteLine($"Error: {ex.Message}"); Environment.Exit(1); } } /// /// Handles the add command to add a new track /// private async Task HandleAddCommand(string[] args) { // Check if we have at least the command and file path if (args.Length < 2) { Console.WriteLine("Error: WAV file path is required."); Console.WriteLine(); Console.WriteLine("Usage: DeepDrftCli add [-i|--interactive] [track-name] [artist] [album] [genre] [release-date]"); Console.WriteLine(" DeepDrftCli add -i (interactive mode)"); Console.WriteLine("Example: DeepDrftCli add \"song.wav\" \"My Song\" \"Artist Name\" \"Album Name\" \"Rock\" \"2024-01-01\""); Console.WriteLine("Example: DeepDrftCli add \"song.wav\" --interactive"); return; } var wavFilePath = args[1]; // Validate that the file path is not a flag if (wavFilePath.StartsWith("-")) { Console.WriteLine("Error: WAV file path is required and cannot be a flag."); Console.WriteLine(); Console.WriteLine("Usage: DeepDrftCli add [-i|--interactive] [track-name] [artist] [album] [genre] [release-date]"); Console.WriteLine(" DeepDrftCli add -i (interactive mode)"); Console.WriteLine("Example: DeepDrftCli add \"song.wav\" \"My Song\" \"Artist Name\" \"Album Name\" \"Rock\" \"2024-01-01\""); Console.WriteLine("Example: DeepDrftCli add \"song.wav\" --interactive"); return; } var isInteractive = args.Contains("-i") || args.Contains("--interactive"); // Filter out the interactive flags from args for processing var filteredArgs = args.Where(arg => arg != "-i" && arg != "--interactive").ToArray(); string trackName; string artist; string? album; string? genre; DateOnly? releaseDate = null; if (isInteractive) { // Interactive mode - prompt for metadata var metadata = PromptForMetadata(wavFilePath, filteredArgs); trackName = metadata.TrackName; artist = metadata.Artist; album = metadata.Album; genre = metadata.Genre; releaseDate = metadata.ReleaseDate; } else { // Traditional command-line mode if (filteredArgs.Length < 4) { Console.WriteLine("Usage: DeepDrftCli add [album] [genre] [release-date]"); Console.WriteLine(" DeepDrftCli add -i (interactive mode)"); Console.WriteLine("Example: DeepDrftCli add \"song.wav\" \"My Song\" \"Artist Name\" \"Album Name\" \"Rock\" \"2024-01-01\""); Console.WriteLine("Example: DeepDrftCli add \"song.wav\" --interactive"); return; } trackName = filteredArgs[2]; artist = filteredArgs[3]; album = filteredArgs.Length > 4 ? filteredArgs[4] : null; genre = filteredArgs.Length > 5 ? filteredArgs[5] : null; if (filteredArgs.Length > 6 && DateOnly.TryParse(filteredArgs[6], out var parsedDate)) { releaseDate = parsedDate; } } Console.WriteLine($"Adding track: {trackName} by {artist}"); Console.WriteLine($"Processing WAV file: {wavFilePath}"); try { // Initialize tracks vault if needed await _contentTrackService.InitializeTracksVaultAsync(); // Add track to FileDatabase and get entity var trackEntity = await _contentTrackService.AddTrackFromWavAsync( wavFilePath, trackName, artist, album, genre, releaseDate); if (trackEntity == null) { Console.WriteLine("Failed to process audio file"); return; } // Add track to SQL database var result = await _webTrackService.Create(trackEntity); if (result.Success && result.Value != null) { Console.WriteLine($"✓ Track added successfully!"); Console.WriteLine($" ID: {result.Value.Id}"); Console.WriteLine($" Name: {result.Value.TrackName}"); Console.WriteLine($" Artist: {result.Value.Artist}"); Console.WriteLine($" Album: {result.Value.Album ?? "N/A"}"); Console.WriteLine($" Genre: {result.Value.Genre ?? "N/A"}"); Console.WriteLine($" Release Date: {result.Value.ReleaseDate?.ToString() ?? "N/A"}"); Console.WriteLine($" Entry Key: {result.Value.EntryKey}"); } else { var errorMessage = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; Console.WriteLine($"Failed to save track to database: {errorMessage}"); } } catch (FileNotFoundException) { Console.WriteLine($"Error: WAV file not found: {wavFilePath}"); } catch (ArgumentException ex) { Console.WriteLine($"Error: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Error adding track: {ex.Message}"); _logger.LogError(ex, "Failed to add track"); } } /// /// Handles the list command to show all tracks /// private async Task HandleListCommand() { try { Console.WriteLine("Retrieving tracks from database..."); var result = await _webTrackService.GetAll(); if (!result.Success || result.Value == null) { var errorMessage = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; Console.WriteLine($"Failed to retrieve tracks: {errorMessage}"); return; } var tracks = result.Value; if (tracks.Count == 0) { Console.WriteLine("No tracks found in database."); return; } Console.WriteLine($"\nFound {tracks.Count} tracks:"); Console.WriteLine(new string('-', 80)); Console.WriteLine($"{"ID",-5} {"Name",-25} {"Artist",-20} {"Album",-15} {"Genre",-10}"); Console.WriteLine(new string('-', 80)); foreach (var track in tracks) { Console.WriteLine($"{track.Id,-5} {CliUtils.TruncateString(track.TrackName, 25),-25} {CliUtils.TruncateString(track.Artist, 20),-20} {CliUtils.TruncateString(track.Album ?? "", 15),-15} {CliUtils.TruncateString(track.Genre ?? "", 10),-10}"); } } catch (Exception ex) { Console.WriteLine($"Error listing tracks: {ex.Message}"); _logger.LogError(ex, "Failed to list tracks"); } } /// /// Shows help information /// private void ShowHelp() { Console.WriteLine("DeepDrft CLI - Audio Track Management Tool"); Console.WriteLine(); Console.WriteLine("Usage:"); Console.WriteLine(" DeepDrftCli gui - Launch interactive GUI mode"); Console.WriteLine(" DeepDrftCli [command] [options] - Run command-line mode"); Console.WriteLine(); Console.WriteLine("Commands:"); Console.WriteLine(" add [album] [genre] [release-date]"); Console.WriteLine(" - Adds a WAV file to both SQL and FileDatabase"); Console.WriteLine(" - Example: DeepDrftCli add \"song.wav\" \"My Song\" \"Artist\" \"Album\" \"Rock\" \"2024-01-01\""); Console.WriteLine(); Console.WriteLine(" add -i|--interactive [track-name] [artist] [album] [genre] [release-date]"); Console.WriteLine(" - Adds a WAV file with interactive metadata prompts"); Console.WriteLine(" - Any provided command-line arguments will be used as defaults"); Console.WriteLine(" - Example: DeepDrftCli add \"song.wav\" -i"); Console.WriteLine(" - Example: DeepDrftCli add \"song.wav\" --interactive \"My Song\""); Console.WriteLine(); Console.WriteLine(" list"); Console.WriteLine(" - Lists all tracks in the database"); Console.WriteLine(); Console.WriteLine(" help"); Console.WriteLine(" - Shows this help information"); Console.WriteLine(); Console.WriteLine("Interactive Mode Features:"); Console.WriteLine(" - Prompts for each metadata field individually"); Console.WriteLine(" - Shows file name being processed"); Console.WriteLine(" - Supports default values and fallback to command-line args"); Console.WriteLine(" - Required fields: Track Name, Artist"); Console.WriteLine(" - Optional fields: Album, Genre, Release Date"); Console.WriteLine(" - Summary confirmation before proceeding"); Console.WriteLine(); Console.WriteLine("Notes:"); Console.WriteLine(" - Only WAV files are supported"); Console.WriteLine(" - Release date format: YYYY-MM-DD"); Console.WriteLine(" - Arguments with spaces should be quoted"); Console.WriteLine(" - Use * to indicate required fields in interactive mode"); } /// /// Prompts user for track metadata interactively /// private TrackMetadata PromptForMetadata(string wavFilePath, string[] args) { Console.WriteLine(); Console.WriteLine("=== Interactive Metadata Entry ==="); Console.WriteLine($"Processing file: {Path.GetFileName(wavFilePath)}"); Console.WriteLine("Press Enter to use default values or skip optional fields."); Console.WriteLine(); // Check if any metadata was provided via command line (fallback support) var trackName = args.Length > 2 ? args[2] : null; var artist = args.Length > 3 ? args[3] : null; var album = args.Length > 4 ? args[4] : null; var genre = args.Length > 5 ? args[5] : null; DateOnly? releaseDate = null; if (args.Length > 6 && DateOnly.TryParse(args[6], out var parsedDate)) releaseDate = parsedDate; // Prompt for track name (required) trackName ??= PromptForInput("Track Name", required: true); // Prompt for artist (required) artist ??= PromptForInput("Artist", required: true); // Prompt for album (optional) album ??= PromptForInput("Album", defaultValue: album); // Prompt for genre (optional) genre ??= PromptForInput("Genre", defaultValue: genre); // Prompt for release date (optional) if (releaseDate == null) { var releaseDateInput = PromptForInput("Release Date (YYYY-MM-DD)", defaultValue: releaseDate?.ToString()); if (!string.IsNullOrWhiteSpace(releaseDateInput) && DateOnly.TryParse(releaseDateInput, out var newReleaseDate)) { releaseDate = newReleaseDate; } } Console.WriteLine(); Console.WriteLine("=== Summary ==="); Console.WriteLine($"Track Name: {trackName}"); Console.WriteLine($"Artist: {artist}"); Console.WriteLine($"Album: {album ?? "N/A"}"); Console.WriteLine($"Genre: {genre ?? "N/A"}"); Console.WriteLine($"Release Date: {releaseDate?.ToString() ?? "N/A"}"); Console.WriteLine(); if (!ConfirmProceed("Proceed with these details?")) { Console.WriteLine("Operation cancelled."); Environment.Exit(0); } return new TrackMetadata { TrackName = trackName, Artist = artist, Album = album, Genre = genre, ReleaseDate = releaseDate }; } /// /// Prompts user for a single input field /// private string PromptForInput(string fieldName, bool required = false, string? defaultValue = null) { while (true) { var prompt = $"{fieldName}"; if (!string.IsNullOrWhiteSpace(defaultValue)) prompt += $" [{defaultValue}]"; if (required) prompt += " *"; prompt += ": "; Console.Write(prompt); var input = Console.ReadLine(); // Handle null input (Ctrl+C scenario) if (input == null) { Console.WriteLine(); Console.WriteLine("Operation cancelled."); Environment.Exit(0); } input = input.Trim(); // Use default value if input is empty and default exists if (string.IsNullOrWhiteSpace(input) && !string.IsNullOrWhiteSpace(defaultValue)) return defaultValue; // If not required and input is empty, return empty string if (!required && string.IsNullOrWhiteSpace(input)) return string.Empty; // Check if required field is provided if (required && string.IsNullOrWhiteSpace(input)) { Console.WriteLine($"Error: {fieldName} is required. Please provide a value."); continue; } return input; } } /// /// Prompts user for yes/no confirmation /// private bool ConfirmProceed(string message) { while (true) { Console.Write($"{message} (y/n): "); var input = Console.ReadKey(); Console.WriteLine(); switch (input.KeyChar.ToString().ToLowerInvariant()) { case "y": return true; case "n": return false; default: Console.WriteLine("Please enter 'y' for yes or 'n' for no."); break; } } } /// /// Represents track metadata for interactive input /// private record TrackMetadata { public required string TrackName { get; init; } public required string Artist { get; init; } public string? Album { get; init; } public string? Genre { get; init; } public DateOnly? ReleaseDate { get; init; } } }