jk 0.2.7

A jj-native terminal UI for Jujutsu
use jk_cli::{
    JjCommandRunner, JjStatus, JjWorkspaces, RecordingJjCommandRunner, StatusQuery,
    SystemJjCommandRunner, WorkspaceInspectionQuery,
};
use jk_core::{CommandSource, SourceAction, SourceView};
use jk_tui::rendered_view::RenderedView;
use jk_tui::workspaces_view::{WorkspaceViewSnapshot, WorkspacesView};

use crate::refresh::refresh_workspaces_with_runner;
use crate::state::{AppState, AppView};
use crate::workspaces::{update_stale_success_message, workspace_view_snapshot};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WorkspaceInspectionKind {
    Log,
    Status,
    Diff,
}

pub fn push_status(state: &mut AppState, status_source: &JjStatus) {
    push_status_with_runner(state, status_source, SystemJjCommandRunner);
}

pub fn push_status_with_runner<R: JjCommandRunner>(
    state: &mut AppState,
    status_source: &JjStatus,
    runner: R,
) {
    if !matches!(state.views.active(), AppView::Log(_)) {
        return;
    }

    let query = StatusQuery::default();
    let mut runner = RecordingJjCommandRunner::new(
        runner,
        &mut state.history,
        CommandSource::new(SourceView::Log, SourceAction::OpenStatus),
    );
    match status_source.load_query_with_runner(&query, &mut runner) {
        Ok(snapshot) => {
            state.views.push(AppView::Status {
                view: RenderedView::new(snapshot),
                query,
            });
        }
        Err(error) => {
            if let AppView::Log(log) = state.views.active_mut() {
                log.show_error(error.to_string());
            }
        }
    }
}

pub fn open_workspaces(state: &mut AppState, workspaces_source: &JjWorkspaces) {
    open_workspaces_with_runner(state, workspaces_source, SystemJjCommandRunner);
}

pub fn open_workspaces_with_runner<R: JjCommandRunner>(
    state: &mut AppState,
    workspaces_source: &JjWorkspaces,
    runner: R,
) {
    if matches!(state.views.active(), AppView::Workspaces { .. }) {
        let AppState { views, history, .. } = state;
        if let AppView::Workspaces { view } = views.active_mut() {
            refresh_workspaces_with_runner(view, history, workspaces_source, runner);
        }
        return;
    }

    push_workspaces_with_runner(
        state,
        workspaces_source,
        CommandSource::new(SourceView::Log, SourceAction::WorkspaceList),
        runner,
    );
}

fn push_workspaces_with_runner<R: JjCommandRunner>(
    state: &mut AppState,
    workspaces_source: &JjWorkspaces,
    source: CommandSource,
    runner: R,
) {
    let mut runner = RecordingJjCommandRunner::new(runner, &mut state.history, source);
    let view = match workspaces_source.load_list_with_runner(&mut runner) {
        Ok(snapshot) => WorkspacesView::new(workspace_view_snapshot(snapshot)),
        Err(error) => {
            let mut view = WorkspacesView::new(WorkspaceViewSnapshot::new(Vec::new()));
            view.show_error(error.to_string());
            view
        }
    };
    push_workspace_view(state, view);
}

pub fn push_workspace_view(state: &mut AppState, view: WorkspacesView) {
    state.views.push(AppView::Workspaces { view });
}

pub fn push_selected_workspace_status(state: &mut AppState, workspaces_source: &JjWorkspaces) {
    push_selected_workspace_inspection_with_runner(
        state,
        workspaces_source,
        WorkspaceInspectionKind::Status,
        SystemJjCommandRunner,
    );
}

pub fn push_selected_workspace_log(state: &mut AppState, workspaces_source: &JjWorkspaces) {
    push_selected_workspace_inspection_with_runner(
        state,
        workspaces_source,
        WorkspaceInspectionKind::Log,
        SystemJjCommandRunner,
    );
}

#[cfg(test)]
pub fn push_selected_workspace_log_with_runner<R: JjCommandRunner>(
    state: &mut AppState,
    workspaces_source: &JjWorkspaces,
    runner: R,
) {
    push_selected_workspace_inspection_with_runner(
        state,
        workspaces_source,
        WorkspaceInspectionKind::Log,
        runner,
    );
}

pub fn push_selected_workspace_diff(state: &mut AppState, workspaces_source: &JjWorkspaces) {
    push_selected_workspace_inspection_with_runner(
        state,
        workspaces_source,
        WorkspaceInspectionKind::Diff,
        SystemJjCommandRunner,
    );
}

fn push_selected_workspace_inspection_with_runner<R: JjCommandRunner>(
    state: &mut AppState,
    workspaces_source: &JjWorkspaces,
    kind: WorkspaceInspectionKind,
    runner: R,
) {
    let query = {
        let AppView::Workspaces { view } = state.views.active_mut() else {
            return;
        };
        let Some(row) = view.selected_row() else {
            return;
        };
        let Some(root) = row.root().map(ToOwned::to_owned) else {
            view.show_error(format!("workspace `{}` has no root", row.name));
            return;
        };
        WorkspaceInspectionQuery::new(root)
    };

    let command_source = match kind {
        WorkspaceInspectionKind::Log => {
            CommandSource::new(SourceView::Workspaces, SourceAction::WorkspaceLog)
        }
        WorkspaceInspectionKind::Status => {
            CommandSource::new(SourceView::Workspaces, SourceAction::WorkspaceStatus)
        }
        WorkspaceInspectionKind::Diff => {
            CommandSource::new(SourceView::Workspaces, SourceAction::WorkspaceDiff)
        }
    };
    let mut runner = RecordingJjCommandRunner::new(runner, &mut state.history, command_source);
    let snapshot = match kind {
        WorkspaceInspectionKind::Log => workspaces_source.load_log_with_runner(&query, &mut runner),
        WorkspaceInspectionKind::Status => {
            workspaces_source.load_status_with_runner(&query, &mut runner)
        }
        WorkspaceInspectionKind::Diff => {
            workspaces_source.load_diff_with_runner(&query, &mut runner)
        }
    };
    match snapshot {
        Ok(snapshot) => {
            let view = RenderedView::new(snapshot);
            let app_view = match kind {
                WorkspaceInspectionKind::Log => AppView::WorkspaceLog { view, query },
                WorkspaceInspectionKind::Status => AppView::WorkspaceStatus { view, query },
                WorkspaceInspectionKind::Diff => AppView::WorkspaceDiff { view, query },
            };
            state.views.push(app_view);
        }
        Err(error) => {
            if let AppView::Workspaces { view } = state.views.active_mut() {
                view.show_error(error.to_string());
            }
        }
    }
}

pub fn update_selected_workspace_stale(state: &mut AppState, workspaces_source: &JjWorkspaces) {
    let (workspace_name, query) = {
        let AppView::Workspaces { view } = state.views.active_mut() else {
            return;
        };
        let Some(row) = view.selected_row() else {
            view.show_status("No workspace selected");
            return;
        };
        let workspace_name = row.name.clone();
        let Some(root) = row.root().map(ToOwned::to_owned) else {
            view.show_error(format!("workspace `{}` has no root", row.name));
            return;
        };
        (workspace_name, WorkspaceInspectionQuery::new(root))
    };

    let mut update_runner = crate::recording_runner(
        &mut state.history,
        CommandSource::new(SourceView::Workspaces, SourceAction::WorkspaceUpdateStale),
    );
    match workspaces_source.update_stale_with_runner(&query, &mut update_runner) {
        Ok(outcome) => {
            let success = update_stale_success_message(
                &workspace_name,
                &outcome.title,
                &outcome.stderr,
                &outcome.stdout,
            );
            let refresh_result = {
                let mut refresh_runner = crate::recording_runner(
                    &mut state.history,
                    CommandSource::new(SourceView::Workspaces, SourceAction::Refresh),
                );
                workspaces_source.load_list_with_runner(&mut refresh_runner)
            };
            if let AppView::Workspaces { view } = state.views.active_mut() {
                match refresh_result {
                    Ok(snapshot) => {
                        view.refresh(workspace_view_snapshot(snapshot));
                        view.show_status(success);
                    }
                    Err(error) => {
                        view.show_status(format!("{success}; refresh failed: {error}"));
                    }
                }
            }
        }
        Err(error) => {
            if let AppView::Workspaces { view } = state.views.active_mut() {
                view.show_error(error.to_string());
            }
        }
    }
}