use termichart_core::{Candle, Point, Range};

/// Computes x and y ranges from a slice of candles with 5% padding.
///
/// The x range spans `[min_time, max_time]` and the y range spans
/// `[min_low, max_high]`, each padded by 5% of their span.
///
/// Returns `(x_range, y_range)`. If `candles` is empty, both ranges default
/// to `0.0..1.0`.
pub fn auto_range(candles: &[Candle]) -> (Range, Range) {
    if candles.is_empty() {
        return (
            Range { min: 0.0, max: 1.0 },
            Range { min: 0.0, max: 1.0 },
        );
    }

    let mut x_min = candles[0].time;
    let mut x_max = candles[0].time;
    let mut y_min = candles[0].low;
    let mut y_max = candles[0].high;

    for c in &candles[1..] {
        if c.time < x_min {
            x_min = c.time;
        }
        if c.time > x_max {
            x_max = c.time;
        }
        if c.low < y_min {
            y_min = c.low;
        }
        if c.high > y_max {
            y_max = c.high;
        }
    }

    let x_range = Range { min: x_min, max: x_max }.with_padding(0.05);
    let y_range = Range { min: y_min, max: y_max }.with_padding(0.05);

    (x_range, y_range)
}

/// Computes x and y ranges from a slice of points with 5% padding.
///
/// Returns `(x_range, y_range)`. If `points` is empty, both ranges default
/// to `0.0..1.0`.
pub fn auto_range_points(points: &[Point]) -> (Range, Range) {
    if points.is_empty() {
        return (
            Range { min: 0.0, max: 1.0 },
            Range { min: 0.0, max: 1.0 },
        );
    }

    let mut x_min = points[0].x;
    let mut x_max = points[0].x;
    let mut y_min = points[0].y;
    let mut y_max = points[0].y;

    for p in &points[1..] {
        if p.x < x_min {
            x_min = p.x;
        }
        if p.x > x_max {
            x_max = p.x;
        }
        if p.y < y_min {
            y_min = p.y;
        }
        if p.y > y_max {
            y_max = p.y;
        }
    }

    let x_range = Range { min: x_min, max: x_max }.with_padding(0.05);
    let y_range = Range { min: y_min, max: y_max }.with_padding(0.05);

    (x_range, y_range)
}

/// Rounds `x` to a "nice" number.
///
/// If `round` is `true`, returns the nearest nice number; otherwise returns
/// the ceiling nice number. A nice number is of the form `1 * 10^n`,
/// `2 * 10^n`, or `5 * 10^n`.
fn nice_number(x: f64, round: bool) -> f64 {
    if x == 0.0 {
        return 0.0;
    }

    let exp = x.abs().log10().floor();
    let frac = x.abs() / 10.0_f64.powf(exp);
    let sign = if x < 0.0 { -1.0 } else { 1.0 };

    let nice_frac = if round {
        if frac < 1.5 {
            1.0
        } else if frac < 3.0 {
            2.0
        } else if frac < 7.0 {
            5.0
        } else {
            10.0
        }
    } else {
        // ceiling
        if frac <= 1.0 {
            1.0
        } else if frac <= 2.0 {
            2.0
        } else if frac <= 5.0 {
            5.0
        } else {
            10.0
        }
    };

    sign * nice_frac * 10.0_f64.powf(exp)
}

/// Generates a set of "nice" evenly-spaced tick values for a given range.
///
/// The algorithm picks a rounded step size so that tick labels fall on
/// human-friendly numbers (multiples of 1, 2, 5, 10, ...).
///
/// `max_ticks` is a *hint*; the returned vector may contain up to
/// `max_ticks + 1` values.
pub fn nice_ticks(range: &Range, max_ticks: usize) -> Vec<f64> {
    if max_ticks == 0 {
        return Vec::new();
    }

    let span = range.span();
    if span == 0.0 {
        return vec![range.min];
    }

    let step = nice_number(span / max_ticks as f64, true);
    if step == 0.0 {
        return vec![range.min];
    }

    let start = (range.min / step).floor() * step;
    let mut ticks = Vec::new();
    let mut v = start;

    // Safety limit to avoid infinite loops with pathological inputs.
    let limit = max_ticks * 4;
    let mut count = 0;

    while v <= range.max + step * 0.5 {
        if v >= range.min - step * 0.5 {
            // Round to avoid floating-point noise.
            let rounded = (v * 1e12).round() / 1e12;
            ticks.push(rounded);
        }
        v += step;
        count += 1;
        if count > limit {
            break;
        }
    }

    ticks
}

/// Converts regular OHLCV candles into Heikin-Ashi candles.
///
/// Heikin-Ashi smooths price action by averaging values:
/// - **HA Close** = (Open + High + Low + Close) / 4
/// - **HA Open**  = (prev HA Open + prev HA Close) / 2
/// - **HA High**  = max(High, HA Open, HA Close)
/// - **HA Low**   = min(Low, HA Open, HA Close)
///
/// The first candle uses the original open. Time and volume are preserved.
pub fn to_heikin_ashi(candles: &[Candle]) -> Vec<Candle> {
    if candles.is_empty() {
        return Vec::new();
    }

    let mut ha = Vec::with_capacity(candles.len());

    let first = &candles[0];
    let ha_close = (first.open + first.high + first.low + first.close) / 4.0;
    let ha_open = (first.open + first.close) / 2.0;
    let ha_high = first.high.max(ha_open).max(ha_close);
    let ha_low = first.low.min(ha_open).min(ha_close);
    ha.push(Candle {
        time: first.time,
        open: ha_open,
        high: ha_high,
        low: ha_low,
        close: ha_close,
        volume: first.volume,
    });

    for i in 1..candles.len() {
        let c = &candles[i];
        let prev = &ha[i - 1];

        let ha_close = (c.open + c.high + c.low + c.close) / 4.0;
        let ha_open = (prev.open + prev.close) / 2.0;
        let ha_high = c.high.max(ha_open).max(ha_close);
        let ha_low = c.low.min(ha_open).min(ha_close);

        ha.push(Candle {
            time: c.time,
            open: ha_open,
            high: ha_high,
            low: ha_low,
            close: ha_close,
            volume: c.volume,
        });
    }

    ha
}

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

    #[test]
    fn auto_range_basic() {
        let candles = vec![
            Candle { time: 1.0, open: 10.0, high: 15.0, low: 8.0, close: 12.0, volume: 100.0 },
            Candle { time: 2.0, open: 12.0, high: 20.0, low: 10.0, close: 18.0, volume: 200.0 },
            Candle { time: 3.0, open: 18.0, high: 22.0, low: 16.0, close: 20.0, volume: 150.0 },
        ];
        let (xr, yr) = auto_range(&candles);
        // x span = 3 - 1 = 2, padding = 0.1
        assert!((xr.min - 0.9).abs() < 1e-10);
        assert!((xr.max - 3.1).abs() < 1e-10);
        // y: low=8, high=22, span=14, padding=0.7
        assert!((yr.min - 7.3).abs() < 1e-10);
        assert!((yr.max - 22.7).abs() < 1e-10);
    }

    #[test]
    fn auto_range_empty() {
        let (xr, yr) = auto_range(&[]);
        assert_eq!(xr.min, 0.0);
        assert_eq!(xr.max, 1.0);
        assert_eq!(yr.min, 0.0);
        assert_eq!(yr.max, 1.0);
    }

    #[test]
    fn auto_range_points_basic() {
        let points = vec![
            Point { x: 0.0, y: 10.0 },
            Point { x: 100.0, y: 50.0 },
        ];
        let (xr, yr) = auto_range_points(&points);
        assert!(xr.min < 0.0);
        assert!(xr.max > 100.0);
        assert!(yr.min < 10.0);
        assert!(yr.max > 50.0);
    }

    #[test]
    fn nice_ticks_basic() {
        let range = Range { min: 0.0, max: 100.0 };
        let ticks = nice_ticks(&range, 5);
        assert!(!ticks.is_empty());
        // All ticks should be within the padded range.
        for &t in &ticks {
            assert!(t >= -10.0 && t <= 110.0);
        }
        // Ticks should be monotonically increasing.
        for w in ticks.windows(2) {
            assert!(w[1] > w[0]);
        }
    }

    #[test]
    fn nice_ticks_zero_span() {
        let range = Range { min: 5.0, max: 5.0 };
        let ticks = nice_ticks(&range, 5);
        assert_eq!(ticks, vec![5.0]);
    }

    #[test]
    fn nice_ticks_zero_max() {
        let range = Range { min: 0.0, max: 100.0 };
        let ticks = nice_ticks(&range, 0);
        assert!(ticks.is_empty());
    }

    #[test]
    fn nice_number_rounds() {
        assert!((nice_number(12.3, true) - 10.0).abs() < 1e-10);
        assert!((nice_number(45.0, true) - 50.0).abs() < 1e-10);
        assert!((nice_number(0.073, true) - 0.1).abs() < 1e-10);
    }
}
