///|
/// Route computation from file paths.
///
/// Rules:
/// - `content/index.md` → `/`
/// - `content/guide/index.md` → `/guide/`
/// - `content/guide/getting-started.md` → `/guide/getting-started/`
///
/// The `source_dir` prefix (e.g. "content") is stripped from the
/// filepath before route computation.
pub fn filepath_to_route(filepath : String, source_dir : String) -> String {
  let rel = if filepath.has_prefix(source_dir + "/") {
    let n = source_dir.length() + 1
    filepath[n:].to_owned()
  } else {
    filepath
  }
  let without_ext = strip_extension(rel)
  if without_ext == "index" || without_ext.has_suffix("/index") {
    let n = without_ext.length()
    let dir_end = if without_ext == "index" { 0 } else { n - 6 }
    let dir = if dir_end == 0 { "" } else { without_ext[0:dir_end].to_owned() }
    return "/\{dir}"
  }
  return "/\{without_ext}/"
}

///|
/// Remove the file extension from a path.
fn strip_extension(path : String) -> String {
  let len = path.length()
  for i = len - 1; i >= 0; i = i - 1 {
    let ch = path[i:i + 1]
    if ch == "." {
      return path[0:i].to_owned()
    }
  }
  return path
}

///|
/// Detect SourceFormat from a file extension.
pub fn detect_format(path : String) -> SourceFormat? {
  if path.has_suffix(".md") {
    Some(SourceFormat::Markdown)
  } else if path.has_suffix(".typ") {
    Some(SourceFormat::Typst)
  } else if path.has_suffix(".html") {
    Some(SourceFormat::Html)
  } else {
    None
  }
}