use {
	crate::{
		prelude::*,
		spa::{SpaBool, SpaIdTable, SpaIdValue, SpaPod, SpaPodBuilder, SpaPrimitive, SpaType, SpaValue},
	},
	libspa::{
		pod::{
			deserialize::{DeserializeError, PodDeserialize, PodDeserializer},
			serialize::{GenError, PodSerialize, PodSerializer},
			CanonicalFixedSizedPod, ChoiceValue, Object, Property, PropertyFlags, Value, ValueArray,
		},
		utils::{Choice, ChoiceEnum, ChoiceFlags, Fd, Id},
	},
};
impl SpaPod {
	#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
	pub fn spa_id(&self) -> Option<Id> {
		self.id().map(Id)
	}
	#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
	pub fn spa_fd(&self) -> Option<Fd> {
		self.fd().map(Fd)
	}
	#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
	pub fn deserialize<'de, P: PodDeserialize<'de>>(&'de self) -> Result<P, DeserializeError<&'de [u8]>> {
		unsafe { PodDeserializer::deserialize_ptr(NonNull::new_unchecked(self.spa_pod_raw() as *const _ as *mut _)) }
	}
	#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
	pub fn serialize<P: PodSerialize>(value: &P) -> Result<Self, GenError> {
		use std::io::Cursor;
		let mut data = Vec::new();
		let len = {
			let write = Cursor::new(&mut data);
			let (_, len) = PodSerializer::serialize(write, value)?;
			len
		} as usize;
		Ok(Self::with_pod(&data[..len]))
	}
	#[cfg(none)]
	#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
	pub fn to_pod_value(&self) -> Result<Value, DeserializeError<&[u8]>> {
		self.deserialize()
	}
	fn pod_choice<T: SpaPrimitive + CanonicalFixedSizedPod>(&self, choice_type: SpaIdValue) -> Result<Choice<T>, Error> {
		let mut values = self.array_iterator::<T>();
		let mut next_value = || {
			values.next().ok_or_else(|| {
				Error::new(
					LibraryErrorEnum::InvalidArgument,
					&format!("wrong number of values for {choice_type:?} choice"),
				)
			})
		};
		let flags = ChoiceFlags::empty();
		let default = next_value()?;
		Ok(Choice(flags, match choice_type.number() {
			libspa_sys::SPA_CHOICE_None => ChoiceEnum::None(default),
			libspa_sys::SPA_CHOICE_Range => ChoiceEnum::Range {
				default,
				min: next_value()?,
				max: next_value()?,
			},
			libspa_sys::SPA_CHOICE_Step => ChoiceEnum::Step {
				default,
				min: next_value()?,
				max: next_value()?,
				step: next_value()?,
			},
			libspa_sys::SPA_CHOICE_Enum => ChoiceEnum::Enum {
				default,
				alternatives: values.collect(),
			},
			libspa_sys::SPA_CHOICE_Flags => ChoiceEnum::Flags {
				default,
				flags: values.collect(),
			},
			_ =>
				return Err(Error::new(
					LibraryErrorEnum::InvalidArgument,
					&format!("unknown choice type: {choice_type:?}"),
				)),
		}))
	}
	#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
	pub fn to_pod_value(&self) -> Result<Value, Error> {
		Ok(match () {
			_ if self.is_none() => Value::None,
			_ if self.is_boolean() => Value::Bool(self.boolean().unwrap()),
			_ if self.is_id() => Value::Id(self.spa_id().unwrap()),
			_ if self.is_int() => Value::Int(self.int().unwrap()),
			_ if self.is_long() => Value::Long(self.long().unwrap()),
			_ if self.is_float() => Value::Float(self.float().unwrap()),
			_ if self.is_double() => Value::Double(self.double().unwrap()),
			_ if self.is_string() => Value::String(self.string().unwrap().into()),
			_ if self.is_bytes() => Value::Bytes(self.bytes().unwrap().into()),
			_ if self.is_pointer() => Value::Pointer(0, self.pointer().unwrap()),
			_ if self.is_fd() => Value::Fd(self.spa_fd().unwrap()),
			_ if self.is_rectangle() => Value::Rectangle(self.spa_rectangle().unwrap()),
			_ if self.is_fraction() => Value::Fraction(self.spa_fraction().unwrap()),
			_ if self.is_struct() => self.parse_struct(|parser| {
				self
					.iterator()
					.into_iter()
					.map(|pod| pod.to_pod_value())
					.collect::<Result<_, Error>>()
					.map(Value::Struct)
			})?,
			_ if self.is_object() => {
				let type_ = self.spa_type().unwrap();
				let ids = type_.object_id_values_table().unwrap();
				let values = type_.values_table().unwrap();
				self.parse_object(|parser, id_name| {
					let value_id = ids.find_value_from_short_name(id_name.unwrap()).unwrap(); Ok::<_, Error>(Value::Object(Object {
						type_: type_.into_glib(),
						id: value_id.number(),
						properties: self
							.spa_properties()
							.map(|(id, pod)| {
								pod.to_pod_value().map(|value| Property {
									key: SpaIdValue::result_number(id),
									flags: PropertyFlags::empty(),
									value,
								})
							})
							.collect::<Result<_, Error>>()?,
					}))
				})?
			},
			_ if self.is_sequence() => {
				wp_warning!("unsupported sequence spa type for {self:?}");
				Value::Struct(
					self
						.iterator()
						.into_iter()
						.map(|pod| pod.control().unwrap())
						.map(|(offset, type_name, value)| {
							wp_warning!("discarding sequence context ({offset}, {type_name}) for {value:?}");
							value.to_pod_value()
						})
						.collect::<Result<_, _>>()?,
				)
			},
			_ if self.is_array() => {
				let child = self.array_child().unwrap();
				let type_ = child.spa_type().unwrap();
				Value::ValueArray(match type_ {
					_ if child.is_none() => ValueArray::None(self.array_pointers().into_iter().map(drop).collect()),
					_ if child.is_boolean() => ValueArray::Bool(self.array_iterator::<SpaBool>().map(Into::into).collect()),
					_ if child.is_int() => ValueArray::Int(self.array_iterator().collect()),
					_ if child.is_long() => ValueArray::Long(self.array_iterator().collect()),
					_ if child.is_float() => ValueArray::Float(self.array_iterator().collect()),
					_ if child.is_double() => ValueArray::Double(self.array_iterator().collect()),
					_ if child.is_id() => ValueArray::Id(self.array_iterator().collect()),
					_ if child.is_fd() => ValueArray::Fd(self.array_iterator().collect()),
					_ if child.is_rectangle() => ValueArray::Rectangle(self.array_iterator().collect()),
					_ if child.is_fraction() => ValueArray::Fraction(self.array_iterator().collect()),
					type_ =>
						return Err(Error::new(
							LibraryErrorEnum::InvalidArgument,
							&format!("unsupported SPA array child type {type_:?}"),
						)),
				})
			},
			_ if self.is_choice() => {
				let child = self.choice_child().unwrap();
				let type_ = child.spa_type();
				let choice_type = self.choice_type().ok_or_else(|| {
					Error::new(
						LibraryErrorEnum::InvalidArgument,
						&format!("unknown choice type for {child:?}"),
					)
				})?;
				Value::Choice(match type_ {
					_ if child.is_int() => ChoiceValue::Int(child.pod_choice(choice_type)?),
					_ if child.is_long() => ChoiceValue::Long(child.pod_choice(choice_type)?),
					_ if child.is_float() => ChoiceValue::Float(child.pod_choice(choice_type)?),
					_ if child.is_double() => ChoiceValue::Double(child.pod_choice(choice_type)?),
					_ if child.is_id() => ChoiceValue::Id(child.pod_choice(choice_type)?),
					_ if child.is_fd() => ChoiceValue::Fd(child.pod_choice(choice_type)?),
					_ if child.is_rectangle() => ChoiceValue::Rectangle(child.pod_choice(choice_type)?),
					_ if child.is_fraction() => ChoiceValue::Fraction(child.pod_choice(choice_type)?),
					type_ =>
						return Err(Error::new(
							LibraryErrorEnum::InvalidArgument,
							&format!("unsupported SPA choice child type {type_:?}"),
						)),
				})
			},
			_ =>
				return Err(Error::new(
					LibraryErrorEnum::InvalidArgument,
					&format!("unsupported SPA type {:?}", self.spa_type()),
				)),
		})
	}
	pub fn debug(&self) -> DebugValue<'static, 'static> {
		DebugValue::with_value(self.to_pod_value().unwrap())
	}
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl TryInto<Value> for SpaPod {
	type Error = Error;
	fn try_into(self) -> Result<Value, Error> {
		self.to_pod_value()
	}
}
#[derive(Copy, Clone)]
struct DebugProperty<'a> {
	object: &'a Object,
	property: &'a Property,
}
impl<'a> DebugProperty<'a> {
	fn parent_values_table(&self) -> Option<SpaIdTable> {
		let type_ = SpaType::from_id(self.object.type_);
		type_.and_then(|ty| ty.values_table())
	}
	fn key_value(&self) -> Option<SpaIdValue> {
		self
			.parent_values_table()
			.and_then(|table| table.find_value(self.property.key))
	}
	fn key_table(&self) -> Option<SpaIdTable> {
		let key = self.key_value()?;
		let (key_type, table) = key.value_type();
		let key_type = key_type?;
		if key_type.into_glib() == libspa_sys::SPA_TYPE_Array {
			let (_, table) = key.array_item_type();
			table
		} else {
			table
		}
	}
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
pub struct DebugValue<'v, 'o> {
	container: Option<DebugProperty<'o>>,
	value: Cow<'v, Value>,
}
impl<'v, 'o> DebugValue<'v, 'o> {
	pub fn new(value: &'v Value) -> Self {
		Self {
			container: None,
			value: Cow::Borrowed(value),
		}
	}
	fn with_value(value: Value) -> Self {
		Self {
			container: None,
			value: Cow::Owned(value),
		}
	}
}
#[allow(non_local_definitions)]
impl<'v, 'o> Debug for DebugValue<'v, 'o> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		struct DebugType(SpaType);
		impl Debug for DebugType {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				if let Some(name) = self.0.name() {
					write!(f, "{name} ({:?})", self.0.into_glib())
				} else {
					write!(f, "{}", self.0.into_glib())
				}
			}
		}
		struct DebugIdValue(SpaIdValue);
		impl Debug for DebugIdValue {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				if let Some(short_name) = self.0.short_name() {
					write!(f, "{short_name:?} ({:?})", self.0.number())
				} else {
					write!(f, "{}", self.0.number())
				}
			}
		}
		struct DebugValueList<I>(I);
		impl<'v, 'o, I: Clone + Iterator<Item = DebugValue<'v, 'o>>> Debug for DebugValueList<I> {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				f.debug_list().entries(self.0.clone()).finish()
			}
		}
		impl<'a> Debug for DebugProperty<'a> {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				let mut f = f.debug_struct("Property");
				if let Some(key) = self.key_value() {
					f.field("key", &DebugIdValue(key));
				} else {
					f.field("key", &self.property.key);
				}
				f.field("flags", &self.property.flags)
					.field("value", &DebugValue {
						container: Some(DebugProperty {
							object: self.object,
							property: &self.property,
						}),
						value: Cow::Borrowed(&self.property.value),
					})
					.finish()
			}
		}
		struct DebugPropertyList<'a> {
			object: &'a Object,
		}
		impl<'a> Debug for DebugPropertyList<'a> {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				f.debug_list()
					.entries(self.object.properties.iter().map(|property| DebugProperty {
						object: self.object,
						property,
					}))
					.finish()
			}
		}
		struct DebugId<'a> {
			container: Option<DebugProperty<'a>>,
			id: Id,
		}
		impl<'a> Debug for DebugId<'a> {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				let table = self.container.and_then(|prop| prop.key_table());
				if let Some(id) = table.and_then(|table| table.find_value(self.id.0)) {
					write!(f, "{:?}", DebugIdValue(id))
				} else {
					write!(f, "{:?}", self.id)
				}
			}
		}
		struct DebugIdList<'a> {
			container: Option<DebugProperty<'a>>,
			ids: &'a [Id],
		}
		impl<'a> Debug for DebugIdList<'a> {
			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				f.debug_list()
					.entries(self.ids.iter().copied().map(|id| DebugId {
						container: self.container,
						id,
					}))
					.finish()
			}
		}
		match &*self.value {
			&Value::Id(id) => f
				.debug_tuple("Id")
				.field(&DebugId {
					container: self.container,
					id,
				})
				.finish(),
			Value::ValueArray(ValueArray::Id(ids)) => f
				.debug_tuple("Ids")
				.field(&DebugIdList {
					container: self.container,
					ids,
				})
				.finish(),
			Value::Struct(values) => f
				.debug_tuple("Struct")
				.field(&DebugValueList(values.iter().map(|value| DebugValue {
					container: self.container,
					value: Cow::Borrowed(value),
				})))
				.finish(),
			Value::Object(obj) => {
				let type_ = SpaType::from_id(obj.type_);
				let id_table = type_.and_then(|ty| ty.object_id_values_table());
				let mut f = f.debug_struct("Object");
				if let Some(type_) = type_ {
					f.field("type", &DebugType(type_));
				} else {
					f.field("type", &obj.type_);
				}
				if let Some(id) = id_table.and_then(|table| table.find_value(obj.id)) {
					f.field("id", &DebugIdValue(id));
				} else {
					f.field("id", &obj.id);
				}
				f.field("properties", &DebugPropertyList { object: obj }).finish()
			},
			Value::Choice(choice) => {
				let mut f = f.debug_tuple("Choice");
				match choice {
					ChoiceValue::Id(id) => todo!(),
					choice => f.field(choice),
				}
				.finish()
			},
			value => write!(f, "{value:?}"),
		}
	}
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl SpaPrimitive for Id {
	const TYPE: SpaType = SpaType::ID;
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl SpaValue for Id {
	fn add_to_builder(&self, builder: &SpaPodBuilder) {
		builder.add_id(self.0)
	}
	type Owned = Self;
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl<'a> TryFrom<&'a SpaPod> for Id {
	type Error = GlibNoneError;
	fn try_from(pod: &'a SpaPod) -> Result<Self, Self::Error> {
		pod.id().map(Id).ok_or(GlibNoneError)
	}
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl SpaPrimitive for Fd {
	const TYPE: SpaType = SpaType::FD;
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl SpaValue for Fd {
	fn add_to_builder(&self, builder: &SpaPodBuilder) {
		builder.add_fd(self.0)
	}
	type Owned = Self;
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl<'a> TryFrom<&'a SpaPod> for Fd {
	type Error = GlibNoneError;
	fn try_from(pod: &'a SpaPod) -> Result<Self, Self::Error> {
		pod.fd().map(Fd).ok_or(GlibNoneError)
	}
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl SpaValue for Value {
	fn add_to_builder(&self, builder: &SpaPodBuilder) {
		builder.add_pod(todo!())
	}
	type Owned = Self;
}
#[cfg_attr(docsrs, doc(cfg(feature = "libspa")))]
impl<'a> TryFrom<&'a SpaPod> for Value {
	type Error = Error;
	fn try_from(pod: &'a SpaPod) -> Result<Self, Self::Error> {
		pod.to_pod_value()
	}
}