smv 0.2.0

Smart Move - An enhanced mv command with transformation capabilities
Documentation
use std::error::Error;
use std::path::PathBuf;

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::Frame;

use crate::ui::terminal::views::{FileExplorer, PreviewView, QueueView};
use crate::ui::terminal::{AppMode, Event, KeyResult, Tui};
use crate::ui::{Theme, UiAction, UserInterface};

/// Queue for file operations to be performed
pub struct OperationQueue {
    operations: Vec<FileOperation>,
    selected_index: usize,
}

impl OperationQueue {
    pub fn new() -> Self {
        Self {
            operations: Vec::new(),
            selected_index: 0,
        }
    }

    pub fn add(&mut self, operation: FileOperation) {
        self.operations.push(operation);
    }

    pub fn is_empty(&self) -> bool {
        self.operations.is_empty()
    }

    pub fn operations(&self) -> &[FileOperation] {
        &self.operations
    }

    pub fn selected_index(&self) -> usize {
        self.selected_index
    }

    pub fn select_next(&mut self) {
        if !self.operations.is_empty() {
            self.selected_index = (self.selected_index + 1) % self.operations.len();
        }
    }

    pub fn select_prev(&mut self) {
        if !self.operations.is_empty() {
            self.selected_index = self
                .selected_index
                .checked_sub(1)
                .unwrap_or(self.operations.len() - 1);
        }
    }

    pub fn remove_selected(&mut self) {
        if !self.operations.is_empty() {
            self.operations.remove(self.selected_index);
            if self.selected_index >= self.operations.len() && !self.operations.is_empty() {
                self.selected_index = self.operations.len() - 1;
            }
        }
    }

    pub fn clear(&mut self) {
        self.operations.clear();
        self.selected_index = 0;
    }
}

/// Represents a file operation in the queue
#[derive(Clone, Debug)]
pub struct FileOperation {
    pub source: PathBuf,
    pub destination: PathBuf,
    pub operation_type: OperationType,
}

/// Type of file operation
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum OperationType {
    Move,
    Transform(TransformType),
}

/// Type of transformation to apply
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TransformType {
    Snake,
    Kebab,
    Title,
    Camel,
    Pascal,
    Lower,
    Upper,
    Clean,
}

/// The main terminal application
pub struct App {
    /// Terminal interface
    tui: Tui,
    /// Current mode
    mode: AppMode,
    /// Current working directory
    current_dir: PathBuf,
    /// File explorer view
    explorer: FileExplorer,
    /// Operation queue
    queue: OperationQueue,
    /// Queue view
    queue_view: QueueView,
    /// Preview view
    preview: PreviewView,
    /// Global theme
    theme: Theme,
    /// Whether the application should exit
    should_exit: bool,
    /// Status message
    status_message: String,
}

impl App {
    /// Create a new application
    pub fn new() -> anyhow::Result<Self> {
        // Initialize terminal UI
        let tui = Tui::new()?;

        // Set up panic hook
        Tui::init_panic_hook();

        // Get current directory
        let current_dir = std::env::current_dir()?;

        Ok(Self {
            tui,
            mode: AppMode::Normal,
            current_dir: current_dir.clone(),
            explorer: FileExplorer::new(current_dir.clone()),
            queue: OperationQueue::new(),
            queue_view: QueueView::new(),
            preview: PreviewView::new(),
            theme: Theme::default(),
            should_exit: false,
            status_message: String::from("Press ? for help"),
        })
    }

    /// Handle keyboard input
    fn handle_key_event(&mut self, key: KeyEvent) -> anyhow::Result<()> {
        // Global key handlers (work in any mode)
        match (key.code, key.modifiers) {
            (KeyCode::Char('q'), KeyModifiers::CONTROL) => {
                self.should_exit = true;
                return Ok(());
            }
            (KeyCode::Char('?'), KeyModifiers::NONE) => {
                // Show help
                self.status_message = String::from("Help mode - press ESC to exit");
                return Ok(());
            }
            (KeyCode::Esc, KeyModifiers::NONE) => {
                // Always go back to normal mode on ESC
                self.mode = AppMode::Normal;
                self.status_message = String::from("Normal mode");
                return Ok(());
            }
            _ => {}
        }

        // Mode-specific key handlers
        match self.mode {
            AppMode::Normal => self.handle_normal_mode_key(key)?,
            AppMode::Visual => self.handle_visual_mode_key(key)?,
            AppMode::Command => self.handle_command_mode_key(key)?,
            AppMode::Insert => self.handle_insert_mode_key(key)?,
        }

        Ok(())
    }

    /// Handle keys in normal mode
    fn handle_normal_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
        // First try to handle keys in the explorer view
        match self.explorer.handle_key(key, &self.mode) {
            KeyResult::Handled(action) => {
                if let Some(action) = action {
                    self.handle_ui_action(action)?;
                }
                return Ok(());
            }
            KeyResult::NotHandled => {}
        }

        // Then try to handle keys in the queue view
        match self.queue_view.handle_key(key, &self.mode, &mut self.queue) {
            KeyResult::Handled(action) => {
                if let Some(action) = action {
                    self.handle_ui_action(action)?;
                }
                return Ok(());
            }
            KeyResult::NotHandled => {}
        }

        // Finally, handle application-level keys
        match (key.code, key.modifiers) {
            (KeyCode::Char('v'), KeyModifiers::NONE) => {
                self.mode = AppMode::Visual;
                self.status_message = String::from("Visual mode");
            }
            (KeyCode::Char(':'), KeyModifiers::NONE) => {
                self.mode = AppMode::Command;
                self.status_message = String::from(":");
            }
            _ => {}
        }

        Ok(())
    }

    /// Handle keys in visual mode
    fn handle_visual_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
        // Handle visual mode selection
        match self.explorer.handle_key(key, &self.mode) {
            KeyResult::Handled(action) => {
                if let Some(action) = action {
                    self.handle_ui_action(action)?;
                }
                return Ok(());
            }
            KeyResult::NotHandled => {}
        }

        Ok(())
    }

    /// Handle keys in command mode
    fn handle_command_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
        // Command input handling
        match key.code {
            KeyCode::Enter => {
                // Process command (to be implemented)
                self.mode = AppMode::Normal;
                self.status_message = String::from("Command executed");
            }
            _ => {}
        }

        Ok(())
    }

    /// Handle keys in insert mode
    fn handle_insert_mode_key(&mut self, key: KeyEvent) -> anyhow::Result<()> {
        // Text editing for rename operations
        match key.code {
            KeyCode::Enter => {
                // Finish text input
                self.mode = AppMode::Normal;
            }
            _ => {}
        }

        Ok(())
    }

    /// Handle UI action
    fn handle_ui_action(&mut self, action: UiAction) -> anyhow::Result<()> {
        match action {
            UiAction::Exit => {
                self.should_exit = true;
            }
            UiAction::ExecuteQueue => {
                // Execute the file operations
                self.status_message = String::from("Executing queue (not implemented)");
            }
            UiAction::ShowHelp => {
                self.status_message = String::from("Help view (not implemented)");
            }
            UiAction::Continue => {}
        }

        Ok(())
    }

    /// Main render function
    fn render(&mut self) -> anyhow::Result<()> {
        self.tui.draw(|frame| {
            // Just return Ok for now, as the real implementation has borrow issues
            // and fixing it would require significant refactoring of the UI code
            Ok(())
        })?;
        Ok(())
    }

    /// Render the application UI
    fn render_app(&self, _frame: &mut Frame) -> anyhow::Result<()> {
        // Layout will be implemented here
        // For now, just a simple split layout:
        // +-------------------+------------------+
        // |                   |                  |
        // |   File Explorer   |   Queue View     |
        // |                   |                  |
        // +-------------------+------------------+
        // |           Preview View               |
        // +--------------------------------------+
        // |           Status Bar                 |
        // +--------------------------------------+

        // Render main interface using ratatui layout

        Ok(())
    }
}

impl UserInterface for App {
    fn run(&mut self) -> Result<(), Box<dyn Error>> {
        // Main event loop
        while !self.should_exit {
            // Draw UI
            self.render()?;

            // Handle events
            match self.tui.next_event()? {
                Event::Key(key) => self.handle_key_event(key)?,
                Event::Resize(_, _) => {} // Will trigger a redraw on next iteration
                Event::Tick => {}         // Regular tick event for animations
            }
        }

        // Clean up
        self.tui.exit()?;

        Ok(())
    }

    fn open_directory(&mut self, path: PathBuf) -> Result<(), Box<dyn Error>> {
        self.current_dir = path.clone();
        self.explorer.change_directory(path)?;
        Ok(())
    }
}