cargo-port 0.1.1

A TUI for inspecting and managing Rust projects
use crossterm::event::KeyCode;
use ratatui::layout::Position;
use tui_pane::Hittable;
use tui_pane::Viewport;

use crate::sccache::StatsResult;
use crate::tui::pane::HoverTarget;
use crate::tui::panes::PaneId;

#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct SccacheTarget {
    pub(super) label: String,
    pub(super) value: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) enum SccacheStatus {
    Loading { source: String },
    NotConfigured,
    Ready { source: String, lines: Vec<String> },
    Failed { source: String, lines: Vec<String> },
}

pub struct SccachePane {
    status:            SccacheStatus,
    request_id:        u64,
    line_targets:      Vec<Option<usize>>,
    selectable_values: Vec<SccacheTarget>,
    pub viewport:      Viewport,
}

impl SccachePane {
    pub const fn new() -> Self {
        Self {
            status:            SccacheStatus::NotConfigured,
            request_id:        0,
            line_targets:      Vec::new(),
            selectable_values: Vec::new(),
            viewport:          Viewport::new(),
        }
    }

    pub(super) const fn status(&self) -> &SccacheStatus { &self.status }

    pub const fn viewport_mut(&mut self) -> &mut Viewport { &mut self.viewport }

    pub(super) fn set_line_targets(
        &mut self,
        line_targets: Vec<Option<usize>>,
        selectable_values: Vec<SccacheTarget>,
    ) {
        self.line_targets = line_targets;
        self.selectable_values = selectable_values;
    }

    pub fn row_at(&self, pos: Position) -> Option<usize> {
        let inner = self.viewport.content_area();
        if inner.width == 0 || inner.height == 0 || !inner.contains(pos) {
            return None;
        }
        let line_index = usize::from(pos.y.saturating_sub(inner.y)) + self.viewport.scroll_offset();
        self.line_targets.get(line_index).copied().flatten()
    }

    pub(super) fn line_for_selection(&self, selection: usize) -> Option<usize> {
        self.line_targets
            .iter()
            .position(|target| target.is_some_and(|target| target == selection))
    }

    pub(super) fn selected_copy_value(&self) -> Option<&SccacheTarget> {
        self.selectable_values.get(self.viewport.pos())
    }

    pub fn show_not_configured(&mut self) {
        self.status = SccacheStatus::NotConfigured;
        self.viewport.home();
    }

    pub fn start_loading(&mut self, source: String) -> u64 {
        self.request_id = self.request_id.saturating_add(1);
        self.status = SccacheStatus::Loading { source };
        self.viewport.home();
        self.request_id
    }

    pub fn apply_result(&mut self, request_id: u64, result: StatsResult) {
        if self.request_id != request_id {
            return;
        }
        let source = match &self.status {
            SccacheStatus::Loading { source }
            | SccacheStatus::Ready { source, .. }
            | SccacheStatus::Failed { source, .. } => source.clone(),
            SccacheStatus::NotConfigured => return,
        };
        self.status = match result {
            StatsResult::Ready(lines) => SccacheStatus::Ready { source, lines },
            StatsResult::Failed(lines) => SccacheStatus::Failed { source, lines },
        };
        self.viewport.home();
    }

    pub fn handle_navigation_key(&mut self, code: KeyCode) {
        match code {
            KeyCode::Up => self.viewport.up(),
            KeyCode::Down => self.viewport.down(),
            KeyCode::Home => self.viewport.home(),
            KeyCode::End => {
                let last = self.viewport.len().saturating_sub(1);
                self.viewport.set_pos(last);
            },
            KeyCode::PageUp => self.viewport.page_up(),
            KeyCode::PageDown => self.viewport.page_down(),
            _ => {},
        }
    }
}

impl Hittable<HoverTarget> for SccachePane {
    fn hit_test_at(&self, pos: Position) -> Option<HoverTarget> {
        self.row_at(pos).map(|row| HoverTarget::PaneRow {
            pane: PaneId::Sccache,
            row,
        })
    }
}

impl Default for SccachePane {
    fn default() -> Self { Self::new() }
}