//! Temporary Flight Restrictions (TFRs): a neutral, source-agnostic model and
//! the [`TfrProvider`] capability trait.
//!
//! A TFR is an FDC-numbered restriction over a polygon for a window of time.
//! The verbatim [`text`](Tfr::text) is authoritative for the details
//! (altitudes, exceptions, purpose); the structured fields support filtering —
//! "is my position inside an active TFR?" — through the polygon
//! [`area`](Tfr::area). Vertical limits are intentionally not modelled: the
//! feeds carry them unreliably (placeholder values are common), so the text is
//! the source of truth for altitude.

use async_trait::async_trait;
use chrono::{DateTime, Utc};

use crate::model::{Area, GeoPoint};
use crate::provider::{ContextProvider, ProviderError};

/// The category of a temporary flight restriction.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum TfrKind {
    /// Scheduled (sporting event, airshow, VIP, …).
    Scheduled,
    /// Wildfire / firefighting operations (14 CFR 91.137).
    Firefighting,
    /// Presidential / VIP movement.
    Presidential,
    /// Security / national-defense.
    Security,
    /// Space operations.
    SpaceOperations,
    /// A category this crate does not model; the source's verbatim label.
    Other(String),
}

impl TfrKind {
    /// Map a source `type` label to a kind.
    pub fn from_label(label: &str) -> Self {
        match label.trim().to_ascii_uppercase().as_str() {
            "SCHEDULED" => Self::Scheduled,
            "FIREFIGHTING" => Self::Firefighting,
            "PRESIDENTIAL" => Self::Presidential,
            "SECURITY" => Self::Security,
            "SPACE" | "SPACE_OPERATIONS" | "SPACEOPERATIONS" => Self::SpaceOperations,
            other => Self::Other(other.to_owned()),
        }
    }
}

/// A temporary flight restriction.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Tfr {
    /// FDC identifier, e.g. `"FDC 6/7864"`.
    pub id: String,
    /// Category.
    pub kind: TfrKind,
    /// Controlling facility / location (ICAO), when carried.
    pub location: Option<String>,
    /// First instant in force, when parseable.
    pub start: Option<DateTime<Utc>>,
    /// Last instant in force, when parseable.
    pub end: Option<DateTime<Utc>>,
    /// The restricted lateral extent (a polygon), when carried. Use
    /// [`Tfr::contains`] to test whether a point falls inside.
    pub area: Option<Area>,
    /// Verbatim TFR text — authoritative for altitudes, exceptions, and purpose.
    pub text: String,
}

impl Tfr {
    /// A TFR with its identity, kind, and text; structured fields unset.
    pub fn new(id: impl Into<String>, kind: TfrKind, text: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            kind,
            location: None,
            start: None,
            end: None,
            area: None,
            text: text.into(),
        }
    }

    /// Set the controlling location.
    #[must_use]
    pub fn with_location(mut self, location: Option<String>) -> Self {
        self.location = location;
        self
    }

    /// Set the active window.
    #[must_use]
    pub fn with_period(mut self, start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>) -> Self {
        self.start = start;
        self.end = end;
        self
    }

    /// Set the lateral extent.
    #[must_use]
    pub fn with_area(mut self, area: Option<Area>) -> Self {
        self.area = area;
        self
    }

    /// Whether `point` falls within the TFR's lateral extent: `Some(true)` /
    /// `Some(false)` when the extent can be tested, `None` when the TFR carries
    /// no area or one that cannot be resolved without a location database.
    /// Containment is lateral only — the vertical limits are in
    /// [`text`](Tfr::text).
    pub fn contains(&self, point: GeoPoint) -> Option<bool> {
        self.area.as_ref().and_then(|area| area.contains(point))
    }
}

/// A provider that can serve active TFRs.
///
/// The FAA feed returns the entire active set in one call, so the trait takes
/// no query; filtering to a location or point is the caller's job (see
/// [`Tfr::contains`]).
#[async_trait]
pub trait TfrProvider: ContextProvider {
    /// Fetch every active TFR the source knows.
    async fn tfrs(&self) -> Result<Vec<Tfr>, ProviderError>;
}

#[cfg(test)]
mod tests;
