use madi_core::{Tag, Token};
const OPEN: &[char] = &['(', '[', '{', '“', '‘', '「', '『', '《', '〈', '【', '〔'];
const CLOSE: &[char] = &[')', ']', '}', '”', '’', '」', '』', '》', '〉', '】', '〕'];
const STRAIGHT: &[char] = &['"', '\''];
fn depths(chars: &[char]) -> Vec<i32> {
let n = chars.len();
let mut delta = vec![0i32; n];
for (i, &c) in chars.iter().enumerate() {
if OPEN.contains(&c) || c == '<' {
delta[i] = 1;
} else if CLOSE.contains(&c) || c == '>' {
delta[i] = -1;
}
}
for (k, &q) in STRAIGHT.iter().enumerate() {
let pos: Vec<usize> = (0..n)
.filter(|&i| chars[i] == q)
.filter(|&i| !(k == 1 && i > 0 && chars[i - 1].is_ascii_alphanumeric() && chars.get(i + 1).is_some_and(|x| x.is_ascii_alphanumeric())))
.collect();
for pair in pos.chunks(2) {
if pair.len() == 2 {
delta[pair[0]] = 1;
delta[pair[1]] = -1;
}
}
}
let mut d = 0i32;
delta
.into_iter()
.map(|x| {
d = (d + x).max(0);
d
})
.collect()
}
fn is_decor(c: char) -> bool {
('\u{3131}'..='\u{3163}').contains(&c) || (!c.is_alphanumeric() && !c.is_whitespace() && !is_term(c) && !OPEN.contains(&c) && !CLOSE.contains(&c) && !STRAIGHT.contains(&c) && c != ',' && c != ':')
}
fn is_term(c: char) -> bool {
matches!(c, '.' | '?' | '!' | '…' | '。' | '?' | '!')
}
pub fn split_line(line: &str, toks: &[Token]) -> Vec<(usize, usize)> {
let chars: Vec<char> = line.chars().collect();
let n = chars.len();
let dep = depths(&chars);
let depth_at = |i: usize| if i == 0 { 0 } else { dep[i - 1] };
let mut cuts: Vec<usize> = Vec::new(); let cont_at = |s2: usize| {
toks.iter().find(|t| t.start >= s2).is_some_and(|t| {
matches!(t.tag, Tag::JKQ | Tag::VCP | Tag::EC | Tag::ETM | Tag::VX)
|| (matches!(t.tag, Tag::VV | Tag::VX) && t.form == "하")
})
};
let last_word_tag = |i: usize| toks.iter().rev().find(|t| t.end <= i && !matches!(t.tag, Tag::SF | Tag::SE | Tag::SP | Tag::SS | Tag::SO | Tag::SW)).map(|t| t.tag);
let mut i = 0;
while i < n {
if is_term(chars[i]) {
let mut j = i + 1;
while j < n && is_term(chars[j]) {
j += 1;
}
let quoted = j < n && (CLOSE.contains(&chars[j]) || STRAIGHT.contains(&chars[j]) || chars[j] == '>');
let digit_dot = chars[i] == '.' && j == i + 1 && i > 0 && chars[i - 1].is_ascii_digit() && j < n && chars[j].is_ascii_digit();
let ellipsis = chars[i..j].iter().any(|&c| c == '…') || chars[i..j].iter().filter(|&&c| c == '.').count() >= 2;
let ell_ok = !ellipsis
|| !matches!(last_word_tag(i), Some(Tag::JKS | Tag::JKC | Tag::JKG | Tag::JKO | Tag::JKB | Tag::JKV | Tag::JKQ | Tag::JC) | None);
let mut w0 = i;
while w0 > 0 && !chars[w0 - 1].is_whitespace() {
w0 -= 1;
}
let list_no = chars[i] == '.' && j == i + 1 && i > w0 && i - w0 <= 2 && chars[w0..i].iter().all(|c| c.is_ascii_digit());
let mut s1 = j;
while s1 < n && chars[s1].is_whitespace() {
s1 += 1;
}
if (j >= n || chars[j].is_whitespace()) && !quoted && !digit_dot && depth_at(i) == 0 && ell_ok && !list_no && (s1 >= n || !cont_at(s1)) {
let mut e1 = s1;
while e1 < n && !chars[e1].is_whitespace() && is_decor(chars[e1]) {
e1 += 1;
}
if e1 > s1 && (e1 >= n || chars[e1].is_whitespace()) {
cuts.push(e1);
} else {
cuts.push(j);
}
} else if j < n && !quoted && !digit_dot && depth_at(i) == 0 && chars[j].is_alphabetic() && !chars[j].is_ascii()
&& matches!(last_word_tag(i), Some(Tag::EF)) && !cont_at(j) {
cuts.push(j);
} else if quoted {
let mut m = j;
while m < n && (CLOSE.contains(&chars[m]) || STRAIGHT.contains(&chars[m]) || chars[m] == '>') {
m += 1;
}
if (m >= n || chars[m].is_whitespace()) && dep[m - 1] == 0 {
let mut s2 = m;
while s2 < n && chars[s2].is_whitespace() {
s2 += 1;
}
if !cont_at(s2) && s2 > m {
cuts.push(m);
}
}
}
i = j;
continue;
}
i += 1;
}
let mut groups: Vec<(usize, usize)> = Vec::new(); let mut a = 0;
for k in 1..=toks.len() {
if k == toks.len() || toks[k].space_before {
if a < k {
groups.push((a, k));
}
a = k;
}
}
for (gi, &(a, b)) in groups.iter().enumerate() {
if gi + 1 == groups.len() {
break; }
let mut k = b;
let mut saw_yo = false;
while k > a {
let t = &toks[k - 1];
let jamo = !t.form.is_empty() && t.form.chars().all(|c| ('\u{3131}'..='\u{3163}').contains(&c)); let trailing = matches!(t.tag, Tag::SW | Tag::SO | Tag::SE | Tag::SS | Tag::SP | Tag::NA | Tag::NF | Tag::NV)
|| (t.tag == Tag::JX && t.form == "요")
|| (k - 1 > a && matches!(t.form.as_str(), "용" | "염" | "욤" | "여") && !matches!(t.tag, Tag::EF | Tag::EC)) || (jamo && k - 1 > a);
if !trailing {
break;
}
if t.tag == Tag::SS || (t.tag == Tag::SP && t.form != "~") {
k = a;
break;
}
if t.tag == Tag::JX && t.form == "요" {
saw_yo = true;
}
k -= 1;
}
if k <= a {
continue;
}
let lt = &toks[k - 1];
let ec_final = lt.tag == Tag::EC
&& ["구먼", "구만", "니께", "니까요", "드라구", "더라구", "라구", "걸랑", "거든", "잖아", "쥬", "죠"].iter().any(|x| lt.form.ends_with(x));
let ec_yo = saw_yo && lt.tag == Tag::EC;
let ef = (lt.tag == Tag::EF || ec_final || ec_yo)
&& !matches!(lt.form.as_str(), "ㄴ가" | "는가" | "은가" | "ㄴ지" | "는지" | "은지" | "ㄹ지" | "을지")
&& !lt.form.ends_with('데');
let etn = lt.tag == Tag::ETN && matches!(lt.form.as_str(), "ㅁ" | "음") && k - 1 > a && matches!(toks[k - 2].tag, Tag::VV | Tag::VA | Tag::VX | Tag::VCP | Tag::EP | Tag::XSV | Tag::XSA);
if !(ef || etn) {
continue;
}
let end = toks[b - 1].end.max(toks[k - 1].end).min(n);
if end < n && is_term(chars[end.saturating_sub(1)]) {
continue;
}
let (na, _) = groups[gi + 1];
if cont_at(toks[na].start) {
continue;
}
let decor = |g: (usize, usize)| {
toks[g.0..g.1].iter().all(|t| {
matches!(t.tag, Tag::SF | Tag::SE | Tag::SW | Tag::SO | Tag::SS | Tag::SP | Tag::NA | Tag::NF | Tag::NV)
|| (!t.form.is_empty() && t.form.chars().all(|c| ('\u{3131}'..='\u{3163}').contains(&c)))
})
};
let mut b = b;
let mut gj = gi + 1;
let mut has_sp = false;
while gj < groups.len() && decor(groups[gj]) {
has_sp |= toks[groups[gj].0..groups[gj].1].iter().any(|t| t.tag == Tag::SP && t.form != "~");
b = groups[gj].1;
gj += 1;
}
if has_sp {
continue; }
if gj > gi + 1 && toks[b - 1].end <= n && is_term(chars[toks[b - 1].end.saturating_sub(1)]) {
continue; }
let end = end.max(toks[b - 1].end.min(n));
if depth_at(end.min(n)) != 0 {
continue;
}
let mut s = end;
while s < n && chars[s].is_whitespace() {
s += 1;
}
if s > end {
cuts.push(end);
}
}
cuts.sort_unstable();
cuts.dedup();
let mut out = Vec::new();
let mut start = 0;
for c in cuts.into_iter().chain(std::iter::once(n)) {
let (mut s, mut e) = (start, c.min(n));
while s < e && chars[s].is_whitespace() {
s += 1;
}
while e > s && chars[e - 1].is_whitespace() {
e -= 1;
}
if s < e {
out.push((s, e));
}
start = c;
}
out
}