checkpoint-ts 0.1.2

Interactive checkpoint system for TypeScript/JavaScript development. Debug with TUI, inspect state, skip functions, inject values.
use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect},
    style::Stylize,
    widgets::{Block, Borders, Cell, Paragraph, Row, Table},
};

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct VariableEditorState {
    pub current_function: Option<String>,
    pub current_line: Option<u32>,
    pub function_parameters: Vec<Variable>,
    pub local_variables: Vec<Variable>,
    pub selected_section: SelectedSection,
    pub selected_index: usize,
}

#[derive(Clone, Debug)]
pub struct Variable {
    pub name: String,
    pub var_type: String,
    pub value: String,
    pub is_editing: bool,
}

#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum SelectedSection {
    Parameters,
    LocalVariables,
    Actions,
}

impl Default for VariableEditorState {
    fn default() -> Self {
        Self {
            current_function: Some("calculateTax".to_string()),
            current_line: Some(45),
            function_parameters: vec![
                Variable {
                    name: "price".to_string(),
                    var_type: "number".to_string(),
                    value: "99.99".to_string(),
                    is_editing: false,
                },
                Variable {
                    name: "quantity".to_string(),
                    var_type: "number".to_string(),
                    value: "3".to_string(),
                    is_editing: false,
                },
                Variable {
                    name: "region".to_string(),
                    var_type: "string".to_string(),
                    value: "\"US\"".to_string(),
                    is_editing: false,
                },
            ],
            local_variables: vec![
                Variable {
                    name: "subtotal".to_string(),
                    var_type: "number".to_string(),
                    value: "299.97".to_string(),
                    is_editing: false,
                },
                Variable {
                    name: "taxRate".to_string(),
                    var_type: "number".to_string(),
                    value: "undefined".to_string(),
                    is_editing: false,
                },
                Variable {
                    name: "finalAmount".to_string(),
                    var_type: "number".to_string(),
                    value: "undefined".to_string(),
                    is_editing: false,
                },
            ],
            selected_section: SelectedSection::Parameters,
            selected_index: 0,
        }
    }
}

impl VariableEditorState {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn _set_function(&mut self, function: String, line: u32) {
        self.current_function = Some(function);
        self.current_line = Some(line);
    }

    pub fn _add_parameter(&mut self, variable: Variable) {
        self.function_parameters.push(variable);
    }

    pub fn _add_local_variable(&mut self, variable: Variable) {
        self.local_variables.push(variable);
    }

    pub fn _update_variable_value(
        &mut self,
        section: SelectedSection,
        index: usize,
        new_value: String,
    ) {
        match section {
            SelectedSection::Parameters => {
                if let Some(var) = self.function_parameters.get_mut(index) {
                    var.value = new_value;
                }
            }
            SelectedSection::LocalVariables => {
                if let Some(var) = self.local_variables.get_mut(index) {
                    var.value = new_value;
                }
            }
            _ => {}
        }
    }

    pub fn _start_editing(&mut self, section: SelectedSection, index: usize) {
        match section {
            SelectedSection::Parameters => {
                if let Some(var) = self.function_parameters.get_mut(index) {
                    var.is_editing = true;
                }
            }
            SelectedSection::LocalVariables => {
                if let Some(var) = self.local_variables.get_mut(index) {
                    var.is_editing = true;
                }
            }
            _ => {}
        }
    }

    pub fn _stop_editing(&mut self) {
        for var in &mut self.function_parameters {
            var.is_editing = false;
        }
        for var in &mut self.local_variables {
            var.is_editing = false;
        }
    }
}

pub fn draw(frame: &mut Frame, area: Rect, state: &VariableEditorState) {
    let main_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3),
            Constraint::Min(6),
            Constraint::Min(6),
            Constraint::Length(3),
        ])
        .split(area);

    draw_function_info(frame, main_layout[0], state);
    draw_parameters_table(frame, main_layout[1], state);
    draw_local_variables_table(frame, main_layout[2], state);
    draw_actions(frame, main_layout[3]);
}

fn draw_function_info(frame: &mut Frame, area: Rect, state: &VariableEditorState) {
    let function_name = state.current_function.as_deref().unwrap_or("Unknown");
    let line_number = state.current_line.unwrap_or(0);

    let info_text = format!(
        "Function: {}()                            Line: {}",
        function_name, line_number
    );

    frame.render_widget(
        Paragraph::new(info_text).block(
            Block::default()
                .borders(Borders::ALL)
                .title("Variable Editor".blue().bold()),
        ),
        area,
    );
}

fn draw_parameters_table(frame: &mut Frame, area: Rect, state: &VariableEditorState) {
    let header = Row::new(vec![
        Cell::from("Name").style(ratatui::style::Style::default().bold()),
        Cell::from("Type").style(ratatui::style::Style::default().bold()),
        Cell::from("Current Value").style(ratatui::style::Style::default().bold()),
        Cell::from("Actions").style(ratatui::style::Style::default().bold()),
    ]);

    let rows: Vec<Row> = state
        .function_parameters
        .iter()
        .map(|var| {
            let actions = if var.is_editing {
                "[Editing...] [Enter] [Esc]"
            } else {
                "[Edit] [Reset] [Watch]"
            };

            Row::new(vec![
                Cell::from(var.name.clone()),
                Cell::from(var.var_type.clone()),
                Cell::from(var.value.clone()),
                Cell::from(actions),
            ])
        })
        .collect();

    let table = Table::new(
        rows,
        [
            Constraint::Percentage(20),
            Constraint::Percentage(15),
            Constraint::Percentage(35),
            Constraint::Percentage(30),
        ],
    )
    .header(header)
    .block(
        Block::default()
            .borders(Borders::ALL)
            .title("Function Parameters".yellow().bold()),
    );

    frame.render_widget(table, area);
}

fn draw_local_variables_table(frame: &mut Frame, area: Rect, state: &VariableEditorState) {
    let header = Row::new(vec![
        Cell::from("Name").style(ratatui::style::Style::default().bold()),
        Cell::from("Type").style(ratatui::style::Style::default().bold()),
        Cell::from("Current Value").style(ratatui::style::Style::default().bold()),
        Cell::from("Actions").style(ratatui::style::Style::default().bold()),
    ]);

    let rows: Vec<Row> = state
        .local_variables
        .iter()
        .map(|var| {
            let actions = if var.is_editing {
                "[Editing...] [Enter] [Esc]"
            } else {
                "[Edit] [Reset] [Watch]"
            };

            Row::new(vec![
                Cell::from(var.name.clone()),
                Cell::from(var.var_type.clone()),
                Cell::from(var.value.clone()),
                Cell::from(actions),
            ])
        })
        .collect();

    let table = Table::new(
        rows,
        [
            Constraint::Percentage(20),
            Constraint::Percentage(15),
            Constraint::Percentage(35),
            Constraint::Percentage(30),
        ],
    )
    .header(header)
    .block(
        Block::default()
            .borders(Borders::ALL)
            .title("Local Variables".cyan().bold()),
    );

    frame.render_widget(table, area);
}

fn draw_actions(frame: &mut Frame, area: Rect) {
    let actions_text = "[Enter] Apply Changes    [Esc] Cancel    [Tab] Next Field    [R] Reset All";

    frame.render_widget(
        Paragraph::new(actions_text).block(
            Block::default()
                .borders(Borders::ALL)
                .title("Actions".green().bold()),
        ),
        area,
    );
}