use std::sync::Arc;
use tokio::sync::Mutex;
use wasmtime::component::Resource;

use crate::plugin::api::gui::{PluginGui, PluginInventory};
use crate::plugin::loader::wasm::wasm_host::{
    state::{GuiResource, PluginHostState},
    wit::v0_1::pumpkin::plugin::{
        gui::{self, Gui, GuiType},
        item_stack::ItemStack as WitHostItemStack,
    },
};

impl PluginHostState {
    fn get_gui_res(&self, res: &Resource<Gui>) -> wasmtime::Result<&GuiResource> {
        self.resource_table
            .get::<GuiResource>(&Resource::new_own(res.rep()))
            .map_err(wasmtime::Error::from)
    }
}

impl gui::Host for PluginHostState {}

impl gui::HostGui for PluginHostState {
    async fn new(
        &mut self,
        gui_type: GuiType,
        title: Resource<
            crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent,
        >,
    ) -> wasmtime::Result<Resource<Gui>> {
        let title = self.get_text_provider(&title)?;
        let window_type = match gui_type {
            GuiType::Generic9x1 => pumpkin_data::screen::WindowType::Generic9x1,
            GuiType::Generic9x2 => pumpkin_data::screen::WindowType::Generic9x2,
            GuiType::Generic9x3 => pumpkin_data::screen::WindowType::Generic9x3,
            GuiType::Generic9x4 => pumpkin_data::screen::WindowType::Generic9x4,
            GuiType::Generic9x5 => pumpkin_data::screen::WindowType::Generic9x5,
            GuiType::Generic9x6 => pumpkin_data::screen::WindowType::Generic9x6,
            GuiType::Generic3x3 => pumpkin_data::screen::WindowType::Generic3x3,
            GuiType::Crafter3x3 => pumpkin_data::screen::WindowType::Crafter3x3,
            GuiType::Anvil => pumpkin_data::screen::WindowType::Anvil,
            GuiType::Beacon => pumpkin_data::screen::WindowType::Beacon,
            GuiType::BlastFurnace => pumpkin_data::screen::WindowType::BlastFurnace,
            GuiType::BrewingStand => pumpkin_data::screen::WindowType::BrewingStand,
            GuiType::Crafting => pumpkin_data::screen::WindowType::Crafting,
            GuiType::Enchantment => pumpkin_data::screen::WindowType::Enchantment,
            GuiType::Furnace => pumpkin_data::screen::WindowType::Furnace,
            GuiType::Grindstone => pumpkin_data::screen::WindowType::Grindstone,
            GuiType::Hopper => pumpkin_data::screen::WindowType::Hopper,
            GuiType::Lectern => pumpkin_data::screen::WindowType::Lectern,
            GuiType::Loom => pumpkin_data::screen::WindowType::Loom,
            GuiType::Merchant => pumpkin_data::screen::WindowType::Merchant,
            GuiType::ShulkerBox => pumpkin_data::screen::WindowType::ShulkerBox,
            GuiType::Smithing => pumpkin_data::screen::WindowType::Smithing,
            GuiType::Smoker => pumpkin_data::screen::WindowType::Smoker,
            GuiType::CartographyTable => pumpkin_data::screen::WindowType::CartographyTable,
            GuiType::Stonecutter => pumpkin_data::screen::WindowType::Stonecutter,
        };

        let size = match window_type {
            pumpkin_data::screen::WindowType::Generic9x2 => 18,
            pumpkin_data::screen::WindowType::Generic9x4 => 36,
            pumpkin_data::screen::WindowType::Generic9x5 => 45,
            pumpkin_data::screen::WindowType::Generic9x6 => 54,
            pumpkin_data::screen::WindowType::Generic3x3 => 9,
            pumpkin_data::screen::WindowType::Generic9x1
            | pumpkin_data::screen::WindowType::Hopper => 5,
            _ => 27, // Default
        };

        let gui = Arc::new(Mutex::new(PluginGui {
            window_type,
            title,
            inventory: Arc::new(PluginInventory::new(size)),
            allow_grab_items: true,
            allow_put_items: true,
        }));

        self.add_gui(gui)
    }

    async fn set_item(
        &mut self,
        res: Resource<Gui>,
        slot: u32,
        item: Resource<WitHostItemStack>,
    ) -> wasmtime::Result<()> {
        let gui = self.get_gui_res(&res)?.provider.lock().await;
        let mut slots = gui.inventory.slots.write().await;
        if (slot as usize) < slots.len() {
            let item_stack = self.get_item_stack(&item)?;
            let item_stack = item_stack.lock().await.clone();
            slots[slot as usize] = item_stack;
        }
        Ok(())
    }

    async fn get_item(
        &mut self,
        res: Resource<Gui>,
        slot: u32,
    ) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
        let stack = {
            let gui = self.get_gui_res(&res)?.provider.lock().await;
            let slots = gui.inventory.slots.read().await;
            if (slot as usize) < slots.len() {
                let stack = &slots[slot as usize];
                if stack.is_empty() {
                    None
                } else {
                    Some(stack.clone())
                }
            } else {
                None
            }
        };

        if let Some(stack) = stack {
            Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?))
        } else {
            Ok(None)
        }
    }

    async fn get_type(&mut self, res: Resource<Gui>) -> wasmtime::Result<GuiType> {
        let gui = self.get_gui_res(&res)?.provider.lock().await;
        Ok(match gui.window_type {
            pumpkin_data::screen::WindowType::Generic9x1 => GuiType::Generic9x1,
            pumpkin_data::screen::WindowType::Generic9x2 => GuiType::Generic9x2,
            pumpkin_data::screen::WindowType::Generic9x3 => GuiType::Generic9x3,
            pumpkin_data::screen::WindowType::Generic9x4 => GuiType::Generic9x4,
            pumpkin_data::screen::WindowType::Generic9x5 => GuiType::Generic9x5,
            pumpkin_data::screen::WindowType::Generic9x6 => GuiType::Generic9x6,
            pumpkin_data::screen::WindowType::Generic3x3 => GuiType::Generic3x3,
            pumpkin_data::screen::WindowType::Crafter3x3 => GuiType::Crafter3x3,
            pumpkin_data::screen::WindowType::Anvil => GuiType::Anvil,
            pumpkin_data::screen::WindowType::Beacon => GuiType::Beacon,
            pumpkin_data::screen::WindowType::BlastFurnace => GuiType::BlastFurnace,
            pumpkin_data::screen::WindowType::BrewingStand => GuiType::BrewingStand,
            pumpkin_data::screen::WindowType::Crafting => GuiType::Crafting,
            pumpkin_data::screen::WindowType::Enchantment => GuiType::Enchantment,
            pumpkin_data::screen::WindowType::Furnace => GuiType::Furnace,
            pumpkin_data::screen::WindowType::Grindstone => GuiType::Grindstone,
            pumpkin_data::screen::WindowType::Hopper => GuiType::Hopper,
            pumpkin_data::screen::WindowType::Lectern => GuiType::Lectern,
            pumpkin_data::screen::WindowType::Loom => GuiType::Loom,
            pumpkin_data::screen::WindowType::Merchant => GuiType::Merchant,
            pumpkin_data::screen::WindowType::ShulkerBox => GuiType::ShulkerBox,
            pumpkin_data::screen::WindowType::Smithing => GuiType::Smithing,
            pumpkin_data::screen::WindowType::Smoker => GuiType::Smoker,
            pumpkin_data::screen::WindowType::CartographyTable => GuiType::CartographyTable,
            pumpkin_data::screen::WindowType::Stonecutter => GuiType::Stonecutter,
        })
    }

    async fn get_title(
        &mut self,
        res: Resource<Gui>,
    ) -> wasmtime::Result<
        Resource<
            crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent,
        >,
    > {
        let title = {
            let gui = self.get_gui_res(&res)?.provider.lock().await;
            gui.title.clone()
        };
        self.add_text_component(title)
            .map_err(|_| wasmtime::Error::msg("Failed to add text component resource"))
    }

    async fn get_size(&mut self, res: Resource<Gui>) -> wasmtime::Result<u32> {
        use pumpkin_world::inventory::Inventory;
        let gui = self.get_gui_res(&res)?.provider.lock().await;
        Ok(gui.inventory.size() as u32)
    }

    async fn clear_items(&mut self, res: Resource<Gui>) -> wasmtime::Result<()> {
        use pumpkin_world::inventory::Clearable;
        let gui = self.get_gui_res(&res)?.provider.lock().await;
        gui.inventory.clear().await;
        Ok(())
    }

    async fn set_allow_grab_items(
        &mut self,
        res: Resource<Gui>,
        allow: bool,
    ) -> wasmtime::Result<()> {
        let mut gui = self.get_gui_res(&res)?.provider.lock().await;
        gui.allow_grab_items = allow;
        Ok(())
    }

    async fn get_allow_grab_items(&mut self, res: Resource<Gui>) -> wasmtime::Result<bool> {
        let gui = self.get_gui_res(&res)?.provider.lock().await;
        Ok(gui.allow_grab_items)
    }

    async fn set_allow_put_items(
        &mut self,
        res: Resource<Gui>,
        allow: bool,
    ) -> wasmtime::Result<()> {
        let mut gui = self.get_gui_res(&res)?.provider.lock().await;
        gui.allow_put_items = allow;
        Ok(())
    }

    async fn get_allow_put_items(&mut self, res: Resource<Gui>) -> wasmtime::Result<bool> {
        let gui = self.get_gui_res(&res)?.provider.lock().await;
        Ok(gui.allow_put_items)
    }

    async fn drop(&mut self, rep: Resource<Gui>) -> wasmtime::Result<()> {
        self.resource_table
            .delete::<GuiResource>(Resource::new_own(rep.rep()))
            .map_err(wasmtime::Error::from)?;
        Ok(())
    }
}
