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