use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use slack_morphism::prelude::*;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use super::SlackState;
use super::final_body::folded_paragraphs;
use super::governor::GatedWrites;
use super::handler::sync_step_group;
use super::tool_group::GroupEntry;
pub(crate) const OPTIONS_TOOL: &str = "suggest_options";
pub(crate) fn take_trailing_answer(steps: &mut Vec<GroupEntry>) -> Option<String> {
let end = steps
.iter()
.rposition(|e| matches!(e, GroupEntry::Tool { name, .. } if name == OPTIONS_TOOL))
.unwrap_or(steps.len());
let start = steps[..end]
.iter()
.rposition(|e| !matches!(e, GroupEntry::Note(_)))
.map_or(0, |i| i + 1);
let notes: Vec<String> = steps[start..end]
.iter()
.filter_map(|e| match e {
GroupEntry::Note(text) if !text.trim().is_empty() => Some(text.clone()),
_ => None,
})
.collect();
if notes.is_empty() {
return None;
}
steps.drain(start..end);
Some(notes.join("\n\n"))
}
pub(crate) fn held_paragraphs(held: &[String]) -> Vec<String> {
if held.is_empty() {
return Vec::new();
}
folded_paragraphs(Some(held.join("\n\n")))
}
pub(crate) async fn drain_note_tasks(handles: &Arc<StdMutex<Vec<JoinHandle<()>>>>) {
let pending = {
let mut g = handles.lock().expect("poisoned");
std::mem::take(&mut *g)
};
for h in pending {
if let Err(e) = h.await {
tracing::warn!(error = %e, "Slack options host: narration task panicked");
}
}
}
pub(crate) async fn post_halted_answer<'a>(
session: &SlackClientSession<'a, slack_morphism::hyper_tokio::SlackClientHyperHttpsConnector>,
slack_state: &Arc<SlackState>,
channel: SlackChannelId,
thread_ts: Option<SlackTs>,
group_ts: &Arc<Mutex<Option<SlackTs>>>,
steps: &Arc<Mutex<Vec<GroupEntry>>>,
posted: &Arc<Mutex<Vec<String>>>,
) {
let answer = {
let mut s = steps.lock().await;
take_trailing_answer(&mut s)
};
let Some(answer) = answer else {
return;
};
let entries = steps.lock().await.clone();
sync_step_group(
session,
slack_state,
channel.clone(),
thread_ts.clone(),
group_ts,
entries,
)
.await;
let blocks = super::blocks::blocks_from_mrkdwn(&answer);
let mut req = SlackApiChatPostMessageRequest::new(
channel,
SlackMessageContent::new()
.with_text(answer.clone())
.with_blocks(blocks),
);
if let Some(ts) = thread_ts {
req = req.with_thread_ts(ts);
}
if let Err(e) = session.post(&req).await {
tracing::error!("Slack: failed to post the answer held before the buttons: {e}");
}
posted.lock().await.push(answer);
}