//! Lazy loading for low-priority entities.
//!
//! Files classified as Archive (Priority::Archive / IndexingMode::Skip) are not
//! indexed at startup. When a search query hits one of these files, the
//! LazyIndexer queues it for on-demand indexing so future searches include it.

use std::collections::VecDeque;
use std::path::PathBuf;
use std::sync::Arc;

use tokio::sync::Mutex;
use tracing::{debug, warn};

use crate::agents::elrond::Elrond;
use crate::db::SqliteDb;

/// Maximum number of files queued for lazy indexing at once.
const MAX_QUEUE_SIZE: usize = 500;

/// A request to lazily index a file.
#[derive(Debug, Clone)]
struct LazyRequest {
    file_path: String,
    /// The index mode to promote to (e.g., "graph_only" or "full").
    target_mode: String,
}

/// Queues low-priority files for on-demand indexing when first accessed.
///
/// When a search returns a file that was previously skipped or only had
/// metadata indexed, the LazyIndexer queues it for deeper indexing in
/// the background.
pub struct LazyIndexer {
    queue: Mutex<VecDeque<LazyRequest>>,
    elrond: Arc<Elrond>,
    sqlite: Arc<SqliteDb>,
    workspace_root: PathBuf,
}

impl LazyIndexer {
    pub fn new(elrond: Arc<Elrond>, sqlite: Arc<SqliteDb>, workspace_root: PathBuf) -> Self {
        Self {
            queue: Mutex::new(VecDeque::new()),
            elrond,
            sqlite,
            workspace_root,
        }
    }

    /// Queue a file for lazy indexing if it's currently in a low-priority mode.
    ///
    /// Returns `true` if the file was queued, `false` if already indexed or queue full.
    pub async fn queue_if_needed(&self, file_path: &str) -> bool {
        // Check current index mode from metadata.
        let current_mode = match self.sqlite.get_index_metadata(file_path).await {
            Ok(Some(meta)) => meta.index_mode,
            _ => return false, // Not tracked or error — skip.
        };

        // Only queue files that are in skip or metadata_only mode.
        match current_mode.as_str() {
            "skip" | "metadata_only" => {}
            _ => return false, // Already indexed at graph_only or full level.
        }

        let target = if current_mode == "skip" {
            "graph_only"
        } else {
            "full"
        };

        let mut queue = self.queue.lock().await;

        // Dedup: don't queue the same file twice.
        if queue.iter().any(|r| r.file_path == file_path) {
            return false;
        }

        if queue.len() >= MAX_QUEUE_SIZE {
            debug!(
                queue_size = queue.len(),
                "Lazy indexer queue full, dropping request"
            );
            return false;
        }

        queue.push_back(LazyRequest {
            file_path: file_path.to_string(),
            target_mode: target.to_string(),
        });

        debug!(
            file = %file_path,
            from = %current_mode,
            to = %target,
            queue_size = queue.len(),
            "Queued file for lazy indexing"
        );
        true
    }

    /// Process up to `batch_size` queued files.
    /// Returns the number of files successfully indexed.
    pub async fn process_batch(&self, batch_size: usize) -> usize {
        let batch: Vec<LazyRequest> = {
            let mut queue = self.queue.lock().await;
            let n = batch_size.min(queue.len());
            queue.drain(..n).collect()
        };

        let mut indexed = 0;
        for req in &batch {
            let result = match req.target_mode.as_str() {
                "full" => self.elrond.index_file(&req.file_path).await,
                "graph_only" => self.elrond.index_file_graph_only(&req.file_path).await,
                _ => continue,
            };

            match result {
                Ok(()) => {
                    // Update metadata to reflect new index mode.
                    let _ = self
                        .sqlite
                        .upsert_index_metadata(
                            &req.file_path,
                            // Keep existing priority (we're promoting mode, not priority).
                            self.current_priority(&req.file_path).await,
                            None,
                            &req.target_mode,
                            self.file_size(&req.file_path),
                            0,
                        )
                        .await;
                    indexed += 1;
                    debug!(file = %req.file_path, mode = %req.target_mode, "Lazy indexed file");
                }
                Err(e) => {
                    warn!(
                        file = %req.file_path,
                        error = %e,
                        "Failed to lazy-index file"
                    );
                }
            }
        }

        if indexed > 0 {
            debug!(indexed, total_batch = batch.len(), "Lazy indexing batch complete");
        }

        indexed
    }

    /// Number of files currently queued.
    pub async fn queue_len(&self) -> usize {
        self.queue.lock().await.len()
    }

    /// Drain the entire queue (e.g., on shutdown).
    pub async fn clear_queue(&self) {
        self.queue.lock().await.clear();
    }

    async fn current_priority(&self, file_path: &str) -> i32 {
        self.sqlite
            .get_index_metadata(file_path)
            .await
            .ok()
            .flatten()
            .map(|m| m.priority)
            .unwrap_or(2) // Default to Reference priority
    }

    fn file_size(&self, file_path: &str) -> u64 {
        let full_path = self.workspace_root.join(file_path);
        std::fs::metadata(full_path)
            .map(|m| m.len())
            .unwrap_or(0)
    }
}

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

    #[test]
    fn test_max_queue_size() {
        assert_eq!(MAX_QUEUE_SIZE, 500);
    }

    #[test]
    fn test_lazy_request_debug() {
        let req = LazyRequest {
            file_path: "src/main.rs".to_string(),
            target_mode: "full".to_string(),
        };
        let debug_str = format!("{:?}", req);
        assert!(debug_str.contains("src/main.rs"));
        assert!(debug_str.contains("full"));
    }
}
