1use anyhow::Result;
4use clap::Args;
5use wowlab_common::output;
6use wowlab_types::game::SpecId;
7
8use crate::{
9 encounter_fixture::EncounterFixture,
10 provider::{ComparisonConfig, SimProvider},
11 render,
12 run::{FightDurationSeconds, IterationCount, RunParameters, RunParametersError},
13 simc::SimcProvider,
14 trace,
15 wowlab::WowlabProvider,
16};
17
18#[derive(Args, Debug)]
19#[expect(
20 clippy::struct_excessive_bools,
21 reason = "independent CLI feature switches are represented directly by clap"
22)]
23pub(crate) struct CompareArgs {
24 pub spec: String,
26
27 #[arg(long)]
29 pub timeline: bool,
30
31 #[arg(long)]
33 pub resources: bool,
34
35 #[arg(long, default_value = "30")]
37 pub timeline_duration: u32,
38
39 #[arg(long, default_value = "300")]
41 pub duration: u32,
42
43 #[arg(long, default_value = "1")]
45 pub iterations: u32,
46
47 #[arg(long)]
49 pub trace: bool,
50
51 #[arg(long)]
53 pub race: Option<String>,
54
55 #[arg(long, value_enum)]
57 pub encounter_fixture: Option<EncounterFixture>,
58
59 #[arg(long)]
61 pub no_bugs: bool,
62
63 #[arg(long)]
65 pub simc_debug: bool,
66}
67
68impl TryFrom<&CompareArgs> for RunParameters {
69 type Error = RunParametersError;
70
71 fn try_from(args: &CompareArgs) -> Result<Self, Self::Error> {
72 let fight_duration = FightDurationSeconds::try_from(args.duration)?;
73 let iterations = IterationCount::try_from(args.iterations)?;
74
75 Ok(Self::new(iterations, fight_duration))
76 }
77}
78
79pub(crate) fn run(args: &CompareArgs) -> Result<()> {
80 let parameters = RunParameters::try_from(args)?;
81 let spec = SpecId::from_manifest_slug(&args.spec)
82 .ok_or_else(|| anyhow::anyhow!("unknown spec slug: {}", args.spec))?;
83
84 let trace_path = trace::init_trace_log(args.trace, &args.spec)?;
85
86 output::header(&format!("Comparing {} (wowlab vs SimC)", args.spec));
87
88 if let Some(ref path) = trace_path {
89 output::detail(&format!("Trace log: {}", path.display()));
90 }
91
92 output::blank();
93
94 let config = ComparisonConfig {
95 spec,
96 parameters,
97 race: args.race.clone(),
98 bugs: !args.no_bugs,
99 simc_debug: args.simc_debug,
100 };
101
102 let wl_provider = WowlabProvider::new(args.encounter_fixture);
103 let sc_provider = SimcProvider;
104
105 let wl = wl_provider.run(&config)?;
106 let sc = sc_provider.run(&config)?;
107
108 if let Some(fixture) = args.encounter_fixture {
109 render::print_encounter_banner(&args.spec, parameters, fixture.slug());
110 } else {
111 render::print_banner(&args.spec, parameters);
112 }
113
114 render::print_dps_comparison(&wl, &sc, wl_provider.name(), sc_provider.name());
115 render::print_spell_comparison(&wl, &sc);
116
117 if let Some(report) = &wl.encounter_debug {
118 crate::encounter_debug::print_report(report);
119 }
120
121 if args.resources {
122 render::print_resource_comparison(&wl, &sc);
123 }
124
125 if args.timeline {
126 render::print_timeline(&wl, &sc, f64::from(args.timeline_duration));
127 }
128
129 if let Some(ref path) = trace_path {
130 output::blank();
131 output::detail(&format!("Trace log written to: {}", path.display()));
132 }
133
134 Ok(())
135}