use std::{fmt::{self, Display}, str};

use base64::{Engine, engine::general_purpose::STANDARD_NO_PAD_INDIFFERENT};

use super::traits::{ListMimes, Mimelist};

/// Enable drag support: `OSC 72 ; t=o:x=1 ; machine id ST`
pub struct EnableDrag<'a>(pub &'a str);

impl Display for EnableDrag<'_> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "\x1b]72;t=o:x=1;{}\x1b\\", self.0)
	}
}

/// Enable drop support: `OSC 72 ; t=a ; MIME list ST`
pub struct EnableDrop<M>(pub M);

impl<M: Mimelist> Display for EnableDrop<M> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "\x1b]72;t=a;{}\x1b\\", ListMimes(self.0.clone()))
	}
}

/// Disable drag support: `OSC 72 ; t=o:x=2 ST`
pub struct DisableDrag;

impl Display for DisableDrag {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b]72;t=o:x=2\x1b\\") }
}

/// Disable drop support: `OSC 72 ; t=A ST`
pub struct DisableDrop;

impl Display for DisableDrop {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b]72;t=A\x1b\\") }
}

/// Agree drag: `OSC 72 ; t=o:o=operation ST`
pub enum AgreeDrag<M> {
	Copy(M),
	Move(M),
	Either(M),
}

impl<M: Mimelist> Display for AgreeDrag<M> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::Copy(mimes) => write!(f, "\x1b]72;t=o:o=1;{}\x1b\\", ListMimes(mimes.clone())),
			Self::Move(mimes) => write!(f, "\x1b]72;t=o:o=2;{}\x1b\\", ListMimes(mimes.clone())),
			Self::Either(mimes) => write!(f, "\x1b]72;t=o:o=3;{}\x1b\\", ListMimes(mimes.clone())),
		}
	}
}

/// Agree drop: `OSC 72 ; t=m:o=O ; MIME list ST`
pub enum AgreeDrop<M> {
	Reject,
	Copy(M),
	Move(M),
}

impl<M: Mimelist> Display for AgreeDrop<M> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::Reject => write!(f, "\x1b]72;t=m:o=0\x1b\\"),
			Self::Copy(mimes) => write!(f, "\x1b]72;t=m:o=1;{}\x1b\\", ListMimes(mimes.clone())),
			Self::Move(mimes) => write!(f, "\x1b]72;t=m:o=2;{}\x1b\\", ListMimes(mimes.clone())),
		}
	}
}

/// Start dragging: `OSC 72 ; t=P:x=-1 ST`
pub struct StartDrag;

impl Display for StartDrag {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b]72;t=P:x=-1\x1b\\") }
}

/// Start requesting dropped data: `OSC 72 ; t=r:x=idx ST`
pub struct StartDrop(pub u8);

impl Display for StartDrop {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "\x1b]72;t=r:x={}\x1b\\", self.0)
	}
}

/// Present drag data: `OSC 72 ; t=p:x=idx ; base64 encoded data ST`
pub struct PresentDrag<'a>(pub u8, pub &'a [u8]);

impl Display for PresentDrag<'_> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		let b64 = STANDARD_NO_PAD_INDIFFERENT.encode(self.1).into_bytes();
		let chunks = b64.len().div_ceil(4096);

		for (i, chunk) in b64.chunks(4096).enumerate() {
			let s = unsafe { str::from_utf8_unchecked(chunk) };
			if i == 0 {
				write!(f, "\x1b]72;t=p:x={}:m={};{s}\x1b\\", self.0, (chunks > 1) as u8)?;
			} else {
				write!(f, "\x1b]72;m={};{s}\x1b\\", (i + 1 < chunks) as u8)?;
			}
		}

		write!(f, "\x1b]72;t=p:x={}\x1b\\", self.0)
	}
}

/// Present drag icon data:
/// `OSC 72 ; t=p:x=-1:y=fmt:X=width:Y=height:o=opacity ; base64 payload ST`
pub struct PresentDragIcon<'a> {
	pub format:  u8,
	pub opacity: u16,
	pub width:   u32,
	pub height:  u32,
	pub data:    &'a [u8],
}

impl Display for PresentDragIcon<'_> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		let b64 = STANDARD_NO_PAD_INDIFFERENT.encode(self.data).into_bytes();
		let chunks = b64.len().div_ceil(4096);

		for (i, chunk) in b64.chunks(4096).enumerate() {
			let s = unsafe { str::from_utf8_unchecked(chunk) };
			if i == 0 {
				write!(
					f,
					"\x1b]72;t=p:x=-1:y={}:X={}:Y={}:o={}:m={};{s}\x1b\\",
					self.format,
					self.width,
					self.height,
					self.opacity,
					(chunks > 1) as u8
				)?;
			} else {
				write!(f, "\x1b]72;m={};{s}\x1b\\", (i + 1 < chunks) as u8)?;
			}
		}

		Ok(())
	}
}

/// Finish requesting dropped data: `OSC 72 ; t=r:o=operation ST`
#[derive(Clone, Copy)]
pub enum FinishDrop {
	Copy = 1,
	Move = 2,
}

impl Display for FinishDrop {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "\x1b]72;t=r:o={}\x1b\\", *self as u8)
	}
}
