use crate::{
    player::player_inventory::PlayerInventory,
    screen_handler::{
        InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
        ScreenHandlerFuture,
    },
    slot::NormalSlot,
};
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
use pumpkin_world::inventory::Inventory;
use std::{any::Any, sync::Arc};

/// Builder for custom GUIs.
pub struct GUIBuilder {
    screen_type: WindowType,
    rows: u8,
    columns: u8,
    inventory: Arc<dyn Inventory>,
    allow_grab_items: bool,
    allow_put_items: bool,
}

impl GUIBuilder {
    /// Creates a new `GUIBuilder` with a basic 9x3 layout.
    pub fn new(screen_type: WindowType, inventory: Arc<dyn Inventory>) -> Self {
        let (rows, columns) = match screen_type {
            WindowType::Generic9x1 => (1, 9),
            WindowType::Generic9x2 => (2, 9),
            WindowType::Generic9x4 => (4, 9),
            WindowType::Generic9x5 => (5, 9),
            WindowType::Generic9x6 => (6, 9),
            WindowType::Generic3x3 | WindowType::Crafter3x3 => (3, 3),
            WindowType::Hopper => (1, 5),
            _ => (3, 9), // Default to 9x3
        };

        Self {
            screen_type,
            rows,
            columns,
            inventory,
            allow_grab_items: true,
            allow_put_items: true,
        }
    }

    /// Sets whether players can grab items out of the inventory.
    #[must_use]
    pub const fn allow_grab_items(mut self, allow: bool) -> Self {
        self.allow_grab_items = allow;
        self
    }

    /// Sets whether players can put items into the inventory from their own.
    #[must_use]
    pub const fn allow_put_items(mut self, allow: bool) -> Self {
        self.allow_put_items = allow;
        self
    }

    /// Builds the `GUIScreenHandler`.
    pub async fn build(
        self,
        sync_id: u8,
        player_inventory: &Arc<PlayerInventory>,
    ) -> GUIScreenHandler {
        let mut behaviour = ScreenHandlerBehaviour::new(sync_id, Some(self.screen_type));
        behaviour.allow_grab_items = self.allow_grab_items;
        behaviour.allow_put_items = self.allow_put_items;
        behaviour.container_slots = (self.rows * self.columns) as usize;

        let mut handler = GUIScreenHandler {
            inventory: self.inventory.clone(),
            rows: self.rows,
            columns: self.columns,
            behaviour,
        };

        self.inventory.on_open().await;

        handler.add_inventory_slots();
        let player_inventory_trait: Arc<dyn Inventory> = player_inventory.clone();
        handler.add_player_slots(&player_inventory_trait);

        handler
    }
}

pub struct GUIScreenHandler {
    pub inventory: Arc<dyn Inventory>,
    pub rows: u8,
    pub columns: u8,
    behaviour: ScreenHandlerBehaviour,
}

impl GUIScreenHandler {
    fn add_inventory_slots(&mut self) {
        for i in 0..self.rows {
            for j in 0..self.columns {
                self.add_slot(Arc::new(NormalSlot::new(
                    self.inventory.clone(),
                    (j + i * self.columns) as usize,
                )));
            }
        }
    }
}

impl ScreenHandler for GUIScreenHandler {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn get_behaviour(&self) -> &ScreenHandlerBehaviour {
        &self.behaviour
    }

    fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour {
        &mut self.behaviour
    }

    fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
        Box::pin(async move {
            self.default_on_closed(player).await;
            self.inventory.on_close().await;
        })
    }

    fn quick_move<'a>(
        &'a mut self,
        _player: &'a dyn InventoryPlayer,
        slot_index: i32,
    ) -> ItemStackFuture<'a> {
        Box::pin(async move {
            let mut stack_left = ItemStack::EMPTY.clone();
            let slot = self.get_behaviour().slots[slot_index as usize].clone();

            if slot.has_stack().await {
                let mut slot_stack = slot.get_stack().await;
                stack_left = slot_stack.clone();
                let container_slots = i32::from(self.rows * self.columns);

                if slot_index < container_slots {
                    // From container to player
                    if !self.get_behaviour().allow_grab_items {
                        return ItemStack::EMPTY.clone();
                    }
                    if !self
                        .insert_item(
                            &mut slot_stack,
                            container_slots,
                            self.get_behaviour().slots.len() as i32,
                            true,
                        )
                        .await
                    {
                        return ItemStack::EMPTY.clone();
                    }
                } else {
                    // From player to container
                    if !self.get_behaviour().allow_put_items {
                        return ItemStack::EMPTY.clone();
                    }
                    if !self
                        .insert_item(&mut slot_stack, 0, container_slots, false)
                        .await
                    {
                        // Move from player area to inventory (start)
                        return ItemStack::EMPTY.clone();
                    }
                }

                if slot_stack.is_empty() {
                    slot.set_stack(ItemStack::EMPTY.clone()).await;
                } else {
                    slot.set_stack(slot_stack).await;
                }
            }

            stack_left
        })
    }
}
