//! Length-prefixed JSON framing over any tokio AsyncRead / AsyncWrite.
//!
//! Frame: `[u32 little-endian length][serde_json body]`. No header, no magic,
//! no compression. Identical shape to greetd's protocol so existing CLI
//! tools (socat / a Python script) work for diagnostics.

use serde::{ de::DeserializeOwned, Serialize };
use tokio::io::{ AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt };

const MAX_FRAME: usize = 1 << 20;	// 1 MiB hard cap — sanity check against malformed senders

#[ derive( Debug ) ]
pub enum WireError
{
	Io( std::io::Error ),
	Json( serde_json::Error ),
	FrameTooLarge( usize ),
}

impl std::fmt::Display for WireError
{
	fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result
	{
		match self
		{
			WireError::Io( e )            => write!( f, "io: {e}" ),
			WireError::Json( e )          => write!( f, "json: {e}" ),
			WireError::FrameTooLarge( n ) => write!( f, "frame too large: {n} bytes (max {MAX_FRAME})" ),
		}
	}
}

impl std::error::Error for WireError {}

impl From<std::io::Error> for WireError
{
	fn from( e: std::io::Error ) -> Self { WireError::Io( e ) }
}

impl From<serde_json::Error> for WireError
{
	fn from( e: serde_json::Error ) -> Self { WireError::Json( e ) }
}

pub async fn read_message<R, T>( reader: &mut R ) -> Result<T, WireError>
where
	R: AsyncRead + Unpin,
	T: DeserializeOwned,
{
	let mut len_buf = [ 0u8; 4 ];
	reader.read_exact( &mut len_buf ).await?;
	let len = u32::from_le_bytes( len_buf ) as usize;

	if len > MAX_FRAME { return Err( WireError::FrameTooLarge( len ) ); }

	let mut body = vec![ 0u8; len ];
	reader.read_exact( &mut body ).await?;

	Ok( serde_json::from_slice( &body )? )
}

pub async fn write_message<W, T>( writer: &mut W, msg: &T ) -> Result<(), WireError>
where
	W: AsyncWrite + Unpin,
	T: Serialize,
{
	let body = serde_json::to_vec( msg )?;
	if body.len() > MAX_FRAME { return Err( WireError::FrameTooLarge( body.len() ) ); }

	let len = ( body.len() as u32 ).to_le_bytes();
	writer.write_all( &len ).await?;
	writer.write_all( &body ).await?;
	writer.flush().await?;
	Ok( () )
}
