//! Glob-based file search with pattern matching and result limits.
//!
//! **Status**: Not yet implemented (TOOLS-SEARCH-02)
//!
//! This module will implement:
//! - Glob pattern matching
//! - Recursive directory traversal
//! - Result count and byte limits
//! - Exclude pattern filtering

use crate::config::SearchConfig;
use crate::error::{ToolError, ToolResult};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Request to search for files matching glob patterns.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobRequest {
    /// Base path to search within.
    pub path: String,

    /// Glob pattern to match (e.g., "**/*.rs", "src/**/*.toml").
    pub pattern: String,

    /// Optional exclude patterns (in addition to defaults).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub exclude: Option<Vec<String>>,

    /// Optional maximum results (overrides config default).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_results: Option<u32>,
}

/// Response from glob search.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobResponse {
    /// Paths matching the glob pattern.
    pub matches: Vec<PathBuf>,

    /// Whether results were truncated due to limits.
    pub truncated: bool,
}

/// Search for files matching glob patterns.
///
/// This implementation:
/// 1. Validates path is within allowed roots
/// 2. Compiles glob pattern using `globset`
/// 3. Traverses directory tree recursively using `walkdir`
/// 4. Matches files against pattern
/// 5. Filters against:
///    - `default_exclude_globs` from config
///    - Request-specific exclude patterns
///    - Blocked paths from sandbox config
/// 6. Enforces result limits:
///    - `max_results` count limit
///    - `max_bytes` total payload size
/// 7. Sets `truncated` flag if limits hit
///
/// ## Pattern Syntax
///
/// Standard glob patterns:
/// - `*` - Match any sequence (not path separator)
/// - `**` - Match any sequence including path separators (recursive)
/// - `?` - Match single character
/// - `[abc]` - Match character class
///
/// Examples:
/// - `**/*.rs` - All Rust files recursively
/// - `src/**/*.toml` - TOML files under src/
/// - `test_*.py` - Python test files in current dir
///
/// ## Error Cases
///
/// - Path outside allowed roots → `ToolError::SandboxViolation`
/// - Invalid glob pattern → `ToolError::InvalidInput`
/// - I/O errors during traversal → `ToolError::Io`
///
/// ## Performance
///
/// - Stops early when limits reached
/// - Skips excluded directories entirely (no traversal)
///
/// ## See Also
///
/// - Task spec: `docs/building/04_acp_client/04_tasks_02_tools_and_sandboxing.md#TOOLS-SEARCH-02`
pub async fn glob_search(
    request: GlobRequest,
    config: &SearchConfig,
) -> ToolResult<GlobResponse> {
    use crate::path::blocklist::compile_blocklist;
    use globset::GlobBuilder;
    use std::path::Path;
    use walkdir::WalkDir;

    // Canonicalize the base path
    let base_path = dunce::canonicalize(Path::new(&request.path)).map_err(|e| {
        if e.kind() == std::io::ErrorKind::NotFound {
            ToolError::NotFound {
                path: request.path.clone(),
            }
        } else {
            ToolError::Io(e)
        }
    })?;

    // Compile the glob pattern
    let glob = GlobBuilder::new(&request.pattern)
        .literal_separator(false) // Allow ** to match path separators
        .build()
        .map_err(|e| ToolError::InvalidInput(format!("Invalid glob pattern: {}", e)))?;

    let glob_matcher = glob.compile_matcher();

    // Compile exclude patterns
    let mut exclude_patterns = config.default_exclude_globs.clone();
    if let Some(ref extra_excludes) = request.exclude {
        exclude_patterns.extend_from_slice(extra_excludes);
    }

    let exclude_compiled = if !exclude_patterns.is_empty() {
        Some(compile_blocklist(&exclude_patterns)?)
    } else {
        None
    };

    // Determine max results
    let max_results = request.max_results.unwrap_or(config.max_results);
    let max_bytes = config.max_bytes;

    // Walk the directory tree
    let mut matches = Vec::new();
    let mut total_bytes = 0u64;
    let mut truncated = false;

    for entry in WalkDir::new(&base_path)
        .follow_links(false)
        .into_iter()
        .filter_entry(|e| {
            // Skip excluded directories early to avoid traversing them
            if let Some(ref exclude) = exclude_compiled {
                if exclude.glob_set().is_match(e.path()) {
                    return false;
                }
            }
            true
        })
    {
        // Check if we've hit the result limit
        if matches.len() >= max_results as usize {
            truncated = true;
            break;
        }

        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue, // Skip entries we can't read
        };

        // Skip directories (we only want files)
        if entry.file_type().is_dir() {
            continue;
        }

        let entry_path = entry.path();

        // Check against glob pattern (use relative path from base)
        let relative_path = entry_path.strip_prefix(&base_path).unwrap_or(entry_path);
        if !glob_matcher.is_match(relative_path) && !glob_matcher.is_match(entry_path) {
            continue;
        }

        // Check exclude patterns (files level)
        if let Some(ref exclude) = exclude_compiled {
            if exclude.glob_set().is_match(entry_path) {
                continue;
            }
        }

        // Check byte limit (approximate - using path length as proxy)
        let path_bytes = entry_path.to_string_lossy().len() as u64;
        if total_bytes + path_bytes > max_bytes {
            truncated = true;
            break;
        }

        total_bytes += path_bytes;
        matches.push(entry_path.to_path_buf());
    }

    Ok(GlobResponse { matches, truncated })
}
