pub(crate) const MIN_DECISION_LEN: usize = 30;
#[cfg(test)]
const MAX_TITLE_LEN: usize = 120;

#[cfg(test)]
pub(crate) fn build_title(content: &str, request: &str, label: &str) -> String {
    let source = if content.len() >= 20 {
        content
    } else {
        request
    };
    if source.is_empty() {
        return format!("Session {label}");
    }
    let truncated = truncate_at_boundary(source, MAX_TITLE_LEN - label.len() - 5);
    format!("{truncated} — {label}")
}

pub(crate) fn truncate_at_boundary(text: &str, max_len: usize) -> String {
    let text = text.trim();
    if text.len() <= max_len {
        return text.to_string();
    }
    let safe_end = crate::db::truncate_str(text, max_len).len();
    let slice = &text[..safe_end];
    for sep in ['。', '；', ';', '.', '，', ','] {
        if let Some(pos) = slice.rfind(sep) {
            if pos > safe_end / 2 {
                return text[..pos + sep.len_utf8()].trim().to_string();
            }
        }
    }
    if let Some(pos) = slice.rfind(' ') {
        if pos > safe_end / 2 {
            return text[..pos].to_string();
        }
    }
    text.chars().take(max_len).collect()
}

pub(crate) fn build_content(body: &str, request: &str) -> String {
    if request.is_empty() {
        body.to_string()
    } else {
        format!(
            "[Context: {}]\n\n{}",
            truncate_at_boundary(request, 150),
            body
        )
    }
}

pub(crate) fn split_into_items(text: &str) -> Vec<String> {
    let mut items = Vec::new();
    let mut current = String::new();

    for line in text.lines() {
        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }
        let is_new_item = trimmed.starts_with("• ")
            || trimmed.starts_with("- ")
            || trimmed.starts_with("* ")
            || trimmed.starts_with("· ")
            || trimmed
                .chars()
                .next()
                .map(|ch| ch.is_ascii_digit())
                .unwrap_or(false)
                && trimmed.contains(". ");

        if is_new_item {
            if !current.trim().is_empty() {
                items.push(current.trim().to_string());
            }
            let content = trimmed
                .trim_start_matches(['•', '-', '*', '·'])
                .trim_start();
            let content = if content
                .chars()
                .next()
                .map(|ch| ch.is_ascii_digit())
                .unwrap_or(false)
            {
                content
                    .find(". ")
                    .map(|pos| &content[pos + 2..])
                    .unwrap_or(content)
            } else {
                content
            };
            current = content.to_string();
        } else {
            if !current.is_empty() {
                current.push(' ');
            }
            current.push_str(trimmed);
        }
    }
    if !current.trim().is_empty() {
        items.push(current.trim().to_string());
    }

    if items.len() <= 1 {
        let semi_split: Vec<String> = text
            .trim()
            .split('；')
            .flat_map(|segment| segment.split(';'))
            .map(|segment| segment.trim().to_string())
            .filter(|segment| segment.len() >= MIN_DECISION_LEN)
            .collect();
        if semi_split.len() > 1 {
            return semi_split;
        }
    }

    items
}
