use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
use qrcode::QrCode;
use image::Luma;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct ChunkData {
    id: usize,
    text: String,
    frame: usize,
}

#[pyclass]
pub struct MemvidEncoder {
    chunks: Vec<String>,
}

#[pymethods]
impl MemvidEncoder {
    #[new]
    fn new() -> Self {
        MemvidEncoder { chunks: Vec::new() }
    }

    fn add_text(&mut self, text: String, chunk_size: usize, overlap: usize) {
        let mut start = 0;
        while start < text.len() {
            let mut end = start + chunk_size;
            if end > text.len() {
                end = text.len();
            } else {
                // Try to break at sentence boundary
                if let Some(last_period) = text[start..end].rfind('.') {
                    if last_period > (chunk_size as f64 * 0.8) as usize {
                        end = start + last_period + 1;
                    }
                }
            }
            
            let chunk = text[start..end].trim().to_string();
            if !chunk.is_empty() {
                self.chunks.push(chunk);
            }
            
            if end == text.len() {
                break;
            }
            start = end - overlap;
        }
    }

    fn build(&self, output_file: String, _index_file: String) -> PyResult<()> {
        use std::process::{Command, Stdio};
        use std::io::Write;

        if self.chunks.is_empty() {
            return Err(PyValueError::new_err("No chunks to encode"));
        }

        // Spawn FFmpeg process
        let mut child = Command::new("ffmpeg")
            .arg("-y")
            .arg("-f").arg("rawvideo")
            .arg("-pixel_format").arg("gray8")
            .arg("-video_size").arg("1280x720")
            .arg("-framerate").arg("30")
            .arg("-i").arg("-") // Read from stdin
            .arg("-c:v").arg("libx264")
            .arg("-pix_fmt").arg("yuv420p")
            .arg(&output_file)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped()) // Capture stderr for debugging
            .spawn()
            .map_err(|e| PyValueError::new_err(format!("Failed to spawn ffmpeg: {}", e)))?;

        {
            let stdin = child.stdin.as_mut().ok_or_else(|| PyValueError::new_err("Failed to open ffmpeg stdin"))?;

            // Generate and pipe frames
            for (i, chunk) in self.chunks.iter().enumerate() {
                let data = ChunkData {
                    id: i,
                    text: chunk.clone(),
                    frame: i,
                };
                let json_data = serde_json::to_string(&data).map_err(|e| PyValueError::new_err(e.to_string()))?;
                
                let code = QrCode::new(json_data).map_err(|e| PyValueError::new_err(e.to_string()))?;
                let image = code.render::<Luma<u8>>().build();
                let image = image::DynamicImage::ImageLuma8(image);
                let image = image.resize(1280, 720, image::imageops::FilterType::Lanczos3);
                
                // Convert to raw gray8 bytes
                let raw_bytes = image.to_luma8().into_raw();
                
                stdin.write_all(&raw_bytes).map_err(|e| PyValueError::new_err(format!("Failed to write to ffmpeg stdin: {}", e)))?;
            }
        } // stdin is closed here, signaling EOF to ffmpeg

        let output = child.wait_with_output().map_err(|e| PyValueError::new_err(format!("Failed to wait on ffmpeg: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(PyValueError::new_err(format!("FFmpeg encoding failed: {}", stderr)));
        }

        Ok(())
    }
    
    fn get_chunks(&self) -> Vec<String> {
        self.chunks.clone()
    }
}
