oyo 0.1.50

CLI TUI for oyo - Step-through diff viewer
use crate::blame::BlameInfo;
use crate::config::{
    DiffExtentMarkerMode, DiffExtentMarkerScope, DiffForegroundMode, DiffHighlightMode,
    FoldContextMode, SyntaxMode,
};
use crate::syntax::SyntaxSide;
use oyo_core::diff::DiffResult;
use oyo_core::{multi::BlameSource, AnimationFrame, StepDirection};
use ratatui::style::Color;
use ratatui::text::Line;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;

/// Animation phase for smooth transitions
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationPhase {
    /// No animation happening
    Idle,
    /// Fading out the old content
    FadeOut,
    /// Fading in the new content
    FadeIn,
}

/// View mode for displaying diffs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ViewMode {
    /// Single pane showing both old and new with markers
    #[default]
    UnifiedPane,
    /// Split view with old on left, new on right
    Split,
    /// Evolution view - shows file morphing, deletions just disappear
    Evolution,
    /// Blame view - code with per-line blame gutter
    Blame,
    /// Preview view - rendered markdown or source text
    Preview,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct FoldContextKey {
    pub(crate) file_index: usize,
    pub(crate) start_change_id: usize,
    pub(crate) end_change_id: usize,
}

#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct FoldContextExpansion {
    pub(crate) top: usize,
    pub(crate) bottom: usize,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum FoldContextDirection {
    Top,
    Bottom,
}

#[derive(Clone, Debug)]
pub(crate) struct FoldContextRegion {
    pub(crate) key: FoldContextKey,
    pub(crate) hidden_lines: usize,
    pub(crate) scope_line: Option<usize>,
    pub(crate) scope_hint: Option<String>,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct FoldContextHit {
    pub(crate) x: u16,
    pub(crate) y: u16,
    pub(crate) width: u16,
    pub(crate) height: u16,
    pub(crate) key: FoldContextKey,
    pub(crate) direction: FoldContextDirection,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct FoldContextRenderRow {
    pub(crate) row: usize,
    pub(crate) key: FoldContextKey,
    pub(crate) top_x: u16,
    pub(crate) top_width: u16,
    pub(crate) bottom_x: u16,
    pub(crate) bottom_width: u16,
}

impl ViewMode {
    /// Cycle to the next view mode
    pub fn next(self) -> Self {
        match self {
            ViewMode::UnifiedPane => ViewMode::Split,
            ViewMode::Split => ViewMode::Evolution,
            ViewMode::Evolution => ViewMode::Blame,
            ViewMode::Blame => ViewMode::Preview,
            ViewMode::Preview => ViewMode::UnifiedPane,
        }
    }

    /// Cycle to the previous view mode
    pub fn prev(self) -> Self {
        match self {
            ViewMode::UnifiedPane => ViewMode::Blame,
            ViewMode::Split => ViewMode::UnifiedPane,
            ViewMode::Evolution => ViewMode::Split,
            ViewMode::Blame => ViewMode::Evolution,
            ViewMode::Preview => ViewMode::Blame,
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct HunkStart {
    pub(crate) idx: usize,
    pub(crate) change_id: Option<usize>,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct HunkBounds {
    pub(crate) start: HunkStart,
    pub(crate) end: HunkStart,
}

pub(crate) const FILE_PANEL_MIN_WIDTH: u16 = 24;
pub(crate) const DIFF_VIEW_MIN_WIDTH: u16 = 50;

#[derive(Clone, Copy, Debug)]
pub(crate) struct NoStepState {
    pub(crate) current_hunk: usize,
    pub(crate) cursor_change: Option<usize>,
    pub(crate) last_nav_was_hunk: bool,
}

#[derive(Clone, Copy, Debug)]
pub(crate) enum StepEdge {
    Start,
    End,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct StepEdgeHint {
    pub(crate) change_id: Option<usize>,
    pub(crate) edge: StepEdge,
    pub(crate) until: Instant,
}

#[derive(Clone, Copy, Debug)]
pub(crate) enum HunkEdge {
    First,
    Last,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct HunkEdgeHint {
    pub(crate) edge: HunkEdge,
    pub(crate) until: Instant,
}

#[derive(Clone, Debug)]
pub(crate) struct BlameStepHint {
    pub(crate) change_id: usize,
    pub(crate) text: String,
}

#[derive(Clone, Debug)]
pub(crate) struct BlameDisplay {
    pub(crate) group_key: String,
    pub(crate) text: String,
    pub(crate) author_time: Option<i64>,
    pub(crate) uncommitted: bool,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) struct BlameCacheKey {
    pub(crate) path: PathBuf,
    pub(crate) line: usize,
    pub(crate) source: BlameSource,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) struct BlamePrefetchKey {
    pub(crate) path: PathBuf,
    pub(crate) source: BlameSource,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct BlamePrefetchRange {
    pub(crate) start: usize,
    pub(crate) end: usize,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct BlameRenderKey {
    pub(crate) file_index: usize,
    pub(crate) current_step: usize,
    pub(crate) current_hunk: usize,
    pub(crate) hunk_preview_mode: bool,
    pub(crate) preview_from_backward: bool,
    pub(crate) stepping: bool,
    pub(crate) line_wrap: bool,
    pub(crate) wrap_width: usize,
    pub(crate) blame_width: u16,
    pub(crate) view_len: usize,
    pub(crate) window_start: usize,
    pub(crate) animation_frame: AnimationFrame,
    pub(crate) cache_rev: u64,
    pub(crate) time_bucket: i64,
}

pub(crate) struct BlameRenderCache {
    pub(crate) key: BlameRenderKey,
    pub(crate) wrap_counts: Vec<usize>,
    pub(crate) extra_rows_after_line: Vec<usize>,
    pub(crate) extra_texts_after_line: Vec<Vec<String>>,
    pub(crate) display_texts: Vec<String>,
    pub(crate) bar_colors: Vec<Option<Color>>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct UnifiedRenderKey {
    pub(crate) file_index: usize,
    pub(crate) frame: AnimationFrame,
    pub(crate) current_step: usize,
    pub(crate) active_change: Option<usize>,
    pub(crate) cursor_change: Option<usize>,
    pub(crate) peek_state: Option<PeekState>,
    pub(crate) animating_hunk: Option<usize>,
    pub(crate) step_direction: StepDirection,
    pub(crate) current_hunk: usize,
    pub(crate) last_nav_was_hunk: bool,
    pub(crate) hunk_preview_mode: bool,
    pub(crate) preview_from_backward: bool,
    pub(crate) show_hunk_extent_while_stepping: bool,
    pub(crate) placeholder_view: bool,
    pub(crate) fold_context: FoldContextMode,
    pub(crate) viewport_height: usize,
    pub(crate) windowed: bool,
    pub(crate) window_start: usize,
    pub(crate) stepping: bool,
    pub(crate) line_wrap: bool,
    pub(crate) wrap_width: usize,
    pub(crate) scroll_offset: usize,
    pub(crate) horizontal_scroll: usize,
    pub(crate) diff_bg: bool,
    pub(crate) diff_fg: DiffForegroundMode,
    pub(crate) diff_highlight: DiffHighlightMode,
    pub(crate) diff_extent_marker: DiffExtentMarkerMode,
    pub(crate) diff_extent_marker_scope: DiffExtentMarkerScope,
    pub(crate) diff_extent_marker_context: bool,
    pub(crate) gutter_signs: bool,
    pub(crate) strikethrough_deletions: bool,
    pub(crate) search_query: String,
    pub(crate) search_active: bool,
    pub(crate) search_revision: u64,
    pub(crate) diff_revision: u64,
    pub(crate) syntax_mode: SyntaxMode,
    pub(crate) syntax_theme: String,
    pub(crate) theme_is_light: bool,
    pub(crate) syntax_epoch: u64,
    pub(crate) step_edge_hint: bool,
    pub(crate) hunk_edge_hint: bool,
    pub(crate) blame_hunk_hint: Option<String>,
    pub(crate) review_mode: bool,
    pub(crate) review_editor_active: bool,
    pub(crate) review_revision: u64,
    pub(crate) review_preview_hover: Option<String>,
    pub(crate) review_preview_hover_id: Option<u64>,
    pub(crate) review_preview_edit_hover: Option<u64>,
    pub(crate) review_preview_reply_hover: Option<u64>,
    pub(crate) review_preview_resolve_hover: Option<u64>,
    pub(crate) review_preview_delete_hover: Option<u64>,
    pub(crate) review_preview_overflow_hover: Option<u64>,
    pub(crate) review_preview_flash: Option<u64>,
    pub(crate) fold_context_hover: Option<(FoldContextKey, FoldContextDirection)>,
}

pub(crate) struct UnifiedRenderModel {
    pub(crate) key: UnifiedRenderKey,
    pub(crate) gutter_lines: Vec<Line<'static>>,
    pub(crate) content_lines: Vec<Line<'static>>,
    pub(crate) bg_lines: Option<Vec<Line<'static>>>,
    pub(crate) display_len: usize,
    pub(crate) max_line_width: usize,
    pub(crate) primary_display_idx: Option<usize>,
    pub(crate) active_display_idx: Option<usize>,
    /// Preview rows for review comments.
    pub(crate) review_preview_rows: Vec<crate::views::ReviewPreviewRow>,
    /// Avatar rows for review comments: (row_idx, avatar)
    pub(crate) review_avatar_rows: Vec<(usize, crate::views::ReviewNoteAvatar)>,
    pub(crate) fold_context_rows: Vec<FoldContextRenderRow>,
}

#[derive(Clone, Debug)]
pub(crate) struct BlameRequest {
    pub(crate) repo_root: PathBuf,
    pub(crate) path: PathBuf,
    pub(crate) source: BlameSource,
    pub(crate) start: usize,
    pub(crate) end: usize,
}

#[derive(Clone, Debug)]
pub(crate) struct BlameResponse {
    pub(crate) path: PathBuf,
    pub(crate) source: BlameSource,
    pub(crate) start: usize,
    pub(crate) end: usize,
    pub(crate) entries: Vec<(usize, BlameInfo)>,
}

#[derive(Clone, Debug)]
pub(crate) struct DiffRequest {
    pub(crate) file_index: usize,
    pub(crate) old: Arc<str>,
    pub(crate) new: Arc<str>,
}

pub(crate) struct ContentResponse {
    pub generation: u64,
    pub file_index: usize,
    pub identity: oyo_core::multi::PendingFileContent,
    pub content: oyo_core::multi::PreparedFileContent,
}

#[derive(Clone, Debug)]
pub(crate) struct DiffResponse {
    pub(crate) file_index: usize,
    pub(crate) old: Arc<str>,
    pub(crate) new: Arc<str>,
    pub(crate) diff: Result<DiffResult, String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeekScope {
    Change,
    Hunk,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeekMode {
    Old,
    Modified,
    Mixed,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PeekState {
    pub scope: PeekScope,
    pub mode: PeekMode,
}

#[derive(Debug, Clone)]
pub(crate) struct SyntaxScopeCache {
    pub(crate) file_index: usize,
    pub(crate) side: SyntaxSide,
    pub(crate) line_num: usize,
    pub(crate) label: String,
}