sbom-tools 0.1.18

Semantic SBOM diff and analysis tool
Documentation
//! Matrix state types.

use super::multi_diff::{MultiViewSearchState, SortDirection};
use crate::tui::views::MatrixPanel;

pub struct MatrixState {
    pub selected_row: usize,
    pub selected_col: usize,
    pub sbom_count: usize,
    pub active_panel: MatrixPanel,
    /// Search state
    pub search: MultiViewSearchState,
    /// Sort by field
    pub sort_by: MatrixSortBy,
    /// Sort direction
    pub sort_direction: SortDirection,
    /// Similarity threshold filter
    pub threshold: SimilarityThreshold,
    /// Zoom/Focus mode - highlight single row/column
    pub focus_mode: bool,
    /// Focused row index (when `focus_mode` is true)
    pub focus_row: Option<usize>,
    /// Focused column index (when `focus_mode` is true)
    pub focus_col: Option<usize>,
    /// Show diff for selected pair
    pub show_pair_diff: bool,
    /// Show export options
    pub show_export_options: bool,
    /// Show clustering details
    pub show_clustering_details: bool,
    /// Highlight current row/column
    pub highlight_row_col: bool,
    /// Selected cluster index (for navigation)
    pub selected_cluster: usize,
    /// Total clusters
    pub total_clusters: usize,
}

impl MatrixState {
    pub fn new() -> Self {
        Self {
            selected_row: 0,
            selected_col: 1,
            sbom_count: 0,
            active_panel: MatrixPanel::Matrix,
            search: MultiViewSearchState::new(),
            sort_by: MatrixSortBy::default(),
            sort_direction: SortDirection::default(),
            threshold: SimilarityThreshold::default(),

            focus_mode: false,
            focus_row: None,
            focus_col: None,
            show_pair_diff: false,
            show_export_options: false,
            show_clustering_details: false,
            highlight_row_col: true,
            selected_cluster: 0,
            total_clusters: 0,
        }
    }

    pub fn new_with_size(count: usize) -> Self {
        Self {
            selected_row: 0,
            selected_col: usize::from(count > 1),
            sbom_count: count,
            active_panel: MatrixPanel::Matrix,
            search: MultiViewSearchState::new(),
            sort_by: MatrixSortBy::default(),
            sort_direction: SortDirection::default(),
            threshold: SimilarityThreshold::default(),

            focus_mode: false,
            focus_row: None,
            focus_col: None,
            show_pair_diff: false,
            show_export_options: false,
            show_clustering_details: false,
            highlight_row_col: true,
            selected_cluster: 0,
            total_clusters: 0,
        }
    }

    pub const fn move_up(&mut self) {
        if self.selected_row > 0 {
            self.selected_row -= 1;
        }
    }

    pub const fn move_down(&mut self) {
        if self.sbom_count > 0 && self.selected_row < self.sbom_count - 1 {
            self.selected_row += 1;
        }
    }

    pub const fn move_left(&mut self) {
        if self.selected_col > 0 {
            self.selected_col -= 1;
        }
    }

    pub const fn move_right(&mut self) {
        if self.sbom_count > 0 && self.selected_col < self.sbom_count - 1 {
            self.selected_col += 1;
        }
    }

    pub const fn toggle_panel(&mut self) {
        self.active_panel = match self.active_panel {
            MatrixPanel::Matrix => MatrixPanel::Details,
            MatrixPanel::Details => MatrixPanel::Matrix,
        };
    }

    pub const fn toggle_sort(&mut self) {
        self.sort_by = self.sort_by.next();
    }

    pub const fn toggle_sort_direction(&mut self) {
        self.sort_direction.toggle();
    }

    pub const fn toggle_threshold(&mut self) {
        self.threshold = self.threshold.next();
    }

    pub const fn toggle_focus_mode(&mut self) {
        self.focus_mode = !self.focus_mode;
        if self.focus_mode {
            self.focus_row = Some(self.selected_row);
            self.focus_col = Some(self.selected_col);
        } else {
            self.focus_row = None;
            self.focus_col = None;
        }
    }

    pub const fn focus_on_row(&mut self, row: usize) {
        self.focus_mode = true;
        self.focus_row = Some(row);
        self.focus_col = None;
    }

    pub const fn focus_on_col(&mut self, col: usize) {
        self.focus_mode = true;
        self.focus_row = None;
        self.focus_col = Some(col);
    }

    pub const fn clear_focus(&mut self) {
        self.focus_mode = false;
        self.focus_row = None;
        self.focus_col = None;
    }

    pub const fn toggle_pair_diff(&mut self) {
        // Only toggle if not same cell
        if self.selected_row != self.selected_col {
            self.show_pair_diff = !self.show_pair_diff;
        }
    }

    pub const fn close_pair_diff(&mut self) {
        self.show_pair_diff = false;
    }

    pub const fn toggle_export_options(&mut self) {
        self.show_export_options = !self.show_export_options;
    }

    pub const fn close_export_options(&mut self) {
        self.show_export_options = false;
    }

    pub const fn toggle_clustering_details(&mut self) {
        self.show_clustering_details = !self.show_clustering_details;
    }

    pub const fn close_clustering_details(&mut self) {
        self.show_clustering_details = false;
    }

    pub const fn toggle_row_col_highlight(&mut self) {
        self.highlight_row_col = !self.highlight_row_col;
    }

    pub const fn select_next_cluster(&mut self) {
        if self.total_clusters > 0 && self.selected_cluster < self.total_clusters - 1 {
            self.selected_cluster += 1;
        }
    }

    pub const fn select_prev_cluster(&mut self) {
        if self.selected_cluster > 0 {
            self.selected_cluster -= 1;
        }
    }

    /// Check if cell passes current threshold filter
    pub fn passes_threshold(&self, similarity: f64) -> bool {
        match self.threshold {
            SimilarityThreshold::None => true,
            SimilarityThreshold::High => similarity >= 0.9,
            SimilarityThreshold::Medium => similarity >= 0.7,
            SimilarityThreshold::Low => similarity < 0.5,
        }
    }
}

impl Default for MatrixState {
    fn default() -> Self {
        Self::new()
    }
}

/// Similarity threshold presets for matrix view
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SimilarityThreshold {
    #[default]
    None,
    /// Show only high similarity (>= 90%)
    High,
    /// Show only medium similarity (>= 70%)
    Medium,
    /// Show only low similarity (< 50%)
    Low,
}

impl SimilarityThreshold {
    pub const fn label(self) -> &'static str {
        match self {
            Self::None => "All",
            Self::High => ">= 90%",
            Self::Medium => ">= 70%",
            Self::Low => "< 50%",
        }
    }

    pub const fn next(self) -> Self {
        match self {
            Self::None => Self::High,
            Self::High => Self::Medium,
            Self::Medium => Self::Low,
            Self::Low => Self::None,
        }
    }
}

/// Sort options for matrix view
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MatrixSortBy {
    #[default]
    Name,
    AvgSimilarity,
    ComponentCount,
    Cluster,
}

impl MatrixSortBy {
    pub const fn label(self) -> &'static str {
        match self {
            Self::Name => "Name",
            Self::AvgSimilarity => "Avg Similarity",
            Self::ComponentCount => "Components",
            Self::Cluster => "Cluster",
        }
    }

    pub const fn next(self) -> Self {
        match self {
            Self::Name => Self::AvgSimilarity,
            Self::AvgSimilarity => Self::ComponentCount,
            Self::ComponentCount => Self::Cluster,
            Self::Cluster => Self::Name,
        }
    }
}