// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use tokio::sync::{mpsc::{self, Sender as MpscSender, Receiver as MpscReceiver, error::TrySendError}, oneshot::Receiver as OneshotReceiver};
use serde::Serialize;

use super::{CHANNEL_SIZE, error::ErrorContainer};

use crate::net::serp::{Error, Sender, ResponseSender};

pub struct SenderTask<M>(MpscSender<M>);

pub enum SerpResponse {
	Success,
	Understood(String),
}

impl<M> SenderTask<M> where M: Serialize + Send + Sync + 'static {
	pub fn new(stream: Sender, error: ErrorContainer) -> SenderTask<M> {
		let (sender, receiver) = mpsc::channel(CHANNEL_SIZE);
		tokio::spawn(Self::sender_task(stream, receiver, error));
		SenderTask(sender)
	}

	pub fn new_pending_response(stream: ResponseSender, response_receiver: OneshotReceiver<SerpResponse>, error: ErrorContainer) -> SenderTask<M> {
		let (sender, receiver) = mpsc::channel(CHANNEL_SIZE);
		tokio::spawn(async move {
			match Self::get_sender(stream, response_receiver).await {
				Ok(stream) => Self::sender_task(stream, receiver, error).await,
				Err(err) => error.insert(err),
			}
		});
		SenderTask(sender)
	}

	async fn sender_task(mut stream: Sender, mut receiver: MpscReceiver<M>, error: ErrorContainer) {
		error.insert(loop {
			if let Some(msg) = receiver.recv().await {
				if let Err(err) = stream.send(&msg).await {
					break err;
				}
			} else {
				break Error::Disconnected(None);
			}
		});
	}

	async fn get_sender(stream: ResponseSender, response_receiver: OneshotReceiver<SerpResponse>) -> Result<Sender, Error> {
		match response_receiver.await {
			Ok(SerpResponse::Success) => stream.success_unflushed().await,
			Ok(SerpResponse::Understood(msg)) => {
				stream.understood(&msg).await;
				Err(Error::Disconnected(None))
			},
			Err(_) => Err(Error::Disconnected(None)),
		}
	}

	pub fn send(&self, msg: M, error: &ErrorContainer) -> Result<(), Error> {
		self.0.try_send(msg).map_err(|err| match err {
			TrySendError::Closed(_) => error.get(),
			TrySendError::Full(_) => Error::Other(String::from("sync sender is full")),
		})
	}
}
