// TODO: Consider inlining this file into internal

use cef::{Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};

pub(crate) enum MessageType {
	Initialized,
	SendToJS,
	SendToNative,
}
impl From<MessageType> for MessageInfo {
	fn from(val: MessageType) -> Self {
		match val {
			MessageType::Initialized => MessageInfo {
				name: "initialized".to_string(),
				target: cef_process_id_t::PID_BROWSER.into(),
			},
			MessageType::SendToJS => MessageInfo {
				name: "send_to_js".to_string(),
				target: cef_process_id_t::PID_RENDERER.into(),
			},
			MessageType::SendToNative => MessageInfo {
				name: "send_to_native".to_string(),
				target: cef_process_id_t::PID_BROWSER.into(),
			},
		}
	}
}
impl TryFrom<String> for MessageType {
	type Error = ();
	fn try_from(value: String) -> Result<Self, Self::Error> {
		match value.as_str() {
			"initialized" => Ok(MessageType::Initialized),
			"send_to_js" => Ok(MessageType::SendToJS),
			"send_to_native" => Ok(MessageType::SendToNative),
			_ => Err(()),
		}
	}
}

pub(crate) struct MessageInfo {
	name: String,
	target: ProcessId,
}

pub(crate) trait SendMessage {
	fn send_message(&self, message_type: MessageType, message: &[u8]);
}
impl SendMessage for Option<V8Context> {
	fn send_message(&self, message_type: MessageType, message: &[u8]) {
		let Some(context) = self else {
			tracing::error!("Current V8 context is not available, cannot send message");
			return;
		};

		context.send_message(message_type, message);
	}
}
impl SendMessage for V8Context {
	fn send_message(&self, message_type: MessageType, message: &[u8]) {
		let Some(frame) = self.frame() else {
			tracing::error!("Current V8 context does not have a frame, cannot send message");
			return;
		};

		frame.send_message(message_type, message);
	}
}
impl SendMessage for Frame {
	fn send_message(&self, message_type: MessageType, message: &[u8]) {
		let MessageInfo { name, target } = message_type.into();

		let Some(mut process_message) = cef::process_message_create(Some(&name.as_str().into())) else {
			tracing::error!("Failed to create process message: {}", name);
			return;
		};
		let Some(arg_list) = process_message.argument_list() else { return };
		let mut value = ::cef::binary_value_create(Some(message));
		arg_list.set_binary(0, value.as_mut());

		self.send_process_message(target, Some(&mut process_message));
	}
}

pub(crate) struct UnpackedMessage<'a> {
	pub(crate) message_type: MessageType,
	pub(crate) data: &'a [u8],
}

trait Sealed {}
impl Sealed for cef::ProcessMessage {}
#[allow(private_bounds)]
pub(crate) trait UnpackMessage: Sealed {
	/// # Safety
	///
	/// The caller must ensure that the message is valid.
	/// Message should come from cef.
	unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>>;
}
impl UnpackMessage for cef::ProcessMessage {
	unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>> {
		let pointer: *mut cef::sys::_cef_string_utf16_t = self.name().into();
		let message = unsafe { super::utility::pointer_to_string(pointer) };
		let Ok(message_type) = message.try_into() else {
			tracing::error!("Failed to get message type from process message");
			return None;
		};
		let arglist = self.argument_list()?;
		let binary = arglist.binary(0)?;
		let size = binary.size();
		let ptr = binary.raw_data();
		let buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) };
		Some(UnpackedMessage { message_type, data: buffer })
	}
}
