use std::ops::Range;
use kimun_core::nfs::VaultPath;
use kimun_core::note::{NoteDetails, scan};
pub fn section_range(note_text: &str, heading: &str, chunk_text: &str) -> Option<Range<usize>> {
if !chunk_text.is_empty()
&& let Some(start) = note_text.find(chunk_text)
{
return Some(start..start + chunk_text.len());
}
let (chunks, _links) = NoteDetails::chunks_and_links_of(&VaultPath::root(), note_text);
let chunk = chunks.iter().find(|c| {
c.breadcrumb_last()
.is_some_and(|h| h.eq_ignore_ascii_case(heading))
})?;
if let Some(start) = note_text.find(&chunk.text) {
return Some(start..start + chunk.text.len());
}
scan::heading_section_range(note_text, heading)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn section_range_prefers_exact_chunk_text() {
let note = "# a\nalpha body\n# b\nbeta body\n";
let r = section_range(note, "b", "beta body").unwrap();
assert_eq!(¬e[r], "beta body");
}
#[test]
fn section_range_falls_back_to_heading_match() {
let note = "# intro\nreal text here\n";
let r = section_range(note, "INTRO", "normalized text").unwrap();
assert!(note[r].contains("real text here"));
}
#[test]
fn section_range_gives_up_gracefully() {
assert!(section_range("# x\nbody\n", "missing", "nope").is_none());
}
#[test]
fn section_range_matches_via_chunk_when_exact_text_absent_but_chunk_matches() {
let note = "# one\nfirst body\n# two\nsecond body\n";
let r = section_range(note, "two", "does not appear literally").unwrap();
assert!(note[r].contains("second body"));
}
#[test]
fn section_range_prefers_the_first_occurrence_on_a_duplicate_chunk_text() {
let note = "# a\nshared body\n# b\nshared body\n";
let r = section_range(note, "b", "shared body").unwrap();
assert_eq!(r, 4..15, "first occurrence, under heading a, wins");
}
}