//! D-Bus surface types for `org.liberux.Contacts1`.
//!
//! These mirror the wire structs documented in `docs/dbus-api.md`. They are
//! deliberately separate from the domain types in `crate::model` so the bus
//! representation can evolve independently — e.g. NULL strings round to the
//! empty string, i64 ids are clamped to u32 (`Contacts1` interface contract).

use std::collections::HashMap;

use serde::{ Deserialize, Serialize };
use zbus::zvariant::{ OwnedValue, Type };

use crate::model::contact::{ Contact, ContactSummary };
use crate::model::data_item::DataRow;
use crate::model::raw_contact::RawContact;

/// Wire signature: `(uussssuxx)`.
#[ derive( Debug, Clone, Serialize, Deserialize, Type ) ]
pub struct RawContactDbus
{
	pub id: u32,
	pub contact_id: u32,
	pub account_name: String,
	pub account_type: String,
	pub display_name: String,
	pub sourceid: String,
	pub version: u32,
	pub created_at: i64,
	pub updated_at: i64,
}

impl From<RawContact> for RawContactDbus
{
	fn from( r: RawContact ) -> Self
	{
		Self
		{
			id:           r.id           as u32,
			contact_id:   r.contact_id   as u32,
			account_name: r.account_name.unwrap_or_default(),
			account_type: r.account_type.unwrap_or_default(),
			display_name: r.display_name.unwrap_or_default(),
			sourceid:     r.sourceid.unwrap_or_default(),
			version:      r.version      as u32,
			created_at:   r.created_at,
			updated_at:   r.updated_at,
		}
	}
}

/// Wire signature: `(usubuxx)`.
#[ derive( Debug, Clone, Serialize, Deserialize, Type ) ]
pub struct ContactDbus
{
	pub id: u32,
	pub display_name: String,
	pub photo_data_id: u32,
	pub starred: bool,
	pub times_contacted: u32,
	pub last_time_contacted: i64,
	pub created_at: i64,
}

impl From<Contact> for ContactDbus
{
	fn from( c: Contact ) -> Self
	{
		Self
		{
			id:                  c.id              as u32,
			display_name:        c.display_name.unwrap_or_default(),
			photo_data_id:       c.photo_id.unwrap_or( 0 ) as u32,
			starred:             c.starred,
			times_contacted:     c.times_contacted as u32,
			last_time_contacted: c.last_time_contacted.unwrap_or( 0 ),
			created_at:          c.created_at,
		}
	}
}

/// Wire signature: `(ussbsis)`.
///
/// A contact plus its primary phone, for list views. `photo_uri` is empty when
/// there is no photo (same `liberux-contacts:///data/<id>` scheme as
/// `QueryContacts`); `number`/`phone_label` are empty and `phone_type` is `-1`
/// when the contact has no phone.
#[ derive( Debug, Clone, Serialize, Deserialize, Type ) ]
pub struct ContactSummaryDbus
{
	pub id: u32,
	pub display_name: String,
	pub photo_uri: String,
	pub starred: bool,
	pub number: String,
	pub phone_type: i32,
	pub phone_label: String,
}

impl From<ContactSummary> for ContactSummaryDbus
{
	fn from( c: ContactSummary ) -> Self
	{
		let photo_uri = match c.photo_id
		{
			Some( pid ) if pid > 0 => format!( "liberux-contacts:///data/{}", pid ),
			_                      => String::new(),
		};
		Self
		{
			id:           c.id as u32,
			display_name: c.display_name.unwrap_or_default(),
			photo_uri,
			starred:      c.starred,
			number:       c.number.unwrap_or_default(),
			phone_type:   c.phone_type.unwrap_or( -1 ),
			phone_label:  c.phone_label.unwrap_or_default(),
		}
	}
}

/// Wire signature: `(usua{sv}bbu)`.
///
/// `fields` carries the per-mimetype named-key dict — see
/// `docs/dbus-api.md` and `crate::model::data_fields` for the per-mimetype
/// keys. `Clone` is intentionally not derived — `OwnedValue` is not Clone
/// in zbus 4, and the wire struct is built once per response anyway.
#[ derive( Debug, Serialize, Deserialize, Type ) ]
pub struct DataDbus
{
	pub id: u32,
	pub mimetype: String,
	pub raw_contact_id: u32,
	pub fields: HashMap<String, OwnedValue>,
	pub is_primary: bool,
	pub is_super_primary: bool,
	pub data_version: u32,
}

impl DataDbus
{
	/// Build the wire struct from a stored `DataRow`. Fails only if a value
	/// cannot be encoded as `OwnedValue`, which should not happen for the
	/// types our codec produces.
	pub fn from_row( row: DataRow ) -> Result<Self, zbus::zvariant::Error>
	{
		let fields = crate::dbus::codec::field_map_to_dict(
			crate::model::data_fields::row_to_fields( &row ),
		)?;
		Ok( Self
		{
			id:               row.id             as u32,
			mimetype:         row.mimetype,
			raw_contact_id:   row.raw_contact_id as u32,
			fields,
			is_primary:       row.is_primary,
			is_super_primary: row.is_super_primary,
			data_version:     row.data_version   as u32,
		} )
	}
}
