///|
/// Clone target result
pub(all) enum GitHubShorthand {
  /// Full repo clone: user/repo -> https://github.com/user/repo
  Repo(String, String?) // github_url, ref?
  /// Subdir clone: user/repo:path -> https://github.com/user/repo, path
  Subdir(String, String, String?) // github_url, subdir_path, ref?
  /// Single file fetch: raw URL, filename
  File(String, String) // raw_url, filename
} derive(Eq, Debug)

///|
fn remote_shorthand_show_string(value : String) -> String {
  let buf = StringBuilder::new()
  buf.write_char('"')
  for c in value {
    if c == '"' {
      buf.write_string("\\\"")
    } else if c == '\\' {
      buf.write_string("\\\\")
    } else if c == '\n' {
      buf.write_string("\\n")
    } else if c == '\r' {
      buf.write_string("\\r")
    } else if c == '\t' {
      buf.write_string("\\t")
    } else {
      buf.write_char(c)
    }
  }
  buf.write_char('"')
  buf.to_string()
}

///|
fn remote_shorthand_show_string_option(value : String?) -> String {
  match value {
    Some(s) => "Some(" + remote_shorthand_show_string(s) + ")"
    None => "None"
  }
}

///|
pub impl Show for GitHubShorthand with fn output(self, logger) {
  match self {
    Repo(url, ref_spec) =>
      logger.write_string(
        "Repo(" +
        remote_shorthand_show_string(url) +
        ", " +
        remote_shorthand_show_string_option(ref_spec) +
        ")",
      )
    Subdir(url, path, ref_spec) =>
      logger.write_string(
        "Subdir(" +
        remote_shorthand_show_string(url) +
        ", " +
        remote_shorthand_show_string(path) +
        ", " +
        remote_shorthand_show_string_option(ref_spec) +
        ")",
      )
    File(url, name) =>
      logger.write_string(
        "File(" +
        remote_shorthand_show_string(url) +
        ", " +
        remote_shorthand_show_string(name) +
        ")",
      )
  }
}

///|
/// Parse clone shorthand or GitHub browser URL.
///
/// Supports:
/// 1. user/repo -> Repo clone (GitHub)
/// 2. user/repo:path -> Subdir clone with sparse checkout
/// 3. user/repo@ref -> Repo clone at ref
/// 4. user/repo@ref:path -> Subdir clone at ref
/// 5. gitlab.com/user/repo -> Repo clone (other hosts)
/// 6. gitlab.com/user/repo:path -> Subdir clone (other hosts)
/// 7. https://github.com/user/repo/tree/branch/path -> Subdir clone
/// 8. https://github.com/user/repo/blob/branch/path -> Single file fetch
///
/// Examples:
/// - "user/repo" -> Repo("https://github.com/user/repo")
/// - "user/repo:src" -> Subdir("https://github.com/user/repo", "src")
/// - "user/repo@main" -> Repo("https://github.com/user/repo", Some("main"))
/// - "user/repo@main:src" -> Subdir("https://github.com/user/repo", "src", Some("main"))
/// - "gitlab.com/user/repo" -> Repo("https://gitlab.com/user/repo")
/// - "https://github.com/user/repo/tree/main/src" -> Subdir(url, "src")
pub fn parse_github_shorthand(url : String) -> GitHubShorthand? {
  // Check for GitHub browser URL first.
  if url.has_prefix("https://github.com/") {
    return parse_github_browser_url(url)
  }
  // Skip if it looks like a local path or full URL.
  if url.has_prefix("./") ||
    url.has_prefix("/") ||
    url.has_prefix("../") ||
    url.has_prefix("https://") ||
    url.has_prefix("http://") ||
    url.has_prefix("git@") ||
    url.has_suffix(".git") {
    return None
  }
  // Check for subdir separator ':'.
  let (repo_part, subdir) = match url.find(":") {
    Some(idx) => {
      let rp = String::unsafe_substring(url, start=0, end=idx)
      let sd = String::unsafe_substring(url, start=idx + 1, end=url.length())
      (rp, if sd.is_empty() { None } else { Some(sd) })
    }
    None => (url, None)
  }
  // Extract @ref from repo part.
  let (repo_base, ref_spec) = match repo_part.rev_find("@") {
    Some(idx) => {
      let base = String::unsafe_substring(repo_part, start=0, end=idx)
      let rf = String::unsafe_substring(
        repo_part,
        start=idx + 1,
        end=repo_part.length(),
      )
      (base, if rf.is_empty() { None } else { Some(rf) })
    }
    None => (repo_part, None)
  }
  // Split repo part by /.
  let parts : Array[String] = repo_base
    .split("/")
    .filter(s => s.length() > 0)
    .map(s => s.to_owned())
    .collect()
  // Determine host and user/repo.
  let (host, owner, repo) = if parts.length() == 2 {
    // user/repo -> GitHub
    ("github.com", parts[0], parts[1])
  } else if parts.length() == 3 && parts[0].contains(".") {
    // host.com/user/repo
    (parts[0], parts[1], parts[2])
  } else {
    return None
  }
  let clone_url = "https://\{host}/\{owner}/\{repo}"
  match subdir {
    Some(sd) => Some(Subdir(clone_url, sd, ref_spec))
    None => Some(Repo(clone_url, ref_spec))
  }
}

///|
/// Check if shorthand conflicts with local directory.
/// Returns the conflicting path if ambiguous.
pub fn check_shorthand_ambiguity(
  url : String,
  dir_exists : (String) -> Bool,
) -> String? {
  // Only check for shorthand patterns (not full URLs).
  if url.has_prefix("https://") ||
    url.has_prefix("http://") ||
    url.has_prefix("git@") ||
    url.has_prefix("file://") ||
    url.has_prefix("./") ||
    url.has_prefix("/") ||
    url.has_prefix("../") ||
    url.has_suffix(".git") {
    return None
  }
  if url.find("/") is None && url.find(":") is None {
    return None
  }
  // Extract repo part (before ':' if present).
  let repo_part = match url.find(":") {
    Some(idx) => String::unsafe_substring(url, start=0, end=idx)
    None => url
  }
  // Strip @ref if present.
  let repo_base = match repo_part.rev_find("@") {
    Some(idx) => String::unsafe_substring(repo_part, start=0, end=idx)
    None => repo_part
  }
  // Check first component.
  let first_slash = repo_base.find("/")
  let first_part = match first_slash {
    Some(idx) => String::unsafe_substring(repo_base, start=0, end=idx)
    None => repo_base
  }
  if dir_exists(first_part) {
    return Some(first_part)
  }
  // Check full repo path (user/repo part).
  let parts : Array[String] = repo_base
    .split("/")
    .filter(s => s.length() > 0)
    .map(s => s.to_owned())
    .collect()
  if parts.length() >= 2 {
    let two_parts = parts[0] + "/" + parts[1]
    if dir_exists(two_parts) {
      return Some(two_parts)
    }
  }
  None
}

///|
/// Parse GitHub browser URL (tree/blob)
/// https://github.com/user/repo/tree/branch/path -> Subdir
/// https://github.com/user/repo/blob/branch/path -> File
fn parse_github_browser_url(url : String) -> GitHubShorthand? {
  // Remove https://github.com/.
  let path = String::unsafe_substring(url, start=19, end=url.length())
  let parts : Array[String] = path
    .split("/")
    .filter(s => s.length() > 0)
    .map(s => s.to_owned())
    .collect()
  // Need at least: user/repo/tree|blob/branch/path.
  // parts[0] = user, parts[1] = repo, parts[2] = tree|blob, parts[3] = branch, parts[4+] = path
  if parts.length() < 5 {
    return None
  }
  let owner = parts[0]
  let repo = parts[1]
  let kind = parts[2] // "tree" or "blob"
  let branch = parts[3]
  // Build path from remaining parts.
  let path_parts : Array[String] = []
  for i in 4.. subdir clone.
    Some(Subdir(github_url, subpath, Some(branch)))
  } else if kind == "blob" {
    // File -> raw fetch.
    let raw_url = "https://raw.githubusercontent.com/\{owner}/\{repo}/\{branch}/\{subpath}"
    let filename = parts[parts.length() - 1]
    Some(File(raw_url, filename))
  } else {
    None
  }
}