use crate::actions::GeneralArgs;
use crate::render::spinner::spin_until_ready;
use crate::render::ui::fuzzy_select_with_key;
use crate::render::ui::multi_fuzzy_select_with_key;
use crate::types::api::state_type::StateType;
use crate::types::context::BergContext;
use crate::types::git::OwnerRepo;
use anyhow::Context;
use forgejo_api::structs::EditMilestoneOption;
use forgejo_api::structs::IssueGetMilestonesListQuery;
use forgejo_api::structs::Milestone;
use strum::Display;
use strum::VariantArray;
use crate::actions::text_manipulation::edit_prompt_for;
use crate::actions::text_manipulation::input_prompt_for;
use crate::actions::text_manipulation::select_prompt_for;
use super::display_milestone;
use clap::Parser;
#[derive(Parser, Debug)]
pub struct EditMilestoneArgs {}
#[derive(Display, PartialEq, Eq, VariantArray)]
enum EditableFields {
Title,
State,
Description,
}
impl EditMilestoneArgs {
pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
let _ = general_args;
let ctx = BergContext::new(self).await?;
let OwnerRepo { owner, repo } = ctx.owner_repo()?;
let milestone = select_milestone(&ctx).await?;
let milestone_id_or_name = milestone
.id
.as_ref()
.map(|id| id.to_string())
.or_else(|| milestone.title.clone())
.context("Selected milestone doesn't have an ID or Name")?;
let options = create_options(&ctx, &milestone).await?;
let updated_milestone = ctx
.client
.issue_edit_milestone(
owner.as_str(),
repo.as_str(),
milestone_id_or_name.as_str(),
options,
)
.await?;
tracing::debug!("{updated_milestone:?}");
Ok(())
}
}
async fn select_milestone(ctx: &BergContext<EditMilestoneArgs>) -> anyhow::Result<Milestone> {
let OwnerRepo { owner, repo } = ctx.owner_repo()?;
let milestones_list = spin_until_ready(ctx.client.issue_get_milestones_list(
owner.as_str(),
repo.as_str(),
IssueGetMilestonesListQuery::default(),
))
.await?;
if milestones_list.is_empty() {
anyhow::bail!("No milestones found in this repository");
}
fuzzy_select_with_key(
&milestones_list,
select_prompt_for("milestone"),
display_milestone,
)
.cloned()
}
async fn create_options(
ctx: &BergContext<EditMilestoneArgs>,
milestone: &Milestone,
) -> anyhow::Result<EditMilestoneOption> {
let selected_update_fields = multi_fuzzy_select_with_key(
EditableFields::VARIANTS,
select_prompt_for("options"),
|_| false,
|f| f.to_string(),
)?;
let mut options = EditMilestoneOption {
description: None,
due_on: None,
state: None,
title: None,
};
if selected_update_fields.contains(&&EditableFields::Title) {
let current_title = milestone.title.as_ref().cloned();
options
.title
.replace(milestone_title(ctx, current_title).await?);
}
if selected_update_fields.contains(&&EditableFields::State) {
let current_state = milestone.state.as_ref().cloned();
options
.state
.replace(milestone_state(ctx, current_state).await?);
}
if selected_update_fields.contains(&&EditableFields::Description) {
let current_description = milestone.description.as_ref().cloned();
options
.description
.replace(milestone_description(ctx, current_description).await?);
}
Ok(options)
}
async fn milestone_title(
_ctx: &BergContext<EditMilestoneArgs>,
current_title: Option<String>,
) -> anyhow::Result<String> {
inquire::Text::new(input_prompt_for("Choose a new milestone title").as_str())
.with_default(
current_title
.as_deref()
.unwrap_or("Enter a milestone title"),
)
.prompt()
.map_err(anyhow::Error::from)
}
async fn milestone_state(
_ctx: &BergContext<EditMilestoneArgs>,
_current_state: Option<String>,
) -> anyhow::Result<String> {
let selected_state =
fuzzy_select_with_key(StateType::VARIANTS, select_prompt_for("states"), |f| {
f.to_string()
})?;
Ok(selected_state.to_string())
}
async fn milestone_description(
_ctx: &BergContext<EditMilestoneArgs>,
current_description: Option<String>,
) -> anyhow::Result<String> {
inquire::Editor::new(edit_prompt_for("a description").as_str())
.with_predefined_text(
current_description
.as_deref()
.unwrap_or("Enter a milestone description"),
)
.prompt()
.map_err(anyhow::Error::from)
}