///| Remote path helpers

///|
fn is_http_url(url : String) -> Bool {
  url.has_prefix("http://") || url.has_prefix("https://")
}

///|
fn strip_file_scheme(url : String) -> String? {
  if url.has_prefix("file://") {
    Some(
      decode_file_url_path(
        String::unsafe_substring(url, start=7, end=url.length()),
      ),
    )
  } else {
    None
  }
}

///|
fn hex_char_to_int(c : Char) -> Int? {
  if c >= '0' && c <= '9' {
    Some(c.to_int() - '0'.to_int())
  } else if c >= 'a' && c <= 'f' {
    Some(10 + c.to_int() - 'a'.to_int())
  } else if c >= 'A' && c <= 'F' {
    Some(10 + c.to_int() - 'A'.to_int())
  } else {
    None
  }
}

///|
fn decode_file_url_path(path : String) -> String {
  let out = StringBuilder::new()
  let mut i = 0
  while i < path.length() {
    let c = path.unsafe_get(i).to_int().unsafe_to_char()
    if c == '%' && i + 2 < path.length() {
      let c1 = path.unsafe_get(i + 1).to_int().unsafe_to_char()
      let c2 = path.unsafe_get(i + 2).to_int().unsafe_to_char()
      match (hex_char_to_int(c1), hex_char_to_int(c2)) {
        (Some(hi), Some(lo)) => {
          out.write_char((hi * 16 + lo).unsafe_to_char())
          i += 3
          continue
        }
        _ => ()
      }
    }
    out.write_char(c)
    i += 1
  }
  out.to_string()
}

///|
fn strip_remote_helper_path(url : String) -> String? {
  match url.find("::") {
    Some(idx) => {
      let start = idx + 2
      if start >= url.length() {
        None
      } else {
        let path = String::unsafe_substring(url, start~, end=url.length())
        if path.length() == 0 {
          None
        } else {
          Some(path)
        }
      }
    }
    None => None
  }
}

///|
fn normalize_local_path(path : String) -> String {
  let parts : Array[String] = []
  for part_view in path.split("/") {
    let part = part_view.to_owned()
    if part == "" || part == "." {
      continue
    } else if part == ".." {
      if parts.length() > 0 && parts[parts.length() - 1] != ".." {
        let _ = parts.pop()
      } else if !path.has_prefix("/") {
        parts.push(part)
      }
    } else {
      parts.push(part)
    }
  }
  let result = parts.join("/")
  if path.has_prefix("/") {
    if result.length() == 0 {
      "/"
    } else {
      "/" + result
    }
  } else if result.length() == 0 {
    "."
  } else {
    result
  }
}

///|
fn decode_bytes_lossy(data : Bytes) -> String {
  @utf8.decode_lossy(data[:])
}

///|
fn trim_string_internal(s : String) -> String {
  let mut start = 0
  let mut end = s.length()
  while start < end {
    let c = s.unsafe_get(start)
    if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
      start += 1
    } else {
      break
    }
  }
  while end > start {
    let c = s.unsafe_get(end - 1)
    if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
      end -= 1
    } else {
      break
    }
  }
  if start == 0 && end == s.length() {
    s
  } else {
    String::unsafe_substring(s, start~, end~)
  }
}

///|
pub fn resolve_local_repo_path(
  fs : &@bit.RepoFileSystem,
  root : String,
  url : String,
) -> String? {
  let raw0 = match strip_file_scheme(url) {
    Some(path) => path
    None => url
  }
  let raw = match strip_remote_helper_path(raw0) {
    Some(path) => path
    None => raw0
  }
  if is_http_url(raw) || raw.has_prefix("git@") {
    return None
  }
  if raw.has_prefix("/") {
    if fs.is_dir(raw) {
      return Some(raw)
    }
    return None
  }
  let candidate = normalize_local_path(root + "/" + raw)
  if fs.is_dir(candidate) {
    return Some(candidate)
  }
  let parent = match root.rev_find("/") {
    Some(i) => String::unsafe_substring(root, start=0, end=i)
    None => "."
  }
  let parent_candidate = normalize_local_path(parent + "/" + raw)
  if fs.is_dir(parent_candidate) {
    return Some(parent_candidate)
  }
  {
    let base = match @bitio.env_current_dir() {
      Some(dir) => dir
      None =>
        match @bitio.env_get("PWD") {
          Some(dir) => dir
          None => @bitio.env_get("GIT_SHIM_PWD").unwrap_or(".")
        }
    }
    let alt = normalize_local_path(base + "/" + raw)
    if fs.is_dir(alt) {
      return Some(alt)
    }
  }
  if fs.is_dir(raw) {
    return raw |> normalize_local_path |> Some
  }
  None
}

///|
/// Resolve .git file to actual git directory (for submodules)
pub fn resolve_gitdir(fs : &@bit.RepoFileSystem, bit_path : String) -> String {
  if fs.is_dir(bit_path) {
    return bit_path
  }
  // It's a file with "gitdir: " content
  let content = decode_bytes_lossy(fs.read_file(bit_path)) catch {
    _ => return bit_path
  }
  let trimmed = trim_string_internal(content)
  if trimmed.has_prefix("gitdir: ") {
    let target = String::unsafe_substring(
      trimmed,
      start=8,
      end=trimmed.length(),
    )
    // If relative path, resolve relative to parent of .git file
    if !target.has_prefix("/") {
      let parent = match bit_path.rev_find("/") {
        Some(i) => String::unsafe_substring(bit_path, start=0, end=i)
        None => "."
      }
      return parent + "/" + target
    }
    return target
  }
  bit_path
}

///|
pub fn detect_git_dir(
  fs : &@bit.RepoFileSystem,
  path : String,
) -> (String, Bool)? {
  let bare_head = fs.is_file(path + "/HEAD")
  let bare_objs = fs.is_dir(path + "/objects")
  if bare_head && bare_objs {
    return Some((path, true))
  }
  let bit_path = path + "/.git"
  if fs.is_dir(bit_path) || fs.is_file(bit_path) {
    let resolved = resolve_gitdir(fs, bit_path)
    return Some((resolved, false))
  }
  None
}

///|
pub fn is_bare_git_dir(git_dir : String) -> Bool {
  if git_dir == ".git" {
    return false
  }
  if git_dir.has_suffix("/.git") {
    return false
  }
  true
}