//! BAM utility functions shared across analysis modes.
//!
//! Provides CIGAR-aware position lookup and quality computation helpers
//! that are independent of any variant-specific logic.
//!
//! Used by:
//! - `counting/alignment.rs` — `median_qual` for alignment quality scoring
//! - `counting/engine.rs` — `find_read_pos` for position lookup
//! - `hla/aggregate.rs` (future) — quality metrics on HLA-assigned reads

use rust_htslib::bam::record::Cigar;
use rust_htslib::bam::Record;

/// Find the read index corresponding to a genomic position.
///
/// Walks the CIGAR string to translate a reference-coordinate position
/// into the corresponding index in the read's query sequence. Returns
/// `None` if the position falls in a deletion or is not covered by the read.
pub fn find_read_pos(record: &Record, target_pos: i64) -> Option<usize> {
    let cigar = record.cigar();
    let mut ref_pos = record.pos();
    let mut read_pos = 0;

    for op in cigar.iter() {
        match op {
            Cigar::Match(len) | Cigar::Equal(len) | Cigar::Diff(len) => {
                if target_pos >= ref_pos && target_pos < ref_pos + *len as i64 {
                    return Some(read_pos + (target_pos - ref_pos) as usize);
                }
                ref_pos += *len as i64;
                read_pos += *len as usize;
            }
            Cigar::Ins(len) => {
                read_pos += *len as usize;
            }
            Cigar::Del(len) | Cigar::RefSkip(len) => {
                if target_pos >= ref_pos && target_pos < ref_pos + *len as i64 {
                    return None; // Position is deleted
                }
                ref_pos += *len as i64;
            }
            Cigar::SoftClip(len) => {
                read_pos += *len as usize;
            }
            Cigar::HardClip(_) | Cigar::Pad(_) => {}
        }
    }
    None
}

/// Compute the median quality of bases that pass the minimum threshold.
///
/// Follows the GATK `BaseQuality` annotation standard (median rather than min)
/// to prevent a single low-quality outlier from penalizing an entire read's
/// contribution to fragment consensus.
///
/// Returns 0 if no qualifying bases.
#[inline]
pub fn median_qual(quals: &[u8], min_baseq: u8) -> u8 {
    let mut filtered: Vec<u8> = quals.iter()
        .copied()
        .filter(|&q| q >= min_baseq)
        .collect();
    if filtered.is_empty() { return 0; }
    filtered.sort_unstable();
    filtered[filtered.len() / 2]
}
