//! Core types and utility structures.
//!
//! Defines fundamental types used across the crate: [`ImagerySource`], [`Bbox`],
//! [`Intersects`], and comparison operators.
use anyhow::Result;
use std::process::Command;

/// Runs a GDAL command-line tool with the given arguments.
///
/// Replaces the duplicated `.spawn().expect().wait().expect()` pattern.
pub fn run_gdal_command(argv: &[&str]) {
    Command::new(argv[0])
        .args(&argv[1..])
        .spawn()
        .expect("failed to start gdal command")
        .wait()
        .expect("failed to wait for gdal command");
}
use clap;
use clap::Parser;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::num::ParseIntError;
use std::str::FromStr;
use strum_macros::EnumIter;
/// Comparison operators for numeric filters.
///
/// Used with cloud cover and land cover thresholds in queries.
#[derive(Debug, Clone, clap::ValueEnum, Copy)]
pub enum Cmp {
    /// Less than (`<`)
    Less,
    /// Greater than (`>`)
    Greater,
    /// Less than or equal (`<=`)
    LessEqual,
    /// Greater than or equal (`>=`)
    GreaterEqual,
    /// Equal (`=`)
    Equal,
}

/// The type of imagery provider backend.
#[derive(Debug, Clone, clap::ValueEnum, PartialEq, Copy, Eq, Hash, Default)]
pub enum ImageryProviderType {
    /// Local PostgreSQL database (Apollo filestore)
    Local,
    /// Remote STAC API endpoint
    #[default]
    Stac,
}

/// Supported satellite imagery data sources.
///
/// Each variant wraps an [`ImageryProvider`] containing the
/// provider type and endpoint URL.
#[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIter)]
pub enum ImagerySource {
    /// Digital Earth Australia STAC catalog
    Dea(ImageryProvider),
    /// Apollo local filestore PostgreSQL database
    Apollo(ImageryProvider),
    /// Element84 AWS-hosted STAC catalog
    Element(ImageryProvider),
    /// Microsoft Planetary Computer STAC catalog
    PlanetaryComputer(ImageryProvider),
}

/// Configuration for an imagery data provider.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ImageryProvider {
    /// The provider type (STAC or Local)
    pub provider_type: ImageryProviderType,
    /// The provider endpoint URL or database path
    pub url: String,
}

/// CLI-compatible imagery source selector.
///
/// Used with `clap::ValueEnum` for command-line argument parsing.
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum ImagerySourceClap {
    /// Digital Earth Australia
    Dea,
    /// Apollo local filestore
    Apollo,
    /// Element84
    Element,
    /// Microsoft Planetary Computer
    PlanetaryComputer,
}

impl ImagerySource {
    /// Get Url for the Imagerey Source.
    ///
    /// Examples:
    /// ```
    /// use rss_core::DEA;
    ///
    /// let source = &DEA;
    /// let url = source.get_url();
    /// assert_eq!(url, "https://explorer.sandbox.dea.ga.gov.au/stac/search")
    /// ```
    pub fn get_url(&self) -> String {
        let url: String = match &self {
            ImagerySource::Dea(p) => String::from(&p.url),
            ImagerySource::Apollo(p) => String::from(&p.url),
            ImagerySource::Element(p) => String::from(&p.url),
            ImagerySource::PlanetaryComputer(p) => String::from(&p.url),
        };
        url
    }

    /// Get name for the Imagerey Source.
    ///
    /// Examples:
    /// ```
    /// use rss_core::DEA;
    ///
    /// let source = &DEA;
    /// let name = &source.name();
    /// assert_eq!(name, "DEA")
    /// ```
    pub fn name(&self) -> String {
        let name: String = match &self {
            ImagerySource::Dea(_) => "DEA".to_owned(),
            ImagerySource::Apollo(_) => "Apollo".to_owned(),
            ImagerySource::Element(_) => "Element84".to_owned(),
            ImagerySource::PlanetaryComputer(_) => "PlanetaryComputer".to_owned(),
        };
        name
    }
}

impl From<&ImagerySource> for ImagerySource {
    fn from(source: &ImagerySource) -> Self {
        source.clone()
    }
}

/// Axis-aligned bounding box in geographic coordinates.
///
/// Supports parsing from comma-separated strings (e.g., `"141.0,-26.0,142.0,-25.0"`).
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Parser)]

pub struct Bbox {
    /// Minimum X (west) coordinate
    pub xmin: f32,
    /// Minimum Y (south) coordinate
    pub ymin: f32,
    /// Maximum X (east) coordinate
    pub xmax: f32,
    /// Maximum Y (north) coordinate
    pub ymax: f32,
}

impl FromStr for Bbox {
    type Err = ParseIntError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(',').collect();
        Ok(Bbox {
            xmin: parts[0].parse().unwrap(),
            xmax: parts[1].parse().unwrap(),
            ymin: parts[2].parse().unwrap(),
            ymax: parts[3].parse().unwrap(),
        })
    }
}

impl fmt::Display for Bbox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}, {}, {}, {}", self.xmin, self.ymin, self.xmax, self.ymax)
    }
}

use gdal::vector::Geometry;
/// Spatial intersection filter for imagery queries.
#[derive(Clone)]
pub enum Intersects<'a> {
    /// Filter by scene/tile IDs (e.g., `["56jmr"]` or `["p091r077"]`)
    Scene(Vec<&'a str>),
    /// Filter by axis-aligned bounding box
    Bbox(Bbox),
    /// Filter by arbitrary polygon geometry
    Polygon(Geometry),
}

/// Extracts a bounding box from a polygon geometry.
pub fn bbox_from_polygon(polygon: &gdal::vector::Geometry) -> Bbox {
    let envelope = polygon.envelope();

    Bbox {
        xmin: envelope.MinX as f32,
        ymin: envelope.MinY as f32,
        xmax: envelope.MaxX as f32,
        ymax: envelope.MaxY as f32,
    }
}
