use std::collections::HashSet;
use std::sync::{Arc, OnceLock};
use crate::data::{HEADER_WORDS_DATA, LAYERED_WORDS_DATA, MARKER_WORDS_DATA};
use crate::types::{ArtistRole, RawTrack, Warning};
use crate::words::{
ascii_lower, at, chars, in_vocab, is_whitespace, is_ws, split_words_chars, string, sub,
PhraseTable,
};
fn header_words() -> &'static HashSet<&'static str> {
static WORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
WORDS.get_or_init(|| HEADER_WORDS_DATA.get("words").strings().collect())
}
fn layered_words() -> &'static HashSet<&'static str> {
static WORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
WORDS.get_or_init(|| LAYERED_WORDS_DATA.get("words").strings().collect())
}
pub(crate) fn marker_table(extra: &[String]) -> Arc<PhraseTable<()>> {
static DEFAULT: OnceLock<Arc<PhraseTable<()>>> = OnceLock::new();
let entry = |w: &str| (marker_words(w).join(" "), ());
let base = MARKER_WORDS_DATA.get("words").strings().map(entry);
if extra.is_empty() {
return DEFAULT.get_or_init(|| Arc::new(PhraseTable::new(base))).clone();
}
Arc::new(PhraseTable::new(base.chain(extra.iter().map(|w| entry(w)))))
}
fn is_marker_break(ch: char) -> bool {
ch == '/' || ch == '-' || is_ws(ch)
}
fn is_bracket(ch: char) -> bool {
matches!(ch, '(' | ')' | '[' | ']' | '{' | '}')
}
pub(crate) fn marker_words(text: &str) -> Vec<String> {
text.split(is_marker_break)
.map(|piece| piece.trim_matches(is_bracket))
.filter(|piece| !piece.is_empty())
.map(str::to_string)
.collect()
}
pub(crate) fn layered_prefix(text: &[char]) -> usize {
let mut i = 0;
while is_whitespace(at(text, i)) {
i += 1;
}
let mut j = i;
while j < text.len() && !is_whitespace(at(text, j)) {
j += 1;
}
if j == i || j == text.len() || !in_vocab(&string(sub(text, i, j)), layered_words()) {
return 0;
}
while is_whitespace(at(text, j)) {
j += 1;
}
if j < text.len() {
j
} else {
0
}
}
pub(crate) fn is_blank(raw: &str) -> bool {
raw.chars().all(is_ws)
}
fn trim_ws(s: &str) -> &str {
s.trim_matches(is_ws)
}
pub(crate) fn is_link(rest: &str) -> bool {
let head: String = trim_ws(rest).chars().take(8).collect();
let t = ascii_lower(&head);
t.starts_with("http://") || t.starts_with("https://") || t.starts_with("www.")
}
pub(crate) fn is_header(rest: &str) -> bool {
let t = trim_ws(rest);
if t.ends_with(':') {
return true;
}
let words = split_words_chars(&chars(t));
words.len() <= 2 && words.first().is_some_and(|first| in_vocab(first, header_words()))
}
pub(crate) fn has_marker_word(track_part: &str, markers: &PhraseTable<()>) -> bool {
let words = marker_words(track_part);
(0..words.len()).any(|i| markers.match_at(&words, i).is_some())
}
pub(crate) fn has_separator(track: &RawTrack, empty: bool) -> bool {
if empty || track.warnings.contains(&Warning::NoSeparator) {
return false;
}
track.flags.unknown_artist || track.artists.iter().any(|a| a.role == ArtistRole::Primary)
}