//! Doc-lifecycle plugin for VAPORA

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

use doc_lifecycle_core::prelude::*;
use doc_lifecycle_core::rag_indexer::DocumentChunk;

use crate::config::PluginConfig;
use crate::Result;

/// Main plugin interface for doc-lifecycle integration
#[derive(Debug)]
pub struct DocLifecyclePlugin {
    config: PluginConfig,
    classifier: Classifier,
    consolidator: Consolidator,
    rag_indexer: RagIndexer,
    mdbook_generator: Option<MdBookGenerator>,
}

impl DocLifecyclePlugin {
    /// Create a new doc-lifecycle plugin
    pub fn new(config: PluginConfig) -> Result<Self> {
        let mdbook_generator = if config.generate_mdbook {
            let mdbook_config = doc_lifecycle_core::mdbook_generator::MdBookConfig::new(
                PathBuf::from("book"),
                "VAPORA Project".to_string(),
            );
            Some(MdBookGenerator::new(mdbook_config))
        } else {
            None
        };

        Ok(Self {
            config,
            classifier: Classifier::new(),
            consolidator: Consolidator::new(),
            rag_indexer: RagIndexer::new(),
            mdbook_generator,
        })
    }

    /// Process documentation for a completed task
    pub async fn process_task_docs(&mut self, task_id: &str) -> Result<()> {
        tracing::info!("Processing task docs for task {}", task_id);

        if self.config.auto_classify {
            self.classify_session_docs(task_id).await?;
        }

        if self.config.auto_consolidate {
            self.consolidate_docs().await?;
        }

        if self.config.generate_rag_index {
            self.update_rag_index().await?;
        }

        if self.config.generate_mdbook {
            self.generate_mdbook().await?;
        }

        Ok(())
    }

    async fn classify_session_docs(&self, task_id: &str) -> Result<()> {
        let session_dir = PathBuf::from(".coder");
        if !session_dir.exists() {
            tracing::debug!(task_id = %task_id, "No .coder/ directory; skipping classification");
            return Ok(());
        }

        let docs = collect_md_docs(&session_dir).await?;
        for (path, content) in &docs {
            match self.classifier.classify(path, Some(content)) {
                Ok(result) => {
                    tracing::info!(
                        task_id = %task_id,
                        path = %path.display(),
                        doc_type = %result.doc_type,
                        confidence = result.confidence,
                        "Session doc classified"
                    );
                }
                Err(e) => {
                    tracing::warn!(
                        task_id = %task_id,
                        path = %path.display(),
                        error = %e,
                        "Failed to classify session doc"
                    );
                }
            }
        }
        Ok(())
    }

    async fn consolidate_docs(&self) -> Result<()> {
        let docs = collect_md_docs(&self.config.docs_root).await?;
        if docs.is_empty() {
            return Ok(());
        }

        let matches = self.consolidator.find_duplicates(&docs)?;
        for m in &matches {
            tracing::warn!(
                path1 = %m.path1.display(),
                path2 = %m.path2.display(),
                score = m.score,
                "Possible duplicate docs detected; manual review recommended"
            );
        }
        tracing::info!(
            duplicates = matches.len(),
            docs_scanned = docs.len(),
            "Consolidation complete"
        );
        Ok(())
    }

    async fn update_rag_index(&self) -> Result<()> {
        let docs = collect_md_docs(&self.config.docs_root).await?;
        if docs.is_empty() {
            return Ok(());
        }

        let mut all_chunks = Vec::new();
        for (path, content) in &docs {
            let chunks = self.rag_indexer.chunk_document(path.clone(), content)?;
            all_chunks.extend(chunks);
        }

        let embeddings = self.rag_indexer.generate_embeddings(&all_chunks).await?;

        let indexed: Vec<DocumentChunk> = all_chunks
            .into_iter()
            .zip(embeddings)
            .map(|(mut chunk, emb)| {
                chunk.metadata.embedding = Some(emb);
                chunk
            })
            .collect();

        self.rag_indexer.build_index(&indexed)?;
        tracing::info!(chunks = indexed.len(), "RAG index updated");
        Ok(())
    }

    async fn generate_mdbook(&self) -> Result<()> {
        if let Some(generator) = &self.mdbook_generator {
            generator.generate()?;
        }
        Ok(())
    }
}

/// Recursively collect all `.md` files under `root` and return their content.
async fn collect_md_docs(root: &Path) -> Result<Vec<(PathBuf, String)>> {
    let mut result = Vec::new();
    if !root.exists() {
        return Ok(result);
    }

    let mut stack: Vec<PathBuf> = vec![root.to_path_buf()];
    while let Some(dir) = stack.pop() {
        let mut entries = tokio::fs::read_dir(&dir).await?;
        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            let file_type = entry.file_type().await?;
            if file_type.is_dir() {
                stack.push(path);
            } else if path.extension().and_then(|e| e.to_str()) == Some("md") {
                match tokio::fs::read_to_string(&path).await {
                    Ok(content) => result.push((path, content)),
                    Err(e) => {
                        tracing::warn!(path = %path.display(), error = %e, "Failed to read doc file");
                    }
                }
            }
        }
    }
    Ok(result)
}
