use anyhow::{bail, Result};
use dialoguer::{Confirm, Editor, FuzzySelect, Input};
use crate::cache::{Cache, Project, StatusOption};
use crate::config::Config;
use crate::github::GitHubClient;
pub struct IssueInput {
pub repo_full_name: String,
pub title: String,
pub description: String,
pub project: Option<Project>,
pub status: Option<StatusOption>,
}
pub fn collect_issue_input(config: &Config, cache: &Cache) -> Result<IssueInput> {
// Sort repos by descending usage count so frequently used ones appear first.
let mut sorted_repos = cache.repos.clone();
sorted_repos.sort_by(|a, b| {
let count_a = cache.usage.repos.get(&a.full_name).copied().unwrap_or(0);
let count_b = cache.usage.repos.get(&b.full_name).copied().unwrap_or(0);
count_b.cmp(&count_a)
});
let repo_names: Vec<String> = sorted_repos.iter().map(|r| r.full_name.clone()).collect();
if repo_names.is_empty() {
bail!("No repositories found in cache. Run `pike setup` first.");
}
let repo_idx = FuzzySelect::new()
.with_prompt("Select repository")
.items(&repo_names)
.interact()?;
let repo_full_name = repo_names[repo_idx].clone();
let default_title = format!("unknown-{}", rand::random::<u16>());
let title: String = Input::new()
.with_prompt("Issue title")
.default(default_title)
.interact()?;
let description = if Confirm::new()
.with_prompt("Add a description?")
.default(false)
.interact()?
{
Editor::new()
.edit("")
.ok()
.flatten()
.map(|t| t.trim().to_string())
.unwrap_or_default()
} else {
String::new()
};
let project = select_project(config, cache)?;
let status = if let Some(ref proj) = project {
select_status(proj)?
} else {
None
};
Ok(IssueInput {
repo_full_name,
title,
description,
project,
status,
})
}
fn select_project(config: &Config, cache: &Cache) -> Result<Option<Project>> {
if cache.projects.is_empty() {
return Ok(None);
}
// Sort projects by descending usage count so frequently used ones appear first.
let mut sorted_projects = cache.projects.clone();
sorted_projects.sort_by(|a, b| {
let count_a = cache.usage.projects.get(&a.id).copied().unwrap_or(0);
let count_b = cache.usage.projects.get(&b.id).copied().unwrap_or(0);
count_b.cmp(&count_a)
});
let project_items: Vec<String> = sorted_projects.iter().map(|p| p.title.clone()).collect();
let mut options = vec!["Skip (no project)".to_string()];
options.extend(project_items.clone());
let default_idx = config
.default_project
.as_ref()
.and_then(|dp| project_items.iter().position(|p| p == dp))
.map(|i| i + 1)
.unwrap_or(0);
let idx = FuzzySelect::new()
.with_prompt("Select project")
.items(&options)
.default(default_idx)
.interact()?;
if idx == 0 {
Ok(None)
} else {
Ok(Some(sorted_projects[idx - 1].clone()))
}
}
fn select_status(project: &Project) -> Result<Option<StatusOption>> {
if project.status_options.is_empty() {
return Ok(None);
}
let mut options = vec!["No status".to_string()];
options.extend(project.status_options.iter().map(|s| s.name.clone()));
let idx = FuzzySelect::new()
.with_prompt("Select status")
.items(&options)
.default(0)
.interact()?;
if idx == 0 {
Ok(None)
} else {
Ok(Some(project.status_options[idx - 1].clone()))
}
}
pub async fn run_setup(token: &str) -> Result<()> {
println!(" GITHUB_TOKEN found");
let default_org: String = Input::new()
.with_prompt("Default organization")
.interact()?;
let config = Config {
default_org: Some(default_org.clone()),
default_project: None,
favorite_repos: Vec::new(),
};
config.save()?;
println!(" Config saved");
let client = GitHubClient::new(token);
println!("Fetching repositories...");
let repos = client.list_org_repos(&default_org).await?;
println!(" Fetched {} repositories", repos.len());
println!("Fetching projects...");
let projects = client.list_org_projects(&default_org).await?;
println!(" Fetched {} projects", projects.len());
let cache = Cache { repos, projects, ..Cache::default() };
cache.save()?;
println!(" Cache saved");
Ok(())
}