//! Definition of all the KDE Connect core packets.
//!
//! ### Status
//!
//! - [x] `kdeconnect.identity`
//! - [x] `kdeconnect.battery`
//! - [x] `kdeconnect.clipboard`
//! - [x] `kdeconnect.clipboard.connect`
//! - [x] `kdeconnect.connectivity_report`
//! - [x] `kdeconnect.contacts.request_all_uids_timestamps`
//! - [x] `kdeconnect.contacts.request_vcards_by_uid`
//! - [x] `kdeconnect.contacts.response_uids_timestamps`
//! - [x] `kdeconnect.contacts.response_vcards`
//! - [x] `kdeconnect.digitizer.session`
//! - [x] `kdeconnect.digitizer`
//! - [x] `kdeconnect.findmyphone.request`
//! - [x] `kdeconnect.lock`
//! - [x] `kdeconnect.lock.request`
//! - [x] `kdeconnect.mousepad.echo`
//! - [x] `kdeconnect.mousepad.keyboardstate`
//! - [x] `kdeconnect.mousepad.request`
//! - [x] `kdeconnect.mpris`
//! - [x] `kdeconnect.mpris.request`
//! - [x] `kdeconnect.notification`
//! - [x] `kdeconnect.notification.action`
//! - [x] `kdeconnect.notification.reply`
//! - [x] `kdeconnect.notification.request`
//! - [x] `kdeconnect.ping`
//! - [x] `kdeconnect.presenter`
//! - [x] `kdeconnect.runcommand`
//! - [x] `kdeconnect.runcommand.request`
//! - [x] `kdeconnect.sftp`
//! - [x] `kdeconnect.sftp.request`
//! - [x] `kdeconnect.share.request`
//! - [x] `kdeconnect.share.request.update`
//! - [x] `kdeconnect.sms.attachment_file`
//! - [x] `kdeconnect.sms.messages`
//! - [x] `kdeconnect.sms.request`
//! - [x] `kdeconnect.sms.request_attachment`
//! - [x] `kdeconnect.sms.request_conversation`
//! - [x] `kdeconnect.sms.request_conversations`
//! - [x] `kdeconnect.systemvolume`
//! - [x] `kdeconnect.systemvolume.request`
//! - [x] `kdeconnect.telephony`
//! - [x] `kdeconnect.telephony.request_mute`
//!
//! Most of the documentation of this module is copy-pasted from the original
//! [KDE Connection specification](https://invent.kde.org/network/kdeconnect-meta/blob/master/protocol.md).
use hashbrown::HashMap;

#[cfg(not(feature = "std"))]
use alloc::{string::{String, ToString}, format};

use serde::{Deserialize, Serialize, de::Error};
use serde_json::Value;

use crate::io::{TcpStreamImpl, TlsStreamImpl};

pub mod battery;
pub mod clipboard;
pub mod connectivity_report;
pub mod contacts;
pub mod digitizer;
pub mod identity;
pub mod lock;
pub mod mousepad;
pub mod mpris;
pub mod notification;
pub mod pair;
pub mod ping;
pub mod presenter;
pub mod runcommand;
pub mod sftp;
pub mod share;
pub mod sms;
pub mod system_volume;
pub mod telephony;

/// Custom serde deserializer to handle both integer or string values in certain packet
/// fields.
///
/// It's needed because some KDE Connect clients send some fields (e.g `id`, `timestamp`)
/// as string instead of integer.
pub(super) fn deserialize_number_or_string<'de, D: serde::Deserializer<'de>>(
    de: D,
) -> Result<u64, D::Error> {
    match Value::deserialize(de)? {
        Value::Number(n) => n
            .as_u64()
            .ok_or_else(|| Error::custom("JSON number cannot be converted to u64")),
        Value::String(s) => s
            .parse::<u64>()
            .map_err(|e| Error::custom(format!("String cannot be parsed as u64: {e}"))),
        _ => Err(Error::custom("unknown type, expected number or string")),
    }
}

/// Custom serde deserializer to handle both integer or string values in certain packet
/// fields which are optional.
///
/// It's needed because some KDE Connect clients send some fields (e.g `id`, `timestamp`)
/// as string instead of integer.
pub(super) fn deserialize_number_or_string_in_option<'de, D: serde::Deserializer<'de>>(
    de: D,
) -> Result<Option<u64>, D::Error> {
    match Value::deserialize(de)? {
        Value::Number(n) => {
            Ok(Some(n.as_u64().ok_or_else(|| {
                Error::custom("JSON number cannot be converted to u64")
            })?))
        }
        Value::String(s) => {
            Ok(Some(s.parse::<u64>().map_err(|e| {
                Error::custom(format!("String cannot be parsed as u64: {e}"))
            })?))
        }
        _ => Err(Error::custom("unknown type, expected number or string")),
    }
}

/// The payload of a [`NetworkPacket`].
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", content = "body")]
pub enum NetworkPacketBody {
    /// The KDE Connect identity packet is used to identify devices and their capabilities.
    #[serde(rename = "kdeconnect.identity")]
    Identity(identity::IdentityPacket),

    /// The KDE Connect pair packet is used to negotiate pairing between devices.
    #[serde(rename = "kdeconnect.pair")]
    Pair(pair::PairPacket),

    /// This packet is a battery status update.
    #[serde(rename = "kdeconnect.battery")]
    Battery(battery::BatteryPacket),

    /// This packet is sent when the clipboard content changes. In other words, it is typically sent when the selection owner changes.
    #[serde(rename = "kdeconnect.clipboard")]
    Clipboard(clipboard::ClipboardPacket),

    /// This packet is only sent when a device connects.
    #[serde(rename = "kdeconnect.clipboard.connect")]
    ClipboardConnect(clipboard::ClipboardConnectPacket),

    /// This packet is a connectivity report.
    #[serde(rename = "kdeconnect.connectivity_report")]
    ConnectivityReport(connectivity_report::ConnectivityReportPacket),

    /// This packet is a request for a list of contact UIDs with modification timestamps.
    #[serde(rename = "kdeconnect.contacts.request_all_uids_timestamps")]
    ContactsRequestAllUidsTimestamps(()),

    /// This packet is a request for a list of contact UIDs with vCard data.
    #[serde(rename = "kdeconnect.contacts.request_vcards_by_uid")]
    ContactsRequestVcardByUid(contacts::ContactsRequestVcardsByUid),

    /// This packet is a list of contact UIDs with modification timestamps.
    #[serde(rename = "kdeconnect.contacts.response_uids_timestamps")]
    ContactsResponseUidsTimestamps(contacts::ContactsResponseUidsTimestamp),

    /// This packet is a list of contact UIDs with vCard data.
    #[serde(rename = "kdeconnect.contacts.response_vcards")]
    ContactsResponseVcards(contacts::ContactsResponseVcards),

    /// This packet either starts or stops a drawing tablet session.
    /// The session is started when this packet is sent with a start action.
    /// When it is started, it includes the information necessary to create a fake drawing tablet
    /// device on the receiver. The session is ended when this packet is sent with an end action,
    /// or the device disconnects. It is valid to send both a start or an end packet regardless
    /// whether a session exists or not. If a session exists and a start packet is sent, the session
    /// will be first ended, and then another session will be started. If no session exists and an
    /// end packet is sent, nothing will happen.
    #[serde(rename = "kdeconnect.digitizer.session")]
    DigitizerSession(digitizer::DigitizerSessionPacket),

    /// This packet is a stylus (or finger) event. `kdeconnect.digitizer` packets must not be sent
    /// until a session has been started. You may start a session by sending the
    /// `kdeconnect.digitizer.session` packet with the field action set to start, and filling
    /// the appropriate fields.
    #[serde(rename = "kdeconnect.digitizer")]
    Digitizer(digitizer::DigitizerPacket),

    /// This packet is a request for a device to announce its location.
    /// By convention, sending a second packet cancels the request.
    #[serde(rename = "kdeconnect.findmyphone.request")]
    FindmyphoneRequest(()),

    /// This packet is a lock status update.
    #[serde(rename = "kdeconnect.lock")]
    Lock(lock::LockPacket),

    /// This packet is a request for a lock status update or change.
    #[serde(rename = "kdeconnect.lock.request")]
    LockRequest(lock::LockRequestPacket),

    /// This packet is an echo for a `kdeconnect.mousepad.request` packet.
    #[serde(rename = "kdeconnect.mousepad.echo")]
    MousepadEcho(mousepad::MousepadEchoPacket),

    /// This packet is a keyboard status update.
    #[serde(rename = "kdeconnect.mousepad.keyboardstate")]
    MousepadKeyboardState(mousepad::MousepadKeyboardStatePacket),

    /// This packet is a request for a pointer or keyboard event.
    #[serde(rename = "kdeconnect.mousepad.request")]
    MousepadRequest(mousepad::MousepadRequestPacket),

    /// This packet is used for two things: if it contains a playerList field, it is used to
    /// enumerate the available media players to a remote device; if it contains a player field,
    /// it is used to report the status of a specific media player. Note that player packets can be
    /// incremental, ie: only contain the fields that changed since the last update.
    #[serde(rename = "kdeconnect.mpris")]
    Mpris(mpris::MprisPacket),

    /// This packet is used to request the status of remote media players, issue commands, and
    /// request the transfer of album art payloads.
    #[serde(rename = "kdeconnect.mpris.request")]
    MprisRequest(mpris::MprisRequestPacket),

    /// This packet is a notification.
    #[serde(rename = "kdeconnect.notification")]
    Notification(notification::NotificationPacket),

    /// This packet is an activation of a notification action.
    #[serde(rename = "kdeconnect.notification.action")]
    NotificationAction(notification::NotificationActionPacket),

    /// This packet is a reply for a repliable notification.
    #[serde(rename = "kdeconnect.notification.reply")]
    NotificationReply(notification::NotificationReplyPacket),

    /// This packet is a request for notifications.
    #[serde(rename = "kdeconnect.notification.request")]
    NotificationRequest(notification::NotificationRequestPacket),

    /// This packet is a ping request.
    #[serde(rename = "kdeconnect.ping")]
    Ping(ping::PingPacket),

    /// This packet is a presentation remote event.
    #[serde(rename = "kdeconnect.presenter")]
    Presenter(presenter::PresenterPacket),

    /// This packet is a list of available commands.
    #[serde(rename = "kdeconnect.runcommand")]
    RunCommand(runcommand::RunCommandPacket),

    /// This packet is a runcommand status update.
    #[serde(rename = "kdeconnect.runcommand.request")]
    RunCommandRequest(runcommand::RunCommandRequestPacket),

    /// This packet contains SFTP login information.
    #[serde(rename = "kdeconnect.sftp")]
    Sftp(sftp::SftpPacket),

    /// This packet is a request to start SFTP.
    #[serde(rename = "kdeconnect.sftp.request")]
    SftpRequest(sftp::SftpRequestPacket),

    /// This packet is an upload request.
    #[serde(rename = "kdeconnect.share.request")]
    ShareRequest(share::ShareRequestPacket),

    /// This packet is the metadata for a multi-file transfer.
    /// By convention it is sent in advance of the packets containing the payload, which will
    /// include the same fields, potentially with updated totals.
    #[serde(rename = "kdeconnect.share.request.update")]
    ShareRequestUpdate(share::ShareRequestUpdatePacket),

    /// This packet is a message attachment transfer.
    #[serde(rename = "kdeconnect.sms.attachment_file")]
    SmsAttachmentFile(sms::SmsAttachmentFilePacket),

    /// This packet is a list of messages.
    #[serde(rename = "kdeconnect.sms.messages")]
    SmsMessages(sms::SmsMessagesPacket),

    /// This packet is a request to send an SMS/MMS message.
    #[serde(rename = "kdeconnect.sms.request")]
    SmsRequest(sms::SmsRequestPacket),

    /// This packet is a request for a message attachment.
    #[serde(rename = "kdeconnect.sms.request_attachment")]
    SmsRequestAttachment(sms::SmsRequestAttachmentPacket),

    /// This packet is a request for messages from a thread.
    #[serde(rename = "kdeconnect.sms.request_conversation")]
    SmsRequestConversation(sms::SmsRequestConversationPacket),

    /// This packet is a request for the latest message in each thread.
    #[serde(rename = "kdeconnect.sms.request_conversations")]
    SmsRequestConversations(()),

    /// This packet is a mixer stream state update.
    #[serde(rename = "kdeconnect.systemvolume")]
    SystemVolume(system_volume::SystemVolumePacket),

    /// This packet is a audio stream request. It is used to request both the list of streams and
    /// changes to those streams.
    #[serde(rename = "kdeconnect.systemvolume.request")]
    SystemVolumeRequest(system_volume::SystemVolumeRequestPacket),

    /// This packet is a telephony event, such as the phone ringing.
    #[serde(rename = "kdeconnect.telephony")]
    Telephony(telephony::TelephonyPacket),

    /// This packet is sent to request the ringer be muted.
    #[serde(rename = "kdeconnect.telephony.request_mute")]
    TelephonyRequestMute(()),

    /// Another type of packet that a library user can implement to handle special custom actions.
    #[serde(untagged)]
    Other(Value),
}

impl NetworkPacketBody {
    /// Get the [`NetworkPacketType`] of a [`NetworkPacketBody`].
    pub fn get_type(&self) -> NetworkPacketType {
        match self {
            NetworkPacketBody::Identity(_) => NetworkPacketType::Identity,
            NetworkPacketBody::Pair(_) => NetworkPacketType::Pair,
            NetworkPacketBody::Ping(_) => NetworkPacketType::Ping,
            NetworkPacketBody::Battery(_) => NetworkPacketType::Battery,
            NetworkPacketBody::Clipboard(_) => NetworkPacketType::Clipboard,
            NetworkPacketBody::ClipboardConnect(_) => NetworkPacketType::ClipboardConnect,
            NetworkPacketBody::ConnectivityReport(_) => NetworkPacketType::ConnectivityReport,
            NetworkPacketBody::ContactsRequestAllUidsTimestamps(()) => {
                NetworkPacketType::ContactsRequestAllUidsTimestamps
            }
            NetworkPacketBody::ContactsRequestVcardByUid(_) => {
                NetworkPacketType::ContactsRequestVcardByUid
            }
            NetworkPacketBody::ContactsResponseUidsTimestamps(_) => {
                NetworkPacketType::ContactsResponseUidsTimestamps
            }
            NetworkPacketBody::ContactsResponseVcards(_) => {
                NetworkPacketType::ContactsResponseVcards
            }
            NetworkPacketBody::DigitizerSession(_) => NetworkPacketType::DigitizerSession,
            NetworkPacketBody::Digitizer(_) => NetworkPacketType::Digitizer,
            NetworkPacketBody::FindmyphoneRequest(()) => NetworkPacketType::FindmyphoneRequest,
            NetworkPacketBody::Lock(_) => NetworkPacketType::Lock,
            NetworkPacketBody::LockRequest(_) => NetworkPacketType::LockRequest,
            NetworkPacketBody::MousepadEcho(_) => NetworkPacketType::MousepadEcho,
            NetworkPacketBody::MousepadKeyboardState(_) => NetworkPacketType::MousepadKeyboardState,
            NetworkPacketBody::MousepadRequest(_) => NetworkPacketType::MousepadRequest,
            NetworkPacketBody::Mpris(_) => NetworkPacketType::Mpris,
            NetworkPacketBody::MprisRequest(_) => NetworkPacketType::MprisRequest,
            NetworkPacketBody::Notification(_) => NetworkPacketType::Notification,
            NetworkPacketBody::NotificationAction(_) => NetworkPacketType::NotificationAction,
            NetworkPacketBody::NotificationReply(_) => NetworkPacketType::NotificationReply,
            NetworkPacketBody::NotificationRequest(_) => NetworkPacketType::NotificationRequest,
            NetworkPacketBody::Presenter(_) => NetworkPacketType::Presenter,
            NetworkPacketBody::RunCommand(_) => NetworkPacketType::RunCommand,
            NetworkPacketBody::RunCommandRequest(_) => NetworkPacketType::RunCommandRequest,
            NetworkPacketBody::Sftp(_) => NetworkPacketType::Sftp,
            NetworkPacketBody::SftpRequest(_) => NetworkPacketType::SftpRequest,
            NetworkPacketBody::ShareRequest(_) => NetworkPacketType::ShareRequest,
            NetworkPacketBody::ShareRequestUpdate(_) => NetworkPacketType::ShareRequestUpdate,
            NetworkPacketBody::SmsAttachmentFile(_) => NetworkPacketType::SmsAttachmentFile,
            NetworkPacketBody::SmsMessages(_) => NetworkPacketType::SmsMessages,
            NetworkPacketBody::SmsRequest(_) => NetworkPacketType::SmsRequest,
            NetworkPacketBody::SmsRequestAttachment(_) => NetworkPacketType::SmsRequestAttachment,
            NetworkPacketBody::SmsRequestConversation(_) => {
                NetworkPacketType::SmsRequestConversation
            }
            NetworkPacketBody::SmsRequestConversations(()) => {
                NetworkPacketType::SmsRequestConversations
            }
            NetworkPacketBody::SystemVolume(_) => NetworkPacketType::SystemVolume,
            NetworkPacketBody::SystemVolumeRequest(_) => NetworkPacketType::SystemVolumeRequest,
            NetworkPacketBody::Telephony(_) => NetworkPacketType::Telephony,
            NetworkPacketBody::TelephonyRequestMute(()) => NetworkPacketType::TelephonyRequestMute,
            NetworkPacketBody::Other(value) => NetworkPacketType::Other(
                value
                    .get("type")
                    .unwrap_or(&Value::String("Unknown".into()))
                    .as_str()
                    .unwrap_or("Unknown")
                    .to_string(),
            ),
        }
    }
}

/// The type of a [`NetworkPacket`].
///
/// It's used to define which packet types are supported by a plugin, see
/// [`Plugin::supported_incoming_packets`](`crate::plugin::Plugin::supported_incoming_packets`) and
/// [`Plugin::supported_outgoing_packets`](`crate::plugin::Plugin::supported_outgoing_packets`).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum NetworkPacketType {
    /// The KDE Connect identity packet is used to identify devices and their capabilities.
    #[serde(rename = "kdeconnect.identity")]
    Identity,

    /// The KDE Connect pair packet is used to negotiate pairing between devices.
    #[serde(rename = "kdeconnect.pair")]
    Pair,

    /// This packet is a battery status update.
    #[serde(rename = "kdeconnect.battery")]
    Battery,

    /// This packet is sent when the clipboard content changes. In other words, it is typically sent when the selection owner changes.
    #[serde(rename = "kdeconnect.clipboard")]
    Clipboard,

    /// This packet is only sent when a device connects and allows syncing clipboard text content between devices.
    #[serde(rename = "kdeconnect.clipboard.connect")]
    ClipboardConnect,

    /// This packet allows a device to expose the status of its connectivity.
    #[serde(rename = "kdeconnect.connectivity_report")]
    ConnectivityReport,

    /// This packet is a request for a list of contact UIDs with modification timestamps.
    #[serde(rename = "kdeconnect.contacts.request_all_uids_timestamps")]
    ContactsRequestAllUidsTimestamps,

    /// This packet is a request for a list of contact UIDs with vCard data.
    #[serde(rename = "kdeconnect.contacts.request_vcards_by_uid")]
    ContactsRequestVcardByUid,

    /// This packet is a list of contact UIDs with modification timestamps.
    #[serde(rename = "kdeconnect.contacts.response_uids_timestamps")]
    ContactsResponseUidsTimestamps,

    /// This packet is a list of contact UIDs with vCard data.
    #[serde(rename = "kdeconnect.contacts.response_vcards")]
    ContactsResponseVcards,

    /// This packet either starts or stops a drawing tablet session.
    /// The session is started when this packet is sent with a start action.
    /// When it is started, it includes the information necessary to create a fake drawing tablet
    /// device on the receiver. The session is ended when this packet is sent with an end action,
    /// or the device disconnects. It is valid to send both a start or an end packet regardless
    /// whether a session exists or not. If a session exists and a start packet is sent, the session
    /// will be first ended, and then another session will be started. If no session exists and an
    /// end packet is sent, nothing will happen.
    #[serde(rename = "kdeconnect.digitizer.session")]
    DigitizerSession,

    /// This packet is a stylus (or finger) event. `kdeconnect.digitizer` packets must not be sent
    /// until a session has been started. You may start a session by sending the
    /// `kdeconnect.digitizer.session` packet with the field action set to start, and filling
    /// the appropriate fields.
    #[serde(rename = "kdeconnect.digitizer")]
    Digitizer,

    /// This packet is a request for a device to announce its location.
    /// By convention, sending a second packet cancels the request.
    #[serde(rename = "kdeconnect.findmyphone.request")]
    FindmyphoneRequest,

    /// This packet is a lock status update.
    #[serde(rename = "kdeconnect.lock")]
    Lock,

    /// This packet is a request for a lock status update or change.
    #[serde(rename = "kdeconnect.lock.request")]
    LockRequest,

    /// This packet is an echo for a `kdeconnect.mousepad.request` packet.
    #[serde(rename = "kdeconnect.mousepad.echo")]
    MousepadEcho,

    /// This packet is a keyboard status update.
    #[serde(rename = "kdeconnect.mousepad.keyboardstate")]
    MousepadKeyboardState,

    /// This packet is a request for a pointer or keyboard event.
    #[serde(rename = "kdeconnect.mousepad.request")]
    MousepadRequest,

    /// This packet is used for two things: if it contains a playerList field, it is used to
    /// enumerate the available media players to a remote device; if it contains a player field,
    /// it is used to report the status of a specific media player. Note that player packets can be
    /// incremental, ie: only contain the fields that changed since the last update.
    #[serde(rename = "kdeconnect.mpris")]
    Mpris,

    /// This packet is used to request the status of remote media players, issue commands, and
    /// request the transfer of album art payloads.
    #[serde(rename = "kdeconnect.mpris.request")]
    MprisRequest,

    /// This packet is a notification.
    #[serde(rename = "kdeconnect.notification")]
    Notification,

    /// This packet is an activation of a notification action.
    #[serde(rename = "kdeconnect.notification.action")]
    NotificationAction,

    /// This packet is a reply for a repliable notification.
    #[serde(rename = "kdeconnect.notification.reply")]
    NotificationReply,

    /// This packet is a request for notifications.
    #[serde(rename = "kdeconnect.notification.request")]
    NotificationRequest,

    /// This packet is a ping request.
    #[serde(rename = "kdeconnect.ping")]
    Ping,

    /// This packet is a presentation remote event.
    #[serde(rename = "kdeconnect.presenter")]
    Presenter,

    /// This packet is a list of available commands.
    #[serde(rename = "kdeconnect.runcommand")]
    RunCommand,

    /// This packet is a runcommand status update.
    #[serde(rename = "kdeconnect.runcommand.request")]
    RunCommandRequest,

    /// This packet contains SFTP login information.
    #[serde(rename = "kdeconnect.sftp")]
    Sftp,

    /// This packet is a request to start SFTP.
    #[serde(rename = "kdeconnect.sftp.request")]
    SftpRequest,

    /// This packet is an upload request.
    #[serde(rename = "kdeconnect.share.request")]
    ShareRequest,

    /// This packet is the metadata for a multi-file transfer.
    /// By convention it is sent in advance of the packets containing the payload, which will
    /// include the same fields, potentially with updated totals.
    #[serde(rename = "kdeconnect.share.request.update")]
    ShareRequestUpdate,

    /// This packet is a message attachment transfer.
    #[serde(rename = "kdeconnect.sms.attachment_file")]
    SmsAttachmentFile,

    /// This packet is a list of messages.
    #[serde(rename = "kdeconnect.sms.messages")]
    SmsMessages,

    /// This packet is a request to send an SMS/MMS message.
    #[serde(rename = "kdeconnect.sms.request")]
    SmsRequest,

    /// This packet is a request for a message attachment.
    #[serde(rename = "kdeconnect.sms.request_attachment")]
    SmsRequestAttachment,

    /// This packet is a request for messages from a thread.
    #[serde(rename = "kdeconnect.sms.request_conversation")]
    SmsRequestConversation,

    /// This packet is a request for the latest message in each thread.
    #[serde(rename = "kdeconnect.sms.request_conversations")]
    SmsRequestConversations,

    /// This packet is a mixer stream state update.
    #[serde(rename = "kdeconnect.systemvolume")]
    SystemVolume,

    /// This packet is a audio stream request. It is used to request both the list of streams and
    /// changes to those streams.
    #[serde(rename = "kdeconnect.systemvolume.request")]
    SystemVolumeRequest,

    /// This packet is a telephony event, such as the phone ringing.
    #[serde(rename = "kdeconnect.telephony")]
    Telephony,

    /// This packet is sent to request the ringer be muted.
    #[serde(rename = "kdeconnect.telephony.request_mute")]
    TelephonyRequestMute,

    /// Another type of packet that a library user can implement to handle special custom actions.
    #[serde(untagged)]
    Other(String),
}

/// A root packet structured as defined in the KDE Connection specification.
///
/// <https://invent.kde.org/network/kdeconnect-meta/blob/master/protocol.md#kdeconnect>
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NetworkPacket {
    #[serde(deserialize_with = "deserialize_number_or_string")]
    id: u64,

    /// A dictionary of parameters appropriate for the packet type.
    #[serde(flatten)]
    pub body: NetworkPacketBody,

    /// The size of the payload to expect. There is a currently unused convention of using `-1` to declare a stream of indefinite size.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub range: Option<i64>,

    /// A dictionary of properties necessary for clients to negotiate a transfer channel.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub payload_transfer_info: Option<HashMap<String, Value>>,
}

impl NetworkPacket {
    /// Make a new [`NetworkPacket`] with the corresponding body.
    ///
    /// The type will be inferred from the body variant.
    pub fn new(body: NetworkPacketBody) -> Self {
        Self {
            // This field is deprecated and can be safely populated with a constant value
            id: 0,
            body,
            range: None,
            payload_transfer_info: None,
        }
    }

    pub(crate) fn try_read_from(buf: &[u8]) -> Result<Self, serde_json::Error> {
        serde_json::from_slice::<NetworkPacket>(buf)
    }

    pub(crate) async fn write_to_socket_unencrypted<T: TcpStreamImpl + Unpin>(self, socket: &mut T) {
        let mut serialized_packet = serde_json::to_string(&self).unwrap();
        serialized_packet.push('\n');

        socket
            .write_all(serialized_packet.as_bytes())
            .await
            .unwrap();
    }

    pub(crate) async fn write_to_socket<T: TlsStreamImpl + Unpin>(self, socket: &mut T) {
        let mut serialized_packet = serde_json::to_string(&self).unwrap();
        serialized_packet.push('\n');

        socket
            .write_all(serialized_packet.as_bytes())
            .await
            .unwrap();
    }
}
