subx-cli 1.7.4

AI subtitle processing CLI tool, which automatically matches, renames, and converts subtitle files.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//! Configuration management command implementation with hierarchical settings.
//!
//! This module provides comprehensive configuration management capabilities
//! through the `config` subcommand, enabling users to view, modify, and manage
//! application settings across multiple configuration categories and sources.
//! It supports hierarchical configuration with validation and type safety.
//!
//! # Configuration Architecture
//!
//! ## Configuration Categories
//! - **General**: Basic application behavior and preferences
//! - **AI Settings**: AI service providers, models, and API configuration
//! - **Audio Processing**: Audio analysis and synchronization parameters
//! - **Format Options**: Default output formats and conversion settings
//! - **Cache Management**: Caching behavior and storage configuration
//! - **Sync Settings**: Subtitle timing and synchronization options
//!
//! ## Configuration Sources (Priority Order)
//! 1. **Command-line Arguments**: Highest priority, session-specific
//! 2. **Environment Variables**: Runtime configuration overrides
//! 3. **User Configuration**: Personal settings in user config directory
//! 4. **Project Configuration**: Local project-specific settings
//! 5. **System Configuration**: Global system-wide defaults
//! 6. **Built-in Defaults**: Application default values
//!
//! # Supported Operations
//!
//! ## Set Operation
//! - **Type Validation**: Ensure values match expected data types
//! - **Range Checking**: Validate numeric values are within bounds
//! - **Format Verification**: Check string values follow required patterns
//! - **Dependency Validation**: Verify related settings are compatible
//! - **Backup Creation**: Preserve previous values for rollback
//!
//! ## Get Operation
//! - **Value Display**: Show current effective value
//! - **Source Identification**: Indicate where value originates
//! - **Type Information**: Display expected data type and constraints
//! - **Default Comparison**: Show difference from built-in defaults
//! - **Metadata Display**: Include help text and validation rules
//!
//! ## List Operation
//! - **Categorized Display**: Group settings by functional area
//! - **Source Indicators**: Show which settings are customized
//! - **Value Formatting**: Display values in appropriate format
//! - **Filter Options**: Support for category and status filtering
//! - **Export Capability**: Generate configuration for sharing
//!
//! ## Reset Operation
//! - **Backup Creation**: Automatic backup before reset
//! - **Selective Reset**: Option to reset specific categories
//! - **Confirmation Process**: Interactive confirmation for safety
//! - **Recovery Information**: Instructions for backup restoration
//!
//! # Configuration Keys
//!
//! ## General Settings
//! ```text
//! general.enable_progress_bar     # Boolean: Show progress indicators
//! general.backup_enabled          # Boolean: Automatic file backups
//! general.task_timeout_seconds    # Integer: Operation timeout in seconds
//! ```
//!
//! ## AI Configuration
//! ```text
//! ai.provider                    # String: AI service provider
//! ai.api_key                     # String: OpenAI API authentication
//! ai.model                       # String: GPT model selection
//! ai.max_tokens                  # Integer: Maximum response length
//! ai.base_url                    # String: API endpoint URL
//! ai.max_sample_length           # Integer: Text sample size for analysis
//! ai.temperature                 # Float: Response randomness control
//! ai.retry_attempts              # Integer: API request retry count
//! ai.retry_delay_ms              # Integer: Retry delay in milliseconds
//! ```
//!
//! ## Audio Processing
//! ```text
//! audio.max_offset_seconds       # Float: Maximum sync offset range
//! audio.correlation_threshold    # Float: Minimum correlation for sync
//! audio.dialogue_threshold       # Float: Speech detection sensitivity
//! audio.min_dialogue_duration_ms # Integer: Minimum speech segment length
//! audio.enable_dialogue_detection # Boolean: Advanced audio analysis
//! ```
//!
//! # Examples
//!
//! ```rust,ignore
//! use subx_cli::cli::{ConfigArgs, ConfigAction};
//! use subx_cli::commands::config_command;
//!
//! // Set AI provider
//! let set_args = ConfigArgs {
//!     action: ConfigAction::Set {
//!         key: "ai.provider".to_string(),
//!         value: "openai".to_string(),
//!     },
//! };
//! config_command::execute(set_args).await?;
//!
//! // Get current AI model
//! let get_args = ConfigArgs {
//!     action: ConfigAction::Get {
//!         key: "ai.openai.model".to_string(),
//!     },
//! };
//! config_command::execute(get_args).await?;
//! ```

use crate::cli::output::{active_mode, emit_success, emit_success_with_warnings};
use crate::cli::{ConfigAction, ConfigArgs};
use crate::config::{ConfigService, mask_sensitive_value};
use crate::error::{SubXError, SubXResult};
use serde::Serialize;
use serde_json::{Map, Value};

/// JSON payload for `config set` — the key/value that was just persisted.
#[derive(Debug, Serialize)]
pub struct ConfigSetPayload<'a> {
    /// Configuration key that was modified.
    pub key: &'a str,
    /// New value (sensitive values are masked).
    pub value: String,
}

/// JSON payload for `config get`/`list`/`reset` — the resolved
/// configuration object (or a single-key projection for `get`).
#[derive(Debug, Serialize)]
pub struct ConfigPayload {
    /// Resolved configuration map.
    pub config: Value,
}

fn build_config_value(config_service: &dyn ConfigService) -> SubXResult<Value> {
    let mut config = config_service.get_config()?;
    if let Some(key) = config.ai.api_key.as_ref() {
        config.ai.api_key = Some(mask_sensitive_value("ai.api_key", key));
    }
    serde_json::to_value(&config)
        .map_err(|e| SubXError::config(format!("JSON serialization error: {e}")))
}

/// Build a JSON-friendly view of the configuration using the *tolerant*
/// load path so that an existing strict-invalid `config.toml` does not
/// prevent `config get`/`list` from inspecting it.
///
/// The returned vector contains zero or more advisory warnings: it is
/// non-empty iff the on-disk configuration fails cross-section
/// validation. Callers MUST surface these warnings either via
/// [`emit_success_with_warnings`] (JSON mode) or as stderr lines (text
/// mode) so the user can see *why* the on-disk file is currently
/// invalid before issuing the repair `config set` mutation.
fn build_config_value_for_repair(
    config_service: &dyn ConfigService,
) -> SubXResult<(Value, Vec<String>)> {
    let mut config = config_service.load_for_repair()?;
    let warnings = collect_strict_validation_warnings(&config);
    if let Some(key) = config.ai.api_key.as_ref() {
        config.ai.api_key = Some(mask_sensitive_value("ai.api_key", key));
    }
    let value = serde_json::to_value(&config)
        .map_err(|e| SubXError::config(format!("JSON serialization error: {e}")))?;
    Ok((value, warnings))
}

/// Run cross-section validation on `config` purely for diagnostic
/// purposes. Returns one user-friendly warning string per validation
/// failure; for the typical "single validator error" case the vector
/// has length 1. The returned vector is empty when `config` is
/// strict-valid.
fn collect_strict_validation_warnings(config: &crate::config::Config) -> Vec<String> {
    match crate::config::validator::validate_config(config) {
        Ok(()) => Vec::new(),
        Err(err) => {
            vec![format!("configuration is currently invalid: {err}")]
        }
    }
}

async fn run_config_action(args: ConfigArgs, config_service: &dyn ConfigService) -> SubXResult<()> {
    let mode = active_mode();
    let json_mode = mode.is_json();

    match args.action {
        ConfigAction::Set { key, value } => {
            config_service.set_config_value(&key, &value)?;
            let masked = mask_sensitive_value(&key, &value);
            if json_mode {
                emit_success(
                    mode,
                    "config",
                    ConfigSetPayload {
                        key: &key,
                        value: masked,
                    },
                );
            } else {
                println!("✓ Configuration '{key}' set to '{masked}'");
                if let Ok(current) = config_service.get_config_value(&key) {
                    let masked_current = mask_sensitive_value(&key, &current);
                    println!("  Current value: {masked_current}");
                }
                if let Ok(path) = config_service.get_config_file_path() {
                    println!("  Saved to: {}", path.display());
                }
            }
        }
        ConfigAction::Get { key } => {
            // Tolerant read: load the file directly so users can
            // inspect a strict-invalid configuration.
            let config = config_service.load_for_repair()?;
            let warnings = collect_strict_validation_warnings(&config);
            let value = crate::config::service::read_config_value_from(&config, &key)?;
            let masked = mask_sensitive_value(&key, &value);
            if json_mode {
                let mut obj = Map::new();
                obj.insert(key.clone(), Value::String(masked));
                emit_success_with_warnings(
                    mode,
                    "config",
                    ConfigPayload {
                        config: Value::Object(obj),
                    },
                    warnings,
                );
            } else {
                println!("{masked}");
                for warning in &warnings {
                    eprintln!("warning: {warning}");
                }
            }
        }
        ConfigAction::List => {
            if json_mode {
                let (config_value, warnings) = build_config_value_for_repair(config_service)?;
                let payload = ConfigPayload {
                    config: config_value,
                };
                emit_success_with_warnings(mode, "config", payload, warnings);
            } else {
                let mut config = config_service.load_for_repair()?;
                let warnings = collect_strict_validation_warnings(&config);
                if let Ok(path) = config_service.get_config_file_path() {
                    println!("# Configuration file path: {}\n", path.display());
                }
                if let Some(key) = config.ai.api_key.as_ref() {
                    config.ai.api_key = Some(mask_sensitive_value("ai.api_key", key));
                }
                println!(
                    "{}",
                    toml::to_string_pretty(&config)
                        .map_err(|e| SubXError::config(format!("TOML serialization error: {e}")))?
                );
                for warning in &warnings {
                    eprintln!("warning: {warning}");
                }
            }
        }
        ConfigAction::Reset => {
            config_service.reset_to_defaults()?;
            if json_mode {
                let payload = ConfigPayload {
                    config: build_config_value(config_service)?,
                };
                emit_success(mode, "config", payload);
            } else {
                println!("Configuration reset to default values");
                if let Ok(path) = config_service.get_config_file_path() {
                    println!("Default configuration saved to: {}", path.display());
                }
            }
        }
    }
    Ok(())
}

/// Execute configuration management operations with validation and type safety.
///
/// This function provides the main entry point for all configuration management
/// operations, including setting values, retrieving current configuration,
/// listing all settings, and resetting to defaults. It includes comprehensive
/// validation, error handling, and user-friendly output formatting.
///
/// # Operation Workflow
///
/// ## Set Operation
/// 1. **Configuration Loading**: Load current configuration from all sources
/// 2. **Key Validation**: Verify configuration key exists and is writable
/// 3. **Value Parsing**: Convert string value to appropriate data type
/// 4. **Constraint Checking**: Validate value meets all requirements
/// 5. **Dependency Verification**: Check related settings compatibility
/// 6. **Backup Creation**: Save current value for potential rollback
/// 7. **Value Application**: Update configuration with new value
/// 8. **Persistence**: Save updated configuration to appropriate file
/// 9. **Confirmation**: Display success message with applied value
///
/// ## Get Operation
/// 1. **Configuration Loading**: Load current effective configuration
/// 2. **Key Resolution**: Locate requested configuration setting
/// 3. **Source Identification**: Determine where value originates
/// 4. **Value Formatting**: Format value for appropriate display
/// 5. **Metadata Retrieval**: Gather type and constraint information
/// 6. **Output Generation**: Create comprehensive information display
///
/// ## List Operation
/// 1. **Configuration Loading**: Load all configuration settings
/// 2. **Categorization**: Group settings by functional area
/// 3. **Source Analysis**: Identify customized vs. default values
/// 4. **Formatting**: Prepare values for tabular display
/// 5. **Output Generation**: Create organized configuration overview
///
/// ## Reset Operation
/// 1. **Current State Backup**: Create timestamped configuration backup
/// 2. **User Confirmation**: Interactive confirmation for destructive operation
/// 3. **Default Restoration**: Replace all settings with built-in defaults
/// 4. **Validation**: Verify reset configuration is valid
/// 5. **Persistence**: Save default configuration to user config file
/// 6. **Confirmation**: Display reset completion and backup location
///
/// # Type System Integration
///
/// The configuration system provides strong typing with automatic conversion:
/// - **Boolean Values**: "true", "false", "1", "0", "yes", "no"
/// - **Integer Values**: Decimal notation with range validation
/// - **Float Values**: Decimal notation with precision preservation
/// - **String Values**: UTF-8 text with format validation where applicable
/// - **Array Values**: JSON array format for complex configuration
///
/// # Validation Framework
///
/// Each configuration setting includes comprehensive validation:
/// - **Type Constraints**: Must match expected data type
/// - **Range Limits**: Numeric values within acceptable bounds
/// - **Format Requirements**: String values matching required patterns
/// - **Dependency Rules**: Related settings must be compatible
/// - **Security Checks**: Sensitive values properly protected
///
/// # Arguments
///
/// * `args` - Configuration command arguments containing the specific
///   operation to perform (set, get, list, or reset) along with any
///   required parameters such as key names and values.
///
/// # Returns
///
/// Returns `Ok(())` on successful operation completion, or an error describing:
/// - Configuration loading or parsing failures
/// - Invalid configuration keys or malformed key paths
/// - Type conversion or validation errors
/// - File system access problems during persistence
/// - User cancellation of destructive operations
///
/// # Error Categories
///
/// ## Configuration Errors
/// - **Invalid Key**: Specified configuration key does not exist
/// - **Type Mismatch**: Value cannot be converted to expected type
/// - **Range Error**: Numeric value outside acceptable range
/// - **Format Error**: String value doesn't match required pattern
/// - **Dependency Error**: Value conflicts with related settings
///
/// ## System Errors
/// - **File Access**: Cannot read or write configuration files
/// - **Permission Error**: Insufficient privileges for operation
/// - **Disk Space**: Insufficient space for configuration persistence
/// - **Corruption**: Configuration file is damaged or invalid
///
/// # Security Considerations
///
/// - **Sensitive Values**: API keys and credentials are properly masked in output
/// - **File Permissions**: Configuration files created with appropriate permissions
/// - **Backup Protection**: Backup files inherit security settings
/// - **Validation**: All input values sanitized and validated
///
/// # Examples
///
/// ```rust,ignore
/// use subx_cli::cli::{ConfigArgs, ConfigAction};
/// use subx_cli::commands::config_command;
///
/// // Configure AI service with API key
/// let ai_setup = ConfigArgs {
///     action: ConfigAction::Set {
///         key: "ai.openai.api_key".to_string(),
///         value: "sk-1234567890abcdef".to_string(),
///     },
/// };
/// config_command::execute(ai_setup).await?;
///
/// // Adjust audio processing sensitivity
/// let audio_tuning = ConfigArgs {
///     action: ConfigAction::Set {
///         key: "audio.correlation_threshold".to_string(),
///         value: "0.85".to_string(),
///     },
/// };
/// config_command::execute(audio_tuning).await?;
///
/// // View complete configuration
/// let view_all = ConfigArgs {
///     action: ConfigAction::List,
/// };
/// config_command::execute(view_all).await?;
///
/// // Reset to clean state
/// let reset_config = ConfigArgs {
///     action: ConfigAction::Reset,
/// };
/// config_command::execute(reset_config).await?;
/// ```
pub async fn execute(args: ConfigArgs, config_service: &dyn ConfigService) -> SubXResult<()> {
    run_config_action(args, config_service).await
}

/// Execute configuration management command with injected configuration service.
///
/// This function provides the new dependency injection interface for the config command,
/// accepting a configuration service instead of loading configuration globally.
///
/// # Arguments
///
/// * `args` - Configuration command arguments
/// * `config_service` - Configuration service providing access to settings
///
/// # Returns
///
/// Returns `Ok(())` on successful completion, or an error if the operation fails.
pub async fn execute_with_config(
    args: ConfigArgs,
    config_service: std::sync::Arc<dyn ConfigService>,
) -> SubXResult<()> {
    run_config_action(args, config_service.as_ref()).await
}