//! Modern plotting builtin functions using the world-class runmat-plot system
//!
//! These functions integrate with the configuration system to provide
//! interactive GUI plotting, static exports, and Jupyter notebook support.

use runmat_builtins::Tensor;
use runmat_macros::runtime_builtin;
use runmat_plot::plots::figure::PlotElement;
use runmat_plot::plots::*;
use std::env;

/// Determine the plotting mode from environment/config
fn get_plotting_mode() -> PlottingMode {
    // Check environment variables for plotting configuration
    if let Ok(mode) = env::var("RUSTMAT_PLOT_MODE") {
        match mode.to_lowercase().as_str() {
            "gui" => PlottingMode::Interactive,
            "headless" => PlottingMode::Static,
            "jupyter" => PlottingMode::Jupyter,
            _ => PlottingMode::Auto,
        }
    } else {
        PlottingMode::Auto
    }
}

/// Plotting mode configuration
#[derive(Debug, Clone, Copy)]
enum PlottingMode {
    /// Auto-detect based on environment
    Auto,
    /// Interactive GUI window
    Interactive,
    /// Static file export
    Static,
    /// Jupyter notebook
    Jupyter,
}

/// Execute a plot based on the current mode
fn execute_plot(mut figure: Figure) -> Result<String, String> {
    match get_plotting_mode() {
        PlottingMode::Interactive => interactive_export(&mut figure),
        PlottingMode::Static => static_export(&mut figure, "plot.png"),
        PlottingMode::Jupyter => jupyter_export(&mut figure),
        PlottingMode::Auto => {
            // Auto-detect: prefer Jupyter if available, otherwise interactive
            if env::var("JPY_PARENT_PID").is_ok() || env::var("JUPYTER_RUNTIME_DIR").is_ok() {
                jupyter_export(&mut figure)
            } else {
                interactive_export(&mut figure)
            }
        }
    }
}

/// Launch interactive GUI window using platform-optimal approach
fn interactive_export(figure: &mut Figure) -> Result<String, String> {
    // Use platform-optimal GUI system for best compatibility
    let figure_clone = figure.clone();

    // Try platform-optimal GUI system
    match runmat_plot::show_interactive_platform_optimal(figure_clone) {
        Ok(result) => Ok(result),
        Err(e) => {
            // Return the actual error instead of silently falling back to broken static export
            // This will force us to fix the real GPU rendering issues
            Err(format!(
                "Interactive plotting failed: {e}. Please check GPU/GUI system setup."
            ))
        }
    }
}

/// Export plot as static file using the figure data
fn static_export(figure: &mut Figure, filename: &str) -> Result<String, String> {
    // Extract data from the first plot in the figure for fallback export
    // This is a simplified approach - ideally we'd render the entire figure
    if figure.is_empty() {
        return Err("No plots found in figure to export".to_string());
    }

    match figure.get_plot_mut(0) {
        Some(PlotElement::Line(line_plot)) => {
            let x_data: Vec<f64> = line_plot.x_data.to_vec();
            let y_data: Vec<f64> = line_plot.y_data.to_vec();

            runmat_plot::plot_line(
                &x_data,
                &y_data,
                filename,
                runmat_plot::PlotOptions::default(),
            )
            .map_err(|e| format!("Plot export failed: {e}"))?;

            Ok(format!("Plot saved to {filename}"))
        }
        Some(PlotElement::Scatter(scatter_plot)) => {
            let x_data: Vec<f64> = scatter_plot.x_data.to_vec();
            let y_data: Vec<f64> = scatter_plot.y_data.to_vec();

            runmat_plot::plot_scatter(
                &x_data,
                &y_data,
                filename,
                runmat_plot::PlotOptions::default(),
            )
            .map_err(|e| format!("Plot export failed: {e}"))?;

            Ok(format!("Scatter plot saved to {filename}"))
        }
        Some(PlotElement::Bar(bar_chart)) => {
            let values: Vec<f64> = bar_chart.values.to_vec();

            runmat_plot::plot_bar(
                &bar_chart.labels,
                &values,
                filename,
                runmat_plot::PlotOptions::default(),
            )
            .map_err(|e| format!("Plot export failed: {e}"))?;

            Ok(format!("Bar chart saved to {filename}"))
        }
        Some(PlotElement::Histogram(histogram)) => {
            let data: Vec<f64> = histogram.data.to_vec();

            runmat_plot::plot_histogram(
                &data,
                histogram.bins,
                filename,
                runmat_plot::PlotOptions::default(),
            )
            .map_err(|e| format!("Plot export failed: {e}"))?;

            Ok(format!("Histogram saved to {filename}"))
        }
        Some(PlotElement::PointCloud(point_cloud)) => {
            // For static export, render point cloud as 2D projection (X-Y view)
            let x_data: Vec<f64> = point_cloud
                .positions
                .iter()
                .map(|pos| pos.x as f64)
                .collect();
            let y_data: Vec<f64> = point_cloud
                .positions
                .iter()
                .map(|pos| pos.y as f64)
                .collect();

            runmat_plot::plot_scatter(
                &x_data,
                &y_data,
                filename,
                runmat_plot::PlotOptions::default(),
            )
            .map_err(|e| format!("Point cloud export failed: {e}"))?;

            Ok(format!("Point cloud (2D projection) saved to {filename}"))
        }
        None => Err("No plots found in figure to export".to_string()),
    }
}

/// Export plot for Jupyter
#[cfg(feature = "jupyter")]
fn jupyter_export(figure: &mut Figure) -> Result<String, String> {
    use runmat_plot::jupyter::JupyterBackend;
    let mut backend = JupyterBackend::new();
    backend.display_figure(figure)
}

#[cfg(not(feature = "jupyter"))]
fn jupyter_export(_figure: &mut Figure) -> Result<String, String> {
    Err("Jupyter feature not enabled".to_string())
}

/// Extract numeric vector from a Matrix
fn extract_numeric_vector(matrix: &Tensor) -> Vec<f64> {
    matrix.data.clone()
}

#[runtime_builtin(name = "plot", sink = true)]
fn plot_builtin(x: Tensor, y: Tensor) -> Result<String, String> {
    let x_data = extract_numeric_vector(&x);
    let y_data = extract_numeric_vector(&y);

    if x_data.len() != y_data.len() {
        return Err("X and Y data must have the same length".to_string());
    }

    // Create a line plot
    let line_plot = LinePlot::new(x_data.clone(), y_data.clone())
        .map_err(|e| format!("Failed to create line plot: {e}"))?
        .with_label("Data")
        .with_style(
            glam::Vec4::new(0.0, 0.4, 0.8, 1.0), // Blue
            3.0,
            LineStyle::Solid,
        );

    let mut figure = Figure::new()
        .with_title("Plot")
        .with_labels("X", "Y")
        .with_grid(true);

    figure.add_line_plot(line_plot);

    execute_plot(figure)
}

#[runtime_builtin(name = "scatter", sink = true)]
fn scatter_builtin(x: Tensor, y: Tensor) -> Result<String, String> {
    let x_data = extract_numeric_vector(&x);
    let y_data = extract_numeric_vector(&y);

    if x_data.len() != y_data.len() {
        return Err("X and Y data must have the same length".to_string());
    }

    // Create modern scatter plot
    let scatter_plot = ScatterPlot::new(x_data, y_data)
        .map_err(|e| format!("Failed to create scatter plot: {e}"))?
        .with_label("Data")
        .with_style(
            glam::Vec4::new(0.8, 0.2, 0.2, 1.0), // Red
            5.0,
            MarkerStyle::Circle,
        );

    let mut figure = Figure::new()
        .with_title("Scatter Plot")
        .with_labels("X", "Y")
        .with_grid(true);

    figure.add_scatter_plot(scatter_plot);

    execute_plot(figure)
}

#[runtime_builtin(name = "bar", sink = true)]
fn bar_builtin(values: Tensor) -> Result<String, String> {
    let data = extract_numeric_vector(&values);

    // Create simple string labels
    let labels: Vec<String> = (1..=data.len()).map(|i| format!("Item {i}")).collect();

    // Create modern bar chart
    let bar_chart = BarChart::new(labels, data)
        .map_err(|e| format!("Failed to create bar chart: {e}"))?
        .with_label("Values")
        .with_style(glam::Vec4::new(0.2, 0.6, 0.3, 1.0), 0.8); // Green with 80% width

    let mut figure = Figure::new()
        .with_title("Bar Chart")
        .with_labels("Categories", "Values")
        .with_grid(true);

    figure.add_bar_chart(bar_chart);

    execute_plot(figure)
}

#[runtime_builtin(name = "hist", sink = true)]
fn hist_builtin(values: Tensor) -> Result<String, String> {
    let data = extract_numeric_vector(&values);
    let bins = 10; // Default number of bins

    // Create modern histogram
    let histogram = Histogram::new(data, bins)
        .map_err(|e| format!("Failed to create histogram: {e}"))?
        .with_label("Frequency")
        .with_style(glam::Vec4::new(0.6, 0.3, 0.7, 1.0), false); // Purple, not normalized

    let mut figure = Figure::new()
        .with_title("Histogram")
        .with_labels("Values", "Frequency")
        .with_grid(true);

    figure.add_histogram(histogram);

    execute_plot(figure)
}

// 3D Plotting Functions

#[runtime_builtin(name = "surf", sink = true)]
fn surf_builtin(x: Tensor, y: Tensor, z: Tensor) -> Result<String, String> {
    // Convert matrices to 3D surface format
    // For now, assume z is a flattened grid
    let x_data = extract_numeric_vector(&x);
    let y_data = extract_numeric_vector(&y);
    let z_data_flat = extract_numeric_vector(&z);

    // Reconstruct grid (assuming square for simplicity)
    let grid_size = (z_data_flat.len() as f64).sqrt() as usize;
    if grid_size * grid_size != z_data_flat.len() {
        return Err("Z data must form a square grid".to_string());
    }

    let mut z_grid = Vec::new();
    for i in 0..grid_size {
        let mut row = Vec::new();
        for j in 0..grid_size {
            row.push(z_data_flat[i * grid_size + j]);
        }
        z_grid.push(row);
    }

    // Create surface plot
    let surface = SurfacePlot::new(x_data, y_data, z_grid)
        .map_err(|e| format!("Failed to create surface plot: {e}"))?
        .with_colormap(ColorMap::Viridis)
        .with_label("Surface");

    // For now, return a placeholder until 3D integration is complete
    Ok(format!(
        "3D Surface plot created with {} points",
        surface.len()
    ))
}

#[runtime_builtin(name = "scatter3", sink = true)]
fn scatter3_builtin(x: Tensor, y: Tensor, z: Tensor) -> Result<String, String> {
    let x_data = extract_numeric_vector(&x);
    let y_data = extract_numeric_vector(&y);
    let z_data = extract_numeric_vector(&z);

    if x_data.len() != y_data.len() || y_data.len() != z_data.len() {
        return Err("X, Y, and Z data must have the same length".to_string());
    }

    println!("DEBUG: Creating scatter3 plot with {} points", x_data.len());

    // Create 3D positions
    let positions: Vec<glam::Vec3> = x_data
        .iter()
        .zip(y_data.iter())
        .zip(z_data.iter())
        .map(|((&x, &y), &z)| glam::Vec3::new(x as f32, y as f32, z as f32))
        .collect();

    // Create high-performance point cloud
    let point_cloud = PointCloudPlot::new(positions)
        .with_default_color(glam::Vec4::new(1.0, 0.6, 0.2, 1.0)) // Orange
        .with_default_size(8.0) // Larger size for cube effect
        .with_point_style(PointStyle::Square) // Square points for cubic appearance
        .with_colormap(ColorMap::Hot) // Hot colormap for orange gradient
        .with_label("3D Point Cloud");

    println!(
        "DEBUG: PointCloudPlot created successfully with {} points",
        point_cloud.len()
    );

    // Create figure for 3D visualization and display it
    let mut figure = Figure::new()
        .with_title("High-Performance 3D Point Cloud")
        .with_labels("X", "Y"); // Note: 3D Z-axis support to be added

    // Add the point cloud to the figure
    figure.add_point_cloud_plot(point_cloud);

    println!("DEBUG: 3D Point cloud added to figure, executing plot...");

    // Execute the plot to display the window
    execute_plot(figure)
}

#[runtime_builtin(name = "mesh", sink = true)]
fn mesh_builtin(x: Tensor, y: Tensor, z: Tensor) -> Result<String, String> {
    // Similar to surf but wireframe mode
    let result = surf_builtin(x, y, z)?;
    Ok(result.replace("Surface", "Mesh (wireframe)"))
}
