///| Git reflog implementation

///|
#warnings("-deprecated")
fn reflog_parse_int64(value : StringView) -> Int64 raise {
  @strconv.parse_int64(value)
}

///|
pub struct ReflogEntry {
  old_id : @bit.ObjectId
  new_id : @bit.ObjectId
  author : String
  email : String
  timestamp : Int64
  timezone : String
  message : String
}

///|
/// Check if a reflog exists for the given ref.
pub fn reflog_exists(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  refname : String,
) -> Bool {
  let reflog_path = get_reflog_path(git_dir, refname)
  fs.is_file(reflog_path)
}

///|
/// Get the reflog file path for a given ref.
pub fn get_reflog_path(git_dir : String, refname : String) -> String {
  if refname == "HEAD" {
    git_dir + "/logs/HEAD"
  } else {
    git_dir + "/logs/" + refname
  }
}

///|
/// Read reflog entries for a given ref.
pub fn read_reflog(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  refname : String,
) -> Array[ReflogEntry] raise @bit.GitError {
  let reflog_path = get_reflog_path(git_dir, refname)
  if !fs.is_file(reflog_path) {
    return []
  }
  let content = @utf8.decode_lossy(fs.read_file(reflog_path)[:])
  let entries : Array[ReflogEntry] = []
  for line_view in content.split("\n") {
    let line = line_view.to_owned()
    if line.length() == 0 {
      continue
    }
    match parse_reflog_line(line) {
      Some(entry) => entries.push(entry)
      None => continue
    }
  }
  entries
}

///|
/// Parse a single reflog line.
fn parse_reflog_line(line : String) -> ReflogEntry? {
  // Format:    <>  \t
  // git omits the tab + message entirely for messageless updates, so a line
  // without a tab is valid and carries an empty message.
  let (prefix, message) = match line.find("\t") {
    Some(idx) =>
      (
        String::unsafe_substring(line, start=0, end=idx),
        String::unsafe_substring(line, start=idx + 1, end=line.length()),
      )
    None => (line, "")
  }
  // Parse prefix: old_sha new_sha author_name  timestamp tz
  let parts : Array[String] = []
  for p in prefix.split(" ") {
    parts.push(p.to_owned())
  }
  if parts.length() < 5 {
    return None
  }
  let old_id = @bit.ObjectId::from_hex(parts[0]) catch { _ => return None }
  let new_id = @bit.ObjectId::from_hex(parts[1]) catch { _ => return None }
  // Author name can have spaces, email is in angle brackets
  // Find the email (surrounded by < >)
  let mut author = ""
  let mut email = ""
  let mut timestamp : Int64 = 0L
  let mut timezone = "+0000"
  let mut i = 2
  // Collect author name until we hit <
  while i < parts.length() {
    if parts[i].has_prefix("<") {
      // Found email start
      let email_part = parts[i]
      email = String::unsafe_substring(
        email_part,
        start=1,
        end=email_part.length() - 1,
      )
      i += 1
      break
    }
    if author.length() > 0 {
      author = author + " "
    }
    author = author + parts[i]
    i += 1
  }
  // Next should be timestamp and timezone
  if i < parts.length() {
    timestamp = reflog_parse_int64(parts[i]) catch { _ => 0L }
    i += 1
  }
  if i < parts.length() {
    timezone = parts[i]
  }
  Some({ old_id, new_id, author, email, timestamp, timezone, message })
}

///|
/// Append a reflog entry.
pub fn append_reflog(
  wfs : &@bit.FileSystem,
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
  refname : String,
  old_id : @bit.ObjectId,
  new_id : @bit.ObjectId,
  author : String,
  email : String,
  timestamp : Int64,
  timezone : String,
  message : String,
) -> Unit raise @bit.GitError {
  let reflog_path = get_reflog_path(git_dir, refname)
  // Ensure parent directory exists
  let parent_dir = match reflog_path.rev_find("/") {
    None => ""
    Some(i) => String::unsafe_substring(reflog_path, start=0, end=i)
  }
  if parent_dir.length() > 0 && !rfs.is_dir(parent_dir) {
    wfs.mkdir_p(parent_dir)
  }
  // Format:    <>  \t\n
  // git only emits the tab + message when the message is non-empty; an empty
  // reason produces a line ending right after the timezone (matching
  // log_ref_write_fd's `if (msg && *msg)` guard).
  let prefix = "\{old_id.to_hex()} \{new_id.to_hex()} \{author} <\{email}> \{timestamp} \{timezone}"
  let entry = if message.length() > 0 {
    "\{prefix}\t\{message}\n"
  } else {
    "\{prefix}\n"
  }
  // Append to file
  if rfs.is_file(reflog_path) {
    let existing = @utf8.decode_lossy(rfs.read_file(reflog_path)[:])
    wfs.write_string(reflog_path, existing.to_string() + entry)
  } else {
    wfs.write_string(reflog_path, entry)
  }
}

///|
/// Create an empty reflog file for a ref.
pub fn create_reflog(
  wfs : &@bit.FileSystem,
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
  refname : String,
) -> Unit raise @bit.GitError {
  let reflog_path = get_reflog_path(git_dir, refname)
  let parent_dir = match reflog_path.rev_find("/") {
    None => ""
    Some(i) => String::unsafe_substring(reflog_path, start=0, end=i)
  }
  if parent_dir.length() > 0 && !rfs.is_dir(parent_dir) {
    wfs.mkdir_p(parent_dir)
  }
  if !rfs.is_file(reflog_path) {
    wfs.write_string(reflog_path, "")
  }
}

///|
/// Delete a reflog file.
pub fn delete_reflog(
  wfs : &@bit.FileSystem,
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
  refname : String,
) -> Unit raise @bit.GitError {
  let reflog_path = get_reflog_path(git_dir, refname)
  if rfs.is_file(reflog_path) {
    wfs.remove_file(reflog_path)
  }
}

///|
fn trim_whitespace(s : String) -> String {
  @string_utils.trim_string(s)
}

///|
/// Check if reflogs should be created for a ref based on config.
/// Returns: (should_log, always_log)
/// - should_log: true if this ref should have reflogs
/// - always_log: true if core.logAllRefUpdates=always
pub fn should_log_ref(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  refname : String,
  is_bare : Bool,
) -> (Bool, Bool) raise @bit.GitError {
  // Read core.logAllRefUpdates from config
  let config_path = git_dir + "/config"
  let mut log_all_ref_updates : String? = None
  if fs.is_file(config_path) {
    let content = @utf8.decode_lossy(fs.read_file(config_path)[:])
    let mut in_core = false
    for line_view in content.split("\n") {
      let line = trim_whitespace(line_view.to_owned())
      if line.has_prefix("[core]") {
        in_core = true
        continue
      }
      if line.has_prefix("[") {
        in_core = false
        continue
      }
      if in_core {
        if line.has_prefix("logAllRefUpdates") ||
          line.has_prefix("logallrefupdates") {
          let eq_idx = line.find("=")
          match eq_idx {
            Some(i) =>
              log_all_ref_updates = Some(
                trim_whitespace(
                  String::unsafe_substring(line, start=i + 1, end=line.length()),
                ),
              )
            None => ()
          }
        }
      }
    }
  }
  match log_all_ref_updates {
    Some("always") => (true, true)
    Some("true") | Some("1") =>
      // Log branch/remote refs only, not arbitrary refs.
      (
        refname.has_prefix("refs/heads/") ||
        refname.has_prefix("refs/remotes/") ||
        (!is_bare && refname == "HEAD"),
        false,
      )
    Some("false") | Some("0") => (false, false)
    _ =>
      if is_bare {
        (false, false)
      } else {
        (
          refname.has_prefix("refs/heads/") ||
          refname.has_prefix("refs/remotes/") ||
          refname == "HEAD",
          false,
        )
      }
  }
}