1use super::app::{App, AppMode, AppOverlays, DataContext, ModeStates, TabKind};
4use super::app_states::{
5 MatrixState, MultiDiffState, NavigationContext, SourceDiffState, TimelineState,
6};
7use crate::diff::{DiffResult, MatrixResult, MultiDiffResult, TimelineResult};
8use crate::model::NormalizedSbom;
9use crate::quality::{ComplianceChecker, ComplianceLevel, QualityScorer};
10
11impl App {
12 fn base(mode: AppMode) -> Self {
15 Self {
16 mode,
17 active_tab: crate::config::TuiPreferences::load()
18 .last_tab
19 .as_deref()
20 .and_then(TabKind::from_str_opt)
21 .unwrap_or(TabKind::Summary),
22 data: DataContext {
23 diff_result: None,
24 old_sbom: None,
25 new_sbom: None,
26 sbom: None,
27 multi_diff_result: None,
28 timeline_result: None,
29 matrix_result: None,
30 old_sbom_index: None,
31 new_sbom_index: None,
32 sbom_index: None,
33 old_quality: None,
34 new_quality: None,
35 quality_report: None,
36 old_cra_compliance: None,
37 new_cra_compliance: None,
38 old_compliance_results: None,
39 new_compliance_results: None,
40 old_cra_sidecar: None,
41 new_cra_sidecar: None,
42 matching_threshold: 0.85,
43 #[cfg(feature = "enrichment")]
44 enrichment_stats_old: None,
45 #[cfg(feature = "enrichment")]
46 enrichment_stats_new: None,
47 },
48 tabs: ModeStates {
49 multi_diff: MultiDiffState::new(),
50 timeline: TimelineState::new(),
51 matrix: MatrixState::new(),
52 },
53 overlays: AppOverlays::new(),
54 should_quit: false,
55 status_message: None,
56 status_sticky: false,
57 tick: 0,
58 last_export_path: None,
59 navigation_ctx: NavigationContext::new(),
60 security_cache: crate::tui::security::SecurityAnalysisCache::new(),
61 compliance_state: crate::tui::app_states::PolicyComplianceState::new(),
62 export_template: None,
63 tab_window: crate::tui::shared::TabWindow::default(),
64 last_frame_area: None,
65 summary_view: crate::tui::view_states::SummaryView::new(),
66 components_view: crate::tui::view_states::ComponentsView::new(),
67 dependencies_view: crate::tui::view_states::DependenciesView::new(),
68 licenses_view: crate::tui::view_states::LicensesView::new(),
69 vulnerabilities_view: crate::tui::view_states::VulnerabilitiesView::new(),
70 quality_view: crate::tui::view_states::QualityView::new(),
71 compliance_view: crate::tui::view_states::ComplianceView::new(),
72 sidebyside_view: crate::tui::view_states::SideBySideView::new(),
73 graph_changes_view: crate::tui::view_states::GraphChangesView::new(),
74 source_view: crate::tui::view_states::SourceView::new(),
75 }
76 }
77
78 #[must_use]
80 pub fn new_diff(
81 mut diff_result: DiffResult,
82 old_sbom: NormalizedSbom,
83 new_sbom: NormalizedSbom,
84 old_raw: &str,
85 new_raw: &str,
86 ) -> Self {
87 let old_profile =
89 crate::tui::scoring_profile_for(crate::model::BomProfile::detect(&old_sbom));
90 let old_scorer = QualityScorer::new(old_profile);
91 let old_quality = Some(old_scorer.score(&old_sbom));
92
93 let new_profile =
94 crate::tui::scoring_profile_for(crate::model::BomProfile::detect(&new_sbom));
95 let new_scorer = QualityScorer::new(new_profile);
96 let new_quality = Some(new_scorer.score(&new_sbom));
97
98 let old_cra_compliance =
100 Some(ComplianceChecker::new(ComplianceLevel::CraPhase2).check(&old_sbom));
101 let new_cra_compliance =
102 Some(ComplianceChecker::new(ComplianceLevel::CraPhase2).check(&new_sbom));
103
104 let old_sbom_index = Some(old_sbom.build_index());
106 let new_sbom_index = Some(new_sbom.build_index());
107
108 if diff_result.quality_delta.is_none()
111 && let (Some(oldq), Some(newq)) = (old_quality.as_ref(), new_quality.as_ref())
112 {
113 diff_result.quality_delta = Some(crate::diff::QualityDelta::from_reports(oldq, newq));
114 }
115
116 let mut app = Self::base(AppMode::Diff);
117 let mut source = SourceDiffState::new(old_raw, new_raw);
118 source.populate_annotations(&diff_result);
119 app.source_view = crate::tui::view_states::SourceView::with_state(source);
120 app.data.diff_result = Some(diff_result);
121 app.data.old_sbom = Some(old_sbom);
122 app.data.new_sbom = Some(new_sbom);
123 app.data.old_quality = old_quality;
124 app.data.new_quality = new_quality;
125 app.data.old_cra_compliance = old_cra_compliance;
126 app.data.new_cra_compliance = new_cra_compliance;
127 app.data.old_sbom_index = old_sbom_index;
128 app.data.new_sbom_index = new_sbom_index;
129 app
130 }
131
132 #[must_use]
141 pub fn with_cra_sidecars(
142 mut self,
143 old_sidecar: Option<crate::model::CraSidecarMetadata>,
144 new_sidecar: Option<crate::model::CraSidecarMetadata>,
145 ) -> Self {
146 self.data.old_compliance_results = None;
147 self.data.new_compliance_results = None;
148 if let (Some(sidecar), Some(old_sbom)) = (&old_sidecar, &self.data.old_sbom) {
149 self.data.old_cra_compliance = Some(
150 ComplianceChecker::new(ComplianceLevel::CraPhase2)
151 .with_sidecar(sidecar.clone())
152 .check(old_sbom),
153 );
154 }
155 if let (Some(sidecar), Some(new_sbom)) = (&new_sidecar, &self.data.new_sbom) {
156 self.data.new_cra_compliance = Some(
157 ComplianceChecker::new(ComplianceLevel::CraPhase2)
158 .with_sidecar(sidecar.clone())
159 .check(new_sbom),
160 );
161 }
162 self.data.old_cra_sidecar = old_sidecar;
163 self.data.new_cra_sidecar = new_sidecar;
164 self
165 }
166
167 #[must_use]
169 #[cfg(feature = "enrichment")]
170 pub fn with_enrichment_stats(
171 mut self,
172 old_stats: Option<crate::enrichment::EnrichmentStats>,
173 new_stats: Option<crate::enrichment::EnrichmentStats>,
174 ) -> Self {
175 self.data.enrichment_stats_old = old_stats;
176 self.data.enrichment_stats_new = new_stats;
177 self
178 }
179
180 #[cfg(feature = "enrichment")]
182 #[must_use]
183 pub fn combined_enrichment_stats(&self) -> Option<crate::enrichment::EnrichmentStats> {
184 match (
185 &self.data.enrichment_stats_old,
186 &self.data.enrichment_stats_new,
187 ) {
188 (Some(old), Some(new)) => {
189 let mut combined = old.clone();
190 combined.merge(new);
191 Some(combined)
192 }
193 (Some(stats), None) | (None, Some(stats)) => Some(stats.clone()),
194 (None, None) => None,
195 }
196 }
197
198 #[must_use]
200 pub fn new_multi_diff(result: MultiDiffResult) -> Self {
201 let target_count = result.comparisons.len();
202 let variable_count = result.summary.variable_components.len();
205
206 let mut app = Self::base(AppMode::MultiDiff);
207 app.data.multi_diff_result = Some(result);
208 app.tabs.multi_diff = MultiDiffState::new_with_targets(target_count);
209 app.tabs.multi_diff.total_variable_components = variable_count;
210 app
211 }
212
213 #[must_use]
215 pub fn new_timeline(result: TimelineResult) -> Self {
216 let version_count = result.sboms.len();
217 let component_count = crate::tui::views::filtered_evolution_entries(
223 &result,
224 crate::tui::app::TimelineComponentFilter::All,
225 )
226 .len();
227
228 let mut app = Self::base(AppMode::Timeline);
229 app.data.timeline_result = Some(result);
230 app.tabs.timeline = TimelineState::new_with_versions(version_count);
231 app.tabs.timeline.total_components = component_count;
232 app
233 }
234
235 #[must_use]
237 pub fn new_matrix(result: MatrixResult) -> Self {
238 let sbom_count = result.sboms.len();
239 let cluster_count = result.clustering.as_ref().map_or(0, |c| c.clusters.len());
240
241 let mut app = Self::base(AppMode::Matrix);
242 app.data.matrix_result = Some(result);
243 app.tabs.matrix = MatrixState::new_with_size(sbom_count);
244 app.tabs.matrix.total_clusters = cluster_count;
245 app
246 }
247}