use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ModInfo {
    pub id: String,
    pub name: String,
    pub version: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Panel {
    Left,
    Right,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EditorEvent {
    EntitySelected { entity_id: u64 },
    EntityDeselected,
    PanelToggled { panel: Panel },
    FrameStart { delta_time: f32 },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ModCommand {
    Log { message: String },
    RegisterInspector { name: String, inspector_id: String },
    RegisterMenuItem { path: String, item_id: String },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum UiElement {
    Label {
        text: String,
    },
    Button {
        text: String,
        id: String,
    },
    TextEdit {
        value: String,
        id: String,
    },
    Separator,
    Spacing {
        amount: f32,
    },
    Horizontal {
        children: Vec<UiElement>,
    },
    Vertical {
        children: Vec<UiElement>,
    },
    Group {
        children: Vec<UiElement>,
    },
    Collapsing {
        header: String,
        default_open: bool,
        children: Vec<UiElement>,
    },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum UiResponse {
    ButtonClicked { id: String },
    TextChanged { id: String, value: String },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InspectorRequest {
    RenderUi {
        inspector_id: String,
        entity_id: u64,
    },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InspectorResponse {
    Ui { elements: Vec<UiElement> },
    HasComponent { has: bool },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum MenuRequest {
    RenderUi { item_id: String },
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum MenuResponse {
    Ui { elements: Vec<UiElement> },
}

impl ModCommand {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl EditorEvent {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl ModInfo {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl InspectorRequest {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl InspectorResponse {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl UiResponse {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl MenuRequest {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

impl MenuResponse {
    pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
        postcard::to_allocvec(self)
    }

    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        postcard::from_bytes(bytes).ok()
    }
}

#[cfg(feature = "editor-plugin-guest")]
mod guest {
    use super::*;
    use std::cell::RefCell;

    unsafe extern "C" {
        fn host_send_mod_command(ptr: *const u8, len: u32);
        fn host_send_inspector_response(ptr: *const u8, len: u32);
        fn host_send_menu_response(ptr: *const u8, len: u32);
        fn host_send_mod_info(ptr: *const u8, len: u32);
    }

    thread_local! {
        static EVENT_BUFFER: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
        static PENDING_EVENTS: RefCell<Vec<EditorEvent>> = const { RefCell::new(Vec::new()) };
        static REQUEST_BUFFER: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
        static PENDING_INSPECTOR_REQUESTS: RefCell<Vec<InspectorRequest>> = const { RefCell::new(Vec::new()) };
        static PENDING_MENU_REQUESTS: RefCell<Vec<MenuRequest>> = const { RefCell::new(Vec::new()) };
        static PENDING_UI_RESPONSES: RefCell<Vec<UiResponse>> = const { RefCell::new(Vec::new()) };
    }

    pub fn send_command(command: ModCommand) {
        let Ok(bytes) = command.to_bytes() else {
            return;
        };
        unsafe {
            host_send_mod_command(bytes.as_ptr(), bytes.len() as u32);
        }
    }

    pub fn send_mod_info(info: &ModInfo) {
        let Ok(bytes) = info.to_bytes() else {
            return;
        };
        unsafe {
            host_send_mod_info(bytes.as_ptr(), bytes.len() as u32);
        }
    }

    pub fn log(message: &str) {
        send_command(ModCommand::Log {
            message: message.to_string(),
        });
    }

    pub fn register_inspector(name: &str, inspector_id: &str) {
        send_command(ModCommand::RegisterInspector {
            name: name.to_string(),
            inspector_id: inspector_id.to_string(),
        });
    }

    pub fn register_menu_item(path: &str, item_id: &str) {
        send_command(ModCommand::RegisterMenuItem {
            path: path.to_string(),
            item_id: item_id.to_string(),
        });
    }

    pub fn respond_inspector_ui(elements: Vec<UiElement>) {
        let response = InspectorResponse::Ui { elements };
        let Ok(bytes) = response.to_bytes() else {
            return;
        };
        unsafe {
            host_send_inspector_response(bytes.as_ptr(), bytes.len() as u32);
        }
    }

    pub fn respond_has_component(has: bool) {
        let response = InspectorResponse::HasComponent { has };
        let Ok(bytes) = response.to_bytes() else {
            return;
        };
        unsafe {
            host_send_inspector_response(bytes.as_ptr(), bytes.len() as u32);
        }
    }

    pub fn respond_menu_ui(elements: Vec<UiElement>) {
        let response = MenuResponse::Ui { elements };
        let Ok(bytes) = response.to_bytes() else {
            return;
        };
        unsafe {
            host_send_menu_response(bytes.as_ptr(), bytes.len() as u32);
        }
    }

    pub fn drain_events() -> Vec<EditorEvent> {
        PENDING_EVENTS.with_borrow_mut(std::mem::take)
    }

    pub fn drain_inspector_requests() -> Vec<InspectorRequest> {
        PENDING_INSPECTOR_REQUESTS.with_borrow_mut(std::mem::take)
    }

    pub fn drain_menu_requests() -> Vec<MenuRequest> {
        PENDING_MENU_REQUESTS.with_borrow_mut(std::mem::take)
    }

    pub fn drain_ui_responses() -> Vec<UiResponse> {
        PENDING_UI_RESPONSES.with_borrow_mut(std::mem::take)
    }

    #[unsafe(no_mangle)]
    pub extern "C" fn mod_alloc(size: u32) -> *mut u8 {
        EVENT_BUFFER.with_borrow_mut(|buffer| {
            buffer.resize(size as usize, 0);
            buffer.as_mut_ptr()
        })
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn mod_receive_event(ptr: *const u8, len: u32) {
        let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
        if let Some(event) = EditorEvent::from_bytes(bytes) {
            PENDING_EVENTS.with_borrow_mut(|events| {
                events.push(event);
            });
        }
    }

    #[unsafe(no_mangle)]
    pub extern "C" fn mod_request_alloc(size: u32) -> *mut u8 {
        REQUEST_BUFFER.with_borrow_mut(|buffer| {
            buffer.resize(size as usize, 0);
            buffer.as_mut_ptr()
        })
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn mod_receive_inspector_request(ptr: *const u8, len: u32) {
        let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
        if let Some(request) = InspectorRequest::from_bytes(bytes) {
            PENDING_INSPECTOR_REQUESTS.with_borrow_mut(|requests| {
                requests.push(request);
            });
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn mod_receive_menu_request(ptr: *const u8, len: u32) {
        let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
        if let Some(request) = MenuRequest::from_bytes(bytes) {
            PENDING_MENU_REQUESTS.with_borrow_mut(|requests| {
                requests.push(request);
            });
        }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn mod_receive_ui_response(ptr: *const u8, len: u32) {
        let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
        if let Some(response) = UiResponse::from_bytes(bytes) {
            PENDING_UI_RESPONSES.with_borrow_mut(|responses| {
                responses.push(response);
            });
        }
    }
}

#[cfg(feature = "editor-plugin-guest")]
pub use guest::*;

#[cfg(feature = "editor-plugin-host")]
pub use egui;

#[cfg(feature = "editor-plugin-host")]
pub use egui_tiles;
