use smithay_client_toolkit::{
    error::GlobalError,
    globals::{GlobalData, ProvidesBoundGlobal},
    reexports::{
        client::{
            globals::{BindError, GlobalList},
            Connection, Dispatch, QueueHandle,
        },
        protocols::wp::{
            input_method::zv1::client::{
                zwp_input_method_context_v1::{self, ZwpInputMethodContextV1},
                zwp_input_method_v1::{self, ZwpInputMethodV1},
            },
            text_input::zv1::client::zwp_text_input_v1::{ContentHint, ContentPurpose},
        },
    },
};

pub trait ImeContextV1Handler {
    fn reset(&mut self);
    fn preferred_language(&mut self, lang: &str);
    fn content_type(&mut self, hint: ContentHint, purpose: ContentPurpose);
    fn commit_state(&mut self, serial: u32);
    /// button: e.g. BTN_LEFT / BTN_RIGHT
    /// index = position in preedit
    fn invoke_action(&mut self, button: u32, index: u32);
    fn set_surrounding_text(&mut self, text: &str, cursor: u32, anchor: u32);
}

pub trait ImeV1Handler {
    fn activate(&mut self, ctx: ImeContextV1);
    fn deactivate(&mut self, ctx: ImeContextV1);
}

#[derive(Debug)]
pub struct ImeV1 {
    wl_ime: ZwpInputMethodV1,
}

#[derive(Debug)]
pub struct ImeContextV1 {
    wl_ime_ctx: ZwpInputMethodContextV1,
}

impl From<ZwpInputMethodContextV1> for ImeContextV1 {
    fn from(wl_ime: ZwpInputMethodContextV1) -> Self {
        Self { wl_ime_ctx: wl_ime }
    }
}

impl ImeV1 {
    pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
    where
        State: Dispatch<ZwpInputMethodV1, GlobalData, State> + ImeContextV1Handler + 'static,
    {
        let wl_ime = globals.bind(qh, 1..=1, GlobalData)?;
        // Compositors must advertise Argb8888 and Xrgb8888, so let's reserve space for those formats.
        Ok(Self { wl_ime })
    }

    #[allow(unused)]
    pub fn wl_ime(&self) -> &ZwpInputMethodV1 {
        &self.wl_ime
    }
}

impl ImeContextV1 {
    #[allow(unused)]
    pub fn wl_ime_ctx(&self) -> &ZwpInputMethodContextV1 {
        &self.wl_ime_ctx
    }
}

impl ProvidesBoundGlobal<ZwpInputMethodV1, 1> for ImeV1 {
    fn bound_global(&self) -> Result<ZwpInputMethodV1, GlobalError> {
        Ok(self.wl_ime.clone())
    }
}

#[macro_export]
macro_rules! delegate_ime_v1 {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
            [
                smithay_client_toolkit::reexports::protocols::wp::input_method::zv1::client::zwp_input_method_v1::ZwpInputMethodV1: smithay_client_toolkit::globals::GlobalData
            ] => $crate::wayland::ime_v1::ImeV1
        );
    };
}
#[macro_export]
macro_rules! delegate_ime_context_v1 {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
            [
                smithay_client_toolkit::reexports::protocols::wp::input_method::zv1::client::zwp_input_method_context_v1::ZwpInputMethodContextV1: smithay_client_toolkit::globals::GlobalData// $crate::wayland::ime_v1::ImeContextV1Data
            ] => $crate::wayland::ime_v1::ImeContextV1
        );
    };
}

impl<D> Dispatch<ZwpInputMethodContextV1, GlobalData, D> for ImeContextV1
where
    D: Dispatch<ZwpInputMethodContextV1, GlobalData> + ImeContextV1Handler,
{
    fn event(
        state: &mut D,
        _proxy: &ZwpInputMethodContextV1,
        event: zwp_input_method_context_v1::Event,
        _: &GlobalData,
        _: &Connection,
        _: &QueueHandle<D>,
    ) {
        log::debug!(target: "zwp_input_method_context_v1", "received {event:?}");
        match event {
            zwp_input_method_context_v1::Event::Reset => {
                state.reset();
            }
            zwp_input_method_context_v1::Event::ContentType { hint, purpose } => {
                let Some(hint) = ContentHint::from_bits(hint) else {
                    log::debug!(target: "zwp_input_method_context_v1", "unknown hint: {hint:?}");
                    return;
                };
                let Ok(purpose) = ContentPurpose::try_from(purpose) else {
                    log::debug!(target: "zwp_input_method_context_v1", "unknown purpose: {purpose:?}");
                    return;
                };
                state.content_type(hint, purpose);
            }
            zwp_input_method_context_v1::Event::CommitState { serial } => {
                state.commit_state(serial);
            }
            zwp_input_method_context_v1::Event::InvokeAction { button, index } => {
                state.invoke_action(button, index);
            }
            zwp_input_method_context_v1::Event::SurroundingText {
                text,
                cursor,
                anchor,
            } => {
                state.set_surrounding_text(&text, cursor, anchor);
            }
            zwp_input_method_context_v1::Event::PreferredLanguage { language } => {
                state.preferred_language(&language);
            }
            _ => log::debug!(target: "zwp_input_method_context_v1", "unknown event"),
        }
    }
}

impl<D> Dispatch<ZwpInputMethodV1, GlobalData, D> for ImeV1
where
    D: Dispatch<ZwpInputMethodV1, GlobalData> + ImeV1Handler,
{
    fn event(
        state: &mut D,
        _proxy: &ZwpInputMethodV1,
        event: zwp_input_method_v1::Event,
        _data: &GlobalData,
        _conn: &Connection,
        _qh: &QueueHandle<D>,
    ) {
        match event {
            zwp_input_method_v1::Event::Activate { id } => state.activate(id.into()),
            zwp_input_method_v1::Event::Deactivate { context } => state.deactivate(context.into()),
            _ => log::debug!(target: "zwp_input_method_v1", "unknown event"),
        }
    }
}