ghr-cli 0.7.0

A fast terminal dashboard for GitHub pull requests, issues, and notifications.
use std::time::Instant;

use super::{
    MessageDialog, MessageDialogKind, PendingCommentMode, PrAction, ReviewerAction,
    SUCCESS_DIALOG_AUTO_CLOSE, SetupDialog, item_kind_label, text::truncate_inline,
};
use crate::model::ItemKind;

pub(super) fn refresh_error_status(count: usize, first_error: Option<&str>) -> String {
    let Some(first_error) = first_error else {
        return format!("refresh complete with {count} failed section(s)");
    };

    if first_error.contains("GitHub CLI `gh` is required") {
        return "GitHub CLI missing: install `gh`, then run `gh auth login`".to_string();
    }

    if first_error.contains("Run `gh auth login`") {
        return "GitHub CLI auth required: run `gh auth login`".to_string();
    }

    if is_github_search_rate_limit(first_error) {
        return format!(
            "GitHub search rate limited; using cached data ({count} failed section(s))"
        );
    }

    format!("refresh complete with {count} failed section(s)")
}

pub(super) fn compact_error_label(error: &str) -> String {
    if error.contains("GitHub CLI `gh` is required") {
        return "GitHub CLI missing".to_string();
    }

    if error.contains("Run `gh auth login`") {
        return "GitHub CLI auth required".to_string();
    }

    if is_github_search_rate_limit(error) {
        return "GitHub search rate limited".to_string();
    }

    let summary = error
        .split_once(" failed: ")
        .map(|(_, message)| message)
        .unwrap_or(error)
        .trim();
    truncate_inline(summary, 80)
}

pub(super) fn setup_dialog_from_error(error: &str) -> Option<SetupDialog> {
    if error.contains("GitHub CLI `gh` is required") {
        return Some(SetupDialog::MissingGh);
    }

    if error.contains("Run `gh auth login`") {
        return Some(SetupDialog::AuthRequired);
    }

    None
}

pub(super) fn message_dialog(title: impl Into<String>, body: impl Into<String>) -> MessageDialog {
    MessageDialog {
        title: title.into(),
        body: body.into(),
        kind: MessageDialogKind::Error,
        auto_close_at: None,
    }
}

pub(super) fn retryable_message_dialog(
    title: impl Into<String>,
    body: impl Into<String>,
) -> MessageDialog {
    MessageDialog {
        title: title.into(),
        body: body.into(),
        kind: MessageDialogKind::RetryableError,
        auto_close_at: None,
    }
}

pub(super) fn success_message_dialog(
    title: impl Into<String>,
    body: impl Into<String>,
) -> MessageDialog {
    MessageDialog {
        title: title.into(),
        body: body.into(),
        kind: MessageDialogKind::Success,
        auto_close_at: Some(Instant::now() + SUCCESS_DIALOG_AUTO_CLOSE),
    }
}

pub(super) fn persistent_success_message_dialog(
    title: impl Into<String>,
    body: impl Into<String>,
) -> MessageDialog {
    MessageDialog {
        title: title.into(),
        body: body.into(),
        kind: MessageDialogKind::Success,
        auto_close_at: None,
    }
}

pub(super) fn info_message_dialog(
    title: impl Into<String>,
    body: impl Into<String>,
) -> MessageDialog {
    MessageDialog {
        title: title.into(),
        body: body.into(),
        kind: MessageDialogKind::Info,
        auto_close_at: None,
    }
}

pub(super) fn pr_action_success_title(action: PrAction, item_kind: ItemKind) -> String {
    match action {
        PrAction::Merge => "Pull Request Merged",
        PrAction::Close if item_kind == ItemKind::Issue => "Issue Closed",
        PrAction::Close => "Pull Request Closed",
        PrAction::Reopen if item_kind == ItemKind::Issue => "Issue Reopened",
        PrAction::Reopen => "Pull Request Reopened",
        PrAction::Approve => "Pull Request Approved",
        PrAction::EnableAutoMerge => "Auto-Merge Enabled",
        PrAction::DisableAutoMerge => "Auto-Merge Disabled",
        PrAction::Checkout => "Pull Request Checked Out",
        PrAction::RerunFailedChecks => "Failed Checks Rerun",
        PrAction::UpdateBranch => "Pull Request Branch Updated",
        PrAction::ConvertToDraft => "Pull Request Converted to Draft",
        PrAction::MarkReadyForReview => "Pull Request Ready for Review",
    }
    .to_string()
}

pub(super) fn pr_action_success_body(action: PrAction, _item_kind: ItemKind) -> String {
    match action {
        PrAction::Merge => "GitHub accepted the merge. Refreshing details.",
        PrAction::Close => "GitHub accepted the close action. Refreshing details.",
        PrAction::Reopen => "GitHub accepted the reopen action. Refreshing details.",
        PrAction::Approve => "GitHub accepted the approval. Refreshing details.",
        PrAction::EnableAutoMerge => "GitHub enabled auto-merge. Refreshing details.",
        PrAction::DisableAutoMerge => "GitHub disabled auto-merge. Refreshing details.",
        PrAction::Checkout => "GitHub CLI checked out the pull request locally.",
        PrAction::RerunFailedChecks => {
            "GitHub accepted the failed-check rerun request. Refreshing details."
        }
        PrAction::UpdateBranch => "GitHub accepted the branch update. Refreshing details.",
        PrAction::ConvertToDraft => {
            "GitHub converted the pull request to draft. Refreshing details."
        }
        PrAction::MarkReadyForReview => {
            "GitHub marked the pull request ready for review. Refreshing details."
        }
    }
    .to_string()
}

pub(super) fn pr_action_error_title(action: PrAction, _item_kind: ItemKind) -> String {
    match action {
        PrAction::Merge => "Merge Failed",
        PrAction::Close => "Close Failed",
        PrAction::Reopen => "Reopen Failed",
        PrAction::Approve => "Approve Failed",
        PrAction::EnableAutoMerge => "Enable Auto-Merge Failed",
        PrAction::DisableAutoMerge => "Disable Auto-Merge Failed",
        PrAction::Checkout => "Checkout Failed",
        PrAction::RerunFailedChecks => "Rerun Failed",
        PrAction::UpdateBranch => "Update Branch Failed",
        PrAction::ConvertToDraft => "Convert to Draft Failed",
        PrAction::MarkReadyForReview => "Ready for Review Failed",
    }
    .to_string()
}

pub(super) fn pr_action_error_status(action: PrAction, item_kind: ItemKind) -> String {
    let label = item_kind_label(item_kind);
    match action {
        PrAction::Merge => "pull request merge failed".to_string(),
        PrAction::Close => format!("{label} close failed"),
        PrAction::Reopen => format!("{label} reopen failed"),
        PrAction::Approve => "pull request approval failed".to_string(),
        PrAction::EnableAutoMerge => "pull request auto-merge enable failed".to_string(),
        PrAction::DisableAutoMerge => "pull request auto-merge disable failed".to_string(),
        PrAction::Checkout => "pull request checkout failed".to_string(),
        PrAction::RerunFailedChecks => "failed check rerun failed".to_string(),
        PrAction::UpdateBranch => "pull request branch update failed".to_string(),
        PrAction::ConvertToDraft => "pull request draft conversion failed".to_string(),
        PrAction::MarkReadyForReview => "pull request ready-for-review failed".to_string(),
    }
}

pub(super) fn pr_action_error_body(error: &str) -> String {
    operation_error_body(error)
}

pub(super) fn reviewer_action_success_title(action: ReviewerAction) -> &'static str {
    match action {
        ReviewerAction::Request => "Reviewers Requested",
        ReviewerAction::Remove => "Review Requests Removed",
    }
}

pub(super) fn reviewer_action_success_body(action: ReviewerAction) -> &'static str {
    match action {
        ReviewerAction::Request => "GitHub accepted the review request. Refreshing details.",
        ReviewerAction::Remove => "GitHub removed the requested reviewers. Refreshing details.",
    }
}

pub(super) fn reviewer_action_error_title(action: ReviewerAction) -> &'static str {
    match action {
        ReviewerAction::Request => "Reviewer Request Failed",
        ReviewerAction::Remove => "Reviewer Removal Failed",
    }
}

pub(super) fn reviewer_action_error_status(action: ReviewerAction) -> &'static str {
    match action {
        ReviewerAction::Request => "reviewer request failed",
        ReviewerAction::Remove => "reviewer removal failed",
    }
}

pub(super) fn operation_error_body(error: &str) -> String {
    let message = error
        .split_once(" failed: ")
        .map(|(_, message)| message)
        .unwrap_or(error)
        .trim();
    truncate_inline(message, 900)
}

pub(super) fn comment_pending_dialog(mode: &PendingCommentMode) -> MessageDialog {
    match mode {
        PendingCommentMode::Post => info_message_dialog(
            "Posting Comment",
            "Waiting for GitHub to accept the comment...",
        ),
        PendingCommentMode::ReviewReply { .. } => info_message_dialog(
            "Posting Review Reply",
            "Waiting for GitHub to accept the review reply...",
        ),
        PendingCommentMode::Edit { .. } => info_message_dialog(
            "Updating Comment",
            "Waiting for GitHub to accept the update...",
        ),
        PendingCommentMode::Review { .. } => info_message_dialog(
            "Posting Review Comment",
            "Waiting for GitHub to accept the review comment...",
        ),
        PendingCommentMode::ItemMetadata { field } => message_dialog(
            format!("Updating {}", field.title()),
            format!(
                "Waiting for GitHub to accept the {} update...",
                field.label()
            ),
        ),
    }
}

fn is_github_search_rate_limit(error: &str) -> bool {
    error
        .to_ascii_lowercase()
        .contains("api rate limit exceeded")
}