use alloc::format;
use alloc::string::String;
use crate::components::RiReferenceComponents;
use crate::spec::Spec;
use crate::types::{RiAbsoluteStr, RiReferenceStr, RiString};
pub(super) fn resolve<S: Spec>(
reference: &RiReferenceStr<S>,
base: &RiAbsoluteStr<S>,
) -> RiString<S> {
let r = RiReferenceComponents::from(reference);
let b = RiReferenceComponents::from(base.as_ref());
let (r_scheme, r_authority, r_path, r_query, r_fragment) = r.to_major();
let (b_scheme, b_authority, b_path, b_query, _) = b.to_major();
let t_scheme: &str;
let t_authority: Option<&str>;
let t_path: String;
let t_query: Option<&str>;
if let Some(r_scheme) = r_scheme {
t_scheme = r_scheme;
t_authority = r_authority;
t_path = remove_dot_segments(r_path.into());
t_query = r_query;
} else {
if r_authority.is_some() {
t_authority = r_authority;
t_path = remove_dot_segments(r_path.into());
t_query = r_query;
} else {
if r_path.is_empty() {
t_path = b_path.into();
if r_query.is_some() {
t_query = r_query;
} else {
t_query = b_query;
}
} else {
if r_path.starts_with('/') {
t_path = remove_dot_segments(r_path.into());
} else {
t_path = remove_dot_segments(merge(b_path, r_path, b_authority.is_some()));
}
t_query = r_query;
}
t_authority = b_authority;
}
t_scheme = b_scheme.expect("non-relative IRI must have a scheme");
}
let t_fragment: Option<&str> = r_fragment;
let s = recompose(t_scheme, t_authority, &t_path, t_query, t_fragment);
RiString::<S>::try_from(s).expect("resolution result must be a valid IRI")
}
fn merge(base_path: &str, ref_path: &str, base_authority_defined: bool) -> String {
if base_authority_defined && base_path.is_empty() {
format!("/{}", ref_path)
} else {
let base_path_end = base_path.rfind('/').map_or(0, |s| s + 1);
format!("{}{}", &base_path[..base_path_end], ref_path)
}
}
fn remove_dot_segments(mut input: String) -> String {
let mut output = String::new();
while !input.is_empty() {
if input.starts_with("../") {
input.drain(..3);
} else if input.starts_with("./") {
input.drain(..2);
} else if input.starts_with("/./") {
input.replace_range(..3, "/");
} else if input == "/." {
input.replace_range(..2, "/");
} else if input.starts_with("/../") {
input.replace_range(..4, "/");
remove_last_segment_and_preceding_slash(&mut output);
} else if input == "/.." {
input.replace_range(..3, "/");
remove_last_segment_and_preceding_slash(&mut output);
} else if input == "." {
input.drain(..1);
} else if input == ".." {
input.drain(..2);
} else {
let first_seg_end = if let Some(after_slash) = input.strip_prefix('/') {
after_slash
.find('/')
.map_or_else(|| input.len(), |pos| pos + 1)
} else {
input.find('/').unwrap_or_else(|| input.len())
};
output.extend(input.drain(..first_seg_end));
}
}
output
}
fn remove_last_segment_and_preceding_slash(output: &mut String) {
match output.rfind('/') {
Some(slash_pos) => {
output.drain(slash_pos..);
}
None => output.clear(),
}
}
fn recompose(
scheme: &str,
authority: Option<&str>,
path: &str,
query: Option<&str>,
fragment: Option<&str>,
) -> String {
let mut result = String::new();
result.push_str(scheme);
result.push(':');
if let Some(authority) = authority {
result.push_str("//");
result.push_str(authority);
}
result.push_str(path);
if let Some(query) = query {
result.push('?');
result.push_str(query);
}
if let Some(fragment) = fragment {
result.push('#');
result.push_str(fragment);
}
result
}