use {
    crate::{
        client::ClientError,
        ifs::head_management::{
            Head, HeadState,
            jay_head_manager_session_v1::{JayHeadManagerSessionV1, JayHeadManagerSessionV1Error},
            jay_head_manager_v1::JayHeadManagerV1,
            jay_head_v1::JayHeadV1,
        },
        object::{ObjectId, Version},
        state::ConnectorData,
        utils::clonecell::CloneCell,
    },
    std::rc::Rc,
};

macro_rules! error_ {
    ($error_name:ident, $($tt:tt)*) => {
        #[derive(Debug, thiserror::Error)]
        pub enum $error_name {
            #[error(transparent)]
            ClientError(Box<crate::client::ClientError>),
            #[error(transparent)]
            Common(#[from] super::super::HeadCommonError),
            $($tt)*
        }
        efrom!($error_name, ClientError, crate::client::ClientError);
    };
}

macro_rules! impl_head_ext {
    (
        $snake:ident,
        $camel:ident,
        $macro_name:ident,
        $mgr_module:ident,
        $mgr_name:ident,
        $mgr_id_name:ident,
        $head_module:ident,
        $head_name:ident,
        $head_id_name:ident,
        $version:expr,
        $(filter = $filter:ident,)?
        $(after_announce = $after_announce:ident,)?
        $(after_transaction = $after_transaction:ident,)?
    ) => {
        pub(in super::super) struct $mgr_name {
            pub(in super::super) id: crate::wire::$mgr_id_name,
            pub(in super::super) client: std::rc::Rc<crate::client::Client>,
            pub(in super::super) tracker: crate::leaks::Tracker<Self>,
            pub(in super::super) version: crate::object::Version,
            pub(in super::super) common: std::rc::Rc<super::super::HeadMgrCommon>,
        }

        impl $mgr_name {
            pub(in super::super) const VERSION: crate::object::Version = crate::object::Version($version);
            pub(in super::super) const NAME: &str = concat!("jay_head_manager_ext_", stringify!($snake));
        }

        object_base! {
            self = $mgr_name;
            version = self.version;
        }

        impl crate::object::Object for $mgr_name {}

        simple_add_obj!($mgr_name);

        pub(in super::super) struct $head_name {
            pub(in super::super) id: crate::wire::$head_id_name,
            pub(in super::super) client: std::rc::Rc<crate::client::Client>,
            pub(in super::super) tracker: crate::leaks::Tracker<Self>,
            pub(in super::super) version: crate::object::Version,
            pub(in super::super) common: std::rc::Rc<super::super::HeadCommon>,
        }

        object_base! {
            self = $head_name;
            version = self.version;
        }

        impl crate::object::Object for $head_name {}

        simple_add_obj!($head_name);

        impl $mgr_name {
            pub(in super::super) fn announce(
                &self,
                #[allow(clippy::allow_attributes, unused_variables)]
                connector: &crate::state::ConnectorData,
                common: &std::rc::Rc<super::super::HeadCommon>,
            ) -> Result<Option<std::rc::Rc<$head_name>>, crate::client::ClientError> {
                $(
                    if !self.$filter(connector, common) {
                        return Ok(None);
                    }
                )?
                let obj = std::rc::Rc::new($head_name {
                    id: self.client.new_id()?,
                    client: self.client.clone(),
                    tracker: Default::default(),
                    version: self.version,
                    common: common.clone(),
                });
                track!(self.client, obj);
                self.client.add_server_obj(&obj);
                self.send_head(&obj);
                Ok(Some(obj))
            }

            fn send_head(&self, obj: &$head_name) {
                self.client.event(crate::wire::$mgr_module::Head {
                    self_id: self.id,
                    head: obj.id,
                });
            }
        }

        impl $head_name {
            pub(in super::super) fn after_announce_wrapper(
                &self,
                #[allow(clippy::allow_attributes, unused_variables)]
                shared: &super::super::HeadState,
            ) {
                $(
                    self.$after_announce(shared);
                )?
            }

            pub(in super::super) fn after_transaction_wrapper(
                &self,
                #[allow(clippy::allow_attributes, unused_variables)]
                shared: &super::super::HeadState,
                #[allow(clippy::allow_attributes, unused_variables)]
                tran: &super::super::HeadState,
            ) {
                $(
                    self.$after_transaction(shared, tran);
                )?
            }
        }
    }
}

#[rustfmt::skip]
macro_rules! declare_extension_type_macro {
    (
        ($dollar:tt),
        $snake:ident,
        $camel:ident,
        $macro_name:ident,
        $macro_name_int:ident,
        $mgr_module:ident,
        $mgr_name:ident,
        $mgr_id_name:ident,
        $mgr_request_handler:ident,
        $head_module:ident,
        $head_name:ident,
        $head_id_name:ident,
        $head_request_handler:ident,
        $error_name:ident,
    ) => {
        macro_rules! $macro_name {
            (
                version = $version:expr,
                $dollar(filter = $filter:ident,)?
                $dollar(after_announce = $after_announce:ident,)?
                $dollar(after_transaction = $after_transaction:ident,)?
            ) => {
                $macro_name_int! {
                    ($),
                    version = $version,
                    $dollar(filter = $filter,)?
                    $dollar(after_announce = $after_announce,)?
                    $dollar(after_transaction = $after_transaction,)?
                }
            }
        }

        macro_rules! $macro_name_int {
            (
                ($dollar2:tt),
                version = $version:expr,
                $dollar(filter = $filter:ident,)?
                $dollar(after_announce = $after_announce:ident,)?
                $dollar(after_transaction = $after_transaction:ident,)?
            ) => {
                impl_head_ext!(
                    $snake,
                    $camel,
                    $macro_name,
                    $mgr_module,
                    $mgr_name,
                    $mgr_id_name,
                    $head_module,
                    $head_name,
                    $head_id_name,
                    $version,
                    $dollar(filter = $filter,)?
                    $dollar(after_announce = $after_announce,)?
                    $dollar(after_transaction = $after_transaction,)?
                );

                macro_rules! head_common_req {
                    () => {
                        head_common_req_!($snake);
                    }
                }

                macro_rules! mgr_common_req {
                    () => {
                        mgr_common_req_!($snake);
                    }
                }

                type ErrorName = $error_name;
                type MgrName = $mgr_name;
                type HeadName = $head_name;

                macro_rules! error {
                    ($dollar2($tt:tt)*) => {
                        error_!($error_name, $dollar2($tt)*);
                    }
                }
            }
        }
    };
}

macro_rules! mgr_common_req_ {
    ($snake:ident) => {
        with_builtin_macros::with_eager_expansions! {
            fn destroy(
                &self,
                _req: crate::wire::#{concat_idents!(jay_head_manager_ext_, $snake)}::Destroy,
                _slf: &Rc<Self>,
            ) -> Result<(), Self::Error> {
                self.common.assert_stopped()?;
                self.client.remove_obj(self)?;
                Ok(())
            }
        }
    };
}

macro_rules! head_common_req_ {
    ($snake:ident) => {
        with_builtin_macros::with_eager_expansions! {
            fn destroy(
                &self,
                _req: crate::wire::#{concat_idents!(jay_head_ext_, $snake)}::Destroy,
                _slf: &Rc<Self>,
            ) -> Result<(), Self::Error> {
                self.common.assert_removed()?;
                self.client.remove_obj(self)?;
                Ok(())
            }
        }
    };
}

macro_rules! declare_extensions {
    ($($snake:ident: $camel:ident,)*) => {
        #[derive(linearize::Linearize)]
        pub(super) enum HeadExtension {
            $($camel,)*
        }

        pub(super) fn send_available_extensions(mgr: &JayHeadManagerV1) {
            use linearize::Linearize;
            with_builtin_macros::with_eager_expansions! {
                $(
                    type $camel = super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)};
                    mgr.send_extension(
                        HeadExtension::$camel.linearize() as _,
                        $camel::NAME,
                        $camel::VERSION,
                    );
                )*
            }
        }

        pub(super) fn announce_head(
            session: &Rc<JayHeadManagerSessionV1>,
            head: &Rc<JayHeadV1>,
            connector: &ConnectorData,
        ) -> Result<Rc<Head>, ClientError> {
            session.send_head_start(head, connector.head_managers.name);
            let head = super::Head {
                session: session.clone(),
                common: head.common.clone(),
                head: head.clone(),
                ext: HeadExts {
                    $(
                        $snake: match session.ext.$snake.get() {
                            Some(f) => f.announce(connector, &head.common)?,
                            _ => None,
                        },
                    )*
                },
            };
            session.send_head_complete();
            let shared = &*connector.head_managers.state.borrow();
            $(
                if let Some(ext) = &head.ext.$snake {
                    ext.after_announce_wrapper(shared);
                }
            )*
            Ok(Rc::new(head))
        }

        pub(super) fn bind_extension(session: &JayHeadManagerSessionV1, ext: HeadExtension, name: u32, version: u32, id: ObjectId) -> Result<(), JayHeadManagerSessionV1Error> {
            match ext {
                $(
                    HeadExtension::$camel => {
                        if session.ext.$snake.is_some() {
                            return Err(JayHeadManagerSessionV1Error::AlreadyBound(name));
                        }
                        let version = Version(version);
                        with_builtin_macros::with_eager_expansions! {
                            type T = super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)};
                            if version > T::VERSION {
                                return Err(JayHeadManagerSessionV1Error::UnsupportedVersion(name, version));
                            }
                            let obj = Rc::new(T {
                                id: id.into(),
                                client: session.client.clone(),
                                tracker: Default::default(),
                                version,
                                common: session.common.clone(),
                            });
                        }
                        track!(session.client, obj);
                        session.client.add_client_obj(&obj)?;
                        session.ext.$snake.set(Some(obj));
                    }
                )*
            }
            Ok(())
        }

        with_builtin_macros::with_eager_expansions! {
            #[derive(Default)]
            pub(super) struct MgrExts {
                $(
                    pub(super) $snake: CloneCell<Option<Rc<super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)}>>>,
                )*
            }

            pub(super) struct HeadExts {
                $(
                    pub(super) $snake: Option<Rc<super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadExt, $camel)}>>,
                )*
            }
        }

        impl HeadExts {
            pub(super) fn after_transaction(&self, shared: &HeadState, tran: &HeadState) {
                $(
                    if let Some(ext) = &self.$snake {
                        ext.after_transaction_wrapper(shared, tran);
                    }
                )*
            }
        }

        with_builtin_macros::with_eager_expansions! {
            $(
                declare_extension_type_macro!(
                    ($),
                    $snake,
                    $camel,
                    #{concat_idents!(impl_, $snake)},
                    #{concat_idents!(impl_, $snake, _int)},
                    #{concat_idents!(jay_head_manager_ext_, $snake)},
                    #{concat_idents!(JayHeadManagerExt, $camel)},
                    #{concat_idents!(JayHeadManagerExt, $camel, Id)},
                    #{concat_idents!(JayHeadManagerExt, $camel, RequestHandler)},
                    #{concat_idents!(jay_head_ext_, $snake)},
                    #{concat_idents!(JayHeadExt, $camel)},
                    #{concat_idents!(JayHeadExt, $camel, Id)},
                    #{concat_idents!(JayHeadExt, $camel, RequestHandler)},
                    #{concat_idents!(JayHeadExt, $camel, Error)},
                );
            )*
        }
    };
}

declare_extensions! {
    core_info_v1: CoreInfoV1,
    compositor_space_info_v1: CompositorSpaceInfoV1,
    compositor_space_positioner_v1: CompositorSpacePositionerV1,
    compositor_space_transformer_v1: CompositorSpaceTransformerV1,
    compositor_space_scaler_v1: CompositorSpaceScalerV1,
    compositor_space_enabler_v1: CompositorSpaceEnablerV1,
    connector_info_v1: ConnectorInfoV1,
    mode_info_v1: ModeInfoV1,
    mode_setter_v1: ModeSetterV1,
    physical_display_info_v1: PhysicalDisplayInfoV1,
    non_desktop_info_v1: NonDesktopInfoV1,
    vrr_state_v1: VrrStateV1,
    tearing_state_v1: TearingStateV1,
    format_info_v1: FormatInfoV1,
    drm_color_space_info_v1: DrmColorSpaceInfoV1,
    non_desktop_override_v1: NonDesktopOverrideV1,
    jay_vrr_mode_info_v1: JayVrrModeInfoV1,
    jay_tearing_mode_info_v1: JayTearingModeInfoV1,
    jay_vrr_mode_setter_v1: JayVrrModeSetterV1,
    jay_tearing_mode_setter_v1: JayTearingModeSetterV1,
    format_setter_v1: FormatSetterV1,
    drm_color_space_setter_v1: DrmColorSpaceSetterV1,
    brightness_info_v1: BrightnessInfoV1,
    brightness_setter_v1: BrightnessSetterV1,
}
