use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};
use uuid::Uuid;
use crate::error::KanbusError;
#[derive(Debug, Clone)]
pub struct IssueIdentifierRequest {
pub title: String,
pub existing_ids: HashSet<String>,
pub prefix: String,
}
#[derive(Debug, Clone)]
pub struct IssueIdentifierResult {
pub identifier: String,
}
static TEST_UUID_SEQUENCE: OnceLock<Mutex<Vec<Uuid>>> = OnceLock::new();
pub fn set_test_uuid_sequence(sequence: Option<Vec<Uuid>>) {
let cell = TEST_UUID_SEQUENCE.get_or_init(|| Mutex::new(Vec::new()));
let mut guard = cell.lock().expect("lock test uuid sequence");
*guard = sequence.unwrap_or_default();
}
fn next_uuid() -> Uuid {
let cell = TEST_UUID_SEQUENCE.get_or_init(|| Mutex::new(Vec::new()));
let mut guard = cell.lock().expect("lock test uuid sequence");
if let Some(next) = guard.first().cloned() {
guard.remove(0);
return next;
}
Uuid::new_v4()
}
pub fn format_issue_key(identifier: &str, project_context: bool) -> String {
if identifier.chars().all(|ch| ch.is_ascii_digit()) {
return identifier.to_string();
}
let (key_part, remainder) = if let Some((key, rest)) = identifier.split_once('-') {
if key.is_empty() || rest.is_empty() {
(None, identifier)
} else {
(Some(key), rest)
}
} else {
(None, identifier)
};
let (base, suffix) = if let Some((head, tail)) = remainder.split_once('.') {
(head, Some(tail))
} else {
(remainder, None)
};
let normalized: String = base.chars().filter(|ch| *ch != '-').collect();
let truncated: String = normalized.chars().take(6).collect();
if project_context {
return match suffix {
Some(tail) => format!("{}.{}", truncated, tail),
None => truncated,
};
}
if let Some(key) = key_part {
return match suffix {
Some(tail) => format!("{}-{}.{}", key, truncated, tail),
None => format!("{}-{}", key, truncated),
};
}
match suffix {
Some(tail) => format!("{}.{}", truncated, tail),
None => truncated,
}
}
pub fn generate_issue_identifier(
request: &IssueIdentifierRequest,
) -> Result<IssueIdentifierResult, KanbusError> {
for _ in 0..10 {
let identifier = format!("{}-{}", request.prefix, next_uuid());
if !request.existing_ids.contains(&identifier) {
return Ok(IssueIdentifierResult { identifier });
}
}
Err(KanbusError::IdGenerationFailed(
"unable to generate unique id after 10 attempts".to_string(),
))
}
pub fn generate_many_identifiers(
title: &str,
prefix: &str,
count: usize,
) -> Result<HashSet<String>, KanbusError> {
let mut existing = HashSet::new();
for _ in 0..count {
let request = IssueIdentifierRequest {
title: title.to_string(),
existing_ids: existing.clone(),
prefix: prefix.to_string(),
};
let result = generate_issue_identifier(&request)?;
existing.insert(result.identifier);
}
Ok(existing)
}