//! Real-time renderer for interactive plotting
//!
//! Provides high-performance rendering for interactive features using the
//! existing GPU acceleration (when available) while maintaining 60fps during interactions.

#[cfg(feature = "gpu")]
use crate::render::gpu::GpuRenderer;
use crate::{
    core::plot::{Image, InteractiveViewportSnapshot},
    core::{
        FramePacing, HitResult, InteractivePlotSession, Plot, PlotInputEvent, QualityPolicy,
        ReactiveSubscription, Result, SurfaceTarget, ViewportPoint,
    },
    interactive::{
        event::{Annotation, Point2D, Rectangle},
        state::{DataPoint, DataPointId, InteractionState},
    },
    render::{Color, FontConfig, FontFamily, TextRenderer, skia::SkiaRenderer},
};
use std::{
    collections::HashMap,
    sync::Arc,
    time::{Duration, Instant},
};

#[derive(Clone, Debug)]
pub(crate) enum InteractiveRenderOutput {
    Pixels(Vec<u8>),
    Layers(InteractiveLayerOutput),
}

#[derive(Clone, Debug)]
pub(crate) struct InteractiveLayerOutput {
    pub base: Arc<Image>,
    pub overlays: Vec<Arc<Image>>,
}

/// Real-time renderer for interactive plotting
pub struct RealTimeRenderer {
    // Core rendering components
    #[cfg(feature = "gpu")]
    gpu_renderer: Option<GpuRenderer>,
    cpu_renderer: SkiaRenderer,

    // Rendering state
    current_plot: Option<Plot>,
    interactive_session: Option<InteractivePlotSession>,
    render_cache: RenderCache,
    performance_monitor: PerformanceMonitor,
    last_device_scale: f32,

    // Interactive elements
    hover_highlight_color: Color,
    selection_highlight_color: Color,
    brush_color: Color,
    brush_outline_color: Color,
    annotation_renderer: AnnotationRenderer,

    // Optimization settings
    quality_mode: RenderQuality,
    adaptive_quality: bool,
    target_fps: f64,
}

impl RealTimeRenderer {
    /// Create new real-time renderer
    pub async fn new() -> Result<Self> {
        #[cfg(feature = "gpu")]
        let gpu_renderer = match crate::render::gpu::initialize_gpu_backend().await {
            Ok(_) => match GpuRenderer::new().await {
                Ok(renderer) => {
                    log::info!("Interactive GPU renderer initialized");
                    Some(renderer)
                }
                Err(e) => {
                    log::warn!("GPU not available for interactive mode: {}", e);
                    None
                }
            },
            Err(e) => {
                log::warn!("GPU backend initialization failed: {}", e);
                None
            }
        };

        let cpu_renderer = SkiaRenderer::new(800, 600, crate::render::Theme::default())?;

        Ok(Self {
            #[cfg(feature = "gpu")]
            gpu_renderer,
            cpu_renderer,
            current_plot: None,
            interactive_session: None,
            render_cache: RenderCache::new(),
            performance_monitor: PerformanceMonitor::new(),
            last_device_scale: 1.0,

            hover_highlight_color: Color::new_rgba(255, 165, 0, 180), // Orange with transparency
            selection_highlight_color: Color::new_rgba(255, 0, 0, 120), // Red with transparency
            brush_color: Color::new_rgba(0, 100, 255, 60),            // Blue with high transparency
            brush_outline_color: Color::new_rgba(96, 208, 255, 220),
            annotation_renderer: AnnotationRenderer::new(),

            quality_mode: RenderQuality::Interactive,
            adaptive_quality: true,
            target_fps: 60.0,
        })
    }

    /// Set the current plot for rendering
    pub fn set_plot(&mut self, plot: Plot) {
        self.interactive_session = Some(plot.prepare_interactive());
        self.current_plot = Some(plot);
        self.render_cache.invalidate_all();
    }

    /// Update the renderer's last known device scale for event-time hit testing.
    pub fn set_device_scale(&mut self, device_scale: f32) {
        self.last_device_scale = Self::sanitize_device_scale(device_scale);
    }

    pub(crate) fn apply_session_input(
        &mut self,
        event: PlotInputEvent,
        size_px: (u32, u32),
        device_scale: f32,
    ) -> bool {
        self.set_device_scale(device_scale);
        if let Some(session) = &self.interactive_session {
            self.sync_session_target(session, size_px, self.last_device_scale);
            let dirty_before = session.dirty_domains();
            session.apply_input(event);
            return session.dirty_domains() != dirty_before;
        }
        false
    }

    pub(crate) fn subscribe_reactive<F>(&self, callback: F) -> Option<ReactiveSubscription>
    where
        F: Fn() + Send + Sync + 'static,
    {
        self.interactive_session
            .as_ref()
            .map(|session| session.subscribe_reactive(callback))
    }

    pub(crate) fn viewport_snapshot(&self) -> Result<Option<InteractiveViewportSnapshot>> {
        let Some(session) = &self.interactive_session else {
            return Ok(None);
        };
        session.viewport_snapshot().map(Some)
    }

    pub(crate) fn restore_visible_bounds(
        &mut self,
        visible_bounds: crate::core::ViewportRect,
        size_px: (u32, u32),
        device_scale: f32,
    ) -> bool {
        self.set_device_scale(device_scale);
        if let Some(session) = &self.interactive_session {
            self.sync_session_target(session, size_px, self.last_device_scale);
            return session.restore_visible_bounds(visible_bounds);
        }
        false
    }

    /// Render frame with current interaction state
    pub(crate) fn render_interactive(
        &mut self,
        state: &InteractionState,
        width: u32,
        height: u32,
        device_scale: f32,
    ) -> Result<InteractiveRenderOutput> {
        let frame_start = Instant::now();

        // Update renderer dimensions if needed
        self.update_dimensions(width, height)?;
        self.set_device_scale(device_scale);

        // Adaptive quality based on performance
        if self.adaptive_quality {
            self.update_quality_mode(state);
        }

        if self.interactive_session.is_some() {
            let frame = self.render_session_frame(state, width, height, device_scale)?;
            self.performance_monitor.record_frame(frame_start.elapsed());
            return Ok(frame);
        }

        // Render base plot (cached when possible)
        let mut pixel_data = self.render_base_plot(state, width, height, device_scale)?;

        // Render interactive elements on top
        self.render_hover_highlight(state, &mut pixel_data)?;
        self.render_selection_highlight(state, &mut pixel_data)?;
        self.render_brush_region(state, &mut pixel_data)?;
        self.render_annotations(state, &mut pixel_data)?;
        self.render_tooltip(state, &mut pixel_data)?;

        // Update performance metrics
        self.performance_monitor.record_frame(frame_start.elapsed());

        Ok(InteractiveRenderOutput::Pixels(pixel_data))
    }

    /// Render high-quality static version for export
    pub fn render_publication(
        &mut self,
        plot: &Plot,
        width: u32,
        height: u32,
        dpi: f32,
    ) -> Result<Vec<u8>> {
        // Temporarily switch to high quality mode
        let old_quality = self.quality_mode;
        self.quality_mode = RenderQuality::Publication;

        // Update renderer for high-quality output
        self.cpu_renderer = SkiaRenderer::new(width, height, crate::render::Theme::default())?;

        // Render the plot at high quality
        let plot_clone = plot
            .clone()
            .dpi(dpi as u32)
            .set_output_pixels(width, height);

        let result = match plot_clone.render() {
            Ok(image) => image.pixels,
            Err(e) => {
                log::warn!("Publication render failed: {}, returning white pixels", e);
                vec![255u8; (width * height * 4) as usize]
            }
        };

        // Restore previous quality mode
        self.quality_mode = old_quality;

        Ok(result)
    }

    /// Get data point at screen coordinates
    pub fn get_data_point_at(
        &self,
        screen_pos: Point2D,
        _state: &InteractionState,
    ) -> Option<DataPoint> {
        // In real implementation, this would spatial search through plot data
        // For now, simulate finding a nearby point
        if let Some(session) = &self.interactive_session {
            self.sync_session_target(
                session,
                (self.cpu_renderer.width(), self.cpu_renderer.height()),
                self.last_device_scale,
            );
            match session.hit_test(ViewportPoint::new(screen_pos.x, screen_pos.y)) {
                HitResult::SeriesPoint {
                    series_index,
                    point_index,
                    data_position,
                    ..
                } => {
                    return Some(DataPoint::new(
                        point_index,
                        data_position.x,
                        data_position.y,
                        data_position.y,
                        series_index,
                    ));
                }
                HitResult::HeatmapCell {
                    series_index,
                    row,
                    col,
                    value,
                    ..
                } => {
                    return Some(
                        DataPoint::new(
                            row.saturating_mul(10_000) + col,
                            col as f64,
                            row as f64,
                            value,
                            series_index,
                        )
                        .with_metadata("kind".to_string(), "heatmap".to_string()),
                    );
                }
                HitResult::None => {}
            }
        }

        None
    }

    /// Get all data points in selection region
    pub fn get_points_in_region(
        &self,
        region: Rectangle,
        state: &InteractionState,
    ) -> Vec<DataPointId> {
        let mut points = Vec::new();

        // Convert screen region to data region
        let data_min = state.screen_to_data(region.min);
        let data_max = state.screen_to_data(region.max);
        let data_region = Rectangle::from_points(data_min, data_max);

        // In real implementation, would use spatial indexing to find points efficiently
        // For now, simulate selecting some points
        for i in 0..100 {
            let test_point = Point2D::new(i as f64 % 100.0, (i as f64 * 0.5) % 100.0);

            if data_region.contains(test_point) {
                points.push(DataPointId(i));
            }
        }

        points
    }

    /// Update renderer dimensions
    fn update_dimensions(&mut self, width: u32, height: u32) -> Result<()> {
        if self.cpu_renderer.width() != width || self.cpu_renderer.height() != height {
            self.cpu_renderer = SkiaRenderer::new(width, height, crate::render::Theme::default())?;
            self.render_cache.invalidate_all();
        }
        Ok(())
    }

    /// Update quality mode based on performance
    fn update_quality_mode(&mut self, state: &InteractionState) {
        let active_interaction = state.mouse_button_pressed
            || state.brush_active
            || state.viewport_dirty
            || !matches!(
                state.animation_state,
                crate::interactive::state::AnimationState::Idle
            );
        let current_fps = self.performance_monitor.get_current_fps();
        let is_animating = !matches!(
            state.animation_state,
            crate::interactive::state::AnimationState::Idle
        );

        if active_interaction || is_animating || current_fps < self.target_fps * 0.8 {
            self.quality_mode = RenderQuality::Interactive;
        } else if current_fps > self.target_fps * 0.95 {
            self.quality_mode = RenderQuality::Balanced;
        }
    }

    fn render_session_frame(
        &mut self,
        state: &InteractionState,
        width: u32,
        height: u32,
        device_scale: f32,
    ) -> Result<InteractiveRenderOutput> {
        let session = self
            .interactive_session
            .as_ref()
            .expect("interactive session should exist for session rendering");

        self.sync_session_target(session, (width, height), device_scale);
        session.set_frame_pacing(FramePacing::Display);
        session.set_quality_policy(match self.quality_mode {
            RenderQuality::Interactive => QualityPolicy::Interactive,
            RenderQuality::Balanced => QualityPolicy::Balanced,
            RenderQuality::Publication => QualityPolicy::Publication,
        });
        #[cfg(feature = "gpu")]
        session.set_prefer_gpu(self.gpu_renderer.is_some());

        let frame = match session.render_to_surface(SurfaceTarget {
            size_px: (width, height),
            scale_factor: device_scale,
            time_seconds: 0.0,
        }) {
            Ok(frame) => frame,
            Err(e) => {
                log::warn!(
                    "Interactive session surface rendering failed: {}, returning white pixels",
                    e
                );
                let num_bytes = (width as usize)
                    .saturating_mul(height as usize)
                    .saturating_mul(4);
                return Ok(InteractiveRenderOutput::Pixels(vec![255u8; num_bytes]));
            }
        };

        let mut overlays = Vec::new();
        if let Some(overlay) = frame.layers.overlay.as_ref() {
            overlays.push(Arc::clone(overlay));
        }
        if let Some(local_overlay) = self.render_local_overlay(state, width, height, true)? {
            overlays.push(Arc::new(Image::new(width, height, local_overlay)));
        }

        Ok(InteractiveRenderOutput::Layers(InteractiveLayerOutput {
            base: Arc::clone(&frame.layers.base),
            overlays,
        }))
    }

    /// Render base plot with caching
    fn render_base_plot(
        &mut self,
        state: &InteractionState,
        width: u32,
        height: u32,
        device_scale: f32,
    ) -> Result<Vec<u8>> {
        // Check if we can use cached render
        if !state.needs_redraw && !state.viewport_dirty {
            if let Some(cached) = self
                .render_cache
                .get_base_render(state.zoom_level, state.pan_offset)
            {
                return Ok(cached);
            }
        }

        // Render fresh base plot
        let has_plot = self.current_plot.is_some();
        let pixel_data = if has_plot {
            match self.quality_mode {
                RenderQuality::Interactive => {
                    // Fast rendering for interaction
                    self.render_interactive_quality(state, width, height, device_scale)?
                }
                RenderQuality::Balanced => {
                    // Balanced quality and performance
                    self.render_balanced_quality(state, width, height, device_scale)?
                }
                RenderQuality::Publication => {
                    // High quality rendering
                    self.render_plot_to_pixels(state, width, height, device_scale)?
                }
            }
        } else {
            // No plot set, render empty canvas
            vec![255u8; (width * height * 4) as usize] // White background
        };

        // Cache the render
        self.render_cache
            .store_base_render(state.zoom_level, state.pan_offset, pixel_data.clone());

        Ok(pixel_data)
    }

    /// Render with interactive quality (optimized for speed)
    fn render_interactive_quality(
        &mut self,
        state: &InteractionState,
        width: u32,
        height: u32,
        device_scale: f32,
    ) -> Result<Vec<u8>> {
        self.render_plot_to_pixels(state, width, height, device_scale)
    }

    /// Render with balanced quality
    fn render_balanced_quality(
        &mut self,
        state: &InteractionState,
        width: u32,
        height: u32,
        device_scale: f32,
    ) -> Result<Vec<u8>> {
        self.render_plot_to_pixels(state, width, height, device_scale)
    }

    /// Render the current plot to RGBA pixel data
    fn render_plot_to_pixels(
        &self,
        _state: &InteractionState,
        width: u32,
        height: u32,
        device_scale: f32,
    ) -> Result<Vec<u8>> {
        if let Some(ref plot) = self.current_plot {
            let plot_clone = Self::configure_plot_for_surface(plot, width, height, device_scale);
            match plot_clone.render() {
                Ok(image) => Ok(image.pixels),
                Err(e) => {
                    log::warn!("Plot rendering failed: {}, returning white pixels", e);
                    Ok(vec![255u8; (width * height * 4) as usize])
                }
            }
        } else {
            Ok(vec![255u8; (width * height * 4) as usize])
        }
    }

    fn render_local_overlay(
        &mut self,
        state: &InteractionState,
        width: u32,
        height: u32,
        session_managed_overlay: bool,
    ) -> Result<Option<Vec<u8>>> {
        let render_annotations = !(state.annotations.is_empty()
            || (session_managed_overlay && self.should_defer_nonessential_overlays(state)));
        let render_brush = state.brushed_region.is_some();

        if !render_annotations && !render_brush {
            return Ok(None);
        }

        let mut pixel_data = vec![0u8; (width * height * 4) as usize];
        if render_brush {
            self.render_brush_region(state, &mut pixel_data)?;
        }
        if render_annotations {
            self.render_annotations(state, &mut pixel_data)?;
        }
        Ok(Some(pixel_data))
    }

    fn should_defer_nonessential_overlays(&self, state: &InteractionState) -> bool {
        matches!(self.quality_mode, RenderQuality::Interactive)
            && (state.mouse_button_pressed
                || state.brush_active
                || !matches!(
                    state.animation_state,
                    crate::interactive::state::AnimationState::Idle
                ))
    }

    fn configure_plot_for_surface(plot: &Plot, width: u32, height: u32, device_scale: f32) -> Plot {
        // Keep logical styling stable across HiDPI displays:
        // - window/device scale controls framebuffer density
        // - plot sizing stays anchored to the reference logical DPI
        let min_device_scale = crate::core::constants::dpi::MIN as f32 / crate::core::REFERENCE_DPI;
        let device_scale = if !device_scale.is_finite() || device_scale <= 0.0 {
            1.0
        } else {
            device_scale.max(min_device_scale)
        };
        let interactive_dpi = (crate::core::REFERENCE_DPI * device_scale).round() as u32;

        plot.clone()
            .dpi(interactive_dpi)
            .set_output_pixels(width, height)
    }

    fn sync_session_target(
        &self,
        session: &InteractivePlotSession,
        size_px: (u32, u32),
        device_scale: f32,
    ) {
        session.resize(size_px, Self::sanitize_device_scale(device_scale));
    }

    /// Render hover highlight
    fn render_hover_highlight(
        &mut self,
        state: &InteractionState,
        pixel_data: &mut [u8],
    ) -> Result<()> {
        if self.interactive_session.is_some() {
            return Ok(());
        }
        if let Some(ref hover_point) = state.hover_point {
            let screen_pos = state.data_to_screen(hover_point.position);
            self.draw_highlight_circle(pixel_data, screen_pos, 8.0, self.hover_highlight_color)?;
        }
        Ok(())
    }

    /// Render selection highlights
    fn render_selection_highlight(
        &mut self,
        state: &InteractionState,
        pixel_data: &mut [u8],
    ) -> Result<()> {
        if self.interactive_session.is_some() {
            return Ok(());
        }
        for point_id in &state.selected_points {
            // In real implementation, would look up actual point coordinates
            // For now, simulate highlighting at fixed positions
            let screen_pos = Point2D::new(100.0 + point_id.0 as f64 * 50.0, 100.0);
            self.draw_highlight_circle(
                pixel_data,
                screen_pos,
                6.0,
                self.selection_highlight_color,
            )?;
        }
        Ok(())
    }

    /// Render brush selection region
    fn render_brush_region(
        &mut self,
        state: &InteractionState,
        pixel_data: &mut [u8],
    ) -> Result<()> {
        if let Some(region) = state.brushed_region {
            self.draw_brush_guide_rectangle(pixel_data, region)?;
        }
        Ok(())
    }

    /// Render custom annotations
    fn render_annotations(
        &mut self,
        state: &InteractionState,
        pixel_data: &mut [u8],
    ) -> Result<()> {
        if state.annotations.is_empty() {
            return Ok(());
        }
        for annotation in &state.annotations {
            self.annotation_renderer.render_annotation(
                annotation,
                state,
                pixel_data,
                self.cpu_renderer.width(),
                self.cpu_renderer.height(),
            )?;
        }
        Ok(())
    }

    /// Render tooltip
    fn render_tooltip(&mut self, state: &InteractionState, pixel_data: &mut [u8]) -> Result<()> {
        if self.interactive_session.is_some() {
            return Ok(());
        }
        if state.tooltip_visible && !state.tooltip_content.is_empty() {
            self.draw_tooltip(pixel_data, &state.tooltip_content, state.tooltip_position)?;
        }
        Ok(())
    }

    /// Draw highlight circle at screen position
    fn draw_highlight_circle(
        &self,
        pixel_data: &mut [u8],
        center: Point2D,
        radius: f32,
        color: Color,
    ) -> Result<()> {
        // Simple circle drawing - in production would use proper graphics primitives
        let width = self.cpu_renderer.width() as i32;
        let height = self.cpu_renderer.height() as i32;
        let r_sq = (radius * radius) as i32;

        let cx = center.x as i32;
        let cy = center.y as i32;

        for dy in -(radius as i32)..=(radius as i32) {
            for dx in -(radius as i32)..=(radius as i32) {
                if dx * dx + dy * dy <= r_sq {
                    let x = cx + dx;
                    let y = cy + dy;

                    if x >= 0 && x < width && y >= 0 && y < height {
                        let index = ((y * width + x) * 4) as usize;
                        if index + 3 < pixel_data.len() {
                            // Alpha blend with existing pixel
                            let alpha = color.a as f32 / 255.0;
                            pixel_data[index] = blend_channel(pixel_data[index], color.r, alpha);
                            pixel_data[index + 1] =
                                blend_channel(pixel_data[index + 1], color.g, alpha);
                            pixel_data[index + 2] =
                                blend_channel(pixel_data[index + 2], color.b, alpha);
                        }
                    }
                }
            }
        }

        Ok(())
    }

    /// Draw selection rectangle
    fn draw_selection_rectangle(
        &self,
        pixel_data: &mut [u8],
        region: Rectangle,
        color: Color,
    ) -> Result<()> {
        let width = self.cpu_renderer.width() as i32;
        let height = self.cpu_renderer.height() as i32;

        let x1 = region.min.x as i32;
        let y1 = region.min.y as i32;
        let x2 = region.max.x as i32;
        let y2 = region.max.y as i32;

        let alpha = color.a as f32 / 255.0;

        // Fill rectangle with alpha blending
        for y in y1.max(0)..=y2.min(height - 1) {
            for x in x1.max(0)..=x2.min(width - 1) {
                let index = ((y * width + x) * 4) as usize;
                if index + 3 < pixel_data.len() {
                    pixel_data[index] = blend_channel(pixel_data[index], color.r, alpha);
                    pixel_data[index + 1] = blend_channel(pixel_data[index + 1], color.g, alpha);
                    pixel_data[index + 2] = blend_channel(pixel_data[index + 2], color.b, alpha);
                }
            }
        }

        Ok(())
    }

    fn draw_brush_guide_rectangle(&self, pixel_data: &mut [u8], region: Rectangle) -> Result<()> {
        self.draw_selection_rectangle(pixel_data, region, self.brush_color)?;
        self.draw_rectangle_outline(pixel_data, region, self.brush_outline_color, 2)
    }

    fn draw_rectangle_outline(
        &self,
        pixel_data: &mut [u8],
        region: Rectangle,
        color: Color,
        thickness: i32,
    ) -> Result<()> {
        let width = self.cpu_renderer.width() as i32;
        let height = self.cpu_renderer.height() as i32;
        let x1 = region.min.x.round() as i32;
        let y1 = region.min.y.round() as i32;
        let x2 = region.max.x.round() as i32;
        let y2 = region.max.y.round() as i32;
        let thickness = thickness.max(1);
        let alpha = color.a as f32 / 255.0;

        for y in y1.max(0)..=y2.min(height - 1) {
            for x in x1.max(0)..=x2.min(width - 1) {
                let on_border = x - x1 < thickness
                    || x2 - x < thickness
                    || y - y1 < thickness
                    || y2 - y < thickness;
                if !on_border {
                    continue;
                }
                let index = ((y * width + x) * 4) as usize;
                if index + 3 < pixel_data.len() {
                    pixel_data[index] = blend_channel(pixel_data[index], color.r, alpha);
                    pixel_data[index + 1] = blend_channel(pixel_data[index + 1], color.g, alpha);
                    pixel_data[index + 2] = blend_channel(pixel_data[index + 2], color.b, alpha);
                    pixel_data[index + 3] = color.a;
                }
            }
        }

        Ok(())
    }

    /// Draw tooltip
    fn draw_tooltip(&self, pixel_data: &mut [u8], content: &str, position: Point2D) -> Result<()> {
        const TOOLTIP_FONT_SIZE: f32 = 13.0;
        const TOOLTIP_PADDING_X: f64 = 8.0;
        const TOOLTIP_PADDING_Y: f64 = 6.0;
        const TOOLTIP_CURSOR_GAP: f64 = 12.0;

        let text_renderer = TextRenderer::new();
        let font = FontConfig::new(FontFamily::SansSerif, TOOLTIP_FONT_SIZE);
        let (text_width, text_height) =
            text_renderer
                .measure_text(content, &font)
                .unwrap_or_else(|_| {
                    (
                        content.chars().count() as f32 * TOOLTIP_FONT_SIZE * 0.6,
                        TOOLTIP_FONT_SIZE * 1.2,
                    )
                });

        let tooltip_width = f64::from(text_width) + TOOLTIP_PADDING_X * 2.0;
        let tooltip_height = f64::from(text_height) + TOOLTIP_PADDING_Y * 2.0;
        let view_width = self.cpu_renderer.width() as f64;
        let view_height = self.cpu_renderer.height() as f64;
        let max_left = (view_width - tooltip_width).max(0.0);
        let max_top = (view_height - tooltip_height).max(0.0);

        let mut left = position.x + TOOLTIP_CURSOR_GAP;
        if left + tooltip_width > view_width {
            left = position.x - tooltip_width - TOOLTIP_CURSOR_GAP;
        }
        let mut top = position.y - tooltip_height - TOOLTIP_CURSOR_GAP;
        if top < 0.0 {
            top = position.y + TOOLTIP_CURSOR_GAP;
        }

        left = left.clamp(0.0, max_left);
        top = top.clamp(0.0, max_top);

        let tooltip_rect = Rectangle::new(left, top, left + tooltip_width, top + tooltip_height);

        let tooltip_color = Color::new_rgba(255, 255, 220, 200); // Light yellow
        self.draw_selection_rectangle(pixel_data, tooltip_rect, tooltip_color)?;

        let Some(size) =
            tiny_skia::IntSize::from_wh(self.cpu_renderer.width(), self.cpu_renderer.height())
        else {
            log::debug!("Skipping legacy tooltip text render because frame size is invalid");
            return Ok(());
        };
        let Some(mut pixmap) =
            tiny_skia::PixmapMut::from_bytes(pixel_data, size.width(), size.height())
        else {
            log::debug!("Skipping legacy tooltip text render because pixmap creation failed");
            return Ok(());
        };

        if let Err(err) = text_renderer.render_text_mut(
            &mut pixmap,
            content,
            (left + TOOLTIP_PADDING_X) as f32,
            (top + TOOLTIP_PADDING_Y) as f32,
            &font,
            Color::new_rgba(24, 24, 24, 255),
        ) {
            log::debug!(
                "Skipping legacy tooltip text render after text rasterization failed: {err}"
            );
            return Ok(());
        }

        Ok(())
    }

    fn sanitize_device_scale(device_scale: f32) -> f32 {
        if device_scale.is_finite() && device_scale > 0.0 {
            device_scale
        } else {
            1.0
        }
    }

    /// Get performance statistics
    pub fn get_performance_stats(&self) -> PerformanceStats {
        self.performance_monitor.get_stats()
    }
}

/// Alpha blend two color channels
fn blend_channel(background: u8, foreground: u8, alpha: f32) -> u8 {
    let bg = background as f32 / 255.0;
    let fg = foreground as f32 / 255.0;
    let result = bg * (1.0 - alpha) + fg * alpha;
    (result * 255.0) as u8
}

/// Render cache for performance optimization
struct RenderCache {
    base_renders: HashMap<CacheKey, Vec<u8>>,
    max_entries: usize,
}

#[derive(Hash, PartialEq, Eq, Clone)]
struct CacheKey {
    zoom_level_bits: u64,
    pan_x_bits: u64,
    pan_y_bits: u64,
}

impl RenderCache {
    fn new() -> Self {
        Self {
            base_renders: HashMap::new(),
            max_entries: 10,
        }
    }

    fn get_base_render(
        &self,
        zoom_level: f64,
        pan_offset: crate::interactive::event::Vector2D,
    ) -> Option<Vec<u8>> {
        let key = Self::make_key(zoom_level, pan_offset);
        self.base_renders.get(&key).cloned()
    }

    fn store_base_render(
        &mut self,
        zoom_level: f64,
        pan_offset: crate::interactive::event::Vector2D,
        pixel_data: Vec<u8>,
    ) {
        if self.base_renders.len() >= self.max_entries {
            // Simple LRU - remove first entry
            if let Some(first_key) = self.base_renders.keys().next().cloned() {
                self.base_renders.remove(&first_key);
            }
        }

        let key = Self::make_key(zoom_level, pan_offset);
        self.base_renders.insert(key, pixel_data);
    }

    fn invalidate_all(&mut self) {
        self.base_renders.clear();
    }

    fn make_key(zoom_level: f64, pan_offset: crate::interactive::event::Vector2D) -> CacheKey {
        CacheKey {
            zoom_level_bits: (zoom_level * 100.0) as u64, // Quantize to avoid floating point issues
            pan_x_bits: (pan_offset.x * 100.0) as u64,
            pan_y_bits: (pan_offset.y * 100.0) as u64,
        }
    }
}

/// Render quality modes
#[derive(Debug, Clone, Copy, PartialEq)]
enum RenderQuality {
    Interactive, // Fast rendering for smooth interaction
    Balanced,    // Balance between quality and performance
    Publication, // High quality for static output
}

/// Performance monitoring
struct PerformanceMonitor {
    frame_times: Vec<Duration>,
    frame_count: u64,
    last_fps_calculation: Instant,
    target_frame_time: Duration,
}

impl PerformanceMonitor {
    fn new() -> Self {
        Self {
            frame_times: Vec::with_capacity(60),
            frame_count: 0,
            last_fps_calculation: Instant::now(),
            target_frame_time: Duration::from_nanos(16_666_667), // ~60fps
        }
    }

    fn record_frame(&mut self, frame_time: Duration) {
        self.frame_times.push(frame_time);
        self.frame_count += 1;

        // Keep only recent frame times
        if self.frame_times.len() > 60 {
            self.frame_times.remove(0);
        }
    }

    fn get_current_fps(&self) -> f64 {
        if self.frame_times.is_empty() {
            return 0.0;
        }

        let avg_frame_time: Duration =
            self.frame_times.iter().sum::<Duration>() / self.frame_times.len() as u32;
        1.0 / avg_frame_time.as_secs_f64()
    }

    fn get_stats(&self) -> PerformanceStats {
        PerformanceStats {
            current_fps: self.get_current_fps(),
            frame_count: self.frame_count,
            avg_frame_time: if !self.frame_times.is_empty() {
                self.frame_times.iter().sum::<Duration>() / self.frame_times.len() as u32
            } else {
                Duration::ZERO
            },
        }
    }
}

/// Performance statistics
#[derive(Debug, Clone)]
pub struct PerformanceStats {
    pub current_fps: f64,
    pub frame_count: u64,
    pub avg_frame_time: Duration,
}

/// Annotation renderer
struct AnnotationRenderer;

impl AnnotationRenderer {
    fn new() -> Self {
        Self
    }

    fn render_annotation(
        &self,
        annotation: &Annotation,
        state: &InteractionState,
        pixel_data: &mut [u8],
        width: u32,
        height: u32,
    ) -> Result<()> {
        match annotation {
            Annotation::Text {
                content: _,
                position,
                style: _,
            } => {
                let _ = state.data_to_screen(*position);
            }

            Annotation::Arrow {
                start,
                end,
                style: _,
            } => {
                let _ = state.data_to_screen(*start);
                let _ = state.data_to_screen(*end);
            }

            Annotation::Shape { geometry, style: _ } => {
                let _ = geometry;
            }

            Annotation::Equation {
                latex: _,
                position,
                style: _,
            } => {
                let _ = state.data_to_screen(*position);
            }
        }

        Ok(())
    }
}

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

    #[tokio::test]
    async fn test_renderer_creation() {
        let renderer_result = RealTimeRenderer::new().await;
        assert!(renderer_result.is_ok());
    }

    #[test]
    fn test_render_cache() {
        let mut cache = RenderCache::new();

        let zoom = 1.5;
        let pan = crate::interactive::event::Vector2D::new(10.0, 20.0);
        let test_data = vec![255u8; 100];

        // Store and retrieve
        cache.store_base_render(zoom, pan, test_data.clone());
        let retrieved = cache.get_base_render(zoom, pan);

        assert_eq!(retrieved, Some(test_data));

        // Test cache invalidation
        cache.invalidate_all();
        let retrieved_after_clear = cache.get_base_render(zoom, pan);
        assert_eq!(retrieved_after_clear, None);
    }

    #[test]
    fn test_performance_monitor() {
        let mut monitor = PerformanceMonitor::new();

        // Record some frame times
        monitor.record_frame(Duration::from_millis(16)); // ~60fps
        monitor.record_frame(Duration::from_millis(17));
        monitor.record_frame(Duration::from_millis(15));

        let stats = monitor.get_stats();
        assert!(stats.current_fps > 50.0 && stats.current_fps < 70.0);
        assert_eq!(stats.frame_count, 3);
    }

    #[test]
    fn test_alpha_blending() {
        let background = 100u8;
        let foreground = 200u8;
        let alpha = 0.5;

        let result = blend_channel(background, foreground, alpha);
        let expected = (100.0 * 0.5 + 200.0 * 0.5) as u8;

        assert_eq!(result, expected);
    }

    #[test]
    fn test_configure_plot_for_surface_keeps_logical_size_on_hidpi() {
        #[allow(deprecated)]
        let plot = Plot::new()
            .size_px(800, 600)
            .line(&[0.0, 1.0], &[1.0, 2.0])
            .end_series();

        let configured = RealTimeRenderer::configure_plot_for_surface(&plot, 1600, 1200, 2.0);
        let image = configured
            .render()
            .expect("configured HiDPI plot should render");

        assert_eq!((image.width, image.height), (1600, 1200));
        assert!((configured.get_config().figure.width - 8.0).abs() < f32::EPSILON);
        assert!((configured.get_config().figure.height - 6.0).abs() < f32::EPSILON);
        assert!((configured.get_config().figure.dpi - (REFERENCE_DPI * 2.0)).abs() < f32::EPSILON);
    }

    #[test]
    fn test_configure_plot_for_surface_defaults_device_scale_to_one() {
        #[allow(deprecated)]
        let plot = Plot::new()
            .size_px(800, 600)
            .line(&[0.0, 1.0], &[1.0, 2.0])
            .end_series();

        let configured = RealTimeRenderer::configure_plot_for_surface(&plot, 800, 600, 0.0);
        let image = configured
            .render()
            .expect("configured 1x plot should render");

        assert_eq!((image.width, image.height), (800, 600));
        assert!((configured.get_config().figure.width - 8.0).abs() < f32::EPSILON);
        assert!((configured.get_config().figure.height - 6.0).abs() < f32::EPSILON);
        assert!((configured.get_config().figure.dpi - REFERENCE_DPI).abs() < f32::EPSILON);
    }

    #[test]
    fn test_configure_plot_for_surface_preserves_fractional_hidpi_framebuffer_size() {
        #[allow(deprecated)]
        let plot = Plot::new()
            .size_px(800, 600)
            .line(&[0.0, 1.0], &[1.0, 2.0])
            .end_series();

        let configured = RealTimeRenderer::configure_plot_for_surface(&plot, 1001, 751, 1.5);
        let image = configured
            .render()
            .expect("configured fractional HiDPI plot should render");

        assert_eq!((image.width, image.height), (1001, 751));
        assert!((configured.get_config().figure.width - (1001.0 / 150.0)).abs() < 1e-6);
        assert!((configured.get_config().figure.height - (751.0 / 150.0)).abs() < 1e-6);
        assert!((configured.get_config().figure.dpi - 150.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_configure_plot_for_surface_preserves_sub_1x_device_scale() {
        #[allow(deprecated)]
        let plot = Plot::new()
            .size_px(800, 600)
            .line(&[0.0, 1.0], &[1.0, 2.0])
            .end_series();

        let configured = RealTimeRenderer::configure_plot_for_surface(&plot, 800, 600, 0.75);
        let image = configured
            .render()
            .expect("configured sub-1x plot should render");

        assert_eq!((image.width, image.height), (800, 600));
        assert!((configured.get_config().figure.width - (800.0 / 75.0)).abs() < 1e-6);
        assert!((configured.get_config().figure.height - (600.0 / 75.0)).abs() < 1e-6);
        assert!((configured.get_config().figure.dpi - 75.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_set_device_scale_sanitizes_invalid_values() {
        let runtime = tokio::runtime::Runtime::new().expect("runtime should initialize for tests");
        let mut renderer = runtime
            .block_on(RealTimeRenderer::new())
            .expect("renderer should initialize for tests");

        renderer.set_device_scale(2.0);
        assert!((renderer.last_device_scale - 2.0).abs() < f32::EPSILON);

        renderer.set_device_scale(0.0);
        assert!((renderer.last_device_scale - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_brush_guide_rectangle_draws_outline_over_fill() {
        let runtime = tokio::runtime::Runtime::new().expect("runtime should initialize for tests");
        let mut renderer = runtime
            .block_on(RealTimeRenderer::new())
            .expect("renderer should initialize for tests");
        let mut pixels = vec![
            0u8;
            (renderer.cpu_renderer.width() * renderer.cpu_renderer.height() * 4)
                as usize
        ];
        let mut state = InteractionState::default();
        state.brushed_region = Some(Rectangle::new(24.0, 24.0, 88.0, 88.0));

        renderer
            .render_brush_region(&state, &mut pixels)
            .expect("brush guide should render");

        let width = renderer.cpu_renderer.width() as usize;
        let border_index = ((24usize * width + 24usize) * 4) as usize;
        let interior_index = ((56usize * width + 56usize) * 4) as usize;

        assert!(
            pixels[border_index + 3] > pixels[interior_index + 3],
            "brush outline should be more visible than the fill interior"
        );
    }
}
