use std::io::{self, Write};
use crate::config::{paths::EnvelopePaths, settings::Settings};
use crate::error::{EnvelopeError, EnvelopeResult};
use crate::models::{Account, Money, TransactionStatus};
use crate::services::account::AccountService;
use crate::services::transaction::{CreateTransactionInput, TransactionService};
use crate::storage::Storage;
use super::steps::{
account::AccountSetupStep,
categories::{CategoriesSetupStep, CategoryChoice},
period::PeriodSetupStep,
};
pub struct SetupResult {
pub completed: bool,
pub account: Option<Account>,
pub starting_balance: Money,
}
pub struct SetupWizard {
paths: EnvelopePaths,
}
impl SetupWizard {
pub fn new(paths: EnvelopePaths) -> Self {
Self { paths }
}
pub fn needs_setup(&self, settings: &Settings) -> bool {
!settings.setup_completed && !self.paths.settings_file().exists()
}
pub fn run(&self, storage: &Storage, settings: &mut Settings) -> EnvelopeResult<SetupResult> {
println!();
println!("===========================================");
println!(" Welcome to EnvelopeCLI Setup Wizard!");
println!("===========================================");
println!();
println!("This wizard will help you set up your budget.");
println!("Press Ctrl+C at any time to cancel.");
println!();
let confirm = prompt_string("Ready to begin? (yes/no) [yes]: ")?;
if !confirm.is_empty() && confirm.to_lowercase() != "yes" && confirm.to_lowercase() != "y" {
println!("Setup cancelled.");
return Ok(SetupResult {
completed: false,
account: None,
starting_balance: Money::zero(),
});
}
let account_result = AccountSetupStep::run()?;
let categories_result = CategoriesSetupStep::run()?;
let period_result = PeriodSetupStep::run()?;
println!();
println!("===========================================");
println!(" Setup Summary");
println!("===========================================");
println!();
println!(
"Account: {} ({})",
account_result.account.name, account_result.account.account_type
);
println!("Starting Balance: {}", account_result.starting_balance);
println!(
"Categories: {}",
match categories_result.choice {
CategoryChoice::UseDefaults => "Default categories",
CategoryChoice::Empty => "Empty (add your own)",
CategoryChoice::Customize => "Custom",
}
);
println!("Budget Period: {:?}", period_result.period_type);
println!();
let confirm = prompt_string("Apply these settings? (yes/no) [yes]: ")?;
if !confirm.is_empty() && confirm.to_lowercase() != "yes" && confirm.to_lowercase() != "y" {
println!("Setup cancelled.");
return Ok(SetupResult {
completed: false,
account: None,
starting_balance: Money::zero(),
});
}
println!();
println!("Applying settings...");
if categories_result.choice == CategoryChoice::UseDefaults {
crate::storage::init::initialize_storage(&self.paths)?;
}
let account_service = AccountService::new(storage);
let saved_account = account_service.create(
&account_result.account.name,
account_result.account.account_type,
account_result.starting_balance,
account_result.account.on_budget,
)?;
if !account_result.starting_balance.is_zero() {
let txn_service = TransactionService::new(storage);
let input = CreateTransactionInput {
account_id: saved_account.id,
date: chrono::Local::now().naive_local().date(),
amount: account_result.starting_balance,
payee_name: Some("Starting Balance".to_string()),
category_id: None,
memo: Some("Initial account balance".to_string()),
status: Some(TransactionStatus::Cleared),
};
txn_service.create(input)?;
}
settings.budget_period_type = period_result.period_type;
settings.setup_completed = true;
settings.save(&self.paths)?;
println!();
println!("Setup complete!");
println!();
println!("Your budget is ready. Here are some next steps:");
println!(" - Run 'envelope tui' to open the interactive interface");
println!(" - Run 'envelope budget assign' to allocate funds to categories");
println!(" - Run 'envelope transaction add' to record transactions");
println!();
Ok(SetupResult {
completed: true,
account: Some(saved_account),
starting_balance: account_result.starting_balance,
})
}
pub fn run_minimal(
&self,
_storage: &Storage,
settings: &mut Settings,
) -> EnvelopeResult<SetupResult> {
println!("Initializing EnvelopeCLI...");
crate::storage::init::initialize_storage(&self.paths)?;
settings.setup_completed = true;
settings.save(&self.paths)?;
println!("Initialization complete!");
Ok(SetupResult {
completed: true,
account: None,
starting_balance: Money::zero(),
})
}
}
fn prompt_string(prompt: &str) -> EnvelopeResult<String> {
print!("{}", prompt);
io::stdout()
.flush()
.map_err(|e| EnvelopeError::Io(e.to_string()))?;
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.map_err(|e| EnvelopeError::Io(e.to_string()))?;
Ok(input.trim().to_string())
}