//! Domain types shared across modem, audio, history, contacts and views.
//!
//! These types are intentionally backend-agnostic. ModemManager-specific
//! identifiers stay inside `modem::modemmanager`; this module only carries
//! data the rest of the app reasons about.

use std::time::{ Duration, Instant };

/// Stable identifier for an in-flight call.
///
/// Allocated by the backend the moment a call appears (outgoing dial or
/// incoming ring) and reused for every subsequent event about that call.
#[ derive( Clone, Copy, Debug, Eq, PartialEq, Hash ) ]
pub struct CallId( pub u64 );

#[ derive( Clone, Copy, Debug, Eq, PartialEq ) ]
pub enum CallDirection
{
	Outgoing,
	Incoming,
}

#[ derive( Clone, Copy, Debug, Eq, PartialEq ) ]
pub enum CallState
{
	/// Outgoing call placed, waiting for the remote side to ring.
	Dialing,
	/// Phone is ringing — outgoing means the remote is being alerted,
	/// incoming means our user is being alerted.
	Ringing,
	/// Both sides are connected and audio should be flowing.
	Active,
	/// User pressed accept on an incoming call but the network has not
	/// yet bridged the audio path.
	Accepting,
	/// Local end is tearing the call down.
	Hanging,
	/// Terminal state — call is finished, may be removed from the active
	/// list. The variant carries the reason the call ended.
	Ended( EndReason ),
}

#[ derive( Clone, Copy, Debug, Eq, PartialEq ) ]
pub enum EndReason
{
	LocalHangup,
	RemoteHangup,
	Rejected,
	Missed,
	NetworkError,
	Unknown,
}

/// A single call as the UI sees it.
#[ derive( Clone, Debug ) ]
pub struct Call
{
	pub id:        CallId,
	pub number:    PhoneNumber,
	pub direction: CallDirection,
	pub state:     CallState,
	/// When the call entered its current state — used to render the
	/// in-call timer and to compute durations for the call log.
	pub since:     Instant,
	/// Cumulative time the call has spent in `Active`.
	pub talk_time: Duration,
	/// Local mute toggle.
	pub muted:     bool,
	/// Speakerphone toggle (routing decision lives in the audio module).
	pub speaker:   bool,
}

/// Untyped phone number string. Formatting and validation happen at the
/// edges (input parsing, display rendering); internally we keep the raw
/// digits-and-symbols the backend handed us.
#[ derive( Clone, Debug, Eq, PartialEq, Hash ) ]
pub struct PhoneNumber( pub String );

impl PhoneNumber
{
	pub fn new( raw: impl Into<String> ) -> Self
	{
		Self( raw.into() )
	}

	pub fn as_str( &self ) -> &str
	{
		&self.0
	}
}

/// Reference to a contact resolved out of contacts-server. Carries the
/// minimum the UI needs to render an entry without round-tripping for
/// every redraw.
#[ derive( Clone, Debug ) ]
pub struct ContactRef
{
	pub id:           u64,
	pub display_name: String,
	pub photo_uri:    Option<String>,
	pub starred:      bool,
	/// Primary phone label as contacts-server reports it ("Mobile", "Work"…).
	pub label:        Option<String>,
	/// Primary phone number, for display and one-tap dialing.
	pub number:       Option<PhoneNumber>,
	pub city:         Option<String>,
}

/// One entry in the call log.
#[ derive( Clone, Debug ) ]
pub struct HistoryEntry
{
	pub number:    PhoneNumber,
	pub contact:   Option<ContactRef>,
	pub direction: CallDirection,
	pub end:       EndReason,
	/// Unix epoch milliseconds — same encoding the calllog-server will use.
	pub started_at_ms: i64,
	pub duration:  Duration,
}

/// How a history entry reads in the recents list: the directional glyph
/// and whether it paints in the danger colour.
#[ derive( Clone, Copy, Debug, Eq, PartialEq ) ]
pub enum CallKind
{
	Incoming,
	Outgoing,
	Missed,
}

impl HistoryEntry
{
	pub fn kind( &self ) -> CallKind
	{
		match ( self.direction, self.end )
		{
			( CallDirection::Incoming, EndReason::Missed | EndReason::Rejected ) => CallKind::Missed,
			( CallDirection::Incoming, _ )                                       => CallKind::Incoming,
			( CallDirection::Outgoing, _ )                                       => CallKind::Outgoing,
		}
	}
}
