use serde::{Deserialize, Serialize};

/// A 2-D point used for line charts, indicators, and general plotting.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

/// An OHLCV candle used for candlestick charts.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Candle {
    pub time: f64,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
}

/// A one-dimensional numeric range.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Range {
    pub min: f64,
    pub max: f64,
}

impl Range {
    /// The distance between `max` and `min`.
    pub fn span(&self) -> f64 {
        self.max - self.min
    }

    /// Returns `true` when `value` lies within `[min, max]` (inclusive).
    pub fn contains(&self, value: f64) -> bool {
        value >= self.min && value <= self.max
    }

    /// Expands this range so that it also covers `value`.
    pub fn expand_to_fit(&mut self, value: f64) {
        if value < self.min {
            self.min = value;
        }
        if value > self.max {
            self.max = value;
        }
    }

    /// Returns a new range that is expanded by `pct` percent on each side.
    ///
    /// For example `with_padding(0.1)` adds 10% of the span to both ends.
    pub fn with_padding(&self, pct: f64) -> Range {
        let pad = self.span() * pct;
        Range {
            min: self.min - pad,
            max: self.max + pad,
        }
    }
}

/// A rectangular viewport that maps data-space values into screen-space
/// coordinates (columns and rows).
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Viewport {
    pub x_range: Range,
    pub y_range: Range,
    pub width: u16,
    pub height: u16,
}

impl Viewport {
    /// Maps a data-space x value to a normalised `[0.0, width)` coordinate.
    pub fn map_x(&self, x: f64) -> f64 {
        let span = self.x_range.span();
        if span == 0.0 {
            return 0.0;
        }
        (x - self.x_range.min) / span * (self.width as f64)
    }

    /// Maps a data-space y value to a normalised `[0.0, height)` coordinate.
    ///
    /// The y axis is inverted: the *maximum* data value maps to row 0 (top of
    /// the terminal) and the *minimum* data value maps to `height - 1` (bottom).
    pub fn map_y(&self, y: f64) -> f64 {
        let span = self.y_range.span();
        if span == 0.0 {
            return 0.0;
        }
        (1.0 - (y - self.y_range.min) / span) * (self.height as f64)
    }

    /// Convenience: `map_x` rounded and clamped to a `u16` column index.
    pub fn map_col(&self, x: f64) -> u16 {
        let col = self.map_x(x).round() as i64;
        col.clamp(0, self.width.saturating_sub(1) as i64) as u16
    }

    /// Convenience: `map_y` rounded and clamped to a `u16` row index.
    pub fn map_row(&self, y: f64) -> u16 {
        let row = self.map_y(y).round() as i64;
        row.clamp(0, self.height.saturating_sub(1) as i64) as u16
    }
}

/// An integer rectangle in screen-space (columns / rows).
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct Rect {
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
}

impl Rect {
    /// The column one past the right edge (`x + width`).
    pub fn right(&self) -> u16 {
        self.x + self.width
    }

    /// The row one past the bottom edge (`y + height`).
    pub fn bottom(&self) -> u16 {
        self.y + self.height
    }
}

/// Commonly used time-frame intervals.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum TimeFrame {
    /// 1 minute
    M1,
    /// 5 minutes
    M5,
    /// 15 minutes
    M15,
    /// 1 hour
    H1,
    /// 4 hours
    H4,
    /// 1 day
    D1,
    /// 1 week
    W1,
}

/// The kind of overlay / indicator to draw on top of a chart.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum OverlayKind {
    /// Simple Moving Average over `n` periods.
    Sma(usize),
    /// Exponential Moving Average over `n` periods.
    Ema(usize),
    /// Volume-Weighted Average Price.
    Vwap,
    /// Bollinger Bands with period and standard deviation multiplier (×10 for integer storage)
    BollingerBands(usize, usize),
    /// Relative Strength Index with period
    Rsi(usize),
    /// MACD with fast, slow, signal periods
    Macd(usize, usize, usize),
}
