use crate::physics::orbit::MERCURY_RADIUS;

#[derive(Debug, Clone, Copy)]
pub struct LatLon {
    pub lat_deg: f64,
    pub lon_deg: f64,
}

impl LatLon {
    pub fn new(lat_deg: f64, lon_deg: f64) -> Self {
        Self { lat_deg, lon_deg }
    }

    pub fn lat_rad(&self) -> f64 {
        self.lat_deg.to_radians()
    }

    pub fn lon_rad(&self) -> f64 {
        self.lon_deg.to_radians()
    }

    pub fn to_cartesian(&self) -> (f64, f64, f64) {
        latlon_to_cartesian(self.lat_deg, self.lon_deg)
    }

    pub fn to_ecef(&self, altitude_m: f64) -> Ecef {
        let r = MERCURY_RADIUS + altitude_m;
        let lat = self.lat_rad();
        let lon = self.lon_rad();
        Ecef {
            x: r * lat.cos() * lon.cos(),
            y: r * lat.cos() * lon.sin(),
            z: r * lat.sin(),
        }
    }

    pub fn great_circle_distance(&self, other: &LatLon) -> f64 {
        let d_lat = (other.lat_deg - self.lat_deg).to_radians();
        let d_lon = (other.lon_deg - self.lon_deg).to_radians();
        let a = (d_lat / 2.0).sin().powi(2)
            + self.lat_rad().cos() * other.lat_rad().cos() * (d_lon / 2.0).sin().powi(2);
        2.0 * MERCURY_RADIUS * a.sqrt().asin()
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Ecef {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

impl Ecef {
    pub fn to_latlon(&self) -> LatLon {
        let r = (self.x * self.x + self.y * self.y + self.z * self.z).sqrt();
        let lat = (self.z / r).asin().to_degrees();
        let lon = self.y.atan2(self.x).to_degrees();
        LatLon::new(lat, lon)
    }

    pub fn altitude(&self) -> f64 {
        let r = (self.x * self.x + self.y * self.y + self.z * self.z).sqrt();
        r - MERCURY_RADIUS
    }

    pub fn distance(&self, other: &Ecef) -> f64 {
        let dx = other.x - self.x;
        let dy = other.y - self.y;
        let dz = other.z - self.z;
        (dx * dx + dy * dy + dz * dz).sqrt()
    }
}

pub fn latlon_to_cartesian(lat: f64, lon: f64) -> (f64, f64, f64) {
    let r = MERCURY_RADIUS;
    let lat_rad = lat.to_radians();
    let lon_rad = lon.to_radians();
    let x = r * lat_rad.cos() * lon_rad.cos();
    let y = r * lat_rad.cos() * lon_rad.sin();
    let z = r * lat_rad.sin();
    (x, y, z)
}

pub fn cartesian_to_latlon(x: f64, y: f64, z: f64) -> (f64, f64) {
    let r = (x * x + y * y + z * z).sqrt();
    let lat = (z / r).asin().to_degrees();
    let lon = y.atan2(x).to_degrees();
    (lat, lon)
}

pub fn surface_area_band(lat_min_deg: f64, lat_max_deg: f64) -> f64 {
    let r = MERCURY_RADIUS;
    let sin_min = lat_min_deg.to_radians().sin();
    let sin_max = lat_max_deg.to_radians().sin();
    2.0 * std::f64::consts::PI * r * r * (sin_max - sin_min).abs()
}
