use crate::data::dataframe::DataFrame;
use crate::data::swap;
use crate::types::SheetType;
use crate::ui::text_input::TextInput;
use ratatui::widgets::{ScrollbarState, TableState};
use std::collections::HashMap;
use std::path::PathBuf;
use tempfile::TempDir;
pub struct Sheet {
pub title: String,
pub dataframe: DataFrame,
pub undo_stack: Vec<DataFrame>,
pub table_state: TableState,
pub cursor_col: usize,
pub scroll_state: ScrollbarState,
pub top_row: usize,
pub left_col: usize,
pub sort_col: Option<usize>,
pub sort_desc: bool,
pub search_input: TextInput,
pub search_pattern: Option<String>,
pub search_col: Option<usize>,
pub select_regex_input: TextInput,
pub expr_input: TextInput,
pub edit_input: TextInput,
pub edit_row: usize,
pub edit_col: usize,
pub rename_column_input: TextInput,
pub insert_column_input: TextInput,
pub is_dir_sheet: bool,
pub sqlite_db_path: Option<std::path::PathBuf>,
pub pivot_input: TextInput,
pub sheet_type: SheetType,
}
impl Sheet {
pub fn new(title: String, dataframe: DataFrame) -> Self {
let row_count = dataframe.visible_row_count();
Self {
title,
dataframe,
undo_stack: Vec::new(),
table_state: TableState::default()
.with_selected(0)
.with_selected_column(0),
cursor_col: 0,
scroll_state: ScrollbarState::new(row_count.saturating_sub(1)),
top_row: 0,
left_col: 0,
sort_col: None,
sort_desc: false,
search_input: TextInput::new(),
search_pattern: None,
search_col: None,
select_regex_input: TextInput::new(),
expr_input: TextInput::new(),
edit_input: TextInput::new(),
edit_row: 0,
edit_col: 0,
rename_column_input: TextInput::new(),
insert_column_input: TextInput::new(),
is_dir_sheet: false,
sqlite_db_path: None,
pivot_input: TextInput::new(),
sheet_type: SheetType::Normal,
}
}
pub fn push_undo(&mut self) {
if self.undo_stack.len() >= 50 {
self.undo_stack.remove(0);
}
self.undo_stack.push(self.dataframe.clone());
}
pub fn pop_undo(&mut self) -> bool {
if let Some(df) = self.undo_stack.pop() {
self.dataframe = df;
let cols = self.dataframe.columns.len();
let rows = self.dataframe.visible_row_count();
if self.cursor_col >= cols && cols > 0 {
self.cursor_col = cols.saturating_sub(1);
}
if let Some(s) = self.table_state.selected() {
if s >= rows && rows > 0 {
self.table_state.select(Some(rows.saturating_sub(1)));
}
}
true
} else {
false
}
}
}
pub struct SheetStack {
sheets: Vec<Sheet>,
_swap_dir: TempDir,
swap_root: PathBuf,
swapped: HashMap<usize, PathBuf>,
}
impl SheetStack {
pub fn new(root_sheet: Sheet) -> Self {
let swap_dir = TempDir::new().expect("Failed to create temp dir for sheet swap");
let swap_root = swap_dir.path().to_path_buf();
Self {
sheets: vec![root_sheet],
_swap_dir: swap_dir,
swap_root,
swapped: HashMap::new(),
}
}
pub fn active(&self) -> &Sheet {
self.sheets.last().expect("Sheet stack must never be empty")
}
pub fn active_mut(&mut self) -> &mut Sheet {
self.sheets
.last_mut()
.expect("Sheet stack must never be empty")
}
pub fn depth(&self) -> usize {
self.sheets.len()
}
pub fn can_pop(&self) -> bool {
self.sheets.len() > 1
}
pub fn push(&mut self, sheet: Sheet) {
let prev_idx = self.sheets.len() - 1;
self.swap_out(prev_idx);
self.sheets.push(sheet);
}
pub fn pop(&mut self) -> Sheet {
assert!(self.sheets.len() > 1, "Cannot pop the root sheet");
let popped = self.sheets.pop().unwrap();
let new_top = self.sheets.len() - 1;
self.swap_in(new_top);
popped
}
pub fn clone_parent_dataframe(&mut self) -> Option<DataFrame> {
let depth = self.sheets.len();
if depth < 2 {
return None;
}
let parent_idx = depth - 2;
let was_swapped = self.swapped.contains_key(&parent_idx);
if was_swapped {
self.swap_in(parent_idx);
}
let df = self.sheets[parent_idx].dataframe.clone();
if was_swapped {
self.swap_out(parent_idx);
}
Some(df)
}
fn swap_out(&mut self, idx: usize) {
if self.swapped.contains_key(&idx) {
return; }
let path = self.swap_root.join(format!("sheet_{}.bin", idx));
swap::swap_out(&self.sheets[idx].dataframe, &path)
.expect("Failed to write sheet data to disk");
self.sheets[idx].dataframe = DataFrame::empty();
self.swapped.insert(idx, path);
}
fn swap_in(&mut self, idx: usize) {
if let Some(path) = self.swapped.remove(&idx) {
let df = swap::swap_in(&path).expect("Failed to read sheet data from disk");
self.sheets[idx].dataframe = df;
let _ = std::fs::remove_file(&path);
}
}
}