//! `MapObject`, `MapObjectObservation`, `MapObjectsBundle`, `IgnoredItem` —
//! per `data_model.md §3 Decision entities`.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::mission::Coordinate;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MapObjectSource {
    CentralPulled,
    LocalObserved,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapObject {
    pub h3_cell: u64,
    pub mgrs_key: String,
    pub class: String,
    pub class_group: String,
    pub gps_lat: f64,
    pub gps_lon: f64,
    pub size_width_m: f32,
    pub size_length_m: f32,
    pub confidence: f32,
    pub first_seen: DateTime<Utc>,
    pub last_seen: DateTime<Utc>,
    pub mission_id: String,
    pub source: MapObjectSource,
    pub pending_upload: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DiffKind {
    New,
    Moved,
    Existing,
    RemovedCandidate,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapObjectObservation {
    pub id: Uuid,
    pub h3_cell: u64,
    pub class: String,
    pub class_group: String,
    pub mission_id: String,
    pub uav_id: String,
    pub observed_at_monotonic_ns: u64,
    pub observed_at_wallclock: DateTime<Utc>,
    pub gps_lat: f64,
    pub gps_lon: f64,
    pub mgrs: String,
    pub size_width_m: f32,
    pub size_length_m: f32,
    pub confidence: f32,
    pub diff_kind: DiffKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub photo_ref: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub raw_evidence: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RetentionScope {
    Mission,
    Session,
    UntilExpiry,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IgnoredItemSource {
    CentralPulled,
    LocalAppended,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IgnoredItem {
    pub id: Uuid,
    pub mgrs: String,
    pub h3_cell: u64,
    pub class_group: String,
    pub decline_time: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub operator_id: Option<String>,
    pub mission_id: String,
    pub retention_scope: RetentionScope,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expires_at: Option<DateTime<Utc>>,
    pub source: IgnoredItemSource,
    pub pending_upload: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BundleFreshness {
    Fresh,
    Stale,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapObjectsBundle {
    pub schema_version: String,
    pub mission_id: String,
    /// `[NW, SE]` bounding box.
    pub bbox: [Coordinate; 2],
    #[serde(default)]
    pub map_objects: Vec<MapObject>,
    #[serde(default)]
    pub observations: Vec<MapObjectObservation>,
    #[serde(default)]
    pub ignored_items: Vec<IgnoredItem>,
    pub as_of: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub freshness: Option<BundleFreshness>,
}
