//! Project walker for traversing and analyzing codebases.
// Skeleton phase: allow unused until scan pipeline is wired up
#![allow(dead_code)]
//!
//! The walker:
//! 1. Traverses the project directory (respecting .gitignore)
//! 2. Detects the primary language
//! 3. Maps file paths to ConceptPath segments
//! 4. Filters files based on configuration

mod language;
mod path_mapper;

pub use language::detect_project_language;
pub use path_mapper::PathMapper;

use std::path::Path;

use ignore::WalkBuilder;

use crate::config::AphoriaConfig;
use crate::types::Language;
use crate::AphoriaError;

/// A file discovered during walking.
#[derive(Debug)]
pub struct WalkedFile {
    /// Absolute path to the file.
    pub path: std::path::PathBuf,

    /// Path relative to project root.
    pub relative_path: String,

    /// Detected language.
    pub language: Language,

    /// ConceptPath segments derived from the path.
    pub path_segments: Vec<String>,
}

/// Walk a project directory and yield files for extraction.
pub fn walk_project(root: &Path, config: &AphoriaConfig) -> Result<Vec<WalkedFile>, AphoriaError> {
    if !root.exists() {
        return Err(AphoriaError::ProjectNotFound(root.to_path_buf()));
    }

    let mut files = Vec::new();
    let mapper = PathMapper::new(root, config);

    let walker = WalkBuilder::new(root)
        .hidden(true) // Skip hidden files
        .git_ignore(true) // Respect .gitignore
        .build();

    for entry in walker {
        let entry = entry.map_err(|e| AphoriaError::Walker(e.to_string()))?;
        let path = entry.path();

        // Skip directories
        if path.is_dir() {
            continue;
        }

        // Skip files that are too large
        if let Ok(metadata) = path.metadata() {
            if metadata.len() > config.scan.max_file_size {
                continue;
            }
        }

        // Get relative path
        let relative = path.strip_prefix(root).map_err(|e| AphoriaError::Walker(e.to_string()))?;
        let relative_str = relative.to_string_lossy().to_string();

        // Check exclusions
        if config.scan.exclude.iter().any(|ex| relative_str.starts_with(ex.trim_end_matches('/'))) {
            continue;
        }

        // Detect language
        let language = Language::from_path(path);

        // Skip unknown file types
        if language == Language::Unknown {
            continue;
        }

        // Skip test files if configured
        if !config.scan.include_tests && is_test_file(&relative_str) {
            continue;
        }

        // Map to concept path segments
        let path_segments = mapper.to_segments(&relative_str, language);

        files.push(WalkedFile {
            path: path.to_path_buf(),
            relative_path: relative_str,
            language,
            path_segments,
        });
    }

    Ok(files)
}

/// Check if a file is a test file.
fn is_test_file(path: &str) -> bool {
    let lower = path.to_lowercase();
    lower.contains("test")
        || lower.contains("spec")
        || lower.contains("_test.")
        || lower.contains(".test.")
        || lower.contains("tests/")
        || lower.contains("__tests__")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_test_file() {
        assert!(is_test_file("src/auth/jwt_test.rs"));
        assert!(is_test_file("tests/integration.rs"));
        assert!(is_test_file("src/__tests__/app.tsx"));
        assert!(!is_test_file("src/auth/jwt.rs"));
    }
}
