use super::{DocTarget, utf8::advance_char};
pub(super) fn replace_intradoc_links(s: &str, _target: DocTarget) -> String {
let mut out = String::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if i + 1 < bytes.len() && bytes[i] == b'[' && bytes[i + 1] == b'`' {
let search_start = i + 2;
let mut found = false;
let mut j = search_start;
while j + 1 < bytes.len() {
if bytes[j] == b'`' && bytes[j + 1] == b']' {
let inner = &s[search_start..j];
let converted = inner.replace("::", ".");
out.push('`');
out.push_str(&converted);
out.push('`');
i = j + 2;
if i < bytes.len() && bytes[i] == b'(' {
let mut depth = 1usize;
let mut k = i + 1;
while k < bytes.len() && depth > 0 {
match bytes[k] {
b'(' => depth += 1,
b')' => depth -= 1,
_ => {}
}
k += 1;
}
if depth == 0 {
i = k;
}
}
found = true;
break;
}
j += 1;
}
if !found {
i = advance_char(s, &mut out, i);
}
} else {
i = advance_char(s, &mut out, i);
}
}
out
}
pub(crate) fn wrap_bare_bracket_references(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'[' {
if i + 1 < bytes.len() && bytes[i + 1] == b'`' {
let inner_start = i + 2;
let mut j = inner_start;
let mut found = false;
while j + 1 < bytes.len() {
if bytes[j] == b'`' && bytes[j + 1] == b']' {
let inner = &s[inner_start..j];
out.push('`');
out.push_str(&inner.replace("::", "."));
out.push('`');
i = j + 2;
found = true;
break;
}
j += 1;
}
if !found {
i = advance_char(s, &mut out, i);
}
} else {
let search_start = i + 1;
if let Some(close_pos) = bytes[search_start..].iter().position(|&b| b == b']') {
let bracket_end = search_start + close_pos;
let inner = &s[search_start..bracket_end].trim();
if is_identifier_like(inner) {
out.push('`');
out.push_str(&inner.replace("::", "."));
out.push('`');
i = bracket_end + 1;
} else {
i = advance_char(s, &mut out, i);
}
} else {
i = advance_char(s, &mut out, i);
}
}
} else {
i = advance_char(s, &mut out, i);
}
}
out
}
pub(crate) fn unlink_intradoc_references(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'`' {
let inner_start = i + 1;
let mut j = inner_start;
while j < bytes.len() && bytes[j] != b'`' {
j += 1;
}
out.push('`');
out.push_str(&s[inner_start..j]);
if j < bytes.len() {
out.push('`');
i = j + 1;
} else {
i = j;
}
continue;
}
if bytes[i] != b'[' {
i = advance_char(s, &mut out, i);
continue;
}
if i + 1 < bytes.len() && bytes[i + 1] == b'`' {
let inner_start = i + 2;
if let Some(close) = find_backtick_bracket_close(bytes, inner_start) {
let inner = &s[inner_start..close];
out.push('`');
out.push_str(inner);
out.push('`');
i = close + 2;
i = skip_link_target(bytes, i);
continue;
}
i = advance_char(s, &mut out, i);
continue;
}
let search_start = i + 1;
if let Some(close_pos) = bytes[search_start..].iter().position(|&b| b == b']') {
let bracket_end = search_start + close_pos;
let inner = s[search_start..bracket_end].trim();
let target_is_url = bytes.get(bracket_end + 1) == Some(&b'(') && link_target_is_url(bytes, bracket_end + 1);
if is_identifier_like(inner) && !target_is_url {
out.push('`');
out.push_str(inner);
out.push('`');
i = bracket_end + 1;
i = skip_link_target(bytes, i);
continue;
}
}
i = advance_char(s, &mut out, i);
}
out
}
fn find_backtick_bracket_close(bytes: &[u8], from: usize) -> Option<usize> {
let mut j = from;
while j + 1 < bytes.len() {
if bytes[j] == b'`' && bytes[j + 1] == b']' {
return Some(j);
}
j += 1;
}
None
}
fn skip_link_target(bytes: &[u8], i: usize) -> usize {
if i >= bytes.len() || bytes[i] != b'(' {
return i;
}
let mut depth = 1usize;
let mut k = i + 1;
while k < bytes.len() && depth > 0 {
match bytes[k] {
b'(' => depth += 1,
b')' => depth -= 1,
_ => {}
}
k += 1;
}
if depth == 0 { k } else { i }
}
fn link_target_is_url(bytes: &[u8], open: usize) -> bool {
let start = open + 1;
let Some(rel_close) = bytes[start..].iter().position(|&b| b == b')') else {
return false;
};
let target = std::str::from_utf8(&bytes[start..start + rel_close])
.unwrap_or("")
.trim();
target.starts_with("http://")
|| target.starts_with("https://")
|| target.starts_with("mailto:")
|| target.starts_with("www.")
|| target.starts_with('#')
}
fn is_identifier_like(s: &str) -> bool {
let trimmed = s.trim();
if trimmed.is_empty() {
return false;
}
let first_char = trimmed.chars().next().unwrap();
if !first_char.is_alphabetic() && first_char != '_' {
return false;
}
trimmed
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == ':' || c == '(' || c == ')' || c == '.')
}