use std::fmt::Debug;

use log::{trace, warn};
use tokio::sync::mpsc::Sender;

use super::decoder::Decoder;
use crate::error::Error;
use crate::frame::{Callback, Frame, Parameters};

/// Splits incoming EZSP frames into responses and asynchronous callbacks.
///
/// On EZSP-UART, callbacks are asynchronous by default. The response frame
/// control byte identifies whether a callback arrived asynchronously or as part
/// of a normal command/response exchange.
pub struct Splitter {
    incoming: Decoder,
    responses: Sender<Result<Parameters, Error>>,
    callbacks: Sender<Callback>,
}

impl Splitter {
    /// Create a new `Splitter`.
    ///
    /// Sets the incoming frames decoder as a source for frames
    /// and the responses and callbacks sinks as destinations.
    #[must_use]
    pub const fn new(
        incoming: Decoder,
        responses: Sender<Result<Parameters, Error>>,
        callbacks: Sender<Callback>,
    ) -> Self {
        Self {
            incoming,
            responses,
            callbacks,
        }
    }

    /// Run the splitter.
    ///
    /// Continuously decodes incoming frames and forwards them as responses or callbacks.
    pub async fn run(mut self) {
        while let Some(frame) = self.incoming.decode().await {
            match frame {
                Ok(frame) => {
                    trace!("Received frame: {frame:?}");
                    self.handle_frame(frame).await;
                }
                Err(error) => {
                    warn!("Failed to decode frame: {error}");
                    self.responses
                        .send(Err(error))
                        .await
                        .expect("Response channel should be open. This is a bug.");
                }
            }
        }
    }

    async fn handle_frame(&self, frame: Frame) {
        let (header, parameters) = frame.into();

        match parameters {
            Parameters::Response(response) => {
                trace!("Forwarding response: {response:?}");
                self.handle_response(Parameters::Response(response)).await;
            }
            Parameters::Callback(callback) => {
                if header.is_async_callback() {
                    trace!("Forwarding async callback: {callback:?}");
                    self.handle_callback(callback).await;
                } else {
                    trace!("Forwarding non-async callback as response: {callback:?}");
                    self.handle_response(Parameters::Callback(callback)).await;
                }
            }
        }
    }

    async fn handle_response(&self, parameters: Parameters) {
        self.responses
            .send(Ok(parameters))
            .await
            .expect("Response channel should be open. This is a bug.");
    }

    async fn handle_callback(&self, handler: Callback) {
        self.callbacks
            .send(handler)
            .await
            .expect("Callback channel should be open. This is a bug");
    }
}

impl Debug for Splitter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Splitter")
            .field("incoming", &self.incoming)
            .field("responses", &self.responses)
            .field("callbacks", &"...")
            .finish()
    }
}
