use std::collections::VecDeque;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use tokio::runtime::Handle;
use travelagent_core::cache::{CACHE_VERSION, PrCache, read_cache, write_cache};
use travelagent_core::config::ConfigLoadOutcome;
use travelagent_core::error::TrvError;
use travelagent_core::forge::{ForgeBackend, ForgeType, ForgeWarnHandler, PrId};
use crate::app::App;
use crate::cli::Cli;
use crate::forge_detect::{self, ForgeTarget};
use crate::theme::Theme;
pub fn create_remote_app(
cli_args: &Cli,
pr_arg: &str,
theme: Theme,
config_outcome: &ConfigLoadOutcome,
runtime_handle: Handle,
) -> anyhow::Result<App> {
let rt = &runtime_handle;
let forge_hosts = config_outcome
.config
.as_ref()
.and_then(|cfg| cfg.forge_hosts.as_ref());
let target = forge_detect::parse_pr_arg(pr_arg, forge_hosts)?;
let warn_queue: Arc<Mutex<VecDeque<String>>> = Arc::new(Mutex::new(VecDeque::new()));
let warn_handler = make_warn_handler(Arc::clone(&warn_queue));
let (forge, pr_id, forge_host) = match target {
ForgeTarget::ByNumber(number) => {
let (forge_type, owner, repo, custom_host) =
forge_detect::detect_forge_from_remote(forge_hosts)?;
let pr_id = PrId {
owner,
repo,
number,
};
let forge = create_forge(forge_type, custom_host.as_deref(), warn_handler.clone())?;
(forge, pr_id, custom_host)
}
ForgeTarget::ByUrl {
forge_type,
pr_id,
host,
} => {
let forge = create_forge(forge_type, host.as_deref(), warn_handler.clone())?;
(forge, pr_id, host)
}
};
let cache_host = forge_host.as_deref().unwrap_or(match forge.forge_type() {
travelagent_core::forge::ForgeType::GitHub => "github.com",
travelagent_core::forge::ForgeType::GitLab => "gitlab.com",
});
let metadata = rt.block_on(forge.get_pr(&pr_id))?;
let cache_key = &metadata.head_sha;
let (diff_files, commits) = if let Some(cached) = read_cache(&pr_id, cache_host, cache_key) {
(cached.diff_files, cached.commits)
} else {
let (files, commits_data) = rt.block_on(async {
let files = forge.get_pr_files(&pr_id).await?;
let commits = forge.get_pr_commits(&pr_id).await?;
Ok::<_, TrvError>((files, commits))
})?;
if let Err(e) = write_cache(
&pr_id,
cache_host,
&PrCache {
version: CACHE_VERSION,
head_sha: cache_key.clone(),
metadata: metadata.clone(),
diff_files: files.clone(),
commits: commits_data.clone(),
comments: vec![], },
) {
eprintln!("Warning: failed to write cache: {e}");
}
(files, commits_data)
};
let comments = rt.block_on(forge.get_comments(&pr_id)).unwrap_or_default();
let review_threads = rt
.block_on(forge.get_review_threads(&pr_id))
.unwrap_or_default();
let state_display = metadata.state.display();
let draft_marker = if metadata.is_draft { " (draft)" } else { "" };
eprintln!(
"PR #{}: {} [{state_display}{draft_marker}]",
pr_id.number, metadata.title
);
eprintln!(" {} -> {}", metadata.head_branch, metadata.base_branch);
eprintln!(" {} files changed", diff_files.len());
let _ = io::stderr().flush();
let pr_number = pr_id.number;
let pr_owner = pr_id.owner.clone();
let pr_repo = pr_id.repo.clone();
let mut app = App::new_remote(
theme,
config_outcome
.config
.as_ref()
.and_then(|cfg| cfg.comment_types.clone()),
cli_args.output_to_stdout,
diff_files,
metadata.title.clone(),
pr_number,
&pr_owner,
&pr_repo,
runtime_handle,
Some(forge),
pr_id,
)?;
{
let r = app
.remote_mut()
.expect("new_remote produces remote-mode App");
r.pr_metadata = Some(metadata);
r.pr_commits = commits;
r.remote_comments = comments;
r.review_threads = review_threads;
r.forge_host = forge_host;
}
app.attach_forge_warn_queue(warn_queue);
Ok(app)
}
fn make_warn_handler(queue: Arc<Mutex<VecDeque<String>>>) -> ForgeWarnHandler {
Arc::new(move |msg| {
if let Ok(mut q) = queue.lock() {
q.push_back(msg);
}
})
}
fn create_forge(
forge_type: ForgeType,
custom_host: Option<&str>,
warn_handler: ForgeWarnHandler,
) -> anyhow::Result<std::sync::Arc<dyn ForgeBackend>> {
match forge_type {
ForgeType::GitHub => {
let forge = if let Some(host) = custom_host {
let base_url = format!("https://{host}/api/v3");
travelagent_forge_github::GitHubForge::with_base_url(&base_url)?
} else {
travelagent_forge_github::GitHubForge::new()?
};
Ok(std::sync::Arc::new(forge.with_warn_handler(warn_handler)))
}
ForgeType::GitLab => {
let forge = if let Some(host) = custom_host {
let base_url = format!("https://{host}");
travelagent_forge_gitlab::GitLabForge::with_base_url(&base_url)?
} else {
travelagent_forge_gitlab::GitLabForge::new()?
};
Ok(std::sync::Arc::new(forge.with_warn_handler(warn_handler)))
}
}
}