// RLM Chunking Strategies
// Re-exports chunking from rlm-cli with bridge types for VAPORA integration

use serde::{Deserialize, Serialize};

/// Chunking strategy type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChunkingStrategy {
    /// Fixed-size chunking (character count)
    Fixed,
    /// Semantic chunking (sentence boundaries, paragraph breaks)
    Semantic,
    /// Code-aware chunking (AST-based for Rust, Python, JS, etc.)
    Code,
}

impl std::fmt::Display for ChunkingStrategy {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Fixed => write!(f, "fixed"),
            Self::Semantic => write!(f, "semantic"),
            Self::Code => write!(f, "code"),
        }
    }
}

/// Chunking configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkingConfig {
    /// Strategy to use
    pub strategy: ChunkingStrategy,
    /// Chunk size (in characters for Fixed, approximate for others)
    pub chunk_size: usize,
    /// Overlap between chunks (in characters)
    pub overlap: usize,
}

impl Default for ChunkingConfig {
    fn default() -> Self {
        Self {
            strategy: ChunkingStrategy::Semantic,
            chunk_size: 1000,
            overlap: 100,
        }
    }
}

/// A text chunk with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextChunk {
    /// Chunk content
    pub content: String,
    /// Start index in original document
    pub start_idx: usize,
    /// End index in original document
    pub end_idx: usize,
    /// Metadata (language, file path, etc.)
    pub metadata: Option<serde_json::Value>,
}

/// Chunker trait for splitting documents
pub trait Chunker: Send + Sync {
    /// Chunk a document into pieces
    fn chunk(&self, content: &str) -> crate::Result<Vec<TextChunk>>;
}

/// Fixed-size chunker
pub struct FixedChunker {
    chunk_size: usize,
    overlap: usize,
}

impl FixedChunker {
    pub fn new(chunk_size: usize, overlap: usize) -> Self {
        Self {
            chunk_size,
            overlap,
        }
    }
}

impl Chunker for FixedChunker {
    fn chunk(&self, content: &str) -> crate::Result<Vec<TextChunk>> {
        if content.is_empty() {
            return Ok(Vec::new());
        }

        let mut chunks = Vec::new();
        let mut start = 0;

        while start < content.len() {
            let end = (start + self.chunk_size).min(content.len());
            let chunk_content = content[start..end].to_string();

            chunks.push(TextChunk {
                content: chunk_content,
                start_idx: start,
                end_idx: end,
                metadata: None,
            });

            // Move to next chunk with overlap
            if end >= content.len() {
                break;
            }
            start = end - self.overlap;
        }

        Ok(chunks)
    }
}

/// Semantic chunker (sentence/paragraph boundaries)
pub struct SemanticChunker {
    chunk_size: usize,
    overlap: usize,
}

impl SemanticChunker {
    pub fn new(chunk_size: usize, overlap: usize) -> Self {
        Self {
            chunk_size,
            overlap,
        }
    }

    /// Split text by sentence boundaries
    fn split_sentences(text: &str) -> Vec<&str> {
        // Simple sentence splitting (. ! ? followed by space/newline)
        // TODO: Use rlm-cli's sentence splitter for better accuracy
        let mut sentences = Vec::new();
        let mut start = 0;

        for (i, c) in text.char_indices() {
            if (c == '.' || c == '!' || c == '?') && i + 1 < text.len() {
                let next = text.chars().nth(i + 1);
                if next == Some(' ') || next == Some('\n') {
                    sentences.push(&text[start..=i]);
                    start = i + 1;
                }
            }
        }

        // Add remaining text
        if start < text.len() {
            sentences.push(&text[start..]);
        }

        sentences
    }
}

impl Chunker for SemanticChunker {
    fn chunk(&self, content: &str) -> crate::Result<Vec<TextChunk>> {
        if content.is_empty() {
            return Ok(Vec::new());
        }

        let sentences = Self::split_sentences(content);
        let mut chunks = Vec::new();
        let mut current_chunk = String::new();
        let mut current_start = 0;
        let mut sentence_start = 0;

        for sentence in sentences {
            // If adding this sentence exceeds chunk size, finalize current chunk
            if !current_chunk.is_empty() && current_chunk.len() + sentence.len() > self.chunk_size {
                chunks.push(TextChunk {
                    content: current_chunk.clone(),
                    start_idx: current_start,
                    end_idx: sentence_start,
                    metadata: None,
                });

                // Start new chunk with overlap
                let overlap_start = sentence_start.saturating_sub(self.overlap);
                current_chunk = content[overlap_start..sentence_start].to_string();
                current_start = overlap_start;
            }

            current_chunk.push_str(sentence);
            sentence_start += sentence.len();
        }

        // Add final chunk
        if !current_chunk.is_empty() {
            chunks.push(TextChunk {
                content: current_chunk,
                start_idx: current_start,
                end_idx: content.len(),
                metadata: None,
            });
        }

        Ok(chunks)
    }
}

/// Code-aware chunker (placeholder for AST-based chunking)
pub struct CodeChunker {
    chunk_size: usize,
    overlap: usize,
    #[allow(dead_code)]
    language: Option<String>,
}

impl CodeChunker {
    pub fn new(chunk_size: usize, overlap: usize, language: Option<String>) -> Self {
        Self {
            chunk_size,
            overlap,
            language,
        }
    }
}

impl Chunker for CodeChunker {
    fn chunk(&self, content: &str) -> crate::Result<Vec<TextChunk>> {
        // For now, use semantic chunking
        // TODO: Integrate rlm-cli's AST-based code chunking
        let semantic = SemanticChunker::new(self.chunk_size, self.overlap);
        semantic.chunk(content)
    }
}

/// Factory for creating chunkers
pub fn create_chunker(config: &ChunkingConfig) -> Box<dyn Chunker> {
    match config.strategy {
        ChunkingStrategy::Fixed => Box::new(FixedChunker::new(config.chunk_size, config.overlap)),
        ChunkingStrategy::Semantic => {
            Box::new(SemanticChunker::new(config.chunk_size, config.overlap))
        }
        ChunkingStrategy::Code => {
            Box::new(CodeChunker::new(config.chunk_size, config.overlap, None))
        }
    }
}

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

    #[test]
    fn test_fixed_chunker() {
        let chunker = FixedChunker::new(10, 2);
        let content = "0123456789ABCDEFGHIJ";
        let chunks = chunker.chunk(content).unwrap();

        assert_eq!(chunks.len(), 3);
        assert_eq!(chunks[0].content, "0123456789");
        assert_eq!(chunks[0].start_idx, 0);
        assert_eq!(chunks[0].end_idx, 10);

        assert_eq!(chunks[1].content, "89ABCDEFGH");
        assert_eq!(chunks[1].start_idx, 8);
        assert_eq!(chunks[1].end_idx, 18);

        assert_eq!(chunks[2].content, "GHIJ");
        assert_eq!(chunks[2].start_idx, 16);
        assert_eq!(chunks[2].end_idx, 20);
    }

    #[test]
    fn test_fixed_chunker_empty() {
        let chunker = FixedChunker::new(10, 2);
        let chunks = chunker.chunk("").unwrap();
        assert!(chunks.is_empty());
    }

    #[test]
    fn test_semantic_chunker() {
        let chunker = SemanticChunker::new(50, 10);
        let content = "This is sentence one. This is sentence two. This is sentence three.";
        let chunks = chunker.chunk(content).unwrap();

        assert!(!chunks.is_empty());
        // Should split at sentence boundaries
        assert!(chunks.iter().all(|c| !c.content.is_empty()));
    }

    #[test]
    fn test_create_chunker() {
        let config = ChunkingConfig {
            strategy: ChunkingStrategy::Fixed,
            chunk_size: 100,
            overlap: 20,
        };

        let chunker = create_chunker(&config);
        let chunks = chunker.chunk("test content").unwrap();
        assert_eq!(chunks.len(), 1);
    }

    #[test]
    fn test_chunking_strategy_display() {
        assert_eq!(ChunkingStrategy::Fixed.to_string(), "fixed");
        assert_eq!(ChunkingStrategy::Semantic.to_string(), "semantic");
        assert_eq!(ChunkingStrategy::Code.to_string(), "code");
    }
}
