//! Charts and Data Visualization
//!
//! Professional chart generation for PDF documents:
//! - Line, bar, pie, area, scatter charts
//! - Chart customization (axes, legends, grids)
//! - Integration with Platypus framework
//! - Vector graphics export

use super::error::{EnhancedError, Result};

/// Chart type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChartType {
    Line,
    Bar,
    HorizontalBar,
    StackedBar,
    GroupedBar,
    Pie,
    Pie3D,
    Donut,
    Area,
    StackedArea,
    Scatter,
    Bubble,
    Spider,
    Radar,
    Candlestick,
    BoxPlot,
}

/// Data series for charts
#[derive(Debug, Clone)]
pub struct DataSeries {
    pub name: String,
    pub data: Vec<f64>,
    pub color: Option<(f32, f32, f32)>,
}

impl DataSeries {
    /// Create new data series
    pub fn new(name: impl Into<String>, data: Vec<f64>) -> Self {
        Self {
            name: name.into(),
            data,
            color: None,
        }
    }

    /// Set color
    pub fn with_color(mut self, r: f32, g: f32, b: f32) -> Self {
        self.color = Some((r, g, b));
        self
    }
}

/// Axis configuration
#[derive(Debug, Clone)]
pub struct Axis {
    pub title: String,
    pub show_grid: bool,
    pub show_ticks: bool,
    pub show_labels: bool,
    pub min: Option<f64>,
    pub max: Option<f64>,
    pub tick_interval: Option<f64>,
}

impl Default for Axis {
    fn default() -> Self {
        Self {
            title: String::new(),
            show_grid: true,
            show_ticks: true,
            show_labels: true,
            min: None,
            max: None,
            tick_interval: None,
        }
    }
}

/// Legend configuration
#[derive(Debug, Clone)]
pub struct Legend {
    pub show: bool,
    pub position: LegendPosition,
    pub font_size: f32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LegendPosition {
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
    Left,
    Right,
    Top,
    Bottom,
}

impl Default for Legend {
    fn default() -> Self {
        Self {
            show: true,
            position: LegendPosition::TopRight,
            font_size: 10.0,
        }
    }
}

/// Chart builder
#[derive(Debug)]
pub struct Chart {
    pub chart_type: ChartType,
    pub title: String,
    pub width: f32,
    pub height: f32,
    pub series: Vec<DataSeries>,
    pub labels: Vec<String>,
    pub x_axis: Axis,
    pub y_axis: Axis,
    pub legend: Legend,
    pub show_values: bool,
}

impl Chart {
    /// Create new chart
    pub fn new(chart_type: ChartType) -> Self {
        Self {
            chart_type,
            title: String::new(),
            width: 400.0,
            height: 300.0,
            series: vec![],
            labels: vec![],
            x_axis: Axis::default(),
            y_axis: Axis::default(),
            legend: Legend::default(),
            show_values: false,
        }
    }

    /// Set title
    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Set dimensions
    pub fn size(mut self, width: f32, height: f32) -> Self {
        self.width = width;
        self.height = height;
        self
    }

    /// Add data series
    pub fn add_series(&mut self, series: DataSeries) {
        self.series.push(series);
    }

    /// Set X-axis labels
    pub fn set_labels(&mut self, labels: Vec<String>) {
        self.labels = labels;
    }

    /// Set X-axis configuration
    pub fn x_axis(mut self, axis: Axis) -> Self {
        self.x_axis = axis;
        self
    }

    /// Set Y-axis configuration
    pub fn y_axis(mut self, axis: Axis) -> Self {
        self.y_axis = axis;
        self
    }

    /// Render chart to PDF at the specified position
    pub fn render(&self, pdf_path: &str, page: u32, x: f32, y: f32) -> Result<()> {
        let chart_content = self.generate_pdf_content(x, y)?;

        let mut writer = crate::enhanced::writer::PdfWriter::new();
        writer.add_page_with_content(
            self.width + 2.0 * x,
            self.height + 2.0 * y,
            &chart_content,
        )?;

        let nanos = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_nanos();
        let temp_chart = std::env::temp_dir().join(format!(
            "micropdf_chart_{}_{}.pdf",
            std::process::id(),
            nanos
        ));
        let chart_path = temp_chart.to_string_lossy().to_string();
        writer.save(&chart_path)?;

        let temp_out = std::env::temp_dir().join(format!(
            "micropdf_chart_out_{}_{}.pdf",
            std::process::id(),
            nanos
        ));
        let out_path = temp_out.to_string_lossy().to_string();

        let result = crate::enhanced::overlay::merge_overlay(
            pdf_path,
            &chart_path,
            &out_path,
            &[page as usize],
        )
        .and_then(|_| {
            std::fs::copy(&out_path, pdf_path).map_err(EnhancedError::Io)?;
            Ok(())
        });
        let _ = std::fs::remove_file(&chart_path);
        let _ = std::fs::remove_file(&out_path);
        result?;

        Ok(())
    }

    /// Export chart as standalone PDF
    pub fn save(&self, output_path: &str) -> Result<()> {
        let content = self.generate_pdf_content(20.0, 20.0)?;
        let mut writer = crate::enhanced::writer::PdfWriter::new();
        writer.add_page_with_content(self.width + 40.0, self.height + 40.0, &content)?;
        writer.save(output_path)
    }

    fn generate_pdf_content(&self, origin_x: f32, origin_y: f32) -> Result<String> {
        let mut content = String::new();
        content.push_str("q\n");

        let chart_w = self.width;
        let chart_h = self.height;

        content.push_str("0.8 0.8 0.8 RG 0.5 w\n");
        content.push_str(&format!(
            "{:.2} {:.2} {:.2} {:.2} re S\n",
            origin_x, origin_y, chart_w, chart_h
        ));

        content.push_str("0 0 0 RG 1 w\n");
        content.push_str(&format!(
            "{:.2} {:.2} m {:.2} {:.2} l S\n",
            origin_x,
            origin_y,
            origin_x,
            origin_y + chart_h
        ));
        content.push_str(&format!(
            "{:.2} {:.2} m {:.2} {:.2} l S\n",
            origin_x,
            origin_y,
            origin_x + chart_w,
            origin_y
        ));

        let colors = [
            (0.2f32, 0.4f32, 0.8f32),
            (0.8, 0.2, 0.2),
            (0.2, 0.7, 0.3),
            (0.9, 0.6, 0.1),
            (0.6, 0.2, 0.8),
        ];

        if self.series.is_empty() {
            content.push_str("Q\n");
            return Ok(content);
        }

        let all_values: Vec<f32> = self
            .series
            .iter()
            .flat_map(|s| s.data.iter().map(|&v| v as f32))
            .collect();
        let max_val = all_values
            .iter()
            .cloned()
            .fold(f32::NEG_INFINITY, f32::max)
            .max(1.0);

        match self.chart_type {
            ChartType::Bar => {
                let total_bars: usize = self.series.iter().map(|s| s.data.len()).max().unwrap_or(0);
                if total_bars == 0 {
                    content.push_str("Q\n");
                    return Ok(content);
                }
                let bar_group_w = chart_w / total_bars as f32;
                let bar_w = bar_group_w * 0.7 / self.series.len().max(1) as f32;
                let gap = bar_group_w * 0.15;

                for (si, series) in self.series.iter().enumerate() {
                    let (cr, cg, cb) = colors[si % colors.len()];
                    content.push_str(&format!("{:.2} {:.2} {:.2} rg\n", cr, cg, cb));
                    for (i, val_f64) in series.data.iter().enumerate() {
                        let val = *val_f64 as f32;
                        let bx = origin_x + i as f32 * bar_group_w + gap + si as f32 * bar_w;
                        let bh = (val / max_val) * chart_h;
                        content.push_str(&format!(
                            "{:.2} {:.2} {:.2} {:.2} re f\n",
                            bx, origin_y, bar_w, bh
                        ));
                    }
                }
            }
            ChartType::Line | ChartType::Area => {
                for (si, series) in self.series.iter().enumerate() {
                    if series.data.is_empty() {
                        continue;
                    }
                    let (cr, cg, cb) = colors[si % colors.len()];
                    content.push_str(&format!("{:.2} {:.2} {:.2} RG 1.5 w\n", cr, cg, cb));
                    let step = if series.data.len() > 1 {
                        chart_w / (series.data.len() - 1) as f32
                    } else {
                        chart_w
                    };
                    for (i, val_f64) in series.data.iter().enumerate() {
                        let val = *val_f64 as f32;
                        let px = origin_x + i as f32 * step;
                        let py = origin_y + (val / max_val) * chart_h;
                        if i == 0 {
                            content.push_str(&format!("{:.2} {:.2} m\n", px, py));
                        } else {
                            content.push_str(&format!("{:.2} {:.2} l\n", px, py));
                        }
                    }
                    content.push_str("S\n");
                }
            }
            ChartType::Pie => {
                let total: f32 = self
                    .series
                    .first()
                    .map(|s| s.data.iter().map(|v| *v as f32).sum())
                    .unwrap_or(0.0);
                if total <= 0.0 {
                    content.push_str("Q\n");
                    return Ok(content);
                }
                let cx = origin_x + chart_w / 2.0;
                let cy = origin_y + chart_h / 2.0;
                let radius = chart_w.min(chart_h) / 2.0 * 0.9;
                let mut start_angle = 0.0f32;

                if let Some(series) = self.series.first() {
                    for (i, val_f64) in series.data.iter().enumerate() {
                        let val = *val_f64 as f32;
                        let sweep = (val / total) * 360.0;
                        let (cr, cg, cb) = colors[i % colors.len()];
                        content.push_str(&format!("{:.2} {:.2} {:.2} rg\n", cr, cg, cb));
                        let start_rad = start_angle.to_radians();

                        content.push_str(&format!("{:.2} {:.2} m\n", cx, cy));
                        content.push_str(&format!(
                            "{:.2} {:.2} l\n",
                            cx + radius * start_rad.cos(),
                            cy + radius * start_rad.sin()
                        ));

                        let steps = (sweep / 30.0).ceil().max(1.0) as usize;
                        let angle_step = sweep / steps as f32;
                        for step in 1..=steps {
                            let a = (start_angle + step as f32 * angle_step).to_radians();
                            content.push_str(&format!(
                                "{:.2} {:.2} l\n",
                                cx + radius * a.cos(),
                                cy + radius * a.sin()
                            ));
                        }
                        content.push_str("f\n");
                        start_angle += sweep;
                    }
                }
            }
            _ => {
                let total_bars: usize = self.series.iter().map(|s| s.data.len()).max().unwrap_or(0);
                let bar_w = chart_w / total_bars.max(1) as f32 * 0.8;
                for (si, series) in self.series.iter().enumerate() {
                    let (cr, cg, cb) = colors[si % colors.len()];
                    content.push_str(&format!("{:.2} {:.2} {:.2} rg\n", cr, cg, cb));
                    for (i, val_f64) in series.data.iter().enumerate() {
                        let val = *val_f64 as f32;
                        let bx = origin_x
                            + i as f32 * (chart_w / total_bars.max(1) as f32)
                            + bar_w * 0.1;
                        let bh = (val / max_val) * chart_h;
                        content.push_str(&format!(
                            "{:.2} {:.2} {:.2} {:.2} re f\n",
                            bx, origin_y, bar_w, bh
                        ));
                    }
                }
            }
        }

        if !self.title.is_empty() {
            content.push_str(&format!(
                "BT /F1 12 Tf {:.2} {:.2} Td ({}) Tj ET\n",
                origin_x,
                origin_y + chart_h + 5.0,
                self.title
            ));
        }

        content.push_str("Q\n");
        Ok(content)
    }
}

/// Line chart builder
pub struct LineChart {
    chart: Chart,
}

impl LineChart {
    /// Create new line chart
    pub fn new() -> Self {
        Self {
            chart: Chart::new(ChartType::Line),
        }
    }

    /// Build chart
    pub fn build(self) -> Chart {
        self.chart
    }
}

impl Default for LineChart {
    fn default() -> Self {
        Self::new()
    }
}

/// Bar chart builder
pub struct BarChart {
    chart: Chart,
}

impl BarChart {
    /// Create new bar chart
    pub fn new() -> Self {
        Self {
            chart: Chart::new(ChartType::Bar),
        }
    }

    /// Set stacked mode
    pub fn stacked(mut self) -> Self {
        self.chart.chart_type = ChartType::StackedBar;
        self
    }

    /// Set grouped mode
    pub fn grouped(mut self) -> Self {
        self.chart.chart_type = ChartType::GroupedBar;
        self
    }

    /// Build chart
    pub fn build(self) -> Chart {
        self.chart
    }
}

impl Default for BarChart {
    fn default() -> Self {
        Self::new()
    }
}

/// Pie chart builder
pub struct PieChart {
    chart: Chart,
    exploded: Vec<f32>,
}

impl PieChart {
    /// Create new pie chart
    pub fn new() -> Self {
        Self {
            chart: Chart::new(ChartType::Pie),
            exploded: vec![],
        }
    }

    /// Set 3D mode
    pub fn three_d(mut self) -> Self {
        self.chart.chart_type = ChartType::Pie3D;
        self
    }

    /// Set donut mode
    pub fn donut(mut self) -> Self {
        self.chart.chart_type = ChartType::Donut;
        self
    }

    /// Explode slice
    pub fn explode_slice(&mut self, index: usize, distance: f32) {
        if self.exploded.len() <= index {
            self.exploded.resize(index + 1, 0.0);
        }
        self.exploded[index] = distance;
    }

    /// Build chart
    pub fn build(self) -> Chart {
        self.chart
    }
}

impl Default for PieChart {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_data_series() {
        let series = DataSeries::new("Sales", vec![10.0, 20.0, 30.0]).with_color(1.0, 0.0, 0.0);
        assert_eq!(series.name, "Sales");
        assert_eq!(series.data.len(), 3);
        assert!(series.color.is_some());
    }

    #[test]
    fn test_chart_creation() {
        let chart = Chart::new(ChartType::Line)
            .title("Test Chart")
            .size(500.0, 400.0);
        assert_eq!(chart.title, "Test Chart");
        assert_eq!(chart.width, 500.0);
        assert_eq!(chart.height, 400.0);
    }

    #[test]
    fn test_line_chart() {
        let chart = LineChart::new().build();
        assert_eq!(chart.chart_type, ChartType::Line);
    }

    #[test]
    fn test_bar_chart() {
        let chart = BarChart::new().stacked().build();
        assert_eq!(chart.chart_type, ChartType::StackedBar);
    }

    #[test]
    fn test_pie_chart() {
        let chart = PieChart::new().three_d().build();
        assert_eq!(chart.chart_type, ChartType::Pie3D);
    }
}
