use std::path::Path;

/// Directories that should be skipped entirely during walking.
const IGNORED_DIRS: &[&str] = &[
    ".git",
    ".hg",
    ".svn",
    ".idea",
    ".vscode",
    "__pycache__",
    "node_modules",
    ".DS_Store",
];

/// File extensions and patterns that should be ignored.
const IGNORED_EXTENSIONS: &[&str] = &["swp", "swo", "bak", "tmp", "kate-swp"];

/// File name prefixes/suffixes that indicate temporary files.
const IGNORED_SUFFIXES: &[&str] = &["~"];

const IGNORED_PREFIXES: &[&str] = &["#"];

const IGNORED_NAMES: &[&str] = &[".DS_Store", "Thumbs.db", "desktop.ini"];

/// Returns true if the given path should be ignored.
pub fn should_ignore(path: &Path) -> bool {
    let file_name = match path.file_name().and_then(|n| n.to_str()) {
        Some(name) => name,
        None => return false,
    };

    // Check exact names
    if IGNORED_NAMES.contains(&file_name) {
        return true;
    }

    // Check directory names (for any component in the path)
    for component in path.components() {
        if let Some(s) = component.as_os_str().to_str() {
            if IGNORED_DIRS.contains(&s) {
                return true;
            }
        }
    }

    // Check extensions
    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
        if IGNORED_EXTENSIONS.contains(&ext) {
            return true;
        }
    }

    // Check suffixes
    for suffix in IGNORED_SUFFIXES {
        if file_name.ends_with(suffix) {
            return true;
        }
    }

    // Check prefixes (emacs autosave: #filename# − must have content between the markers)
    for prefix in IGNORED_PREFIXES {
        if file_name.len() > 2 * prefix.len()
            && file_name.starts_with(prefix)
            && file_name.ends_with(prefix)
        {
            return true;
        }
    }

    false
}

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

    #[test]
    fn ignores_swap_files() {
        assert!(should_ignore(Path::new("file.swp")));
        assert!(should_ignore(Path::new("dir/file.swo")));
    }

    #[test]
    fn ignores_backup_files() {
        assert!(should_ignore(Path::new("file.bak")));
        assert!(should_ignore(Path::new("file~")));
    }

    #[test]
    fn ignores_emacs_autosave() {
        assert!(should_ignore(Path::new("#file#")));
    }

    #[test]
    fn ignores_git_directory() {
        assert!(should_ignore(Path::new(".git/config")));
        assert!(should_ignore(Path::new("sub/.git/HEAD")));
    }

    #[test]
    fn ignores_ide_directories() {
        assert!(should_ignore(Path::new(".idea/workspace.xml")));
        assert!(should_ignore(Path::new(".vscode/settings.json")));
    }

    #[test]
    fn ignores_os_files() {
        assert!(should_ignore(Path::new(".DS_Store")));
        assert!(should_ignore(Path::new("Thumbs.db")));
    }

    #[test]
    fn allows_normal_files() {
        assert!(!should_ignore(Path::new("main.rs")));
        assert!(!should_ignore(Path::new("src/lib.rs")));
        assert!(!should_ignore(Path::new("README.md")));
    }

    #[test]
    fn ignores_file_named_tilde() {
        // A file named just "~" ends with ~, so it should be ignored
        assert!(should_ignore(Path::new("~")));
    }

    #[test]
    fn does_not_ignore_lone_hash() {
        // A file named "#" starts with # but does NOT end with #
        // (emacs autosave pattern requires both #start# and #end#)
        assert!(!should_ignore(Path::new("#")));
    }

    #[test]
    fn ignores_deeply_nested_git_dir() {
        assert!(should_ignore(Path::new("a/b/c/.git/objects/pack")));
    }

    #[test]
    fn ignores_tmp_extension() {
        assert!(should_ignore(Path::new("data.tmp")));
        assert!(should_ignore(Path::new("subdir/cache.tmp")));
    }

    #[test]
    fn does_not_ignore_gitignore() {
        // .gitignore is not .git − it should be kept
        assert!(!should_ignore(Path::new(".gitignore")));
        assert!(!should_ignore(Path::new("project/.gitignore")));
    }
}
