//! Convert a project directory name into a human-readable
//! display name (P-NAME-2 from `requirements.md`, ADR-0015 §2).
//!
//! Rules:
//!
//! - Strip a leading `YYYYMMDD-` for temp projects.
//! - Split on `-` (kebab), `_` (snake), or case boundaries
//!   (camelCase / PascalCase).
//! - Title-case each resulting word.
//!
//! Examples (covered by tests below):
//!
//! ```text
//! 20260507-water-buffalo-skating  -> "Water Buffalo Skating"
//! MyOrders                        -> "My Orders"
//! customer_demo                   -> "Customer Demo"
//! exam-1-prep                     -> "Exam 1 Prep"
//! ```

/// Produce a display name from a project directory name.
#[must_use]
pub fn prettify(dirname: &str) -> String {
    let trimmed = strip_date_prefix(dirname);
    let trimmed = strip_temp_marker(trimmed);
    let words = split_into_words(trimmed);
    words
        .into_iter()
        .map(title_case_word)
        .collect::<Vec<_>>()
        .join(" ")
}

/// Strip a leading `[temp]-` segment if present. Temp project
/// directory names look like `20260507-[temp]-water-buffalo-skating`
/// after `strip_date_prefix`, this removes the marker so the
/// display name reduces to just the three random words.
fn strip_temp_marker(s: &str) -> &str {
    s.strip_prefix("[temp]-").unwrap_or(s)
}

/// Strip a leading `YYYYMMDD-` if present. Eight ASCII digits
/// followed by a single `-` are required; anything else is
/// returned unchanged.
fn strip_date_prefix(s: &str) -> &str {
    if s.len() < 9 {
        return s;
    }
    let (head, tail) = s.split_at(9);
    let mut chars = head.chars();
    let date_chars: Vec<char> = chars.by_ref().take(8).collect();
    let separator = chars.next();
    let date_ok = date_chars.len() == 8 && date_chars.iter().all(char::is_ascii_digit);
    if date_ok && separator == Some('-') {
        tail
    } else {
        s
    }
}

/// Split a string into "words" using kebab, snake, and case
/// boundaries. Empty segments are dropped; leading/trailing
/// separators are tolerated.
fn split_into_words(s: &str) -> Vec<String> {
    let mut words: Vec<String> = Vec::new();
    let mut current = String::new();

    let push = |current: &mut String, words: &mut Vec<String>| {
        if !current.is_empty() {
            words.push(std::mem::take(current));
        }
    };

    let mut prev: Option<char> = None;
    for c in s.chars() {
        let is_separator = c == '-' || c == '_';
        if is_separator {
            push(&mut current, &mut words);
            prev = None;
            continue;
        }
        // Case-boundary detection: insert a split before an
        // uppercase letter that follows a lowercase letter or
        // digit (camelCase / PascalCase). Also split before an
        // uppercase letter that begins a run after a lowercase
        // letter (e.g. `MyOrders` -> `My Orders`).
        if let Some(p) = prev
            && c.is_uppercase()
            && (p.is_lowercase() || p.is_ascii_digit())
        {
            push(&mut current, &mut words);
        }
        // Also split before a digit run after letters
        // (e.g. `exam1prep` -> `exam 1 prep`).
        if let Some(p) = prev
            && c.is_ascii_digit()
            && p.is_alphabetic()
        {
            push(&mut current, &mut words);
        }
        // And before letters following a digit (e.g.
        // `1prep` -> `1 prep`).
        if let Some(p) = prev
            && c.is_alphabetic()
            && p.is_ascii_digit()
        {
            push(&mut current, &mut words);
        }
        current.push(c);
        prev = Some(c);
    }
    push(&mut current, &mut words);
    words
}

/// Title-case a single word.
///
/// Uppercases the first character and leaves the rest
/// unchanged; empty strings pass through.
fn title_case_word(word: String) -> String {
    let mut chars = word.chars();
    chars.next().map_or_else(String::new, |first| {
        first.to_uppercase().chain(chars).collect()
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strips_date_prefix_from_temp_project_names() {
        assert_eq!(
            prettify("20260507-water-buffalo-skating"),
            "Water Buffalo Skating"
        );
    }

    #[test]
    fn strips_date_and_temp_marker() {
        assert_eq!(
            prettify("20260507-[temp]-water-buffalo-skating"),
            "Water Buffalo Skating",
        );
    }

    #[test]
    fn handles_pascal_case() {
        assert_eq!(prettify("MyOrders"), "My Orders");
    }

    #[test]
    fn handles_camel_case() {
        assert_eq!(prettify("myOrders"), "My Orders");
    }

    #[test]
    fn handles_snake_case() {
        assert_eq!(prettify("customer_demo"), "Customer Demo");
    }

    #[test]
    fn handles_kebab_case_without_date_prefix() {
        assert_eq!(prettify("customer-demo"), "Customer Demo");
    }

    #[test]
    fn splits_at_digit_boundaries() {
        assert_eq!(prettify("exam1prep"), "Exam 1 Prep");
    }

    #[test]
    fn keeps_kebab_around_digits_intact() {
        assert_eq!(prettify("exam-1-prep"), "Exam 1 Prep");
    }

    #[test]
    fn does_not_strip_non_date_eight_chars() {
        // Eight letters then `-` is not a date prefix.
        assert_eq!(prettify("Customers-orders"), "Customers Orders");
    }

    #[test]
    fn does_not_strip_when_no_separator_after_digits() {
        // Eight digits but no `-` immediately after.
        assert_eq!(prettify("12345678abc"), "12345678 Abc");
    }

    #[test]
    fn handles_consecutive_separators() {
        assert_eq!(prettify("a__b--c"), "A B C");
    }

    #[test]
    fn handles_empty() {
        assert_eq!(prettify(""), "");
    }

    #[test]
    fn handles_single_word() {
        assert_eq!(prettify("orders"), "Orders");
    }

    #[test]
    fn handles_unicode_word() {
        // Non-ASCII letters are preserved; first-char uppercase.
        assert_eq!(prettify("café-règles"), "Café Règles");
    }

    #[test]
    fn handles_mixed_separators_and_case() {
        assert_eq!(
            prettify("MyTeam_lessonPlan-2026"),
            "My Team Lesson Plan 2026"
        );
    }
}
