///|
/// サブディレクトリ履歴のフィルタリング

///|
/// サブディレクトリに影響するコミット履歴を取得
///
/// アルゴリズム:
/// 1. 開始コミットから親方向に辿る
/// 2. 各コミットでサブディレクトリのツリーIDを取得
/// 3. 前のコミットとツリーIDが異なる場合、そのコミットを含める
pub fn SubdirRepo::log(
  self : SubdirRepo,
  rfs : &@bit.RepoFileSystem,
  max_count? : Int = 100,
) -> Array[SubdirCommit] raise SubdirError {
  let start_commit = match self.current_commit {
    Some(id) => id
    None => return []
  }
  filter_subdir_history(
    rfs,
    self.git_dir,
    start_commit,
    self.subdir_path,
    max_count,
  )
}

///|
/// コミットのバイトデータから author と message を抽出
fn parse_commit_metadata(data : Bytes) -> (String, String, Int64) {
  let text = bytes_to_string_local(data)
  let lines = split_lines(text)
  let mut author = ""
  let mut message = ""
  let mut in_message = false
  for line in lines {
    if in_message {
      message = line
      break // 最初の行だけ取得
    } else if line.has_prefix("author ") {
      author = skip_prefix(line, 7)
    } else if line == "" {
      in_message = true
    }
  }
  let timestamp = extract_timestamp_from_author(author)
  (message, author, timestamp)
}

///|
/// Bytes を String に変換(ローカルヘルパー)
fn bytes_to_string_local(data : Bytes) -> String {
  let result = StringBuilder::new()
  for b in data {
    result.write_char(b.to_int().unsafe_to_char())
  }
  result.to_string()
}

///|
/// 文字列を行に分割
fn split_lines(text : String) -> Array[String] {
  let lines : Array[String] = []
  let current = StringBuilder::new()
  for c in text {
    if c == '\n' {
      lines.push(current.to_string())
      // current を再初期化
      let _ = current.to_string()
      // 新しい StringBuilder を作成できないので、長さ0の状態にリセット
    } else if c != '\r' {
      current.write_char(c)
    }
  }
  let last = current.to_string()
  if last.length() > 0 {
    lines.push(last)
  }
  lines
}

///|
/// 文字列の先頭 n 文字をスキップ
fn skip_prefix(s : String, n : Int) -> String {
  let chars = s.to_array()
  if n >= chars.length() {
    return ""
  }
  let result = StringBuilder::new()
  for i in n.. Int64 {
  // author 形式: "Name  1234567890 +0000"
  let chars = author.to_array()
  let mut timestamp_start = 0
  let mut i = 0
  // ">" を見つける
  while i < chars.length() {
    if chars[i] == '>' {
      timestamp_start = i + 2 // "> " の後
      break
    }
    i += 1
  }
  if timestamp_start == 0 || timestamp_start >= chars.length() {
    return 0L
  }
  // タイムスタンプを読む
  let ts = StringBuilder::new()
  i = timestamp_start
  while i < chars.length() && chars[i] >= '0' && chars[i] <= '9' {
    ts.write_char(chars[i])
    i += 1
  }
  let ts_str = ts.to_string()
  if ts_str == "" {
    return 0L
  }
  // 文字列を数値に変換
  let mut result = 0L
  for c in ts_str {
    result = result * 10L + (c.to_int() - 48).to_int64()
  }
  result
}

///|
/// サブディレクトリに影響するコミット履歴をフィルタリング
fn filter_subdir_history(
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
  start_commit : @bit.ObjectId,
  subdir_path : String,
  max_count : Int,
) -> Array[SubdirCommit] raise SubdirError {
  let db = @lib.ObjectDb::load(rfs, git_dir) catch {
    _ => raise IoError("failed to load object db")
  }
  let result : Array[SubdirCommit] = []
  let visited : Map[String, Bool] = Map([])
  let queue : Array[@bit.ObjectId] = [start_commit]
  let mut prev_tree : @bit.ObjectId? = None
  while queue.length() > 0 && result.length() < max_count {
    let commit_id = queue.remove(0)
    let commit_hex = commit_id.to_hex()
    // 既に訪問済みならスキップ
    if visited.contains(commit_hex) {
      continue
    }
    visited[commit_hex] = true
    // コミット情報を取得
    let commit_obj = db.get(rfs, commit_id) catch { _ => continue }
    let obj = match commit_obj {
      Some(o) => o
      None => continue
    }
    let commit_info = @bit.parse_commit(obj.data) catch { _ => continue }
    // サブディレクトリのツリーIDを取得
    let subdir_tree = get_subdir_tree_from_tree(
      db,
      rfs,
      commit_info.tree,
      subdir_path,
    )
    // 前のコミットとツリーIDが異なる場合、このコミットを結果に追加
    let tree_changed = match (prev_tree, subdir_tree) {
      (None, Some(_)) => true // サブディレクトリが初めて現れた
      (Some(prev), Some(curr)) => prev != curr // ツリーが変更された
      (Some(_), None) => true // サブディレクトリが削除された
      (None, None) => false // 変化なし
    }
    if tree_changed {
      match subdir_tree {
        Some(tree_id) => {
          let (message, author, timestamp) = parse_commit_metadata(obj.data)
          result.push({
            id: commit_id,
            subdir_tree: tree_id,
            message,
            author,
            timestamp,
            prev_commit: if result.length() > 0 {
              Some(result[result.length() - 1].id)
            } else {
              None
            },
          })
        }
        None => () // サブディレクトリが存在しない場合はスキップ
      }
    }
    // 前のツリーを更新
    prev_tree = subdir_tree
    // 親コミットをキューに追加
    for parent in commit_info.parents {
      if !visited.contains(parent.to_hex()) {
        queue.push(parent)
      }
    }
  }
  result
}

///|
/// ルートツリーからサブディレクトリのツリーIDを取得(エラーを返さない版)
fn get_subdir_tree_from_tree(
  db : @lib.ObjectDb,
  rfs : &@bit.RepoFileSystem,
  root_tree : @bit.ObjectId,
  subdir_path : String,
) -> @bit.ObjectId? {
  let path_parts = split_subdir_path(subdir_path)
  if path_parts.length() == 0 {
    return Some(root_tree)
  }
  let mut current_tree = root_tree
  for part in path_parts {
    let tree_obj = db.get(rfs, current_tree) catch { _ => return None }
    let tobj = match tree_obj {
      Some(o) => o
      None => return None
    }
    let entries = @bit.parse_tree(tobj.data) catch { _ => return None }
    let mut found_id : @bit.ObjectId? = None
    for entry in entries {
      if entry.name == part && (entry.mode == "040000" || entry.mode == "40000") {
        found_id = Some(entry.id)
        break
      }
    }
    match found_id {
      Some(id) => current_tree = id
      None => return None
    }
  }
  Some(current_tree)
}

///|
/// パスを分割(履歴用)
fn split_subdir_path(path : String) -> Array[String] {
  if path == "" {
    return []
  }
  let parts : Array[String] = []
  let mut current = StringBuilder::new()
  for c in path {
    if c == '/' {
      let s = current.to_string()
      if s.length() > 0 {
        parts.push(s)
        current = StringBuilder::new()
      }
    } else {
      current.write_char(c)
    }
  }
  let s = current.to_string()
  if s.length() > 0 {
    parts.push(s)
  }
  parts
}

///|
/// サブディレクトリの差分を取得
pub fn SubdirRepo::diff(self : SubdirRepo) -> Array[DiffEntry] {
  let bitfs = self.bitfs
  let result : Array[DiffEntry] = []
  // ワーキング層の変更を収集
  let working = bitfs.get_working_files()
  let deleted = bitfs.get_deleted_files()
  // 追加・変更されたファイル
  for path in working {
    // 追加されたファイルとして扱う(変更と追加の区別は簡略化)
    result.push(Added(path, @bit.ObjectId::zero()))
  }
  // 削除されたファイル
  for path in deleted {
    result.push(Deleted(path, @bit.ObjectId::zero()))
  }
  result
}