//! Repository discovery: walk a path tree and find git repos.

use std::path::{Path, PathBuf};

use anyhow::Result;
use walkdir::WalkDir;

/// Walk `start`, discover every directory that contains a `.git` entry
/// (non-symlink). If `recursive` is false, stops descending into a
/// repo's subtree after the first hit (standard behaviour). Symlinked
/// `.git` directories are skipped with a warning (CR-C1 anti-traversal).
///
/// Partial discovery errors (permission denied, etc.) are surfaced as
/// warnings rather than hard failures (CR-I5).
pub(super) fn discover_repos(start: &Path, recursive: bool) -> Result<Vec<PathBuf>> {
    let mut found = Vec::new();
    // CLI-H7: cap traversal depth to prevent pathological nesting.
    let mut it = WalkDir::new(start).follow_links(false).max_depth(32).into_iter();
    while let Some(entry) = it.next() {
        let entry = match entry {
            Ok(e) => e,
            Err(e) => {
                // Don't silently swallow permission/IO errors — partial
                // discovery should be visible (CR-I5).
                tracing::warn!("discover_repos: {e}");
                continue;
            }
        };
        if !entry.file_type().is_dir() {
            continue;
        }
        let git = entry.path().join(".git");
        // CR-C1: refuse `.git` symlinks. `Path::exists` follows them, so an
        // attacker who can write `ln -s / <victim>/.git` would otherwise see
        // the entire target tree indexed (and embeddings exfiltrated via
        // MCP search). `symlink_metadata` errors on missing entries — that
        // covers both "not a repo" and "permission denied" without the
        // follow-symlinks footgun.
        let Ok(meta) = std::fs::symlink_metadata(&git) else {
            continue;
        };
        if meta.file_type().is_symlink() {
            tracing::warn!(
                "discover_repos: skipping {} — .git is a symlink (potential traversal vector)",
                entry.path().display()
            );
            continue;
        }
        found.push(entry.path().to_path_buf());
        if !recursive {
            it.skip_current_dir();
        }
    }
    Ok(found)
}
