//! Insert a contact + several data rows atomically via `ApplyBatch`.
//!
//! Demonstrates the back-reference (`"@0"`) flow that ATL will emit when
//! translating an Android `applyBatch` with `withValueBackReference`.
//!
//! Run with the daemon already on the session bus:
//!
//! ```
//! cargo run --example apply_batch_demo
//! ```
//!
//! `BatchOp` / `BatchResult` are redeclared here because `contacts-server`
//! is a binary crate (no library surface). Their wire shapes match the
//! types in `src/dbus/batch.rs` exactly — `(s s a{sv})` and `(s a{sv})`.

use std::collections::HashMap;

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

#[ derive( Debug, Serialize, Deserialize, Type ) ]
struct BatchOp
{
	kind: String,
	target: String,
	args: HashMap<String, OwnedValue>,
}

#[ derive( Debug, Serialize, Deserialize, Type ) ]
struct BatchResult
{
	kind: String,
	payload: HashMap<String, OwnedValue>,
}

const SERVICE:   &str = "org.liberux.Contacts1";
const PATH:      &str = "/org/liberux/Contacts1";
const INTERFACE: &str = "org.liberux.Contacts1";

const NAME_MIME:  &str = "vnd.android.cursor.item/name";
const PHONE_MIME: &str = "vnd.android.cursor.item/phone_v2";
const EMAIL_MIME: &str = "vnd.android.cursor.item/email_v2";

fn main() -> Result<()>
{
	smol::block_on( async
	{
		let conn = zbus::Connection::session().await?;

		let ops: Vec<BatchOp> = vec![
			// #0: insert raw_contact (returns the new raw_id; later ops
			// reference it via "@0").
			BatchOp
			{
				kind:   "insert_raw".into(),
				target: String::new(),
				args:   build_args( vec![
					( "display_name", text( "Linus Torvalds" ) ),
				] ),
			},
			// #1: structured name attached to @0.
			BatchOp
			{
				kind:   "add_data".into(),
				target: "@0".into(),
				args:   build_args( vec![
					( "mimetype",     text( NAME_MIME ) ),
					( "display_name", text( "Linus Torvalds" ) ),
					( "given_name",   text( "Linus" ) ),
					( "family_name",  text( "Torvalds" ) ),
				] ),
			},
			// #2: phone, type=2 (CELL).
			BatchOp
			{
				kind:   "add_data".into(),
				target: "@0".into(),
				args:   build_args( vec![
					( "mimetype",          text( PHONE_MIME ) ),
					( "number",            text( "+358 50 123 4567" ) ),
					( "type",              int32( 2 ) ),
					( "normalized_number", text( "+358501234567" ) ),
				] ),
			},
			// #3: email, type=2 (WORK).
			BatchOp
			{
				kind:   "add_data".into(),
				target: "@0".into(),
				args:   build_args( vec![
					( "mimetype", text( EMAIL_MIME ) ),
					( "address",  text( "torvalds@kernel.org" ) ),
					( "type",     int32( 2 ) ),
				] ),
			},
		];

		let reply = conn
			.call_method(
				Some( SERVICE ),
				PATH,
				Some( INTERFACE ),
				"ApplyBatch",
				&( ops, ),
			)
			.await?;

		let results: Vec<BatchResult> = reply.body().deserialize()?;

		println!( "ApplyBatch → {} result(s):", results.len() );
		for ( i, r ) in results.iter().enumerate()
		{
			let id_str = match r.payload.get( "id" )
			{
				Some( v ) => format!( "{:?}", v ),
				None      => "—".to_owned(),
			};
			println!( "  #{i:>2}  kind={:<3}  id={}", r.kind, id_str );
		}

		println!();
		println!( "If the daemon was running with RUST_LOG=debug you should see exactly" );
		println!( "ONE \"EDS AddressBook.Refresh()\" line and ONE BatchChanged signal," );
		println!( "regardless of the four operations in the batch." );

		Ok::<(), anyhow::Error>( () )
	} )?;
	Ok( () )
}

fn build_args( pairs: Vec<( &str, OwnedValue )> ) -> HashMap<String, OwnedValue>
{
	pairs.into_iter()
		.map( |( k, v )| ( k.to_owned(), v ) )
		.collect()
}

fn text( s: &str ) -> OwnedValue
{
	Value::from( s.to_owned() ).try_to_owned().expect( "string -> OwnedValue" )
}

fn int32( n: i32 ) -> OwnedValue
{
	Value::from( n ).try_to_owned().expect( "i32 -> OwnedValue" )
}
