use std::collections::BTreeSet;
use trusty_mpm::core::issue_audit::{AuditRow, Verdict};
use crate::commands::issue::config::StateModel;
use super::backend::EpicBackend;
use super::render::{self, PHASES_END, PHASES_START};
pub(crate) const REQ_PHASES_BLOCK: &str = "phases block";
pub(crate) const REQ_PHASE_LINKAGE: &str = "phase linkage";
pub(crate) fn epic_rows<B: EpicBackend>(
backend: &B,
tracker: u64,
model: &StateModel,
) -> anyhow::Result<Vec<AuditRow>> {
let body = backend.body(tracker)?;
if !body.lines().any(|l| l.trim() == PHASES_START) {
return Ok(Vec::new());
}
let children = backend.children(tracker)?;
let table = render::phases_table(&children, model);
let rows = children
.iter()
.filter(|c| render::phase_number_of(&c.title).is_some())
.count();
let block_row = match render::replace_block(&body, PHASES_START, PHASES_END, &table) {
Err(e) => AuditRow::new(REQ_PHASES_BLOCK, Verdict::Fail, e.to_string()),
Ok(regenerated) if regenerated == body => AuditRow::new(
REQ_PHASES_BLOCK,
Verdict::Pass,
format!("matches its {rows} phase issue(s)"),
),
Ok(_) => AuditRow::new(
REQ_PHASES_BLOCK,
Verdict::Fail,
format!("phases block stale — run tm issue epic sync {tracker}"),
),
};
let linked: BTreeSet<u64> = children.iter().map(|c| c.number).collect();
let candidates = backend.phase_titled_issues(tracker)?;
let seen: BTreeSet<u64> = candidates.iter().map(|c| c.number).collect();
let mut failures: Vec<String> = candidates
.iter()
.filter(|c| render::epic_number_of(&c.title) == Some(tracker))
.filter(|c| !linked.contains(&c.number))
.map(|c| {
format!(
"#{} `{}` is phase-titled but not a native sub-issue — link it: gh issue edit \
{tracker} --add-sub-issue {}",
c.number,
c.title.trim(),
c.number
)
})
.collect();
let known = children
.iter()
.filter(|c| render::epic_number_of(&c.title) == Some(tracker))
.count();
let seen_known = children
.iter()
.filter(|c| render::epic_number_of(&c.title) == Some(tracker))
.filter(|c| seen.contains(&c.number))
.count();
if seen_known < known {
failures.push(format!(
"search index returned {seen_known} of {known} known phases — re-run in a minute"
));
}
let linkage_row = if !failures.is_empty() {
AuditRow::new(REQ_PHASE_LINKAGE, Verdict::Fail, failures.join("; "))
} else if known == 0 {
AuditRow::new(
REQ_PHASE_LINKAGE,
Verdict::Info,
"no phase is linked, so the search result cannot be cross-checked — an unlinked \
phase the index has not returned yet is invisible here",
)
} else {
AuditRow::new(
REQ_PHASE_LINKAGE,
Verdict::Pass,
format!("{known} of {known} linked phases seen by search"),
)
};
Ok(vec![block_row, linkage_row])
}