use crate::backlog::{BacklogItem, ItemId, Status};
use crate::error::{Error, Result};
use crate::rank::Rank;
use crate::service::open_board_locked;
use crate::storage::BacklogItemRepository;
use chrono::Utc;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SplitRelationship {
#[default]
None,
Child,
Dependency,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum SplitBody {
#[default]
Copy,
Empty,
Explicit(String),
}
#[derive(Debug, Clone, Default)]
pub struct SplitSpec {
pub titles: Vec<String>,
pub relationship: SplitRelationship,
pub body: SplitBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SplitOutcome {
pub source: BacklogItem,
pub created: Vec<BacklogItem>,
}
pub async fn split_item(
project_dir: &Path,
source: &ItemId,
spec: SplitSpec,
) -> Result<SplitOutcome> {
let SplitSpec {
titles,
relationship,
body,
} = spec;
if titles.is_empty() || titles.iter().any(|title| title.trim().is_empty()) {
return Err(Error::EmptyTitle);
}
let (board_dir, repo, config, _lock) = open_board_locked(project_dir).await?;
let config_path = board_dir.join("config.toml");
let status = config
.columns
.first()
.map(Status::new)
.ok_or_else(|| Error::parse(&config_path, "board config has no columns"))?;
let existing = repo.list().await?;
let mut source_item = existing
.iter()
.find(|item| &item.id == source)
.cloned()
.ok_or_else(|| Error::NotFound(source.clone()))?;
let new_body = match &body {
SplitBody::Copy => source_item.body.clone(),
SplitBody::Empty => String::new(),
SplitBody::Explicit(text) => text.clone(),
};
let mut last_rank = existing.last().map(|item| item.rank.clone());
let mut created = Vec::with_capacity(titles.len());
for title in &titles {
let id = repo.next_id(&config.project.key).await?;
let rank = Rank::after(last_rank.as_ref());
last_rank = Some(rank.clone());
let mut item = BacklogItem::new(id, title, status.clone(), rank, Utc::now())?;
item.body = new_body.clone();
if relationship == SplitRelationship::Child {
item.parent = Some(source.clone());
}
repo.save(&item).await?;
created.push(item);
}
if relationship == SplitRelationship::Dependency {
for item in &created {
if !source_item.depends_on.contains(&item.id) {
source_item.depends_on.push(item.id.clone());
}
}
source_item.updated = Utc::now();
repo.save(&source_item).await?;
}
let ids = created
.iter()
.map(|item| item.id.to_string())
.collect::<Vec<_>>()
.join(", ");
repo.commit(&format!("pinto: split {source} into {ids}"))
.await?;
Ok(SplitOutcome {
source: source_item,
created,
})
}