//! Define the [`Plugin`] trait used to make KDE Connect plugins.
use crate::{
    device::Link,
    error::Result,
    packet::{NetworkPacket, NetworkPacketType},
};

#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};

/// A plugin is a handler receiving specific packet types and emitting new packets.
///
/// Each plugin defines the set of packet types it supports using the [`Plugin::supported_incoming_packets`]
/// and [`Plugin::supported_outgoing_packets`] methods. It can send initialization packets (e.g to
/// implement clipboard functionality where the device sends the clipboard content at startup)
/// during the [`Plugin::on_start`] method. Finally, it handles supported packet types in the
/// [`Plugin::on_packet_received`] method.
///
/// When implementing this trait, you must prepend the `impl` statement with `#[kdeconnect_proto::async_trait]`.
///
/// ### Example
///
/// ```
/// use kdeconnect_proto::{
///     error::Result,
///     device::Link,
///     packet::{NetworkPacket, NetworkPacketType, NetworkPacketBody},
///     plugin::Plugin,
/// };
///
/// struct PingPlugin;
///
/// #[kdeconnect_proto::async_trait]
/// impl Plugin for PingPlugin {
///     fn supported_incoming_packets(&self) -> Vec<NetworkPacketType> {
///        vec![NetworkPacketType::Ping]
///     }
///
///     fn supported_outgoing_packets(&self) -> Vec<NetworkPacketType> {
///         vec![NetworkPacketType::Ping]
///     }
///
///     async fn on_packet_received(&self, packet: &NetworkPacket, link: &Link) -> Result<()> {
///         match &packet.body {
///             NetworkPacketBody::Ping(packet) => {
///                 let device_name = link.info
///                     .device_name
///                     .as_ref()
///                     .unwrap_or(&link.info.device_id);
///                 let msg = if let Some(msg) = &packet.message {
///                     format!(" \"{msg}\"")
///                 } else {
///                     String::new()
///                 };
///                 println!("PING{msg} from {device_name}!");
///             }
///             _ => unreachable!(),
///         }
///         Ok(())
///     }
///
///     async fn on_start(&self, link: &Link) -> Result<()> {
///         link.send(NetworkPacket::ping("Connected")).await;
///         Ok(())
///     }
/// }
/// ```
#[async_trait::async_trait]
pub trait Plugin {
    /// Informative function returning the full list of supported packet types.
    ///
    /// Used to determine the capabilities of the device and if the plugin needs to be called when
    /// a packet is received.
    fn supported_incoming_packets(&self) -> Vec<NetworkPacketType>;

    /// Informative function returning the full list of supported packet types.
    ///
    /// Used to determine the capabilities of the device.
    fn supported_outgoing_packets(&self) -> Vec<NetworkPacketType>;

    /// Method called when a packet is received.
    ///
    /// ### Errors
    ///
    /// This method can return an error which will be shown to users but don't stop the execution
    /// of the event loop.
    async fn on_packet_received(&self, packet: &NetworkPacket, link: &Link) -> Result<()>;

    /// Method called when the plugin is loaded after establishing a connection with a new device.
    ///
    /// It's ensured that the new device is paired with the current device.
    ///
    /// It can be used, for instance, to send initialization packets.
    ///
    /// ### Errors
    ///
    /// If this method returns an error, the plugin is unloaded and its [`Plugin::on_packet_received`]
    /// method will never be called.
    async fn on_start(&self, link: &Link) -> Result<()>;
}
