sql_cli/data/
data_view.rs

1use anyhow::{anyhow, Result};
2use fuzzy_matcher::skim::SkimMatcherV2;
3use fuzzy_matcher::FuzzyMatcher;
4use serde_json::{json, Value};
5use std::collections::BTreeMap;
6use std::sync::Arc;
7use tracing::{debug, info};
8
9use crate::data::data_provider::DataProvider;
10use crate::data::datatable::{DataRow, DataTable, DataValue};
11use crate::data::datavalue_compare::{compare_datavalues, compare_optional_datavalues};
12
13/// Sort order for columns
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SortOrder {
16    Ascending,
17    Descending,
18    None,
19}
20
21/// Sort state tracking
22#[derive(Debug, Clone)]
23pub struct SortState {
24    /// Currently sorted column index (in visible columns order)
25    pub column: Option<usize>,
26    /// Sort order
27    pub order: SortOrder,
28}
29
30/// Position where virtual columns can be inserted
31#[derive(Debug, Clone, PartialEq)]
32pub enum VirtualColumnPosition {
33    /// Before all real columns (leftmost)
34    Left,
35    /// After all real columns (rightmost)  
36    Right,
37    /// At specific column index
38    Index(usize),
39}
40
41/// A virtual column that generates values dynamically
42#[derive(Clone)]
43pub struct VirtualColumn {
44    /// Column name
45    pub name: String,
46    /// Function that generates cell value for a given row index
47    pub generator: Arc<dyn Fn(usize) -> String + Send + Sync>,
48    /// Preferred width for the column
49    pub width: Option<usize>,
50    /// Position where this column should appear
51    pub position: VirtualColumnPosition,
52}
53
54impl std::fmt::Debug for VirtualColumn {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.debug_struct("VirtualColumn")
57            .field("name", &self.name)
58            .field("width", &self.width)
59            .field("position", &self.position)
60            .finish()
61    }
62}
63
64impl Default for SortState {
65    fn default() -> Self {
66        Self {
67            column: None,
68            order: SortOrder::None,
69        }
70    }
71}
72
73/// Key for grouping rows by column values
74#[derive(Debug, Clone, PartialEq)]
75pub struct GroupKey(pub Vec<DataValue>);
76
77// Manual implementation of Eq for BTreeMap compatibility
78impl Eq for GroupKey {}
79
80// Manual implementation of PartialOrd and Ord for BTreeMap compatibility
81impl PartialOrd for GroupKey {
82    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
83        Some(self.cmp(other))
84    }
85}
86
87impl Ord for GroupKey {
88    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
89        // Compare each DataValue in the vectors using centralized comparison
90        for (a, b) in self.0.iter().zip(&other.0) {
91            let ordering = compare_datavalues(a, b);
92            if ordering != std::cmp::Ordering::Equal {
93                return ordering;
94            }
95        }
96        // If all elements are equal, compare lengths
97        self.0.len().cmp(&other.0.len())
98    }
99}
100
101/// A view over a `DataTable` that can filter, sort, and project columns
102/// without modifying the underlying data
103#[derive(Clone)]
104pub struct DataView {
105    /// The underlying immutable data source
106    source: Arc<DataTable>,
107
108    /// Row indices that are visible (after filtering)
109    visible_rows: Vec<usize>,
110
111    /// Column indices that are visible (after projection)
112    visible_columns: Vec<usize>,
113
114    /// Limit and offset for pagination
115    limit: Option<usize>,
116    offset: usize,
117
118    /// Base rows before any filtering (for restoring after clear filter)
119    /// This allows us to clear filters without losing sort order
120    base_rows: Vec<usize>,
121
122    /// Base columns from the original projection (for restoring after unhide all)
123    /// This preserves the original column selection if view was created with specific columns
124    base_columns: Vec<usize>,
125
126    /// Active text filter pattern (if any)
127    filter_pattern: Option<String>,
128
129    /// Active fuzzy filter pattern (if any) - mutually exclusive with `filter_pattern`
130    fuzzy_filter_pattern: Option<String>,
131
132    /// Column search state
133    column_search_pattern: Option<String>,
134    /// Matching columns for column search (index, name)
135    matching_columns: Vec<(usize, String)>,
136    /// Current column search match index
137    current_column_match: usize,
138
139    /// Pinned columns (always shown on left, in order)
140    pinned_columns: Vec<usize>,
141    /// Maximum number of pinned columns allowed
142    max_pinned_columns: usize,
143
144    /// Sort state
145    sort_state: SortState,
146
147    /// Virtual columns that generate dynamic content
148    virtual_columns: Vec<VirtualColumn>,
149}
150
151impl DataView {
152    /// Create a new view showing all data from the table
153    #[must_use]
154    pub fn new(source: Arc<DataTable>) -> Self {
155        let row_count = source.row_count();
156        let col_count = source.column_count();
157        let all_rows: Vec<usize> = (0..row_count).collect();
158        let all_columns: Vec<usize> = (0..col_count).collect();
159
160        Self {
161            source,
162            visible_rows: all_rows.clone(),
163            visible_columns: all_columns.clone(),
164            limit: None,
165            offset: 0,
166            base_rows: all_rows,
167            base_columns: all_columns,
168            filter_pattern: None,
169            fuzzy_filter_pattern: None,
170            column_search_pattern: None,
171            matching_columns: Vec::new(),
172            current_column_match: 0,
173            pinned_columns: Vec::new(),
174            max_pinned_columns: 4,
175            sort_state: SortState::default(),
176            virtual_columns: Vec::new(),
177        }
178    }
179
180    /// Create a view with specific columns
181    #[must_use]
182    pub fn with_columns(mut self, columns: Vec<usize>) -> Self {
183        self.visible_columns = columns.clone();
184        self.base_columns = columns; // Store as the base projection
185        self
186    }
187
188    /// Hide a column by display index (cannot hide pinned columns)
189    pub fn hide_column(&mut self, display_index: usize) -> bool {
190        // Get the actual source column index from the display index
191        if let Some(&source_column_index) = self.visible_columns.get(display_index) {
192            // Cannot hide a pinned column
193            if self.pinned_columns.contains(&source_column_index) {
194                return false;
195            }
196
197            // Remove the column at the display index position
198            self.visible_columns.remove(display_index);
199            true
200        } else {
201            false
202        }
203    }
204
205    /// Hide a column by name (cannot hide pinned columns)
206    pub fn hide_column_by_name(&mut self, column_name: &str) -> bool {
207        if let Some(source_idx) = self.source.get_column_index(column_name) {
208            // Find the display index for this source column
209            if let Some(display_idx) = self
210                .visible_columns
211                .iter()
212                .position(|&idx| idx == source_idx)
213            {
214                self.hide_column(display_idx)
215            } else {
216                false // Column not visible
217            }
218        } else {
219            false
220        }
221    }
222
223    /// Detect columns that are entirely empty (NULL or empty string) in visible rows
224    pub fn detect_empty_columns(&self) -> Vec<usize> {
225        let mut empty_columns = Vec::new();
226
227        // Get column names once to avoid borrowing issues
228        let column_names = self.source.column_names();
229
230        // Check each visible column
231        for &col_idx in &self.visible_columns {
232            let column_name = column_names
233                .get(col_idx)
234                .map_or("unknown", std::string::String::as_str);
235            let mut is_empty = true;
236            let mut sample_values = Vec::new();
237
238            // Sample rows to check if column has any non-empty values
239            // Check all visible rows up to a reasonable limit for performance
240            let rows_to_check = self.visible_rows.len().min(1000);
241
242            for &row_idx in self.visible_rows.iter().take(rows_to_check) {
243                if let Some(value) = self.source.get_value(row_idx, col_idx) {
244                    // Collect first few values for debugging
245                    if sample_values.len() < 3 {
246                        sample_values.push(format!("{value:?}"));
247                    }
248
249                    match value {
250                        DataValue::Null => continue,
251                        DataValue::String(s) if s.is_empty() => continue,
252                        DataValue::String(s) if s.trim().is_empty() => continue, // Handle whitespace-only
253                        DataValue::String(s) if s.eq_ignore_ascii_case("null") => continue, // Handle "null" strings
254                        DataValue::String(s) if s == "NULL" => continue, // Handle "NULL" strings
255                        DataValue::String(s) if s == "nil" => continue,  // Handle "nil" strings
256                        DataValue::String(s) if s == "undefined" => continue, // Handle "undefined" strings
257                        _ => {
258                            is_empty = false;
259                            break;
260                        }
261                    }
262                }
263            }
264
265            if is_empty {
266                tracing::debug!(
267                    "Column '{}' (idx {}) detected as empty. Sample values: {:?}",
268                    column_name,
269                    col_idx,
270                    sample_values
271                );
272                empty_columns.push(col_idx);
273            } else {
274                tracing::debug!(
275                    "Column '{}' (idx {}) has non-empty values. Sample values: {:?}",
276                    column_name,
277                    col_idx,
278                    sample_values
279                );
280            }
281        }
282
283        tracing::info!(
284            "Detected {} empty columns out of {} visible columns",
285            empty_columns.len(),
286            self.visible_columns.len()
287        );
288        empty_columns
289    }
290
291    /// Hide all columns that are entirely empty
292    /// Returns the number of columns hidden
293    pub fn hide_empty_columns(&mut self) -> usize {
294        let empty_columns = self.detect_empty_columns();
295        let count = empty_columns.len();
296
297        // Fix: detect_empty_columns returns source column indices,
298        // but hide_column expects display indices. We need to convert
299        // source indices to display indices or use hide_column_by_name.
300        let column_names = self.source.column_names();
301        for col_idx in empty_columns {
302            if let Some(column_name) = column_names.get(col_idx) {
303                tracing::debug!(
304                    "Hiding empty column '{}' (source index {})",
305                    column_name,
306                    col_idx
307                );
308                self.hide_column_by_name(column_name);
309            }
310        }
311
312        count
313    }
314
315    /// Unhide all columns (restore to the base column projection)
316    /// This restores to the original column selection, not necessarily all source columns
317    pub fn unhide_all_columns(&mut self) {
318        self.visible_columns = self.base_columns.clone();
319    }
320
321    /// Hide all columns (clear all visible columns)
322    pub fn hide_all_columns(&mut self) {
323        self.visible_columns.clear();
324    }
325
326    /// Check if any columns are visible
327    #[must_use]
328    pub fn has_visible_columns(&self) -> bool {
329        !self.visible_columns.is_empty()
330    }
331
332    /// Move a column left in the view (respects pinned columns)
333    /// With wraparound: moving left from first unpinned position moves to last
334    pub fn move_column_left(&mut self, display_column_index: usize) -> bool {
335        if display_column_index >= self.visible_columns.len() {
336            return false;
337        }
338
339        let pinned_count = self.pinned_columns.len();
340
341        // If trying to move a pinned column
342        if display_column_index < pinned_count {
343            // Move within pinned columns only
344            if display_column_index == 0 {
345                // First pinned column - wrap to last pinned position
346                if pinned_count > 1 {
347                    let col = self.pinned_columns.remove(0);
348                    self.pinned_columns.push(col);
349                    self.rebuild_visible_columns();
350                }
351            } else {
352                // Swap with previous pinned column
353                self.pinned_columns
354                    .swap(display_column_index - 1, display_column_index);
355                self.rebuild_visible_columns();
356            }
357            return true;
358        }
359
360        // Moving an unpinned column - can only move within unpinned area
361        if display_column_index == pinned_count {
362            // First unpinned column - wrap to end
363            let col = self.visible_columns.remove(display_column_index);
364            self.visible_columns.push(col);
365        } else {
366            // Normal swap with previous
367            self.visible_columns
368                .swap(display_column_index - 1, display_column_index);
369        }
370        true
371    }
372
373    /// Move a column right in the view (respects pinned columns)
374    /// With wraparound: moving right from last position moves to first
375    pub fn move_column_right(&mut self, display_column_index: usize) -> bool {
376        if display_column_index >= self.visible_columns.len() {
377            return false;
378        }
379
380        let pinned_count = self.pinned_columns.len();
381
382        // If trying to move a pinned column
383        if display_column_index < pinned_count {
384            // Move within pinned columns only
385            if display_column_index == pinned_count - 1 {
386                // Last pinned column - wrap to first pinned position
387                if pinned_count > 1 {
388                    let col = self.pinned_columns.pop().unwrap();
389                    self.pinned_columns.insert(0, col);
390                    self.rebuild_visible_columns();
391                }
392            } else {
393                // Swap with next pinned column
394                self.pinned_columns
395                    .swap(display_column_index, display_column_index + 1);
396                self.rebuild_visible_columns();
397            }
398            return true;
399        }
400
401        // Moving an unpinned column - can only move within unpinned area
402        if display_column_index == self.visible_columns.len() - 1 {
403            // At last position - wrap to first unpinned
404            let col = self.visible_columns.pop().unwrap();
405            self.visible_columns.insert(pinned_count, col);
406        } else {
407            // Normal swap with next
408            self.visible_columns
409                .swap(display_column_index, display_column_index + 1);
410        }
411        true
412    }
413
414    /// Move a column by name to the left
415    pub fn move_column_left_by_name(&mut self, column_name: &str) -> bool {
416        if let Some(source_idx) = self.source.get_column_index(column_name) {
417            if let Some(visible_idx) = self
418                .visible_columns
419                .iter()
420                .position(|&idx| idx == source_idx)
421            {
422                return self.move_column_left(visible_idx);
423            }
424        }
425        false
426    }
427
428    /// Move a column by name to the right
429    pub fn move_column_right_by_name(&mut self, column_name: &str) -> bool {
430        if let Some(source_idx) = self.source.get_column_index(column_name) {
431            if let Some(visible_idx) = self
432                .visible_columns
433                .iter()
434                .position(|&idx| idx == source_idx)
435            {
436                return self.move_column_right(visible_idx);
437            }
438        }
439        false
440    }
441
442    /// Get the names of hidden columns (columns in source but not visible)
443    #[must_use]
444    pub fn get_hidden_column_names(&self) -> Vec<String> {
445        let all_columns = self.source.column_names();
446        let visible_columns = self.column_names();
447
448        all_columns
449            .into_iter()
450            .filter(|col| !visible_columns.contains(col))
451            .collect()
452    }
453
454    /// Check if there are any hidden columns
455    #[must_use]
456    pub fn has_hidden_columns(&self) -> bool {
457        self.visible_columns.len() < self.source.column_count()
458    }
459
460    // ========== Pinned Column Methods ==========
461
462    /// Pin a column by display index (move it to the pinned area on the left)
463    pub fn pin_column(&mut self, display_index: usize) -> Result<()> {
464        // Get the actual source column index from the display index
465        let source_column_index = if let Some(&idx) = self.visible_columns.get(display_index) {
466            idx
467        } else {
468            return Err(anyhow::anyhow!(
469                "Display index {} out of bounds",
470                display_index
471            ));
472        };
473
474        // Check if we've reached the max
475        if self.pinned_columns.len() >= self.max_pinned_columns {
476            return Err(anyhow::anyhow!(
477                "Maximum {} pinned columns allowed",
478                self.max_pinned_columns
479            ));
480        }
481
482        // Check if already pinned
483        if self.pinned_columns.contains(&source_column_index) {
484            return Ok(()); // Already pinned, no-op
485        }
486
487        // Add to pinned columns
488        self.pinned_columns.push(source_column_index);
489
490        // Rebuild visible_columns to reflect pinned layout: pinned columns first, then unpinned
491        self.rebuild_visible_columns();
492
493        Ok(())
494    }
495
496    /// Rebuild `visible_columns` to reflect current pinned column layout
497    /// Pinned columns come first, followed by unpinned columns in original order
498    fn rebuild_visible_columns(&mut self) {
499        let mut new_visible_columns = Vec::new();
500
501        // Add pinned columns first (in the order they were pinned)
502        for &pinned_idx in &self.pinned_columns {
503            new_visible_columns.push(pinned_idx);
504        }
505
506        // Add non-pinned columns in original order
507        for col_idx in 0..self.source.column_count() {
508            if !self.pinned_columns.contains(&col_idx) {
509                new_visible_columns.push(col_idx);
510            }
511        }
512
513        self.visible_columns = new_visible_columns;
514    }
515
516    /// Pin a column by name
517    pub fn pin_column_by_name(&mut self, column_name: &str) -> Result<()> {
518        if let Some(source_idx) = self.source.get_column_index(column_name) {
519            // Find the display index for this source column
520            if let Some(display_idx) = self
521                .visible_columns
522                .iter()
523                .position(|&idx| idx == source_idx)
524            {
525                self.pin_column(display_idx)
526            } else {
527                Err(anyhow::anyhow!("Column '{}' not visible", column_name))
528            }
529        } else {
530            Err(anyhow::anyhow!("Column '{}' not found", column_name))
531        }
532    }
533
534    /// Unpin a column by display index (move it back to regular visible columns)
535    pub fn unpin_column(&mut self, display_index: usize) -> bool {
536        // Get the actual source column index from the display index
537        if let Some(&source_column_index) = self.visible_columns.get(display_index) {
538            if let Some(pos) = self
539                .pinned_columns
540                .iter()
541                .position(|&idx| idx == source_column_index)
542            {
543                self.pinned_columns.remove(pos);
544
545                // Rebuild visible_columns to reflect new layout
546                self.rebuild_visible_columns();
547
548                true
549            } else {
550                false // Not pinned
551            }
552        } else {
553            false // Invalid display index
554        }
555    }
556
557    /// Unpin a column by name
558    pub fn unpin_column_by_name(&mut self, column_name: &str) -> bool {
559        if let Some(source_idx) = self.source.get_column_index(column_name) {
560            // Find the display index for this source column
561            if let Some(display_idx) = self
562                .visible_columns
563                .iter()
564                .position(|&idx| idx == source_idx)
565            {
566                self.unpin_column(display_idx)
567            } else {
568                false // Column not visible
569            }
570        } else {
571            false
572        }
573    }
574
575    /// Clear all pinned columns
576    pub fn clear_pinned_columns(&mut self) {
577        // Move all pinned columns back to visible
578        for col_idx in self.pinned_columns.drain(..) {
579            if !self.visible_columns.contains(&col_idx) {
580                self.visible_columns.push(col_idx);
581            }
582        }
583    }
584
585    /// Check if a column at display index is pinned
586    #[must_use]
587    pub fn is_column_pinned(&self, display_index: usize) -> bool {
588        if let Some(&source_column_index) = self.visible_columns.get(display_index) {
589            self.pinned_columns.contains(&source_column_index)
590        } else {
591            false
592        }
593    }
594
595    /// Get pinned column indices
596    #[must_use]
597    pub fn get_pinned_columns(&self) -> &[usize] {
598        &self.pinned_columns
599    }
600
601    /// Get the names of pinned columns
602    #[must_use]
603    pub fn get_pinned_column_names(&self) -> Vec<String> {
604        let all_columns = self.source.column_names();
605        self.pinned_columns
606            .iter()
607            .filter_map(|&idx| all_columns.get(idx).cloned())
608            .collect()
609    }
610
611    /// Get display order of columns (pinned first, then visible)
612    #[must_use]
613    pub fn get_display_columns(&self) -> Vec<usize> {
614        // visible_columns already contains pinned columns first, then unpinned
615        // (this is maintained by rebuild_visible_columns)
616        self.visible_columns.clone()
617    }
618
619    /// Get display column names in order (pinned first, then visible)
620    #[must_use]
621    pub fn get_display_column_names(&self) -> Vec<String> {
622        let all_columns = self.source.column_names();
623        self.get_display_columns()
624            .iter()
625            .filter_map(|&idx| all_columns.get(idx).cloned())
626            .collect()
627    }
628
629    /// Set maximum number of pinned columns
630    pub fn set_max_pinned_columns(&mut self, max: usize) {
631        self.max_pinned_columns = max;
632        // If we have too many pinned, unpin the extras from the end
633        while self.pinned_columns.len() > max {
634            if let Some(col_idx) = self.pinned_columns.pop() {
635                self.visible_columns.insert(0, col_idx);
636            }
637        }
638    }
639
640    /// Create a view with specific rows
641    #[must_use]
642    pub fn with_rows(mut self, rows: Vec<usize>) -> Self {
643        self.visible_rows = rows.clone();
644        self.base_rows = rows; // Update base_rows so clear_filter restores to this
645        self
646    }
647
648    /// Apply limit and offset
649    #[must_use]
650    pub fn with_limit(mut self, limit: usize, offset: usize) -> Self {
651        self.limit = Some(limit);
652        self.offset = offset;
653        self
654    }
655
656    /// Filter rows based on a predicate
657    pub fn filter<F>(mut self, predicate: F) -> Self
658    where
659        F: Fn(&DataTable, usize) -> bool,
660    {
661        self.visible_rows
662            .retain(|&row_idx| predicate(&self.source, row_idx));
663        // Also update base_rows so that clearing sort preserves the filter
664        self.base_rows = self.visible_rows.clone();
665        self
666    }
667
668    /// Apply a text filter to the view (filters visible rows)
669    pub fn apply_text_filter(&mut self, pattern: &str, case_sensitive: bool) {
670        info!(
671            "DataView::apply_text_filter - pattern='{}', case_sensitive={}, thread={:?}",
672            pattern,
673            case_sensitive,
674            std::thread::current().id()
675        );
676
677        if pattern.is_empty() {
678            info!("DataView::apply_text_filter - empty pattern, clearing filter");
679            self.clear_filter();
680            return;
681        }
682
683        // Clear any existing fuzzy filter (filters are mutually exclusive)
684        if self.fuzzy_filter_pattern.is_some() {
685            info!("DataView::apply_text_filter - clearing existing fuzzy filter");
686            self.fuzzy_filter_pattern = None;
687        }
688
689        // Store the filter pattern
690        self.filter_pattern = Some(pattern.to_string());
691
692        // Filter from base_rows (not visible_rows) to allow re-filtering
693        let pattern_lower = if case_sensitive {
694            pattern.to_string()
695        } else {
696            pattern.to_lowercase()
697        };
698
699        info!(
700            "DataView::apply_text_filter - searching for '{}' in {} base rows",
701            pattern_lower,
702            self.base_rows.len()
703        );
704
705        let mut matched_count = 0;
706        let mut checked_count = 0;
707
708        self.visible_rows = self
709            .base_rows
710            .iter()
711            .copied()
712            .filter(|&row_idx| {
713                checked_count += 1;
714
715                // Check if any cell in the row contains the pattern
716                if let Some(row) = self.source.get_row(row_idx) {
717                    // Log first few rows for debugging
718                    if checked_count <= 3 {
719                        let preview = row
720                            .values
721                            .iter()
722                            .take(5)
723                            .map(std::string::ToString::to_string)
724                            .collect::<Vec<_>>()
725                            .join(", ");
726                        info!(
727                            "DataView::apply_text_filter - row {} preview: {}",
728                            row_idx, preview
729                        );
730                    }
731
732                    for value in &row.values {
733                        let text = value.to_string();
734                        let text_to_match = if case_sensitive {
735                            text.clone()
736                        } else {
737                            text.to_lowercase()
738                        };
739                        if text_to_match.contains(&pattern_lower) {
740                            matched_count += 1;
741                            if checked_count <= 3 {
742                                info!(
743                                    "DataView::apply_text_filter - MATCH in row {} cell: '{}'",
744                                    row_idx, text
745                                );
746                            }
747                            return true;
748                        }
749                    }
750                }
751                false
752            })
753            .collect();
754
755        info!(
756            "DataView::apply_text_filter - checked {} rows, matched {} rows",
757            checked_count, matched_count
758        );
759        info!(
760            "DataView::apply_text_filter - final visible rows: {}",
761            self.visible_rows.len()
762        );
763
764        // Reapply sort if one was active
765        if let Some(sort_column) = self.sort_state.column {
766            if let Some(source_index) = self.visible_columns.get(sort_column) {
767                let ascending = matches!(self.sort_state.order, SortOrder::Ascending);
768                info!(
769                    "DataView::apply_text_filter - reapplying sort on column {} (ascending={})",
770                    sort_column, ascending
771                );
772                let _ = self.apply_sort_internal(*source_index, ascending);
773            }
774        }
775    }
776
777    /// Clear all filters (both text and fuzzy) and restore all base rows
778    pub fn clear_filter(&mut self) {
779        self.filter_pattern = None;
780        self.fuzzy_filter_pattern = None;
781        self.visible_rows = self.base_rows.clone();
782
783        // Reapply sort if one was active
784        if let Some(sort_column) = self.sort_state.column {
785            if let Some(source_index) = self.visible_columns.get(sort_column) {
786                let ascending = matches!(self.sort_state.order, SortOrder::Ascending);
787                let _ = self.apply_sort_internal(*source_index, ascending);
788            }
789        }
790    }
791
792    /// Check if any filter is active (text or fuzzy)
793    #[must_use]
794    pub fn has_filter(&self) -> bool {
795        self.filter_pattern.is_some() || self.fuzzy_filter_pattern.is_some()
796    }
797
798    /// Get the current text filter pattern
799    #[must_use]
800    pub fn get_filter_pattern(&self) -> Option<&str> {
801        self.filter_pattern.as_deref()
802    }
803
804    /// Get the current fuzzy filter pattern
805    #[must_use]
806    pub fn get_fuzzy_filter_pattern(&self) -> Option<&str> {
807        self.fuzzy_filter_pattern.as_deref()
808    }
809
810    /// Apply a fuzzy filter to the view
811    /// Supports both fuzzy matching and exact matching (when pattern starts with ')
812    pub fn apply_fuzzy_filter(&mut self, pattern: &str, case_insensitive: bool) {
813        info!(
814            "DataView::apply_fuzzy_filter - pattern='{}', case_insensitive={}, thread={:?}",
815            pattern,
816            case_insensitive,
817            std::thread::current().id()
818        );
819
820        if pattern.is_empty() {
821            info!("DataView::apply_fuzzy_filter - empty pattern, clearing filter");
822            self.clear_filter();
823            return;
824        }
825
826        // Clear any existing text filter (filters are mutually exclusive)
827        if self.filter_pattern.is_some() {
828            info!("DataView::apply_fuzzy_filter - clearing existing text filter");
829            self.filter_pattern = None;
830        }
831
832        // Store the fuzzy filter pattern
833        self.fuzzy_filter_pattern = Some(pattern.to_string());
834
835        // Check if pattern starts with ' for exact matching
836        let use_exact = pattern.starts_with('\'');
837
838        self.visible_rows = self
839            .base_rows
840            .iter()
841            .copied()
842            .filter(|&row_idx| {
843                // Get all cell values as a single string for matching
844                if let Some(row) = self.source.get_row(row_idx) {
845                    // Concatenate all cell values with spaces
846                    let row_text = row
847                        .values
848                        .iter()
849                        .map(std::string::ToString::to_string)
850                        .collect::<Vec<_>>()
851                        .join(" ");
852
853                    if use_exact && pattern.len() > 1 {
854                        // Exact substring matching (skip the leading ')
855                        let exact_pattern = &pattern[1..];
856                        if case_insensitive {
857                            row_text
858                                .to_lowercase()
859                                .contains(&exact_pattern.to_lowercase())
860                        } else {
861                            row_text.contains(exact_pattern)
862                        }
863                    } else if !use_exact {
864                        // Fuzzy matching
865                        let matcher = if case_insensitive {
866                            SkimMatcherV2::default().ignore_case()
867                        } else {
868                            SkimMatcherV2::default().respect_case()
869                        };
870
871                        // Check if there's a fuzzy match with score > 0
872                        matcher
873                            .fuzzy_match(&row_text, pattern)
874                            .is_some_and(|score| score > 0)
875                    } else {
876                        // Just a single quote - no pattern to match
877                        false
878                    }
879                } else {
880                    false
881                }
882            })
883            .collect();
884
885        // Reapply sort if one was active
886        if let Some(sort_column) = self.sort_state.column {
887            if let Some(source_index) = self.visible_columns.get(sort_column) {
888                let ascending = matches!(self.sort_state.order, SortOrder::Ascending);
889                info!(
890                    "DataView::apply_fuzzy_filter - reapplying sort on column {} (ascending={})",
891                    sort_column, ascending
892                );
893                let _ = self.apply_sort_internal(*source_index, ascending);
894            }
895        }
896    }
897
898    /// Get indices of rows that match the fuzzy filter (for compatibility)
899    #[must_use]
900    pub fn get_fuzzy_filter_indices(&self) -> Vec<usize> {
901        // Return indices relative to the base data, not the view indices
902        self.visible_rows.clone()
903    }
904
905    /// Get the visible row indices
906    #[must_use]
907    pub fn get_visible_rows(&self) -> Vec<usize> {
908        self.visible_rows.clone()
909    }
910
911    /// Group rows by specified columns, returning a map of group keys to `DataViews`
912    /// Each `DataView` contains only the rows that match the group key
913    pub fn group_by(&self, group_columns: &[String]) -> Result<BTreeMap<GroupKey, DataView>> {
914        let mut groups = BTreeMap::new();
915
916        // Get column indices for grouping columns
917        let col_indices: Vec<usize> = group_columns
918            .iter()
919            .map(|col_name| {
920                self.source
921                    .get_column_index(col_name)
922                    .ok_or_else(|| anyhow!("Column '{}' not found for GROUP BY", col_name))
923            })
924            .collect::<Result<Vec<_>>>()?;
925
926        // Track which rows belong to which group
927        let mut group_rows: BTreeMap<GroupKey, Vec<usize>> = BTreeMap::new();
928
929        // Process each visible row
930        for &row_idx in &self.visible_rows {
931            // Build the group key from this row's values
932            let mut key_values = Vec::new();
933            for &col_idx in &col_indices {
934                let value = self
935                    .source
936                    .get_value(row_idx, col_idx)
937                    .cloned()
938                    .unwrap_or(DataValue::Null);
939                key_values.push(value);
940            }
941            let key = GroupKey(key_values);
942
943            // Add this row to the appropriate group
944            group_rows.entry(key).or_default().push(row_idx);
945        }
946
947        // Create a DataView for each group
948        for (key, rows) in group_rows {
949            let mut group_view = DataView::new(Arc::clone(&self.source));
950            // Set the visible rows to only those in this group
951            group_view.visible_rows = rows;
952            // Preserve the column visibility from the parent view
953            group_view.visible_columns = self.visible_columns.clone();
954            group_view.base_rows = group_view.visible_rows.clone();
955            group_view.base_columns = group_view.visible_columns.clone();
956
957            groups.insert(key, group_view);
958        }
959
960        Ok(groups)
961    }
962
963    /// Sort rows by a column (consuming version - returns new Self)
964    /// The `column_index` parameter is the index in the VISIBLE columns
965    pub fn sort_by(mut self, column_index: usize, ascending: bool) -> Result<Self> {
966        self.apply_sort(column_index, ascending)?;
967        Ok(self)
968    }
969
970    /// Sort rows by a column (mutable version - modifies in place)
971    /// The `column_index` parameter is the index in the VISIBLE columns
972    pub fn apply_sort(&mut self, column_index: usize, ascending: bool) -> Result<()> {
973        // Map visible column index to source column index
974        let source_column_index = if column_index < self.visible_columns.len() {
975            self.visible_columns[column_index]
976        } else {
977            return Err(anyhow::anyhow!(
978                "Column index {} out of bounds (visible columns: {})",
979                column_index,
980                self.visible_columns.len()
981            ));
982        };
983
984        // Use internal sort with source column index
985        self.apply_sort_internal(source_column_index, ascending)?;
986
987        // Update sort state with VISIBLE column index
988        self.sort_state.column = Some(column_index);
989        self.sort_state.order = if ascending {
990            SortOrder::Ascending
991        } else {
992            SortOrder::Descending
993        };
994
995        Ok(())
996    }
997
998    /// Internal sort method that works with source column indices
999    fn apply_sort_internal(&mut self, source_column_index: usize, ascending: bool) -> Result<()> {
1000        if source_column_index >= self.source.column_count() {
1001            return Err(anyhow::anyhow!(
1002                "Source column index {} out of bounds",
1003                source_column_index
1004            ));
1005        }
1006
1007        let source = &self.source;
1008        self.visible_rows.sort_by(|&a, &b| {
1009            let val_a = source.get_value(a, source_column_index);
1010            let val_b = source.get_value(b, source_column_index);
1011
1012            let cmp = compare_optional_datavalues(val_a, val_b);
1013
1014            if ascending {
1015                cmp
1016            } else {
1017                cmp.reverse()
1018            }
1019        });
1020
1021        // Don't update base_rows here - we want to preserve the filtered state
1022        // base_rows should only be set by filter operations, not sort operations
1023
1024        Ok(())
1025    }
1026
1027    /// Apply multi-column sorting
1028    /// Each tuple contains (`source_column_index`, ascending)
1029    pub fn apply_multi_sort(&mut self, sort_columns: &[(usize, bool)]) -> Result<()> {
1030        if sort_columns.is_empty() {
1031            return Ok(());
1032        }
1033
1034        // Validate all column indices first
1035        for (col_idx, _) in sort_columns {
1036            if *col_idx >= self.source.column_count() {
1037                return Err(anyhow::anyhow!(
1038                    "Source column index {} out of bounds",
1039                    col_idx
1040                ));
1041            }
1042        }
1043
1044        let source = &self.source;
1045        self.visible_rows.sort_by(|&a, &b| {
1046            // Compare by each column in order until we find a difference
1047            for (col_idx, ascending) in sort_columns {
1048                let val_a = source.get_value(a, *col_idx);
1049                let val_b = source.get_value(b, *col_idx);
1050
1051                let cmp = compare_optional_datavalues(val_a, val_b);
1052
1053                // If values are different, return the comparison
1054                if cmp != std::cmp::Ordering::Equal {
1055                    return if *ascending { cmp } else { cmp.reverse() };
1056                }
1057                // If equal, continue to next column
1058            }
1059
1060            // All columns are equal
1061            std::cmp::Ordering::Equal
1062        });
1063
1064        // Update sort state to reflect the primary sort column
1065        if let Some((primary_col, ascending)) = sort_columns.first() {
1066            // Find the visible column index for the primary sort column
1067            if let Some(visible_idx) = self.visible_columns.iter().position(|&x| x == *primary_col)
1068            {
1069                self.sort_state.column = Some(visible_idx);
1070                self.sort_state.order = if *ascending {
1071                    SortOrder::Ascending
1072                } else {
1073                    SortOrder::Descending
1074                };
1075            }
1076        }
1077
1078        Ok(())
1079    }
1080
1081    /// Toggle sort on a column - cycles through Ascending -> Descending -> None
1082    /// The `column_index` parameter is the index in the VISIBLE columns
1083    pub fn toggle_sort(&mut self, column_index: usize) -> Result<()> {
1084        // Map visible column index to source column index
1085        let source_column_index = if column_index < self.visible_columns.len() {
1086            self.visible_columns[column_index]
1087        } else {
1088            return Err(anyhow::anyhow!(
1089                "Column index {} out of bounds (visible columns: {})",
1090                column_index,
1091                self.visible_columns.len()
1092            ));
1093        };
1094
1095        // Determine next sort state - track by VISIBLE column index for UI consistency
1096        let next_order = if self.sort_state.column == Some(column_index) {
1097            // Same column - cycle through states
1098            match self.sort_state.order {
1099                SortOrder::None => SortOrder::Ascending,
1100                SortOrder::Ascending => SortOrder::Descending,
1101                SortOrder::Descending => SortOrder::None,
1102            }
1103        } else {
1104            // Different column - start with ascending
1105            SortOrder::Ascending
1106        };
1107
1108        // Apply the sort based on the new state using the SOURCE column index
1109        match next_order {
1110            SortOrder::Ascending => {
1111                self.apply_sort_internal(source_column_index, true)?;
1112                // Store the VISIBLE column index for UI state tracking
1113                self.sort_state.column = Some(column_index);
1114                self.sort_state.order = SortOrder::Ascending;
1115            }
1116            SortOrder::Descending => {
1117                self.apply_sort_internal(source_column_index, false)?;
1118                self.sort_state.column = Some(column_index);
1119                self.sort_state.order = SortOrder::Descending;
1120            }
1121            SortOrder::None => {
1122                self.sort_state.column = None;
1123                self.sort_state.order = SortOrder::None;
1124                self.clear_sort();
1125            }
1126        }
1127
1128        Ok(())
1129    }
1130
1131    /// Get the current sort state
1132    #[must_use]
1133    pub fn get_sort_state(&self) -> &SortState {
1134        &self.sort_state
1135    }
1136
1137    /// Get the visible column indices (for debugging)
1138    /// Returns the internal `visible_columns` array which maps visual positions to source column indices
1139    #[must_use]
1140    pub fn get_visible_column_indices(&self) -> Vec<usize> {
1141        self.visible_columns.clone()
1142    }
1143
1144    /// Clear the current sort and restore original row order
1145    pub fn clear_sort(&mut self) {
1146        // Clear sort state
1147        self.sort_state.column = None;
1148        self.sort_state.order = SortOrder::None;
1149
1150        // Restore to base_rows (which maintains WHERE filtering)
1151        // Don't reset base_rows here - it should preserve any WHERE conditions
1152        self.visible_rows = self.base_rows.clone();
1153
1154        // Reapply any active text filter on top of the base rows
1155        if let Some(pattern) = self.filter_pattern.clone() {
1156            let case_insensitive = false; // Would need to track this
1157            self.apply_text_filter(&pattern, case_insensitive);
1158        }
1159    }
1160
1161    // === Virtual Column Management ===
1162
1163    /// Add a virtual column to the view
1164    pub fn add_virtual_column(&mut self, virtual_column: VirtualColumn) {
1165        self.virtual_columns.push(virtual_column);
1166    }
1167
1168    /// Add a row number virtual column
1169    pub fn add_row_numbers(&mut self, position: VirtualColumnPosition) {
1170        let row_num_column = VirtualColumn {
1171            name: "#".to_string(),
1172            generator: Arc::new(|row_index| format!("{}", row_index + 1)),
1173            width: Some(4), // Room for 4-digit row numbers by default
1174            position,
1175        };
1176        self.add_virtual_column(row_num_column);
1177    }
1178
1179    /// Remove all virtual columns of a specific type by name
1180    pub fn remove_virtual_columns(&mut self, name: &str) {
1181        self.virtual_columns.retain(|col| col.name != name);
1182    }
1183
1184    /// Toggle row numbers on/off
1185    pub fn toggle_row_numbers(&mut self) {
1186        if self.virtual_columns.iter().any(|col| col.name == "#") {
1187            self.remove_virtual_columns("#");
1188        } else {
1189            self.add_row_numbers(VirtualColumnPosition::Left);
1190        }
1191    }
1192
1193    /// Check if row numbers are currently shown
1194    #[must_use]
1195    pub fn has_row_numbers(&self) -> bool {
1196        self.virtual_columns.iter().any(|col| col.name == "#")
1197    }
1198
1199    /// Get all column names including virtual columns in display order
1200    #[must_use]
1201    pub fn get_all_column_names(&self) -> Vec<String> {
1202        let mut result = Vec::new();
1203        let all_source_names = self.source.column_names();
1204        // Use get_display_columns() to get columns in correct order (pinned first)
1205        let real_column_names: Vec<String> = self
1206            .get_display_columns()
1207            .iter()
1208            .map(|&i| {
1209                all_source_names
1210                    .get(i)
1211                    .cloned()
1212                    .unwrap_or_else(|| format!("col_{i}"))
1213            })
1214            .collect();
1215
1216        // Insert virtual columns at their specified positions
1217        let mut virtual_left = Vec::new();
1218        let mut virtual_right = Vec::new();
1219        let mut virtual_indexed = Vec::new();
1220
1221        for vcol in &self.virtual_columns {
1222            match vcol.position {
1223                VirtualColumnPosition::Left => virtual_left.push(vcol.name.clone()),
1224                VirtualColumnPosition::Right => virtual_right.push(vcol.name.clone()),
1225                VirtualColumnPosition::Index(idx) => virtual_indexed.push((idx, vcol.name.clone())),
1226            }
1227        }
1228
1229        // Add left virtual columns
1230        result.extend(virtual_left);
1231
1232        // Add real columns with indexed virtual columns interspersed
1233        for (i, real_name) in real_column_names.into_iter().enumerate() {
1234            // Add any virtual columns that should appear at this index
1235            for (idx, vname) in &virtual_indexed {
1236                if *idx == i {
1237                    result.push(vname.clone());
1238                }
1239            }
1240            result.push(real_name);
1241        }
1242
1243        // Add right virtual columns
1244        result.extend(virtual_right);
1245
1246        result
1247    }
1248
1249    /// Get the number of visible rows
1250    #[must_use]
1251    pub fn row_count(&self) -> usize {
1252        let count = self.visible_rows.len();
1253
1254        // Apply limit if set
1255        if let Some(limit) = self.limit {
1256            let available = count.saturating_sub(self.offset);
1257            available.min(limit)
1258        } else {
1259            count.saturating_sub(self.offset)
1260        }
1261    }
1262
1263    /// Get the number of visible columns (including pinned and virtual)
1264    #[must_use]
1265    pub fn column_count(&self) -> usize {
1266        // visible_columns already includes pinned columns (maintained by rebuild_visible_columns)
1267        self.visible_columns.len() + self.virtual_columns.len()
1268    }
1269
1270    /// Get column names for visible columns (including virtual columns in correct positions)
1271    #[must_use]
1272    pub fn column_names(&self) -> Vec<String> {
1273        self.get_all_column_names()
1274    }
1275
1276    /// Get a row by index (respecting limit/offset) including virtual columns
1277    #[must_use]
1278    pub fn get_row(&self, index: usize) -> Option<DataRow> {
1279        let actual_index = index + self.offset;
1280
1281        // Check if within limit
1282        if let Some(limit) = self.limit {
1283            if index >= limit {
1284                return None;
1285            }
1286        }
1287
1288        // Get the actual row index
1289        let row_idx = *self.visible_rows.get(actual_index)?;
1290
1291        // Build a row with all columns (real + virtual) in display order
1292        let mut values = Vec::new();
1293
1294        // Get real column values
1295        let mut real_values = Vec::new();
1296        for &col_idx in &self.get_display_columns() {
1297            let value = self
1298                .source
1299                .get_value(row_idx, col_idx)
1300                .cloned()
1301                .unwrap_or(DataValue::Null);
1302            real_values.push(value);
1303        }
1304
1305        // Organize virtual columns by position
1306        let mut virtual_left = Vec::new();
1307        let mut virtual_right = Vec::new();
1308        let mut virtual_indexed = Vec::new();
1309
1310        for vcol in &self.virtual_columns {
1311            let virtual_value = DataValue::String((vcol.generator)(row_idx));
1312            match vcol.position {
1313                VirtualColumnPosition::Left => virtual_left.push(virtual_value),
1314                VirtualColumnPosition::Right => virtual_right.push(virtual_value),
1315                VirtualColumnPosition::Index(idx) => virtual_indexed.push((idx, virtual_value)),
1316            }
1317        }
1318
1319        // Add left virtual columns
1320        values.extend(virtual_left);
1321
1322        // Add real columns with indexed virtual columns interspersed
1323        for (i, real_value) in real_values.into_iter().enumerate() {
1324            // Add any virtual columns that should appear at this index
1325            for (idx, vvalue) in &virtual_indexed {
1326                if *idx == i {
1327                    values.push(vvalue.clone());
1328                }
1329            }
1330            values.push(real_value);
1331        }
1332
1333        // Add right virtual columns
1334        values.extend(virtual_right);
1335
1336        Some(DataRow::new(values))
1337    }
1338
1339    /// Get all visible rows (respecting limit/offset)
1340    #[must_use]
1341    pub fn get_rows(&self) -> Vec<DataRow> {
1342        let count = self.row_count();
1343        (0..count).filter_map(|i| self.get_row(i)).collect()
1344    }
1345
1346    /// Get the source `DataTable`
1347    #[must_use]
1348    pub fn source(&self) -> &DataTable {
1349        &self.source
1350    }
1351
1352    /// Get the source `DataTable` as Arc (for memory-efficient sharing)
1353    #[must_use]
1354    pub fn source_arc(&self) -> Arc<DataTable> {
1355        Arc::clone(&self.source)
1356    }
1357
1358    /// Check if a column index is visible (either pinned or regular visible)
1359    #[must_use]
1360    pub fn is_column_visible(&self, index: usize) -> bool {
1361        self.pinned_columns.contains(&index) || self.visible_columns.contains(&index)
1362    }
1363
1364    /// Get visible column indices (not including pinned)
1365    #[must_use]
1366    pub fn visible_column_indices(&self) -> &[usize] {
1367        &self.visible_columns
1368    }
1369
1370    /// Get all display column indices (pinned + visible)
1371    #[must_use]
1372    pub fn display_column_indices(&self) -> Vec<usize> {
1373        self.get_display_columns()
1374    }
1375
1376    /// Get visible row indices (before limit/offset)
1377    #[must_use]
1378    pub fn visible_row_indices(&self) -> &[usize] {
1379        &self.visible_rows
1380    }
1381
1382    /// Optimize memory usage by shrinking vectors to fit
1383    pub fn shrink_to_fit(&mut self) {
1384        self.visible_rows.shrink_to_fit();
1385        self.visible_columns.shrink_to_fit();
1386        self.pinned_columns.shrink_to_fit();
1387        self.base_rows.shrink_to_fit();
1388        self.base_columns.shrink_to_fit();
1389        self.matching_columns.shrink_to_fit();
1390        self.virtual_columns.shrink_to_fit();
1391    }
1392
1393    // ========== Column Search Methods ==========
1394
1395    /// Start or update column search with a pattern
1396    pub fn search_columns(&mut self, pattern: &str) {
1397        self.column_search_pattern = if pattern.is_empty() {
1398            None
1399        } else {
1400            Some(pattern.to_string())
1401        };
1402
1403        if pattern.is_empty() {
1404            self.matching_columns.clear();
1405            self.current_column_match = 0;
1406            return;
1407        }
1408
1409        // Search through visible columns
1410        let pattern_lower = pattern.to_lowercase();
1411        self.matching_columns = self
1412            .visible_columns
1413            .iter()
1414            .enumerate()
1415            .filter_map(|(visible_idx, &source_idx)| {
1416                let col_name = &self.source.columns[source_idx].name;
1417                if col_name.to_lowercase().contains(&pattern_lower) {
1418                    debug!(target: "column_search", 
1419                        "Found match: '{}' at visible_idx={}, source_idx={}", 
1420                        col_name, visible_idx, source_idx);
1421                    Some((visible_idx, col_name.clone()))
1422                } else {
1423                    None
1424                }
1425            })
1426            .collect();
1427
1428        debug!(target: "column_search", 
1429            "Total matches found: {}, visible_columns.len()={}, pattern='{}'", 
1430            self.matching_columns.len(), self.visible_columns.len(), pattern);
1431
1432        // Reset to first match
1433        self.current_column_match = 0;
1434    }
1435
1436    /// Clear column search
1437    pub fn clear_column_search(&mut self) {
1438        self.column_search_pattern = None;
1439        self.matching_columns.clear();
1440        self.current_column_match = 0;
1441    }
1442
1443    /// Go to next column search match
1444    pub fn next_column_match(&mut self) -> Option<usize> {
1445        if self.matching_columns.is_empty() {
1446            return None;
1447        }
1448
1449        self.current_column_match = (self.current_column_match + 1) % self.matching_columns.len();
1450        Some(self.matching_columns[self.current_column_match].0)
1451    }
1452
1453    /// Go to previous column search match
1454    pub fn prev_column_match(&mut self) -> Option<usize> {
1455        if self.matching_columns.is_empty() {
1456            return None;
1457        }
1458
1459        if self.current_column_match == 0 {
1460            self.current_column_match = self.matching_columns.len() - 1;
1461        } else {
1462            self.current_column_match -= 1;
1463        }
1464        Some(self.matching_columns[self.current_column_match].0)
1465    }
1466
1467    /// Get current column search pattern
1468    #[must_use]
1469    pub fn column_search_pattern(&self) -> Option<&str> {
1470        self.column_search_pattern.as_deref()
1471    }
1472
1473    /// Get matching columns from search
1474    #[must_use]
1475    pub fn get_matching_columns(&self) -> &[(usize, String)] {
1476        &self.matching_columns
1477    }
1478
1479    /// Get current column match index
1480    #[must_use]
1481    pub fn current_column_match_index(&self) -> usize {
1482        self.current_column_match
1483    }
1484
1485    /// Get current column match (visible column index)
1486    #[must_use]
1487    pub fn get_current_column_match(&self) -> Option<usize> {
1488        if self.matching_columns.is_empty() {
1489            None
1490        } else {
1491            Some(self.matching_columns[self.current_column_match].0)
1492        }
1493    }
1494
1495    /// Check if column search is active
1496    #[must_use]
1497    pub fn has_column_search(&self) -> bool {
1498        self.column_search_pattern.is_some()
1499    }
1500
1501    /// Get only real column names (excluding virtual columns) in display order
1502    fn get_real_column_names(&self) -> Vec<String> {
1503        let all_source_names = self.source.column_names();
1504        let display_columns = self.get_display_columns();
1505
1506        display_columns
1507            .iter()
1508            .filter_map(|&idx| all_source_names.get(idx).cloned())
1509            .collect()
1510    }
1511
1512    /// Extract only real column values from a row (excluding virtual column values)
1513    fn extract_real_values_from_row(&self, full_row: &DataRow) -> Vec<DataValue> {
1514        let mut real_values = Vec::new();
1515        let mut value_idx = 0;
1516
1517        // Count left virtual columns to skip
1518        let left_virtual_count = self
1519            .virtual_columns
1520            .iter()
1521            .filter(|vc| matches!(vc.position, VirtualColumnPosition::Left))
1522            .count();
1523
1524        // Skip left virtual columns
1525        value_idx += left_virtual_count;
1526
1527        // Collect real column values
1528        let real_column_count = self.get_display_columns().len();
1529        for _ in 0..real_column_count {
1530            if value_idx < full_row.values.len() {
1531                real_values.push(full_row.values[value_idx].clone());
1532                value_idx += 1;
1533            }
1534        }
1535
1536        real_values
1537    }
1538
1539    /// Export the visible data as JSON
1540    /// Returns an array of objects where each object represents a row
1541    #[must_use]
1542    pub fn to_json(&self) -> Value {
1543        // Use only real columns for export, not virtual columns
1544        let column_names = self.get_real_column_names();
1545        let mut rows = Vec::new();
1546
1547        // Iterate through visible rows
1548        for row_idx in 0..self.row_count() {
1549            if let Some(full_row) = self.get_row(row_idx) {
1550                // Extract only the real column values (skip virtual columns)
1551                let real_values = self.extract_real_values_from_row(&full_row);
1552
1553                let mut obj = serde_json::Map::new();
1554                for (col_idx, col_name) in column_names.iter().enumerate() {
1555                    if let Some(value) = real_values.get(col_idx) {
1556                        let json_value = match value {
1557                            DataValue::String(s) => json!(s),
1558                            DataValue::InternedString(s) => json!(s.as_ref()),
1559                            DataValue::Integer(i) => json!(i),
1560                            DataValue::Float(f) => json!(f),
1561                            DataValue::Boolean(b) => json!(b),
1562                            DataValue::DateTime(dt) => json!(dt),
1563                            DataValue::Null => json!(null),
1564                        };
1565                        obj.insert(col_name.clone(), json_value);
1566                    }
1567                }
1568                rows.push(json!(obj));
1569            }
1570        }
1571
1572        json!(rows)
1573    }
1574
1575    /// Export the visible data as CSV string
1576    pub fn to_csv(&self) -> Result<String> {
1577        let mut csv_output = String::new();
1578        // Use only real columns for export, not virtual columns
1579        let column_names = self.get_real_column_names();
1580
1581        // Write header
1582        csv_output.push_str(&column_names.join(","));
1583        csv_output.push('\n');
1584
1585        // Write data rows
1586        for row_idx in 0..self.row_count() {
1587            if let Some(full_row) = self.get_row(row_idx) {
1588                // Extract only the real column values (skip virtual columns)
1589                let real_values = self.extract_real_values_from_row(&full_row);
1590
1591                let row_strings: Vec<String> = real_values
1592                    .iter()
1593                    .map(|v| {
1594                        let s = v.to_string();
1595                        // Quote values that contain commas, quotes, or newlines
1596                        if s.contains(',') || s.contains('"') || s.contains('\n') {
1597                            format!("\"{}\"", s.replace('"', "\"\""))
1598                        } else {
1599                            s
1600                        }
1601                    })
1602                    .collect();
1603                csv_output.push_str(&row_strings.join(","));
1604                csv_output.push('\n');
1605            }
1606        }
1607
1608        Ok(csv_output)
1609    }
1610
1611    /// Export the visible data as TSV (Tab-Separated Values) string
1612    pub fn to_tsv(&self) -> Result<String> {
1613        let mut tsv_output = String::new();
1614        // Use only real columns for export, not virtual columns
1615        let column_names = self.get_real_column_names();
1616
1617        // Write header
1618        tsv_output.push_str(&column_names.join("\t"));
1619        tsv_output.push('\n');
1620
1621        // Write data rows
1622        for row_idx in 0..self.row_count() {
1623            if let Some(full_row) = self.get_row(row_idx) {
1624                // Extract only the real column values (skip virtual columns)
1625                let real_values = self.extract_real_values_from_row(&full_row);
1626
1627                let row_strings: Vec<String> = real_values
1628                    .iter()
1629                    .map(std::string::ToString::to_string)
1630                    .collect();
1631                tsv_output.push_str(&row_strings.join("\t"));
1632                tsv_output.push('\n');
1633            }
1634        }
1635
1636        Ok(tsv_output)
1637    }
1638
1639    /// Get all values from a specific column (respecting filters and visible rows)
1640    pub fn get_column_values(&self, column_index: usize) -> Vec<String> {
1641        use tracing::trace;
1642
1643        let mut values = Vec::new();
1644        let row_count = self.row_count();
1645
1646        trace!(
1647            "get_column_values: Getting column {} values from {} visible rows",
1648            column_index,
1649            row_count
1650        );
1651
1652        for row_idx in 0..row_count {
1653            // get_row already respects filters and limit/offset
1654            if let Some(row) = self.get_row(row_idx) {
1655                // column_index is the visual column index (what the user sees)
1656                // row.values is already in visual column order (only display columns)
1657                if let Some(value) = row.values.get(column_index) {
1658                    let str_value = value
1659                        .to_string()
1660                        .replace('\t', "    ")
1661                        .replace('\n', " ")
1662                        .replace('\r', "");
1663                    values.push(str_value);
1664                } else {
1665                    values.push("NULL".to_string());
1666                }
1667            }
1668        }
1669
1670        trace!("get_column_values: Retrieved {} values", values.len());
1671        values
1672    }
1673
1674    /// Get a single cell value (respecting filters)
1675    #[must_use]
1676    pub fn get_cell_value(&self, row_index: usize, column_index: usize) -> Option<String> {
1677        // get_row already respects filters and returns values in visual column order
1678        if let Some(row) = self.get_row(row_index) {
1679            // column_index is the visual column index (what the user sees)
1680            // row.values is already in visual column order (only display columns)
1681            row.values
1682                .get(column_index)
1683                .map(std::string::ToString::to_string)
1684        } else {
1685            None
1686        }
1687    }
1688
1689    /// Get a row as string values (respecting filters)
1690    #[must_use]
1691    pub fn get_row_values(&self, row_index: usize) -> Option<Vec<String>> {
1692        self.get_row(row_index).map(|row| {
1693            row.values
1694                .iter()
1695                .map(std::string::ToString::to_string)
1696                .collect()
1697        })
1698    }
1699
1700    /// Get row values in visual column order (only visible columns)
1701    /// This returns data in the same order as `get_display_column_names()`
1702    #[must_use]
1703    pub fn get_row_visual_values(&self, row_index: usize) -> Option<Vec<String>> {
1704        if let Some(row) = self.get_row(row_index) {
1705            // The row already has values in display order (hidden columns excluded)
1706            // Just convert to strings
1707            let values: Vec<String> = row
1708                .values
1709                .iter()
1710                .map(std::string::ToString::to_string)
1711                .collect();
1712            Some(values)
1713        } else {
1714            None
1715        }
1716    }
1717
1718    /// Get column index mapping for debugging
1719    /// Returns a mapping of visible column index -> (column name, datatable index)
1720    #[must_use]
1721    pub fn get_column_index_mapping(&self) -> Vec<(usize, String, usize)> {
1722        let mut mappings = Vec::new();
1723
1724        for (visible_idx, &datatable_idx) in self.visible_columns.iter().enumerate() {
1725            if let Some(column) = self.source.columns.get(datatable_idx) {
1726                mappings.push((visible_idx, column.name.clone(), datatable_idx));
1727            }
1728        }
1729
1730        mappings
1731    }
1732
1733    /// Get debug information about column visibility and ordering
1734    #[must_use]
1735    pub fn get_column_debug_info(&self) -> String {
1736        let mut info = String::new();
1737        info.push_str("Column Mapping (Visible → DataTable):\n");
1738
1739        let total_columns = self.source.columns.len();
1740        let visible_count = self.visible_columns.len();
1741        let hidden_count = total_columns - visible_count;
1742
1743        info.push_str(&format!(
1744            "Total: {total_columns} columns, Visible: {visible_count}, Hidden: {hidden_count}\n\n"
1745        ));
1746
1747        // Show visible columns with their mappings
1748        for (visible_idx, &datatable_idx) in self.visible_columns.iter().enumerate() {
1749            if let Some(column) = self.source.columns.get(datatable_idx) {
1750                let pinned_marker = if self.pinned_columns.contains(&datatable_idx) {
1751                    " [PINNED]"
1752                } else {
1753                    ""
1754                };
1755                info.push_str(&format!(
1756                    "  V[{:3}] → DT[{:3}] : {}{}\n",
1757                    visible_idx, datatable_idx, column.name, pinned_marker
1758                ));
1759            }
1760        }
1761
1762        // Show hidden columns if any
1763        if hidden_count > 0 {
1764            info.push_str("\nHidden Columns:\n");
1765            for (idx, column) in self.source.columns.iter().enumerate() {
1766                if !self.visible_columns.contains(&idx) {
1767                    info.push_str(&format!("  DT[{:3}] : {}\n", idx, column.name));
1768                }
1769            }
1770        }
1771
1772        // Show pinned columns summary
1773        if !self.pinned_columns.is_empty() {
1774            info.push_str(&format!("\nPinned Columns: {:?}\n", self.pinned_columns));
1775        }
1776
1777        // Show column order changes if any
1778        let is_reordered = self.visible_columns.windows(2).any(|w| w[0] > w[1]);
1779
1780        if is_reordered {
1781            info.push_str("\n⚠️ Column order has been modified from original DataTable order\n");
1782        }
1783
1784        info
1785    }
1786}
1787
1788// Implement DataProvider for compatibility during migration
1789// This allows DataView to be used where DataProvider is expected
1790impl DataProvider for DataView {
1791    fn get_row(&self, index: usize) -> Option<Vec<String>> {
1792        self.get_row(index).map(|row| {
1793            row.values
1794                .iter()
1795                .map(std::string::ToString::to_string)
1796                .collect()
1797        })
1798    }
1799
1800    fn get_column_names(&self) -> Vec<String> {
1801        self.column_names()
1802    }
1803
1804    fn get_row_count(&self) -> usize {
1805        self.row_count()
1806    }
1807
1808    fn get_column_count(&self) -> usize {
1809        self.column_count()
1810    }
1811
1812    fn get_column_widths(&self) -> Vec<usize> {
1813        // Calculate column widths based on visible data
1814        let mut widths = vec![0; self.column_count()];
1815
1816        // Start with column name widths
1817        for (i, name) in self.column_names().iter().enumerate() {
1818            widths[i] = name.len();
1819        }
1820
1821        // Sample visible rows for width calculation
1822        // Only check first 100 visible rows for performance
1823        let sample_size = 100.min(self.row_count());
1824        for row_idx in 0..sample_size {
1825            if let Some(row) = self.get_row(row_idx) {
1826                for (col_idx, value) in row.values.iter().enumerate() {
1827                    if col_idx < widths.len() {
1828                        let display_len = value.to_string().len();
1829                        widths[col_idx] = widths[col_idx].max(display_len);
1830                    }
1831                }
1832            }
1833        }
1834
1835        // Get terminal width to apply smart limits
1836        let terminal_width = crossterm::terminal::size()
1837            .map(|(w, _)| w as usize)
1838            .unwrap_or(120);
1839
1840        // Calculate a reasonable max width based on terminal size
1841        // Reserve space for borders, scrollbars, etc
1842        let available_width = terminal_width.saturating_sub(10);
1843        let visible_cols = self.visible_columns.len().min(10);
1844
1845        // Dynamic max width: divide available space, but cap at 80 chars
1846        let dynamic_max = if visible_cols > 0 {
1847            (available_width / visible_cols).min(80).max(20)
1848        } else {
1849            40
1850        };
1851
1852        // Apply max width limit but ensure minimum readability
1853        for width in &mut widths {
1854            *width = (*width).clamp(6, dynamic_max);
1855        }
1856
1857        widths
1858    }
1859}
1860
1861// Also implement Debug for DataView to satisfy DataProvider requirements
1862impl std::fmt::Debug for DataView {
1863    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1864        f.debug_struct("DataView")
1865            .field("source_name", &self.source.name)
1866            .field("visible_rows", &self.visible_rows.len())
1867            .field("visible_columns", &self.visible_columns.len())
1868            .field("has_filter", &self.filter_pattern.is_some())
1869            .field("has_column_search", &self.column_search_pattern.is_some())
1870            .finish()
1871    }
1872}
1873
1874#[cfg(test)]
1875mod tests {
1876    use super::*;
1877    use crate::data::datatable::{DataColumn, DataRow, DataTable, DataValue};
1878    use std::sync::Arc;
1879
1880    // TODO: Fix this test - temporarily disabled
1881    // #[test]
1882    #[allow(dead_code)]
1883    fn test_hide_empty_columns_index_fix() {
1884        // Create a test DataTable with mixed empty and non-empty columns
1885        let mut table = DataTable::new("test");
1886
1887        // Add columns: name(0), empty1(1), salary(2), empty2(3), department(4)
1888        table.add_column(DataColumn::new("name"));
1889        table.add_column(DataColumn::new("empty1")); // Should be hidden
1890        table.add_column(DataColumn::new("salary"));
1891        table.add_column(DataColumn::new("empty2")); // Should be hidden
1892        table.add_column(DataColumn::new("department"));
1893
1894        // Add test data
1895        table
1896            .add_row(DataRow::new(vec![
1897                DataValue::String("John".to_string()),
1898                DataValue::Null,
1899                DataValue::Integer(50000),
1900                DataValue::Null,
1901                DataValue::String("Engineering".to_string()),
1902            ]))
1903            .unwrap();
1904
1905        table
1906            .add_row(DataRow::new(vec![
1907                DataValue::String("Jane".to_string()),
1908                DataValue::Null,
1909                DataValue::Integer(60000),
1910                DataValue::Null,
1911                DataValue::String("Marketing".to_string()),
1912            ]))
1913            .unwrap();
1914
1915        // Create DataView with all columns visible initially
1916        let mut dataview = DataView::new(Arc::new(table));
1917
1918        // Initial state: all 5 columns should be visible
1919        assert_eq!(dataview.column_count(), 5);
1920
1921        // Hide empty columns - this should hide empty1(1) and empty2(3)
1922        let hidden_count = dataview.hide_empty_columns();
1923
1924        // Should have hidden exactly 2 columns
1925        assert_eq!(hidden_count, 2);
1926
1927        // Should now have 3 columns visible: name(0), salary(2), department(4)
1928        assert_eq!(dataview.column_count(), 3);
1929
1930        // Get final column names
1931        let final_columns = dataview.column_names();
1932
1933        // Verify the correct columns remain visible
1934        assert_eq!(final_columns[0], "name");
1935        assert_eq!(final_columns[1], "salary");
1936        assert_eq!(final_columns[2], "department");
1937
1938        // Verify hidden columns
1939        let hidden_columns = dataview.get_hidden_column_names();
1940        assert!(hidden_columns.contains(&"empty1".to_string()));
1941        assert!(hidden_columns.contains(&"empty2".to_string()));
1942        assert_eq!(hidden_columns.len(), 2);
1943    }
1944}