//! Chunking strategies for code segmentation.
//!
//! Alternative chunking strategies for future use.

#![allow(dead_code)]

use crate::chunker::Chunker;
use crate::error::Result;
use crate::types::{Chunk, ChunkId, ChunkType, Document};

/// Trait for alternative chunking strategies.
pub trait ChunkingStrategy: Send + Sync {
    /// Apply this strategy to create chunks from content.
    fn apply(&self, document: &Document, content: &str) -> Result<Vec<Chunk>>;
}

/// Semantic chunker that respects code structure.
pub struct SemanticChunker {
    max_chunk_size: usize,
    min_chunk_size: usize,
}

impl SemanticChunker {
    /// Create a new semantic chunker.
    pub fn new(max_chunk_size: usize, min_chunk_size: usize) -> Self {
        Self {
            max_chunk_size,
            min_chunk_size,
        }
    }

    /// Find logical break points in content.
    fn find_break_points(&self, content: &str) -> Vec<usize> {
        let mut breaks = Vec::new();
        let mut in_string = false;
        let mut string_char = ' ';
        let mut brace_depth: usize = 0;
        let mut last_break = 0;

        for (i, c) in content.char_indices() {
            // Track string state
            if !in_string && (c == '"' || c == '\'' || c == '`') {
                in_string = true;
                string_char = c;
            } else if in_string && c == string_char {
                in_string = false;
            }

            if in_string {
                continue;
            }

            // Track brace depth
            match c {
                '{' | '(' | '[' => brace_depth += 1,
                '}' | ')' | ']' => brace_depth = brace_depth.saturating_sub(1),
                _ => {}
            }

            // Good break points: end of top-level blocks or significant lines
            if brace_depth == 0 {
                match c {
                    '}' | ';' => {
                        if i - last_break >= self.min_chunk_size {
                            breaks.push(i + 1);
                            last_break = i + 1;
                        }
                    }
                    '\n' => {
                        // Check if this is a blank line (paragraph break)
                        if i + 1 < content.len() {
                            if let Some(next_char) = content[i + 1..].chars().next() {
                                if next_char == '\n' && i - last_break >= self.min_chunk_size {
                                    breaks.push(i + 1);
                                    last_break = i + 1;
                                }
                            }
                        }
                    }
                    _ => {}
                }
            }
        }

        breaks
    }
}

impl ChunkingStrategy for SemanticChunker {
    fn apply(&self, document: &Document, content: &str) -> Result<Vec<Chunk>> {
        if content.len() <= self.max_chunk_size {
            // Content fits in a single chunk
            return Ok(vec![Chunk {
                id: ChunkId::new(),
                document_id: document.id.clone(),
                content: content.to_string(),
                chunk_type: ChunkType::Block,
                start_line: 1,
                end_line: content.lines().count() as u32,
                start_byte: 0,
                end_byte: content.len(),
                symbol_name: None,
                parent_symbol: None,
            }]);
        }

        let break_points = self.find_break_points(content);
        let mut chunks = Vec::new();
        let mut start = 0;

        for &end in &break_points {
            if end - start >= self.min_chunk_size {
                if end - start <= self.max_chunk_size {
                    let chunk_content = &content[start..end];
                    let start_line = content[..start].lines().count() as u32 + 1;
                    let end_line = content[..end].lines().count() as u32;

                    chunks.push(Chunk {
                        id: ChunkId::new(),
                        document_id: document.id.clone(),
                        content: chunk_content.to_string(),
                        chunk_type: ChunkType::Block,
                        start_line,
                        end_line,
                        start_byte: start,
                        end_byte: end,
                        symbol_name: None,
                        parent_symbol: None,
                    });
                    start = end;
                }
            }
        }

        // Handle remaining content
        if start < content.len() {
            let remaining = &content[start..];
            if remaining.len() >= self.min_chunk_size {
                let start_line = content[..start].lines().count() as u32 + 1;
                let end_line = content.lines().count() as u32;

                chunks.push(Chunk {
                    id: ChunkId::new(),
                    document_id: document.id.clone(),
                    content: remaining.to_string(),
                    chunk_type: ChunkType::Block,
                    start_line,
                    end_line,
                    start_byte: start,
                    end_byte: content.len(),
                    symbol_name: None,
                    parent_symbol: None,
                });
            }
        }

        Ok(chunks)
    }
}

impl Chunker for SemanticChunker {
    fn chunk(&self, document: &Document, content: &str) -> Result<Vec<Chunk>> {
        self.apply(document, content)
    }

    fn name(&self) -> &'static str {
        "semantic"
    }
}

/// Sliding window chunker with overlap.
pub struct SlidingWindowChunker {
    window_size: usize,
    overlap: usize,
}

impl SlidingWindowChunker {
    /// Create a new sliding window chunker.
    pub fn new(window_size: usize, overlap: usize) -> Self {
        Self {
            window_size,
            overlap: overlap.min(window_size / 2), // Overlap cannot exceed half the window
        }
    }

    /// Find the best split point near a target position.
    fn find_split_point(&self, content: &str, target: usize) -> usize {
        let search_start = target.saturating_sub(50);
        let search_end = (target + 50).min(content.len());

        if search_start >= search_end {
            return target.min(content.len());
        }

        let search_range = &content[search_start..search_end];

        // Prefer splitting at newlines
        if let Some(newline_pos) = search_range.rfind('\n') {
            return search_start + newline_pos + 1;
        }

        // Then try spaces
        if let Some(space_pos) = search_range.rfind(' ') {
            return search_start + space_pos + 1;
        }

        target.min(content.len())
    }
}

impl ChunkingStrategy for SlidingWindowChunker {
    fn apply(&self, document: &Document, content: &str) -> Result<Vec<Chunk>> {
        if content.len() <= self.window_size {
            return Ok(vec![Chunk {
                id: ChunkId::new(),
                document_id: document.id.clone(),
                content: content.to_string(),
                chunk_type: ChunkType::Block,
                start_line: 1,
                end_line: content.lines().count() as u32,
                start_byte: 0,
                end_byte: content.len(),
                symbol_name: None,
                parent_symbol: None,
            }]);
        }

        let mut chunks = Vec::new();
        let mut start = 0;
        let step = self.window_size - self.overlap;

        while start < content.len() {
            let target_end = (start + self.window_size).min(content.len());
            let end = if target_end < content.len() {
                self.find_split_point(content, target_end)
            } else {
                content.len()
            };

            let chunk_content = &content[start..end];
            let start_line = content[..start].lines().count() as u32 + 1;
            let end_line = content[..end].lines().count() as u32;

            chunks.push(Chunk {
                id: ChunkId::new(),
                document_id: document.id.clone(),
                content: chunk_content.to_string(),
                chunk_type: ChunkType::Block,
                start_line,
                end_line,
                start_byte: start,
                end_byte: end,
                symbol_name: None,
                parent_symbol: None,
            });

            if end >= content.len() {
                break;
            }

            start = (start + step).min(end);
        }

        Ok(chunks)
    }
}

impl Chunker for SlidingWindowChunker {
    fn chunk(&self, document: &Document, content: &str) -> Result<Vec<Chunk>> {
        self.apply(document, content)
    }

    fn name(&self) -> &'static str {
        "sliding_window"
    }
}

/// Line-based chunker that groups lines together.
pub struct LineChunker {
    lines_per_chunk: usize,
    overlap_lines: usize,
}

impl LineChunker {
    /// Create a new line-based chunker.
    pub fn new(lines_per_chunk: usize, overlap_lines: usize) -> Self {
        Self {
            lines_per_chunk,
            overlap_lines: overlap_lines.min(lines_per_chunk / 2),
        }
    }
}

impl ChunkingStrategy for LineChunker {
    fn apply(&self, document: &Document, content: &str) -> Result<Vec<Chunk>> {
        let lines: Vec<&str> = content.lines().collect();

        if lines.len() <= self.lines_per_chunk {
            return Ok(vec![Chunk {
                id: ChunkId::new(),
                document_id: document.id.clone(),
                content: content.to_string(),
                chunk_type: ChunkType::Block,
                start_line: 1,
                end_line: lines.len() as u32,
                start_byte: 0,
                end_byte: content.len(),
                symbol_name: None,
                parent_symbol: None,
            }]);
        }

        let mut chunks = Vec::new();
        let step = self.lines_per_chunk - self.overlap_lines;
        let mut line_start = 0;

        while line_start < lines.len() {
            let line_end = (line_start + self.lines_per_chunk).min(lines.len());
            let chunk_lines = &lines[line_start..line_end];
            let chunk_content = chunk_lines.join("\n");

            // Calculate byte offsets
            let start_byte: usize = lines[..line_start]
                .iter()
                .map(|l| l.len() + 1) // +1 for newline
                .sum();
            let end_byte: usize = lines[..line_end]
                .iter()
                .map(|l| l.len() + 1)
                .sum::<usize>()
                .saturating_sub(1); // Remove trailing newline

            chunks.push(Chunk {
                id: ChunkId::new(),
                document_id: document.id.clone(),
                content: chunk_content,
                chunk_type: ChunkType::Block,
                start_line: line_start as u32 + 1,
                end_line: line_end as u32,
                start_byte,
                end_byte: end_byte.min(content.len()),
                symbol_name: None,
                parent_symbol: None,
            });

            if line_end >= lines.len() {
                break;
            }

            line_start += step;
        }

        Ok(chunks)
    }
}

impl Chunker for LineChunker {
    fn chunk(&self, document: &Document, content: &str) -> Result<Vec<Chunk>> {
        self.apply(document, content)
    }

    fn name(&self) -> &'static str {
        "line"
    }
}

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

    fn make_document() -> Document {
        Document {
            id: crate::types::DocumentId::new(),
            relative_path: PathBuf::from("test.rs"),
            absolute_path: PathBuf::from("/test/test.rs"),
            language: crate::types::Language::Rust,
            content_hash: "test".to_string(),
            size_bytes: 0,
            modified_at: chrono::Utc::now(),
        }
    }

    #[test]
    fn test_sliding_window_chunker() {
        let chunker = SlidingWindowChunker::new(100, 20);
        let doc = make_document();
        let content = "a".repeat(250);

        let chunks = chunker.chunk(&doc, &content).unwrap();
        assert!(chunks.len() >= 2);

        // Verify overlap
        for i in 1..chunks.len() {
            let prev_end = chunks[i - 1].end_byte;
            let curr_start = chunks[i].start_byte;
            assert!(curr_start < prev_end, "Chunks should overlap");
        }
    }

    #[test]
    fn test_line_chunker() {
        let chunker = LineChunker::new(5, 1);
        let doc = make_document();
        let content = (1..=20).map(|i| format!("line {}", i)).collect::<Vec<_>>().join("\n");

        let chunks = chunker.chunk(&doc, &content).unwrap();
        assert!(chunks.len() >= 4);
    }
}
