use anyhow::Result;
use bookforge_core::config::SegmentationConfig;
use bookforge_core::segment::build_segments;
use bookforge_epub::read_epub;
use clap::Args;
use std::path::PathBuf;
use crate::{
LanguageArgs, ProviderArgs,
cost::{estimate_cost_usd_with_pricing, load_pricing},
};
#[derive(Debug, Args)]
pub struct EstimateArgs {
pub input: PathBuf,
#[command(flatten)]
pub language: LanguageArgs,
#[command(flatten)]
pub provider: ProviderArgs,
#[arg(long)]
pub pricing: Option<PathBuf>,
}
pub async fn run(args: EstimateArgs) -> Result<()> {
let book = read_epub(&args.input)?;
let segments = build_segments(&book, &SegmentationConfig::default())?;
let input_tokens = segments
.iter()
.map(|segment| segment.source.token_estimate as u64)
.sum::<u64>();
let output_tokens = (input_tokens as f64 * 1.15).ceil() as u64;
let model = args
.provider
.model
.as_deref()
.unwrap_or_else(|| default_model(&args.provider.provider));
let pricing = load_pricing(args.pricing.as_deref())?;
println!("Input: {}", args.input.display());
println!("Target: {}", args.language.target);
println!("Provider: {}", args.provider.provider);
println!("Model: {model}");
println!("Segments: {}", segments.len());
println!("Estimated input tokens: {input_tokens}");
println!("Estimated output tokens: {output_tokens}");
println!("Pricing: {}", pricing.source_label());
match estimate_cost_usd_with_pricing(
&pricing,
&args.provider.provider,
model,
input_tokens,
0,
output_tokens,
) {
Some(cost) => println!("Estimated cost: ${cost:.6}"),
None => println!("Estimated cost: unavailable for this provider/model"),
}
Ok(())
}
fn default_model(provider: &str) -> &str {
match provider {
"mock" => "mock-prefix-target",
"deepseek" => "deepseek-v4-flash",
"openrouter" => "openrouter/auto",
_ => "unknown",
}
}