use std::{
    pin::Pin,
    sync::{
        Arc,
        atomic::{AtomicBool, AtomicI8, Ordering},
    },
};

use super::BlockEntity;
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
#[repr(i8)]
pub enum DyeColor {
    White = 0,
    Orange = 1,
    Magenta = 2,
    LightBlue = 3,
    Yellow = 4,
    Lime = 5,
    Pink = 6,
    Gray = 7,
    LightGray = 8,
    Cyan = 9,
    Purple = 10,
    Blue = 11,
    Brown = 12,
    Green = 13,
    Red = 14,
    #[default]
    Black = 15,
}

impl From<DyeColor> for String {
    fn from(value: DyeColor) -> Self {
        match value {
            DyeColor::White => "white".to_string(),
            DyeColor::Orange => "orange".to_string(),
            DyeColor::Magenta => "magenta".to_string(),
            DyeColor::LightBlue => "light_blue".to_string(),
            DyeColor::Yellow => "yellow".to_string(),
            DyeColor::Lime => "lime".to_string(),
            DyeColor::Pink => "pink".to_string(),
            DyeColor::Gray => "gray".to_string(),
            DyeColor::LightGray => "light_gray".to_string(),
            DyeColor::Cyan => "cyan".to_string(),
            DyeColor::Purple => "purple".to_string(),
            DyeColor::Blue => "blue".to_string(),
            DyeColor::Brown => "brown".to_string(),
            DyeColor::Green => "green".to_string(),
            DyeColor::Red => "red".to_string(),
            DyeColor::Black => "black".to_string(),
        }
    }
}

impl From<&str> for DyeColor {
    fn from(s: &str) -> Self {
        match s {
            "white" => Self::White,
            "orange" => Self::Orange,
            "magenta" => Self::Magenta,
            "light_blue" => Self::LightBlue,
            "yellow" => Self::Yellow,
            "lime" => Self::Lime,
            "pink" => Self::Pink,
            "gray" => Self::Gray,
            "light_gray" => Self::LightGray,
            "cyan" => Self::Cyan,
            "purple" => Self::Purple,
            "blue" => Self::Blue,
            "brown" => Self::Brown,
            "green" => Self::Green,
            "red" => Self::Red,
            "black" => Self::Black,
            _ => Self::default(),
        }
    }
}

impl From<i8> for DyeColor {
    fn from(s: i8) -> Self {
        match s {
            0 => Self::White,
            1 => Self::Orange,
            2 => Self::Magenta,
            3 => Self::LightBlue,
            4 => Self::Yellow,
            5 => Self::Lime,
            6 => Self::Pink,
            7 => Self::Gray,
            8 => Self::LightGray,
            9 => Self::Cyan,
            10 => Self::Purple,
            11 => Self::Blue,
            12 => Self::Brown,
            13 => Self::Green,
            14 => Self::Red,
            15 => Self::Black,
            _ => Self::default(),
        }
    }
}

impl From<DyeColor> for NbtTag {
    fn from(value: DyeColor) -> Self {
        Self::Byte(value as i8)
    }
}

pub struct SignBlockEntity {
    pub front_text: Text,
    pub back_text: Text,
    pub is_waxed: AtomicBool,
    position: BlockPos,
    pub currently_editing_player: Arc<Mutex<Option<uuid::Uuid>>>,
}

pub struct Text {
    pub has_glowing_text: AtomicBool,
    color: AtomicI8,
    pub messages: Arc<std::sync::Mutex<[Box<str>; 4]>>,
}

impl Clone for Text {
    fn clone(&self) -> Self {
        Self {
            has_glowing_text: AtomicBool::new(self.has_glowing_text.load(Ordering::Relaxed)),
            color: AtomicI8::new(self.color.load(Ordering::Relaxed)),
            messages: self.messages.clone(),
        }
    }
}

impl Default for Text {
    fn default() -> Self {
        Self {
            has_glowing_text: AtomicBool::new(false),
            color: AtomicI8::new(DyeColor::default() as i8),
            messages: Arc::default(),
        }
    }
}

#[allow(clippy::fallible_impl_from)]
impl From<Text> for NbtTag {
    fn from(value: Text) -> Self {
        let mut nbt = NbtCompound::new();
        nbt.put_bool(
            "has_glowing_text",
            value.has_glowing_text.load(Ordering::Relaxed),
        );
        nbt.put_string("color", value.get_color().into());
        nbt.put_list(
            "messages",
            value
                .messages
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .iter()
                .map(|s| Self::String(s.clone()))
                .collect(),
        );
        Self::Compound(nbt)
    }
}

impl From<NbtTag> for Text {
    fn from(tag: NbtTag) -> Self {
        let Some(nbt) = tag.extract_compound() else {
            return Self::default();
        };
        let has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false);
        let color = nbt.get_string("color").unwrap_or("black");
        let messages: Vec<Box<str>> = nbt.get_list("messages").map_or_else(Vec::new, |list| {
            list.iter()
                .filter_map(|tag| tag.extract_string().map(Box::from))
                .collect()
        });
        let get_message =
            |i: usize| -> Box<str> { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) };

        Self {
            has_glowing_text: AtomicBool::new(has_glowing_text),
            color: AtomicI8::new(DyeColor::from(color) as i8),
            messages: Arc::new(std::sync::Mutex::new([
                get_message(0),
                get_message(1),
                get_message(2),
                get_message(3),
            ])),
        }
    }
}

impl Text {
    #[must_use]
    pub fn new(messages: [Box<str>; 4]) -> Self {
        Self {
            has_glowing_text: AtomicBool::new(false),
            color: AtomicI8::new(DyeColor::default() as i8),
            messages: Arc::new(std::sync::Mutex::new(messages)),
        }
    }

    pub fn get_color(&self) -> DyeColor {
        self.color.load(Ordering::Relaxed).into()
    }

    pub fn set_color(&self, color: DyeColor) {
        self.color.store(color as i8, Ordering::Relaxed);
    }
}

impl BlockEntity for SignBlockEntity {
    fn resource_location(&self) -> &'static str {
        Self::ID
    }

    fn get_position(&self) -> BlockPos {
        self.position
    }

    fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
    where
        Self: Sized,
    {
        let front_text = nbt
            .get("front_text")
            .cloned()
            .map(Text::from)
            .unwrap_or_default();
        let back_text = nbt
            .get("back_text")
            .cloned()
            .map(Text::from)
            .unwrap_or_default();
        let is_waxed = nbt.get_bool("is_waxed").unwrap_or(false);
        Self {
            position,
            front_text,
            back_text,
            is_waxed: AtomicBool::new(is_waxed),
            currently_editing_player: Arc::new(Mutex::new(None)),
        }
    }

    fn write_nbt<'a>(
        &'a self,
        nbt: &'a mut NbtCompound,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(async move {
            nbt.put("front_text", self.front_text.clone());
            nbt.put("back_text", self.back_text.clone());
            nbt.put_bool("is_waxed", self.is_waxed.load(Ordering::Relaxed));
        })
    }

    fn chunk_data_nbt(&self) -> Option<NbtCompound> {
        let mut nbt = NbtCompound::new();
        nbt.put("front_text", self.front_text.clone());
        nbt.put("back_text", self.back_text.clone());
        nbt.put_bool("is_waxed", self.is_waxed.load(Ordering::Relaxed));
        Some(nbt)
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

impl SignBlockEntity {
    pub const ID: &'static str = "minecraft:sign";
    #[must_use]
    pub fn new(position: BlockPos, is_front: bool, messages: [Box<str>; 4]) -> Self {
        Self {
            position,
            is_waxed: AtomicBool::new(false),
            front_text: if is_front {
                Text::new(messages.clone())
            } else {
                Text::default()
            },
            back_text: if is_front {
                Text::default()
            } else {
                Text::new(messages)
            },
            currently_editing_player: Arc::new(Mutex::new(None)),
        }
    }
    #[must_use]
    pub fn empty(position: BlockPos) -> Self {
        Self {
            position,
            is_waxed: AtomicBool::new(false),
            front_text: Text::default(),
            back_text: Text::default(),
            currently_editing_player: Arc::new(Mutex::new(None)),
        }
    }
}
