use termichart_core::{ChartConfig, Result, Style, Viewport};
use termichart_render::CellGrid;

const FULL_BLOCK: char = '\u{2588}';

/// A histogram that bins continuous data and renders as bars.
///
/// Given raw `f64` values, the histogram divides the data range into
/// `num_bins` equal-width bins, counts how many values fall into each bin,
/// and renders proportional vertical bars.
pub struct Histogram {
    values: Vec<f64>,
    num_bins: usize,
}

impl Histogram {
    pub fn new(values: Vec<f64>, num_bins: usize) -> Self {
        Self {
            values,
            num_bins: num_bins.max(1),
        }
    }

    /// Compute bin counts from raw values.
    ///
    /// Returns a vector of `(bin_low, bin_high, count)` tuples.
    pub fn compute_bins(&self) -> Vec<(f64, f64, usize)> {
        if self.values.is_empty() {
            return Vec::new();
        }

        let min = self.values.iter().cloned().fold(f64::INFINITY, f64::min);
        let max = self.values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
        let span = max - min;
        let bin_width = if span == 0.0 { 1.0 } else { span / self.num_bins as f64 };

        let mut bins = vec![0usize; self.num_bins];

        for &v in &self.values {
            let idx = ((v - min) / bin_width) as usize;
            let idx = idx.min(self.num_bins - 1);
            bins[idx] += 1;
        }

        bins.iter()
            .enumerate()
            .map(|(i, &count)| {
                let lo = min + i as f64 * bin_width;
                let hi = lo + bin_width;
                (lo, hi, count)
            })
            .collect()
    }

    pub fn render(
        &self,
        grid: &mut CellGrid,
        viewport: &Viewport,
        config: &ChartConfig,
        offset_x: u16,
        offset_y: u16,
    ) -> Result<()> {
        let bins = self.compute_bins();
        if bins.is_empty() {
            return Ok(());
        }

        let chart_w = viewport.width as usize;
        let chart_h = viewport.height as usize;
        let n = bins.len();

        let bar_width = ((chart_w as f64) / (n as f64 * 1.2)).max(1.0) as usize;
        let gap = (bar_width / 3).max(1);
        let step = bar_width + gap;

        let max_count = bins.iter().map(|(_, _, c)| *c).max().unwrap_or(1);

        let color = config.theme.line_color();
        let style = Style {
            fg: color,
            ..Default::default()
        };

        for (i, (_, _, count)) in bins.iter().enumerate() {
            let bar_x = i * step;
            if bar_x >= chart_w {
                break;
            }

            let bar_frac = *count as f64 / max_count as f64;
            let bar_rows = (bar_frac * chart_h as f64).round() as usize;

            for row in 0..bar_rows.min(chart_h) {
                let grid_row = offset_y + (chart_h - 1 - row) as u16;
                for col_offset in 0..bar_width.min(chart_w - bar_x) {
                    let grid_col = offset_x + (bar_x + col_offset) as u16;
                    if grid.in_bounds(grid_col, grid_row) {
                        grid.set(grid_col, grid_row, FULL_BLOCK, style);
                    }
                }
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use termichart_core::{ChartConfig, Range, Viewport};
    use termichart_render::CellGrid;

    #[test]
    fn histogram_bins() {
        let h = Histogram::new(vec![1.0, 2.0, 2.5, 3.0, 8.0, 9.0], 3);
        let bins = h.compute_bins();
        assert_eq!(bins.len(), 3);
        // All values should be accounted for
        let total: usize = bins.iter().map(|(_, _, c)| c).sum();
        assert_eq!(total, 6);
    }

    #[test]
    fn histogram_empty() {
        let h = Histogram::new(vec![], 5);
        assert!(h.compute_bins().is_empty());
    }

    #[test]
    fn histogram_renders() {
        let h = Histogram::new(vec![1.0, 2.0, 3.0, 4.0, 5.0], 5);
        let mut grid = CellGrid::new(40, 10);
        let viewport = Viewport {
            x_range: Range { min: 0.0, max: 5.0 },
            y_range: Range { min: 0.0, max: 5.0 },
            width: 40,
            height: 10,
        };
        let config = ChartConfig::default();
        assert!(h.render(&mut grid, &viewport, &config, 0, 0).is_ok());
    }
}
