//! Bug-item creation and listing operations.

use std::path::Path;

use super::super::create_item_in_backlog;

/// Create a bug file and store it in the database.
///
/// Routes through `create_item_in_backlog`, the single internal creation path.
/// `acceptance_criteria` must be non-empty; an empty slice returns an error.
/// Returns the bug_id (e.g. `"4"`).
#[allow(clippy::too_many_arguments)]
pub fn create_bug_file(
    root: &Path,
    name: &str,
    description: &str,
    steps_to_reproduce: &str,
    actual_result: &str,
    expected_result: &str,
    acceptance_criteria: &[String],
    depends_on: Option<&[u32]>,
) -> Result<String, String> {
    let name_owned = name.to_string();
    let description_owned = description.to_string();
    let steps_owned = steps_to_reproduce.to_string();
    let actual_owned = actual_result.to_string();
    let expected_owned = expected_result.to_string();
    let depends_on_owned: Option<Vec<u32>> = depends_on.map(<[u32]>::to_vec);
    let acs_owned: Vec<String> = acceptance_criteria.to_vec();

    create_item_in_backlog(
        root,
        "bug",
        name,
        acceptance_criteria,
        depends_on,
        move |bug_number| {
            let mut content = String::new();
            content.push_str("---\n");
            content.push_str("type: bug\n");
            content.push_str(&format!("name: \"{}\"\n", name_owned.replace('"', "\\\"")));
            if let Some(ref deps) = depends_on_owned.filter(|d| !d.is_empty()) {
                let nums: Vec<String> = deps.iter().map(|n| n.to_string()).collect();
                content.push_str(&format!("depends_on: [{}]\n", nums.join(", ")));
            }
            content.push_str("---\n\n");
            content.push_str(&format!("# Bug {bug_number}: {name_owned}\n\n"));
            content.push_str("## Description\n\n");
            content.push_str(&description_owned);
            content.push_str("\n\n");
            content.push_str("## How to Reproduce\n\n");
            content.push_str(&steps_owned);
            content.push_str("\n\n");
            content.push_str("## Actual Result\n\n");
            content.push_str(&actual_owned);
            content.push_str("\n\n");
            content.push_str("## Expected Result\n\n");
            content.push_str(&expected_owned);
            content.push_str("\n\n");
            content.push_str("## Acceptance Criteria\n\n");
            for criterion in &acs_owned {
                content.push_str(&format!("- [ ] {criterion}\n"));
            }
            content
        },
    )
}

/// Returns true if the item stem is a bug item.
///
/// Checks the slug-based ID format first (e.g. `"4_bug_login_crash"`), then
/// consults the typed CRDT `item_type` register for numeric-only IDs (story 933).
pub(super) fn is_bug_item(stem: &str) -> bool {
    let after_num = stem.trim_start_matches(|c: char| c.is_ascii_digit());
    if after_num.starts_with("_bug_") {
        return true;
    }
    if after_num.is_empty() {
        return crate::crdt_state::read_item(stem)
            .and_then(|v| v.item_type())
            .is_some_and(|t| t == crate::io::story_metadata::ItemType::Bug);
    }
    false
}

/// Extract bug name from the `# Bug N: name` heading (last-resort fallback when
/// the CRDT name register is empty).
#[allow(clippy::string_slice)] // colon_pos from find(": "); +2 skips ASCII ": " → valid boundary
pub(super) fn extract_bug_name_from_content(content: &str) -> Option<String> {
    for line in content.lines() {
        if let Some(rest) = line.strip_prefix("# Bug ")
            && let Some(colon_pos) = rest.find(": ")
        {
            return Some(rest[colon_pos + 2..].to_string());
        }
    }
    None
}

/// List all open bugs from CRDT + content store.
///
/// Returns a sorted list of `(bug_id, name)` pairs.
pub fn list_bug_files(_root: &Path) -> Result<Vec<(String, String)>, String> {
    let mut bugs = Vec::new();

    for item in crate::pipeline_state::read_all_typed() {
        if !matches!(item.stage, crate::pipeline_state::Stage::Backlog)
            || !is_bug_item(&item.story_id.0)
        {
            continue;
        }
        let sid = item.story_id.0;
        let name = if item.name.is_empty() {
            None
        } else {
            Some(item.name)
        }
        .or_else(|| {
            crate::db::read_content(crate::db::ContentKey::Story(&sid))
                .and_then(|c| extract_bug_name_from_content(&c))
        })
        .unwrap_or_else(|| sid.clone());
        bugs.push((sid, name));
    }

    bugs.sort_by(|a, b| a.0.cmp(&b.0));
    Ok(bugs)
}
