sql_cli/data/
data_view.rs

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