use serde::{Deserialize, Serialize};

use crate::config::Config;
use crate::constants::AccessTokenType;
use crate::error::LarkError;
use crate::req::{FormDataField, FormDataValue, RequestOption};
use crate::service::common::{
    DownloadResp, DownloadStreamResp, EmptyResp, PageIteratorState, RestRequest,
};

// ── Domain types ──

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UserId {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub union_id: Option<String>,
}

impl UserId {
    pub fn user_id(&self) -> Option<&str> {
        self.user_id.as_deref()
    }

    pub fn open_id(&self) -> Option<&str> {
        self.open_id.as_deref()
    }

    pub fn union_id(&self) -> Option<&str> {
        self.union_id.as_deref()
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MessageType(pub String);

impl MessageType {
    pub const TEXT: &'static str = "text";
    pub const POST: &'static str = "post";
    pub const IMAGE: &'static str = "image";
    pub const FILE: &'static str = "file";
    pub const AUDIO: &'static str = "audio";
    pub const MEDIA: &'static str = "media";
    pub const STICKER: &'static str = "sticker";
    pub const INTERACTIVE: &'static str = "interactive";
    pub const SHARE_CHAT: &'static str = "share_chat";
    pub const SHARE_USER: &'static str = "share_user";

    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl From<&str> for MessageType {
    fn from(value: &str) -> Self {
        Self::new(value)
    }
}

impl From<String> for MessageType {
    fn from(value: String) -> Self {
        Self(value)
    }
}

impl From<MessageType> for String {
    fn from(value: MessageType) -> Self {
        value.0
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct I18nNames {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub zh_cn: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub en_us: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub ja_jp: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Emoji {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub emoji_type: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Operator {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator_type: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Sender {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sender_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant_key: Option<String>,
}

impl Sender {
    pub fn open_id(&self) -> Option<&str> {
        (self.id_type.as_deref() == Some("open_id"))
            .then_some(self.id.as_deref())
            .flatten()
    }

    pub fn user_id(&self) -> Option<&str> {
        (self.id_type.as_deref() == Some("user_id"))
            .then_some(self.id.as_deref())
            .flatten()
    }

    pub fn union_id(&self) -> Option<&str> {
        (self.id_type.as_deref() == Some("union_id"))
            .then_some(self.id.as_deref())
            .flatten()
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MessageBody {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Mention {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub key: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant_key: Option<String>,
}

impl Mention {
    pub fn open_id(&self) -> Option<&str> {
        (self.id_type.as_deref() == Some("open_id"))
            .then_some(self.id.as_deref())
            .flatten()
    }

    pub fn user_id(&self) -> Option<&str> {
        (self.id_type.as_deref() == Some("user_id"))
            .then_some(self.id.as_deref())
            .flatten()
    }

    pub fn union_id(&self) -> Option<&str> {
        (self.id_type.as_deref() == Some("union_id"))
            .then_some(self.id.as_deref())
            .flatten()
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Message {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub root_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub parent_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub thread_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub msg_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub create_time: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub update_time: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deleted: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub updated: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sender: Option<Sender>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub body: Option<MessageBody>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mentions: Option<Vec<Mention>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub upper_message_id: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MessageReaction {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reaction_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator: Option<Operator>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub action_time: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reaction_type: Option<Emoji>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Pin {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator_id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub create_time: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReadUser {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant_key: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchMessageSendProgress {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub valid_user_ids_count: Option<i64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub success_user_ids_count: Option<i64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub read_user_ids_count: Option<i64>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchMessageRecallProgress {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub recall: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub recall_count: Option<i64>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchMessageReadUser {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub read_count: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total_count: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListChat {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub avatar: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub owner_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub owner_id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub external: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant_key: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub labels: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_status: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListMember {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member_id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant_key: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListModerator {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_id_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tenant_key: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatManagers {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub manager_id: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RestrictedModeSetting {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub status: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub screenshot_has_permission_setting: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub download_has_permission_setting: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message_has_permission_setting: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatTabConfig {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub icon_key: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub is_built_in: Option<bool>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatTabContent {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub doc: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub meeting_minute: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub task: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatTab {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tab_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tab_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tab_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tab_content: Option<ChatTabContent>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tab_config: Option<ChatTabConfig>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatTopNotice {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub action_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message_id: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatMenuItemRedirectLink {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub common_url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub ios_url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub android_url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pc_url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub web_url: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatMenuItem {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub action_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub redirect_link: Option<ChatMenuItemRedirectLink>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub image_key: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub i18n_names: Option<I18nNames>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatMenuSecondLevel {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_menu_second_level_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_menu_item: Option<ChatMenuItem>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatMenuTopLevel {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_menu_top_level_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_menu_item: Option<ChatMenuItem>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<ChatMenuSecondLevel>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatMenuTree {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chat_menu_top_levels: Option<Vec<ChatMenuTopLevel>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct I18nContent {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub language: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FollowUp {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub i18n_contents: Option<Vec<I18nContent>>,
}

// ── Request body types ──

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub receive_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub msg_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub uuid: Option<String>,
}

impl CreateMessageReqBody {
    pub fn msg_type(mut self, msg_type: impl Into<MessageType>) -> Self {
        self.msg_type = Some(msg_type.into().into());
        self
    }

    pub fn interactive_card(
        receive_id: impl Into<String>,
        card: impl Serialize,
    ) -> Result<Self, serde_json::Error> {
        Ok(Self::interactive_card_content(
            receive_id,
            card_content(card)?,
        ))
    }

    pub fn interactive_card_content(
        receive_id: impl Into<String>,
        content: impl Into<String>,
    ) -> Self {
        Self {
            receive_id: Some(receive_id.into()),
            msg_type: Some(MessageType::INTERACTIVE.to_string()),
            content: Some(content.into()),
            uuid: None,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct ReplyMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub msg_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reply_in_thread: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub uuid: Option<String>,
}

impl ReplyMessageReqBody {
    pub fn msg_type(mut self, msg_type: impl Into<MessageType>) -> Self {
        self.msg_type = Some(msg_type.into().into());
        self
    }

    pub fn interactive_card(card: impl Serialize) -> Result<Self, serde_json::Error> {
        Ok(Self::interactive_card_content(card_content(card)?))
    }

    pub fn interactive_card_content(content: impl Into<String>) -> Self {
        Self {
            content: Some(content.into()),
            msg_type: Some(MessageType::INTERACTIVE.to_string()),
            reply_in_thread: None,
            uuid: None,
        }
    }

    pub fn reply_in_thread(mut self, reply_in_thread: bool) -> Self {
        self.reply_in_thread = Some(reply_in_thread);
        self
    }
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct PatchMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
}

impl PatchMessageReqBody {
    pub fn interactive_card(card: impl Serialize) -> Result<Self, serde_json::Error> {
        Ok(Self::interactive_card_content(card_content(card)?))
    }

    pub fn interactive_card_content(content: impl Into<String>) -> Self {
        Self {
            content: Some(content.into()),
        }
    }
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub msg_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
}

impl UpdateMessageReqBody {
    pub fn msg_type(mut self, msg_type: impl Into<MessageType>) -> Self {
        self.msg_type = Some(msg_type.into().into());
        self
    }

    pub fn interactive_card(card: impl Serialize) -> Result<Self, serde_json::Error> {
        Ok(Self::interactive_card_content(card_content(card)?))
    }

    pub fn interactive_card_content(content: impl Into<String>) -> Self {
        Self {
            msg_type: Some(MessageType::INTERACTIVE.to_string()),
            content: Some(content.into()),
        }
    }
}

fn card_content(card: impl Serialize) -> Result<String, serde_json::Error> {
    serde_json::to_string(&card)
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct ForwardMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub receive_id: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct MergeForwardMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub receive_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UrgentMessageReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct PushFollowUpReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub follow_ups: Option<Vec<FollowUp>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateMessageReactionReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reaction_type: Option<Emoji>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreatePinReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_id: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateChatReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub avatar: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub i18n_names: Option<I18nNames>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub owner_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id_list: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bot_id_list: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub group_message_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_mode: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub join_message_visibility: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub leave_message_visibility: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub membership_approval: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub labels: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub toolkit_ids: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub restricted_mode_setting: Option<RestrictedModeSetting>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub urgent_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub video_conference_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub edit_permission: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_tags: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pin_manage_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hide_member_count_setting: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateChatReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub avatar: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub i18n_names: Option<I18nNames>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub add_member_permission: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub share_card_permission: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub at_all_permission: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub edit_permission: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub owner_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub join_message_visibility: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub leave_message_visibility: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub membership_approval: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub labels: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub toolkit_ids: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub restricted_mode_setting: Option<RestrictedModeSetting>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub group_message_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub urgent_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub video_conference_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pin_manage_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hide_member_count_setting: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct LinkChatReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub validity_period: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateChatMembersReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct DeleteChatMembersReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateChatManagersReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub manager_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct PatchChatAnnouncementReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub revision_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub requests: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateChatModerationReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub moderation_setting: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub moderator_added_list: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub moderator_deleted_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct ChatTabsReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_tabs: Option<Vec<ChatTab>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct DeleteChatTabsReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tab_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct SortChatTabsReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tab_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct PutChatTopNoticeReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_top_notice: Option<Vec<ChatTopNotice>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct PatchChatMenuItemReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub update_fields: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_menu_item: Option<ChatMenuItem>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateChatMenuTreeReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub menu_tree: Option<ChatMenuTree>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct DeleteChatMenuTreeReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_menu_top_level_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct SortChatMenuTreeReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chat_menu_top_level_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct ForwardThreadReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub receive_id: Option<String>,
}

// ── Response data types ──

#[derive(Debug, Clone, Default, Deserialize)]
pub struct MessageRespData {
    pub message_id: Option<String>,
    pub root_id: Option<String>,
    pub parent_id: Option<String>,
    pub thread_id: Option<String>,
    pub msg_type: Option<String>,
    pub create_time: Option<String>,
    pub update_time: Option<String>,
    pub deleted: Option<bool>,
    pub updated: Option<bool>,
    pub chat_id: Option<String>,
    pub sender: Option<Sender>,
    pub body: Option<MessageBody>,
    pub mentions: Option<Vec<Mention>>,
    pub upper_message_id: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct GetMessageRespData {
    pub items: Option<Vec<Message>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ListMessageRespData {
    pub has_more: Option<bool>,
    pub page_token: Option<String>,
    pub items: Option<Vec<Message>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct MergeForwardMessageRespData {
    pub message: Option<Message>,
    pub invalid_message_id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ReadUsersMessageRespData {
    pub items: Option<Vec<ReadUser>>,
    pub has_more: Option<bool>,
    pub page_token: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct UrgentMessageRespData {
    pub invalid_user_id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CreateMessageReactionRespData {
    pub reaction_id: Option<String>,
    pub operator: Option<Operator>,
    pub action_time: Option<String>,
    pub reaction_type: Option<Emoji>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct DeleteMessageReactionRespData {
    pub reaction_id: Option<String>,
    pub operator: Option<Operator>,
    pub action_time: Option<String>,
    pub reaction_type: Option<Emoji>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ListMessageReactionRespData {
    pub items: Option<Vec<MessageReaction>>,
    pub has_more: Option<bool>,
    pub page_token: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct GetProgressBatchMessageRespData {
    pub batch_message_send_progress: Option<BatchMessageSendProgress>,
    pub batch_message_recall_progress: Option<BatchMessageRecallProgress>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ReadUserBatchMessageRespData {
    pub read_user: Option<BatchMessageReadUser>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CreatePinRespData {
    pub pin: Option<Pin>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ListPinRespData {
    pub items: Option<Vec<Pin>>,
    pub has_more: Option<bool>,
    pub page_token: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CreateChatRespData {
    pub chat_id: Option<String>,
    pub avatar: Option<String>,
    pub name: Option<String>,
    pub description: Option<String>,
    pub i18n_names: Option<I18nNames>,
    pub owner_id: Option<String>,
    pub owner_id_type: Option<String>,
    pub urgent_setting: Option<String>,
    pub video_conference_setting: Option<String>,
    pub pin_manage_setting: Option<String>,
    pub add_member_permission: Option<String>,
    pub share_card_permission: Option<String>,
    pub at_all_permission: Option<String>,
    pub edit_permission: Option<String>,
    pub group_message_type: Option<String>,
    pub chat_mode: Option<String>,
    pub chat_type: Option<String>,
    pub chat_tag: Option<String>,
    pub external: Option<bool>,
    pub tenant_key: Option<String>,
    pub join_message_visibility: Option<String>,
    pub leave_message_visibility: Option<String>,
    pub membership_approval: Option<String>,
    pub moderation_permission: Option<String>,
    pub labels: Option<Vec<String>>,
    pub toolkit_ids: Option<Vec<String>>,
    pub restricted_mode_setting: Option<RestrictedModeSetting>,
    pub hide_member_count_setting: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct GetChatRespData {
    pub chat_id: Option<String>,
    pub avatar: Option<String>,
    pub name: Option<String>,
    pub description: Option<String>,
    pub i18n_names: Option<I18nNames>,
    pub owner_id: Option<String>,
    pub owner_id_type: Option<String>,
    pub urgent_setting: Option<String>,
    pub video_conference_setting: Option<String>,
    pub pin_manage_setting: Option<String>,
    pub add_member_permission: Option<String>,
    pub share_card_permission: Option<String>,
    pub at_all_permission: Option<String>,
    pub edit_permission: Option<String>,
    pub group_message_type: Option<String>,
    pub chat_mode: Option<String>,
    pub chat_type: Option<String>,
    pub chat_tag: Option<String>,
    pub external: Option<bool>,
    pub tenant_key: Option<String>,
    pub join_message_visibility: Option<String>,
    pub leave_message_visibility: Option<String>,
    pub membership_approval: Option<String>,
    pub moderation_permission: Option<String>,
    pub labels: Option<Vec<String>>,
    pub toolkit_ids: Option<Vec<String>>,
    pub restricted_mode_setting: Option<RestrictedModeSetting>,
    pub hide_member_count_setting: Option<String>,
    pub user_manager_id_list: Option<Vec<String>>,
    pub bot_manager_id_list: Option<Vec<String>>,
    pub user_count: Option<String>,
    pub bot_count: Option<String>,
    pub chat_status: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct LinkChatRespData {
    pub share_link: Option<String>,
    pub expire_time: Option<String>,
    pub is_permanent: Option<bool>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ListChatRespData {
    pub items: Option<Vec<ListChat>>,
    pub page_token: Option<String>,
    pub has_more: Option<bool>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CreateChatMembersRespData {
    pub invalid_id_list: Option<Vec<String>>,
    pub not_existed_id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct GetChatMembersRespData {
    pub items: Option<Vec<ListMember>>,
    pub page_token: Option<String>,
    pub has_more: Option<bool>,
    pub member_total: Option<i64>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct IsInChatChatMembersRespData {
    pub is_in_chat: Option<bool>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct UpdateChatManagersRespData {
    pub chat_managers: Option<Vec<ChatManagers>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct GetChatAnnouncementRespData {
    pub content: Option<String>,
    pub revision: Option<String>,
    pub create_time: Option<String>,
    pub update_time: Option<String>,
    pub owner_id_type: Option<String>,
    pub owner_id: Option<String>,
    pub modifier_id_type: Option<String>,
    pub modifier_id: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct GetChatModerationRespData {
    pub moderation_setting: Option<String>,
    pub items: Option<Vec<ListModerator>>,
    pub page_token: Option<String>,
    pub has_more: Option<bool>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ChatTabsRespData {
    pub chat_tabs: Option<Vec<ChatTab>>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct PatchChatMenuItemRespData {
    pub chat_menu_item: Option<ChatMenuItem>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct ChatMenuTreeRespData {
    pub menu_tree: Option<ChatMenuTree>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CreateFileRespData {
    pub file_key: Option<String>,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct CreateImageRespData {
    pub image_key: Option<String>,
}

// ── Response wrappers ──

impl_resp!(CreateMessageResp, MessageRespData);
impl_resp!(ReplyMessageResp, MessageRespData);
impl_resp!(UpdateMessageResp, MessageRespData);
impl_resp!(ForwardMessageResp, MessageRespData);
impl_resp!(MergeForwardMessageResp, MergeForwardMessageRespData);
impl_resp!(GetMessageResp, GetMessageRespData);
impl_resp!(ListMessageResp, ListMessageRespData);
impl_resp!(ReadUsersMessageResp, ReadUsersMessageRespData);
impl_resp!(UrgentAppMessageResp, UrgentMessageRespData);
impl_resp!(UrgentPhoneMessageResp, UrgentMessageRespData);
impl_resp!(UrgentSmsMessageResp, UrgentMessageRespData);
impl_resp!(CreateMessageReactionResp, CreateMessageReactionRespData);
impl_resp!(DeleteMessageReactionResp, DeleteMessageReactionRespData);
impl_resp!(ListMessageReactionResp, ListMessageReactionRespData);
impl_resp!(GetProgressBatchMessageResp, GetProgressBatchMessageRespData);
impl_resp!(ReadUserBatchMessageResp, ReadUserBatchMessageRespData);
impl_resp!(CreatePinResp, CreatePinRespData);
impl_resp!(ListPinResp, ListPinRespData);
impl_resp!(CreateChatResp, CreateChatRespData);
impl_resp!(GetChatResp, GetChatRespData);
impl_resp!(LinkChatResp, LinkChatRespData);
impl_resp!(ListChatResp, ListChatRespData);
impl_resp!(CreateChatMembersResp, CreateChatMembersRespData);
impl_resp!(GetChatMembersResp, GetChatMembersRespData);
impl_resp!(IsInChatChatMembersResp, IsInChatChatMembersRespData);
impl_resp!(AddManagersChatManagersResp, UpdateChatManagersRespData);
impl_resp!(DeleteManagersChatManagersResp, UpdateChatManagersRespData);
impl_resp!(GetChatAnnouncementResp, GetChatAnnouncementRespData);
impl_resp!(GetChatModerationResp, GetChatModerationRespData);
impl_resp!(CreateChatTabResp, ChatTabsRespData);
impl_resp!(DeleteTabsChatTabResp, ChatTabsRespData);
impl_resp!(ListTabsChatTabResp, ChatTabsRespData);
impl_resp!(SortTabsChatTabResp, ChatTabsRespData);
impl_resp!(UpdateTabsChatTabResp, ChatTabsRespData);
impl_resp!(PatchChatMenuItemResp, PatchChatMenuItemRespData);
impl_resp!(CreateChatMenuTreeResp, ChatMenuTreeRespData);
impl_resp!(DeleteChatMenuTreeResp, ChatMenuTreeRespData);
impl_resp!(GetChatMenuTreeResp, ChatMenuTreeRespData);
impl_resp!(SortChatMenuTreeResp, ChatMenuTreeRespData);
impl_resp!(CreateFileResp, CreateFileRespData);
impl_resp!(CreateImageResp, CreateImageRespData);
impl_resp!(ForwardThreadResp, MessageRespData);

// ── Iterators ──

#[derive(Debug, Clone)]
pub struct ListMessageIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<Message>,
    container_id_type: String,
    container_id: String,
    start_time: Option<String>,
    end_time: Option<String>,
    sort_type: Option<String>,
    page_size: Option<i64>,
}

impl<'a> ListMessageIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(&mut self, option: &RequestOption) -> Result<Option<Message>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = MessageResource {
            config: self.config,
        };
        let resp = resource
            .list(
                &self.container_id_type,
                &self.container_id,
                self.start_time.as_deref(),
                self.end_time.as_deref(),
                self.sort_type.as_deref(),
                self.page_size,
                self.state.page_token_for_request(),
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct ListMessageReactionIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<MessageReaction>,
    message_id: String,
    reaction_type: Option<String>,
    page_size: Option<i64>,
    user_id_type: Option<String>,
}

impl<'a> ListMessageReactionIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(
        &mut self,
        option: &RequestOption,
    ) -> Result<Option<MessageReaction>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = MessageReactionResource {
            config: self.config,
        };
        let resp = resource
            .list(
                &self.message_id,
                self.reaction_type.as_deref(),
                self.state.page_token_for_request(),
                self.page_size,
                self.user_id_type.as_deref(),
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct ListPinIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<Pin>,
    chat_id: String,
    start_time: Option<String>,
    end_time: Option<String>,
    page_size: Option<i64>,
}

impl<'a> ListPinIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(&mut self, option: &RequestOption) -> Result<Option<Pin>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = PinResource {
            config: self.config,
        };
        let resp = resource
            .list(
                &self.chat_id,
                self.start_time.as_deref(),
                self.end_time.as_deref(),
                self.state.page_token_for_request(),
                self.page_size,
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct ListChatIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<ListChat>,
    user_id_type: Option<String>,
    sort_type: Option<String>,
    page_size: Option<i64>,
}

impl<'a> ListChatIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(&mut self, option: &RequestOption) -> Result<Option<ListChat>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = ChatResource {
            config: self.config,
        };
        let resp = resource
            .list(
                self.user_id_type.as_deref(),
                self.sort_type.as_deref(),
                self.state.page_token_for_request(),
                self.page_size,
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct SearchChatIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<ListChat>,
    user_id_type: Option<String>,
    query: Option<String>,
    page_size: Option<i64>,
}

impl<'a> SearchChatIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(&mut self, option: &RequestOption) -> Result<Option<ListChat>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = ChatResource {
            config: self.config,
        };
        let resp = resource
            .search(
                self.user_id_type.as_deref(),
                self.query.as_deref(),
                self.state.page_token_for_request(),
                self.page_size,
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct GetChatMembersIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<ListMember>,
    chat_id: String,
    member_id_type: Option<String>,
    page_size: Option<i64>,
}

impl<'a> GetChatMembersIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(&mut self, option: &RequestOption) -> Result<Option<ListMember>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = ChatMembersResource {
            config: self.config,
        };
        let resp = resource
            .get(
                &self.chat_id,
                self.member_id_type.as_deref(),
                self.state.page_token_for_request(),
                self.page_size,
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct GetChatModerationIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<ListModerator>,
    chat_id: String,
    user_id_type: Option<String>,
    page_size: Option<i64>,
}

impl<'a> GetChatModerationIterator<'a> {
    pub fn limit(mut self, limit: usize) -> Self {
        self.state = self.state.limit(limit);
        self
    }

    pub fn page_token(mut self, page_token: impl Into<String>) -> Self {
        self.state = self.state.with_page_token(Some(page_token.into()));
        self
    }

    pub fn next_page_token(&self) -> Option<&str> {
        self.state.next_page_token()
    }

    pub async fn next(
        &mut self,
        option: &RequestOption,
    ) -> Result<Option<ListModerator>, LarkError> {
        if let Some(item) = self.state.pop() {
            return Ok(Some(item));
        }
        if !self.state.should_fetch() {
            return Ok(None);
        }

        let resource = ChatModerationResource {
            config: self.config,
        };
        let resp = resource
            .get(
                &self.chat_id,
                self.user_id_type.as_deref(),
                self.state.page_token_for_request(),
                self.page_size,
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(data.items, data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

// ── Resources ──

#[derive(Debug, Clone)]
pub struct MessageResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct GetMessageQuery<'a> {
    pub message_id: &'a str,
    pub user_id_type: Option<&'a str>,
}

impl<'a> GetMessageQuery<'a> {
    pub fn new(message_id: &'a str) -> Self {
        Self {
            message_id,
            user_id_type: None,
        }
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ListMessageQuery<'a> {
    pub container_id_type: &'a str,
    pub container_id: &'a str,
    pub start_time: Option<&'a str>,
    pub end_time: Option<&'a str>,
    pub sort_type: Option<&'a str>,
    pub page_size: Option<i64>,
    pub page_token: Option<&'a str>,
}

impl<'a> ListMessageQuery<'a> {
    pub fn new(container_id_type: &'a str, container_id: &'a str) -> Self {
        Self {
            container_id_type,
            container_id,
            start_time: None,
            end_time: None,
            sort_type: None,
            page_size: None,
            page_token: None,
        }
    }

    pub fn start_time(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.start_time = value.into();
        self
    }

    pub fn end_time(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.end_time = value.into();
        self
    }

    pub fn sort_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.sort_type = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ReadUsersMessageQuery<'a> {
    pub message_id: &'a str,
    pub user_id_type: &'a str,
    pub page_size: Option<i64>,
    pub page_token: Option<&'a str>,
}

impl<'a> ReadUsersMessageQuery<'a> {
    pub fn new(message_id: &'a str, user_id_type: &'a str) -> Self {
        Self {
            message_id,
            user_id_type,
            page_size: None,
            page_token: None,
        }
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }
}

impl<'a> MessageResource<'a> {
    pub async fn create(
        &self,
        receive_id_type: &str,
        body: &CreateMessageReqBody,
        option: &RequestOption,
    ) -> Result<CreateMessageResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/im/v1/messages",
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("receive_id_type", receive_id_type)
        .json_body(body)?
        .send_response::<MessageRespData, CreateMessageResp>()
        .await
    }

    pub async fn reply(
        &self,
        message_id: &str,
        body: &ReplyMessageReqBody,
        option: &RequestOption,
    ) -> Result<ReplyMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/reply");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<MessageRespData, ReplyMessageResp>()
        .await
    }

    pub async fn patch(
        &self,
        message_id: &str,
        body: &PatchMessageReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_empty()
        .await
    }

    pub async fn update(
        &self,
        message_id: &str,
        body: &UpdateMessageReqBody,
        option: &RequestOption,
    ) -> Result<UpdateMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_response::<MessageRespData, UpdateMessageResp>()
        .await
    }

    pub async fn delete(
        &self,
        message_id: &str,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_empty()
        .await
    }

    pub async fn get(
        &self,
        message_id: &str,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<GetMessageResp, LarkError> {
        let query = GetMessageQuery::new(message_id).user_id_type(user_id_type);
        self.get_by_query(&query, option).await
    }

    pub async fn get_by_query(
        &self,
        query: &GetMessageQuery<'_>,
        option: &RequestOption,
    ) -> Result<GetMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{}", query.message_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .send_response::<GetMessageRespData, GetMessageResp>()
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn list(
        &self,
        container_id_type: &str,
        container_id: &str,
        start_time: Option<&str>,
        end_time: Option<&str>,
        sort_type: Option<&str>,
        page_size: Option<i64>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListMessageResp, LarkError> {
        let query = ListMessageQuery {
            container_id_type,
            container_id,
            start_time,
            end_time,
            sort_type,
            page_size,
            page_token,
        };
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListMessageQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListMessageResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::GET,
            "/open-apis/im/v1/messages",
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("container_id_type", query.container_id_type)
        .query("container_id", query.container_id)
        .query("start_time", query.start_time)
        .query("end_time", query.end_time)
        .query("sort_type", query.sort_type)
        .query("page_size", query.page_size)
        .query("page_token", query.page_token)
        .send_response::<ListMessageRespData, ListMessageResp>()
        .await
    }

    pub fn list_by_iterator(
        &self,
        container_id_type: &str,
        container_id: &str,
        start_time: Option<&str>,
        end_time: Option<&str>,
        sort_type: Option<&str>,
        page_size: Option<i64>,
    ) -> ListMessageIterator<'a> {
        ListMessageIterator {
            config: self.config,
            state: PageIteratorState::default(),
            container_id_type: container_id_type.to_owned(),
            container_id: container_id.to_owned(),
            start_time: start_time.map(|v| v.to_owned()),
            end_time: end_time.map(|v| v.to_owned()),
            sort_type: sort_type.map(|v| v.to_owned()),
            page_size,
        }
    }

    pub async fn forward(
        &self,
        message_id: &str,
        receive_id_type: &str,
        uuid: Option<&str>,
        body: &ForwardMessageReqBody,
        option: &RequestOption,
    ) -> Result<ForwardMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/forward");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("receive_id_type", receive_id_type)
        .query("uuid", uuid)
        .json_body(body)?
        .send_response::<MessageRespData, ForwardMessageResp>()
        .await
    }

    pub async fn merge_forward(
        &self,
        receive_id_type: &str,
        uuid: Option<&str>,
        body: &MergeForwardMessageReqBody,
        option: &RequestOption,
    ) -> Result<MergeForwardMessageResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/im/v1/messages/merge_forward",
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("receive_id_type", receive_id_type)
        .query("uuid", uuid)
        .json_body(body)?
        .send_response::<MergeForwardMessageRespData, MergeForwardMessageResp>()
        .await
    }

    pub async fn read_users(
        &self,
        message_id: &str,
        user_id_type: &str,
        page_size: Option<i64>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ReadUsersMessageResp, LarkError> {
        let query = ReadUsersMessageQuery {
            message_id,
            user_id_type,
            page_size,
            page_token,
        };
        self.read_users_by_query(&query, option).await
    }

    pub async fn read_users_by_query(
        &self,
        query: &ReadUsersMessageQuery<'_>,
        option: &RequestOption,
    ) -> Result<ReadUsersMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{}/read_users", query.message_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .query("page_size", query.page_size)
        .query("page_token", query.page_token)
        .send_response::<ReadUsersMessageRespData, ReadUsersMessageResp>()
        .await
    }

    pub async fn push_follow_up(
        &self,
        message_id: &str,
        body: &PushFollowUpReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/push_follow_up");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_empty()
        .await
    }

    pub async fn urgent_app(
        &self,
        message_id: &str,
        user_id_type: &str,
        body: &UrgentMessageReqBody,
        option: &RequestOption,
    ) -> Result<UrgentAppMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/urgent_app");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<UrgentMessageRespData, UrgentAppMessageResp>()
        .await
    }

    pub async fn urgent_phone(
        &self,
        message_id: &str,
        user_id_type: &str,
        body: &UrgentMessageReqBody,
        option: &RequestOption,
    ) -> Result<UrgentPhoneMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/urgent_phone");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<UrgentMessageRespData, UrgentPhoneMessageResp>()
        .await
    }

    pub async fn urgent_sms(
        &self,
        message_id: &str,
        user_id_type: &str,
        body: &UrgentMessageReqBody,
        option: &RequestOption,
    ) -> Result<UrgentSmsMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/urgent_sms");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<UrgentMessageRespData, UrgentSmsMessageResp>()
        .await
    }
}

pub struct MessageReactionResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ListMessageReactionQuery<'a> {
    pub message_id: &'a str,
    pub reaction_type: Option<&'a str>,
    pub page_token: Option<&'a str>,
    pub page_size: Option<i64>,
    pub user_id_type: Option<&'a str>,
}

impl<'a> ListMessageReactionQuery<'a> {
    pub fn new(message_id: &'a str) -> Self {
        Self {
            message_id,
            reaction_type: None,
            page_token: None,
            page_size: None,
            user_id_type: None,
        }
    }

    pub fn reaction_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.reaction_type = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }
}

impl<'a> MessageReactionResource<'a> {
    pub async fn create(
        &self,
        message_id: &str,
        body: &CreateMessageReactionReqBody,
        option: &RequestOption,
    ) -> Result<CreateMessageReactionResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/reactions");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_response::<CreateMessageReactionRespData, CreateMessageReactionResp>()
        .await
    }

    pub async fn delete(
        &self,
        message_id: &str,
        reaction_id: &str,
        option: &RequestOption,
    ) -> Result<DeleteMessageReactionResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{message_id}/reactions/{reaction_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .send_response::<DeleteMessageReactionRespData, DeleteMessageReactionResp>()
        .await
    }

    pub async fn list(
        &self,
        message_id: &str,
        reaction_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i64>,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListMessageReactionResp, LarkError> {
        let query = ListMessageReactionQuery {
            message_id,
            reaction_type,
            page_token,
            page_size,
            user_id_type,
        };
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListMessageReactionQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListMessageReactionResp, LarkError> {
        let path = format!("/open-apis/im/v1/messages/{}/reactions", query.message_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("reaction_type", query.reaction_type)
        .query("page_token", query.page_token)
        .query("page_size", query.page_size)
        .query("user_id_type", query.user_id_type)
        .send_response::<ListMessageReactionRespData, ListMessageReactionResp>()
        .await
    }

    pub fn list_by_iterator(
        &self,
        message_id: &str,
        reaction_type: Option<&str>,
        page_size: Option<i64>,
        user_id_type: Option<&str>,
    ) -> ListMessageReactionIterator<'a> {
        ListMessageReactionIterator {
            config: self.config,
            state: PageIteratorState::default(),
            message_id: message_id.to_owned(),
            reaction_type: reaction_type.map(|v| v.to_owned()),
            page_size,
            user_id_type: user_id_type.map(|v| v.to_owned()),
        }
    }
}

pub struct MessageResourceDownload<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct GetMessageResourceDownloadQuery<'a> {
    pub message_id: &'a str,
    pub file_key: &'a str,
    pub resource_type: &'a str,
}

impl<'a> GetMessageResourceDownloadQuery<'a> {
    pub fn new(message_id: &'a str, file_key: &'a str, resource_type: &'a str) -> Self {
        Self {
            message_id,
            file_key,
            resource_type,
        }
    }
}

impl<'a> MessageResourceDownload<'a> {
    pub async fn get(
        &self,
        message_id: &str,
        file_key: &str,
        resource_type: &str,
        option: &RequestOption,
    ) -> Result<DownloadResp, LarkError> {
        self.get_by_query(
            &GetMessageResourceDownloadQuery::new(message_id, file_key, resource_type),
            option,
        )
        .await
    }

    pub async fn get_by_query(
        &self,
        query: &GetMessageResourceDownloadQuery<'_>,
        option: &RequestOption,
    ) -> Result<DownloadResp, LarkError> {
        self.request(query, option).download().await
    }

    pub async fn get_stream_by_query(
        &self,
        query: &GetMessageResourceDownloadQuery<'_>,
        option: &RequestOption,
    ) -> Result<DownloadStreamResp, LarkError> {
        self.request(query, option).download_stream().await
    }

    fn request<'b>(
        &'b self,
        query: &GetMessageResourceDownloadQuery<'_>,
        option: &'b RequestOption,
    ) -> RestRequest<'b> {
        let path = format!(
            "/open-apis/im/v1/messages/{}/resources/{}",
            query.message_id, query.file_key
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("type", query.resource_type)
    }
}

pub struct BatchMessageResource<'a> {
    config: &'a Config,
}

impl<'a> BatchMessageResource<'a> {
    pub async fn delete(
        &self,
        batch_message_id: &str,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/batch_messages/{batch_message_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .send_empty()
        .await
    }

    pub async fn get_progress(
        &self,
        batch_message_id: &str,
        option: &RequestOption,
    ) -> Result<GetProgressBatchMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/batch_messages/{batch_message_id}/get_progress");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .send_response::<GetProgressBatchMessageRespData, GetProgressBatchMessageResp>()
        .await
    }

    pub async fn read_user(
        &self,
        batch_message_id: &str,
        option: &RequestOption,
    ) -> Result<ReadUserBatchMessageResp, LarkError> {
        let path = format!("/open-apis/im/v1/batch_messages/{batch_message_id}/read_user");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .send_response::<ReadUserBatchMessageRespData, ReadUserBatchMessageResp>()
        .await
    }
}

pub struct PinResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ListPinQuery<'a> {
    pub chat_id: &'a str,
    pub start_time: Option<&'a str>,
    pub end_time: Option<&'a str>,
    pub page_token: Option<&'a str>,
    pub page_size: Option<i64>,
}

impl<'a> ListPinQuery<'a> {
    pub fn new(chat_id: &'a str) -> Self {
        Self {
            chat_id,
            start_time: None,
            end_time: None,
            page_token: None,
            page_size: None,
        }
    }

    pub fn start_time(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.start_time = value.into();
        self
    }

    pub fn end_time(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.end_time = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }
}

impl<'a> PinResource<'a> {
    pub async fn create(
        &self,
        body: &CreatePinReqBody,
        option: &RequestOption,
    ) -> Result<CreatePinResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/im/v1/pins",
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<CreatePinRespData, CreatePinResp>()
        .await
    }

    pub async fn delete(
        &self,
        message_id: &str,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/pins/{message_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_empty()
        .await
    }

    pub async fn list(
        &self,
        chat_id: &str,
        start_time: Option<&str>,
        end_time: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i64>,
        option: &RequestOption,
    ) -> Result<ListPinResp, LarkError> {
        let query = ListPinQuery {
            chat_id,
            start_time,
            end_time,
            page_token,
            page_size,
        };
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListPinQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListPinResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::GET,
            "/open-apis/im/v1/pins",
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("chat_id", query.chat_id)
        .query("start_time", query.start_time)
        .query("end_time", query.end_time)
        .query("page_token", query.page_token)
        .query("page_size", query.page_size)
        .send_response::<ListPinRespData, ListPinResp>()
        .await
    }

    pub fn list_by_iterator(
        &self,
        chat_id: &str,
        start_time: Option<&str>,
        end_time: Option<&str>,
        page_size: Option<i64>,
    ) -> ListPinIterator<'a> {
        ListPinIterator {
            config: self.config,
            state: PageIteratorState::default(),
            chat_id: chat_id.to_owned(),
            start_time: start_time.map(|v| v.to_owned()),
            end_time: end_time.map(|v| v.to_owned()),
            page_size,
        }
    }
}

pub struct FileResource<'a> {
    config: &'a Config,
}

impl<'a> FileResource<'a> {
    pub async fn create(
        &self,
        file_type: &str,
        file_name: &str,
        duration: Option<i64>,
        data: Vec<u8>,
        option: &RequestOption,
    ) -> Result<CreateFileResp, LarkError> {
        let mut fields = vec![
            FormDataField {
                name: "file_type".into(),
                value: FormDataValue::Text(file_type.to_string()),
            },
            FormDataField {
                name: "file_name".into(),
                value: FormDataValue::Text(file_name.to_string()),
            },
            FormDataField {
                name: "file".into(),
                value: FormDataValue::File {
                    filename: file_name.to_string(),
                    data,
                    content_type: None,
                },
            },
        ];
        if let Some(d) = duration {
            fields.push(FormDataField {
                name: "duration".into(),
                value: FormDataValue::Text(d.to_string()),
            });
        }
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/im/v1/files",
            vec![AccessTokenType::Tenant],
            option,
        )
        .form_body(fields)
        .send_response::<CreateFileRespData, CreateFileResp>()
        .await
    }

    pub async fn get(
        &self,
        file_key: &str,
        option: &RequestOption,
    ) -> Result<DownloadResp, LarkError> {
        let path = format!("/open-apis/im/v1/files/{file_key}");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .download()
        .await
    }
}

pub struct ImageResource<'a> {
    config: &'a Config,
}

impl<'a> ImageResource<'a> {
    pub async fn create(
        &self,
        image_type: &str,
        data: Vec<u8>,
        option: &RequestOption,
    ) -> Result<CreateImageResp, LarkError> {
        let fields = vec![
            FormDataField {
                name: "image_type".into(),
                value: FormDataValue::Text(image_type.to_string()),
            },
            FormDataField {
                name: "image".into(),
                value: FormDataValue::File {
                    filename: "image".to_string(),
                    data,
                    content_type: None,
                },
            },
        ];
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/im/v1/images",
            vec![AccessTokenType::Tenant],
            option,
        )
        .form_body(fields)
        .send_response::<CreateImageRespData, CreateImageResp>()
        .await
    }

    pub async fn get(
        &self,
        image_key: &str,
        option: &RequestOption,
    ) -> Result<DownloadResp, LarkError> {
        let path = format!("/open-apis/im/v1/images/{image_key}");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .download()
        .await
    }
}

pub struct ChatResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct GetChatQuery<'a> {
    pub chat_id: &'a str,
    pub user_id_type: Option<&'a str>,
}

impl<'a> GetChatQuery<'a> {
    pub fn new(chat_id: &'a str) -> Self {
        Self {
            chat_id,
            user_id_type: None,
        }
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }
}

#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct ListChatQuery<'a> {
    pub user_id_type: Option<&'a str>,
    pub sort_type: Option<&'a str>,
    pub page_token: Option<&'a str>,
    pub page_size: Option<i64>,
}

impl<'a> ListChatQuery<'a> {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }

    pub fn sort_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.sort_type = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }
}

#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct SearchChatQuery<'a> {
    pub user_id_type: Option<&'a str>,
    pub query: Option<&'a str>,
    pub page_token: Option<&'a str>,
    pub page_size: Option<i64>,
}

impl<'a> SearchChatQuery<'a> {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }

    pub fn query(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.query = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }
}

impl<'a> ChatResource<'a> {
    pub async fn create(
        &self,
        user_id_type: Option<&str>,
        body: &CreateChatReqBody,
        option: &RequestOption,
    ) -> Result<CreateChatResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/im/v1/chats",
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<CreateChatRespData, CreateChatResp>()
        .await
    }

    pub async fn delete(
        &self,
        chat_id: &str,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .send_empty()
        .await
    }

    pub async fn get(
        &self,
        chat_id: &str,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<GetChatResp, LarkError> {
        let query = GetChatQuery::new(chat_id).user_id_type(user_id_type);
        self.get_by_query(&query, option).await
    }

    pub async fn get_by_query(
        &self,
        query: &GetChatQuery<'_>,
        option: &RequestOption,
    ) -> Result<GetChatResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{}", query.chat_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .send_response::<GetChatRespData, GetChatResp>()
        .await
    }

    pub async fn update(
        &self,
        chat_id: &str,
        user_id_type: Option<&str>,
        body: &UpdateChatReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_empty()
        .await
    }

    pub async fn link(
        &self,
        chat_id: &str,
        body: &LinkChatReqBody,
        option: &RequestOption,
    ) -> Result<LinkChatResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/link");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<LinkChatRespData, LinkChatResp>()
        .await
    }

    pub async fn list(
        &self,
        user_id_type: Option<&str>,
        sort_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i64>,
        option: &RequestOption,
    ) -> Result<ListChatResp, LarkError> {
        let query = ListChatQuery {
            user_id_type,
            sort_type,
            page_token,
            page_size,
        };
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListChatQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListChatResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::GET,
            "/open-apis/im/v1/chats",
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .query("sort_type", query.sort_type)
        .query("page_token", query.page_token)
        .query("page_size", query.page_size)
        .send_response::<ListChatRespData, ListChatResp>()
        .await
    }

    pub async fn search(
        &self,
        user_id_type: Option<&str>,
        query: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i64>,
        option: &RequestOption,
    ) -> Result<ListChatResp, LarkError> {
        let query = SearchChatQuery {
            user_id_type,
            query,
            page_token,
            page_size,
        };
        self.search_by_query(&query, option).await
    }

    pub async fn search_by_query(
        &self,
        query: &SearchChatQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListChatResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::GET,
            "/open-apis/im/v1/chats/search",
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .query("query", query.query)
        .query("page_token", query.page_token)
        .query("page_size", query.page_size)
        .send_response::<ListChatRespData, ListChatResp>()
        .await
    }

    pub fn list_by_iterator(
        &self,
        user_id_type: Option<&str>,
        sort_type: Option<&str>,
        page_size: Option<i64>,
    ) -> ListChatIterator<'a> {
        ListChatIterator {
            config: self.config,
            state: PageIteratorState::default(),
            user_id_type: user_id_type.map(|v| v.to_owned()),
            sort_type: sort_type.map(|v| v.to_owned()),
            page_size,
        }
    }

    pub fn search_by_iterator(
        &self,
        user_id_type: Option<&str>,
        query: Option<&str>,
        page_size: Option<i64>,
    ) -> SearchChatIterator<'a> {
        SearchChatIterator {
            config: self.config,
            state: PageIteratorState::default(),
            user_id_type: user_id_type.map(|v| v.to_owned()),
            query: query.map(|v| v.to_owned()),
            page_size,
        }
    }
}

pub struct ChatMembersResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct GetChatMembersQuery<'a> {
    pub chat_id: &'a str,
    pub member_id_type: Option<&'a str>,
    pub page_token: Option<&'a str>,
    pub page_size: Option<i64>,
}

impl<'a> GetChatMembersQuery<'a> {
    pub fn new(chat_id: &'a str) -> Self {
        Self {
            chat_id,
            member_id_type: None,
            page_token: None,
            page_size: None,
        }
    }

    pub fn member_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.member_id_type = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct IsInChatMembersQuery<'a> {
    pub chat_id: &'a str,
}

impl<'a> IsInChatMembersQuery<'a> {
    pub fn new(chat_id: &'a str) -> Self {
        Self { chat_id }
    }
}

impl<'a> ChatMembersResource<'a> {
    pub async fn create(
        &self,
        chat_id: &str,
        member_id_type: Option<&str>,
        succeed_type: Option<&str>,
        body: &CreateChatMembersReqBody,
        option: &RequestOption,
    ) -> Result<CreateChatMembersResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/members");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("member_id_type", member_id_type)
        .query("succeed_type", succeed_type)
        .json_body(body)?
        .send_response::<CreateChatMembersRespData, CreateChatMembersResp>()
        .await
    }

    pub async fn delete(
        &self,
        chat_id: &str,
        member_id_type: Option<&str>,
        body: &DeleteChatMembersReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/members");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("member_id_type", member_id_type)
        .json_body(body)?
        .send_empty()
        .await
    }

    pub async fn get(
        &self,
        chat_id: &str,
        member_id_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i64>,
        option: &RequestOption,
    ) -> Result<GetChatMembersResp, LarkError> {
        let query = GetChatMembersQuery {
            chat_id,
            member_id_type,
            page_token,
            page_size,
        };
        self.get_by_query(&query, option).await
    }

    pub async fn get_by_query(
        &self,
        query: &GetChatMembersQuery<'_>,
        option: &RequestOption,
    ) -> Result<GetChatMembersResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{}/members", query.chat_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("member_id_type", query.member_id_type)
        .query("page_token", query.page_token)
        .query("page_size", query.page_size)
        .send_response::<GetChatMembersRespData, GetChatMembersResp>()
        .await
    }

    pub fn get_by_iterator(
        &self,
        chat_id: &str,
        member_id_type: Option<&str>,
        page_size: Option<i64>,
    ) -> GetChatMembersIterator<'a> {
        GetChatMembersIterator {
            config: self.config,
            state: PageIteratorState::default(),
            chat_id: chat_id.to_owned(),
            member_id_type: member_id_type.map(|v| v.to_owned()),
            page_size,
        }
    }

    pub async fn is_in_chat(
        &self,
        chat_id: &str,
        option: &RequestOption,
    ) -> Result<IsInChatChatMembersResp, LarkError> {
        let query = IsInChatMembersQuery::new(chat_id);
        self.is_in_chat_by_query(&query, option).await
    }

    pub async fn is_in_chat_by_query(
        &self,
        query: &IsInChatMembersQuery<'_>,
        option: &RequestOption,
    ) -> Result<IsInChatChatMembersResp, LarkError> {
        let path = format!(
            "/open-apis/im/v1/chats/{}/members/is_in_chat",
            query.chat_id
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .send_response::<IsInChatChatMembersRespData, IsInChatChatMembersResp>()
        .await
    }

    pub async fn me_join(
        &self,
        chat_id: &str,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/members/me_join");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .send_empty()
        .await
    }
}

pub struct ChatManagersResource<'a> {
    config: &'a Config,
}

impl<'a> ChatManagersResource<'a> {
    pub async fn add_managers(
        &self,
        chat_id: &str,
        member_id_type: Option<&str>,
        body: &UpdateChatManagersReqBody,
        option: &RequestOption,
    ) -> Result<AddManagersChatManagersResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/managers/add_managers");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("member_id_type", member_id_type)
        .json_body(body)?
        .send_response::<UpdateChatManagersRespData, AddManagersChatManagersResp>()
        .await
    }

    pub async fn delete_managers(
        &self,
        chat_id: &str,
        member_id_type: Option<&str>,
        body: &UpdateChatManagersReqBody,
        option: &RequestOption,
    ) -> Result<DeleteManagersChatManagersResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/managers/delete_managers");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("member_id_type", member_id_type)
        .json_body(body)?
        .send_response::<UpdateChatManagersRespData, DeleteManagersChatManagersResp>()
        .await
    }
}

pub struct ChatAnnouncementResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct GetChatAnnouncementQuery<'a> {
    pub chat_id: &'a str,
    pub user_id_type: Option<&'a str>,
}

impl<'a> GetChatAnnouncementQuery<'a> {
    pub fn new(chat_id: &'a str) -> Self {
        Self {
            chat_id,
            user_id_type: None,
        }
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }
}

impl<'a> ChatAnnouncementResource<'a> {
    pub async fn get(
        &self,
        chat_id: &str,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<GetChatAnnouncementResp, LarkError> {
        let query = GetChatAnnouncementQuery::new(chat_id).user_id_type(user_id_type);
        self.get_by_query(&query, option).await
    }

    pub async fn get_by_query(
        &self,
        query: &GetChatAnnouncementQuery<'_>,
        option: &RequestOption,
    ) -> Result<GetChatAnnouncementResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{}/announcement", query.chat_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .send_response::<GetChatAnnouncementRespData, GetChatAnnouncementResp>()
        .await
    }

    pub async fn patch(
        &self,
        chat_id: &str,
        body: &PatchChatAnnouncementReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/announcement");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_empty()
        .await
    }
}

pub struct ChatModerationResource<'a> {
    config: &'a Config,
}

#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct GetChatModerationQuery<'a> {
    pub chat_id: &'a str,
    pub user_id_type: Option<&'a str>,
    pub page_token: Option<&'a str>,
    pub page_size: Option<i64>,
}

impl<'a> GetChatModerationQuery<'a> {
    pub fn new(chat_id: &'a str) -> Self {
        Self {
            chat_id,
            user_id_type: None,
            page_token: None,
            page_size: None,
        }
    }

    pub fn user_id_type(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.user_id_type = value.into();
        self
    }

    pub fn page_token(mut self, value: impl Into<Option<&'a str>>) -> Self {
        self.page_token = value.into();
        self
    }

    pub fn page_size(mut self, value: impl Into<Option<i64>>) -> Self {
        self.page_size = value.into();
        self
    }
}

impl<'a> ChatModerationResource<'a> {
    pub async fn get(
        &self,
        chat_id: &str,
        user_id_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i64>,
        option: &RequestOption,
    ) -> Result<GetChatModerationResp, LarkError> {
        let query = GetChatModerationQuery {
            chat_id,
            user_id_type,
            page_token,
            page_size,
        };
        self.get_by_query(&query, option).await
    }

    pub async fn get_by_query(
        &self,
        query: &GetChatModerationQuery<'_>,
        option: &RequestOption,
    ) -> Result<GetChatModerationResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{}/moderation", query.chat_id);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .query("page_token", query.page_token)
        .query("page_size", query.page_size)
        .send_response::<GetChatModerationRespData, GetChatModerationResp>()
        .await
    }

    pub fn get_by_iterator(
        &self,
        chat_id: &str,
        user_id_type: Option<&str>,
        page_size: Option<i64>,
    ) -> GetChatModerationIterator<'a> {
        GetChatModerationIterator {
            config: self.config,
            state: PageIteratorState::default(),
            chat_id: chat_id.to_owned(),
            user_id_type: user_id_type.map(|v| v.to_owned()),
            page_size,
        }
    }

    pub async fn update(
        &self,
        chat_id: &str,
        user_id_type: Option<&str>,
        body: &UpdateChatModerationReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/moderation");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::User, AccessTokenType::Tenant],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_empty()
        .await
    }
}

pub struct ChatTabResource<'a> {
    config: &'a Config,
}

impl<'a> ChatTabResource<'a> {
    pub async fn create(
        &self,
        chat_id: &str,
        body: &ChatTabsReqBody,
        option: &RequestOption,
    ) -> Result<CreateChatTabResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/chat_tabs");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<ChatTabsRespData, CreateChatTabResp>()
        .await
    }

    pub async fn delete_tabs(
        &self,
        chat_id: &str,
        body: &DeleteChatTabsReqBody,
        option: &RequestOption,
    ) -> Result<DeleteTabsChatTabResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/chat_tabs/delete_tabs");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<ChatTabsRespData, DeleteTabsChatTabResp>()
        .await
    }

    pub async fn list_tabs(
        &self,
        chat_id: &str,
        option: &RequestOption,
    ) -> Result<ListTabsChatTabResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/chat_tabs/list_tabs");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_response::<ChatTabsRespData, ListTabsChatTabResp>()
        .await
    }

    pub async fn sort_tabs(
        &self,
        chat_id: &str,
        body: &SortChatTabsReqBody,
        option: &RequestOption,
    ) -> Result<SortTabsChatTabResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/chat_tabs/sort_tabs");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<ChatTabsRespData, SortTabsChatTabResp>()
        .await
    }

    pub async fn update_tabs(
        &self,
        chat_id: &str,
        body: &ChatTabsReqBody,
        option: &RequestOption,
    ) -> Result<UpdateTabsChatTabResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/chat_tabs/update_tabs");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<ChatTabsRespData, UpdateTabsChatTabResp>()
        .await
    }
}

pub struct ChatTopNoticeResource<'a> {
    config: &'a Config,
}

impl<'a> ChatTopNoticeResource<'a> {
    pub async fn put_top_notice(
        &self,
        chat_id: &str,
        body: &PutChatTopNoticeReqBody,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/top_notice/put_top_notice");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_empty()
        .await
    }

    pub async fn delete_top_notice(
        &self,
        chat_id: &str,
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/top_notice/delete_top_notice");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_empty()
        .await
    }
}

pub struct ChatMenuItemResource<'a> {
    config: &'a Config,
}

impl<'a> ChatMenuItemResource<'a> {
    pub async fn patch(
        &self,
        chat_id: &str,
        menu_item_id: &str,
        body: &PatchChatMenuItemReqBody,
        option: &RequestOption,
    ) -> Result<PatchChatMenuItemResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/menu_items/{menu_item_id}");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_response::<PatchChatMenuItemRespData, PatchChatMenuItemResp>()
        .await
    }
}

pub struct ChatMenuTreeResource<'a> {
    config: &'a Config,
}

impl<'a> ChatMenuTreeResource<'a> {
    pub async fn create(
        &self,
        chat_id: &str,
        body: &CreateChatMenuTreeReqBody,
        option: &RequestOption,
    ) -> Result<CreateChatMenuTreeResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/menu_tree");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_response::<ChatMenuTreeRespData, CreateChatMenuTreeResp>()
        .await
    }

    pub async fn delete(
        &self,
        chat_id: &str,
        body: &DeleteChatMenuTreeReqBody,
        option: &RequestOption,
    ) -> Result<DeleteChatMenuTreeResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/menu_tree");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_response::<ChatMenuTreeRespData, DeleteChatMenuTreeResp>()
        .await
    }

    pub async fn get(
        &self,
        chat_id: &str,
        option: &RequestOption,
    ) -> Result<GetChatMenuTreeResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/menu_tree");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .send_response::<ChatMenuTreeRespData, GetChatMenuTreeResp>()
        .await
    }

    pub async fn sort(
        &self,
        chat_id: &str,
        body: &SortChatMenuTreeReqBody,
        option: &RequestOption,
    ) -> Result<SortChatMenuTreeResp, LarkError> {
        let path = format!("/open-apis/im/v1/chats/{chat_id}/menu_tree/sort");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .json_body(body)?
        .send_response::<ChatMenuTreeRespData, SortChatMenuTreeResp>()
        .await
    }
}

pub struct ThreadResource<'a> {
    config: &'a Config,
}

impl<'a> ThreadResource<'a> {
    pub async fn forward(
        &self,
        thread_id: &str,
        receive_id_type: &str,
        uuid: Option<&str>,
        body: &ForwardThreadReqBody,
        option: &RequestOption,
    ) -> Result<ForwardThreadResp, LarkError> {
        let path = format!("/open-apis/im/v1/threads/{thread_id}/forward");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant],
            option,
        )
        .query("receive_id_type", receive_id_type)
        .query("uuid", uuid)
        .json_body(body)?
        .send_response::<MessageRespData, ForwardThreadResp>()
        .await
    }
}

// ── Version struct ──

pub struct V1<'a> {
    pub message: MessageResource<'a>,
    pub message_reaction: MessageReactionResource<'a>,
    pub message_resource: MessageResourceDownload<'a>,
    pub batch_message: BatchMessageResource<'a>,
    pub pin: PinResource<'a>,
    pub file: FileResource<'a>,
    pub image: ImageResource<'a>,
    pub chat: ChatResource<'a>,
    pub chat_members: ChatMembersResource<'a>,
    pub chat_managers: ChatManagersResource<'a>,
    pub chat_announcement: ChatAnnouncementResource<'a>,
    pub chat_moderation: ChatModerationResource<'a>,
    pub chat_tab: ChatTabResource<'a>,
    pub chat_top_notice: ChatTopNoticeResource<'a>,
    pub chat_menu_item: ChatMenuItemResource<'a>,
    pub chat_menu_tree: ChatMenuTreeResource<'a>,
    pub thread: ThreadResource<'a>,
}

impl<'a> V1<'a> {
    pub fn new(config: &'a Config) -> Self {
        Self {
            message: MessageResource { config },
            message_reaction: MessageReactionResource { config },
            message_resource: MessageResourceDownload { config },
            batch_message: BatchMessageResource { config },
            pin: PinResource { config },
            file: FileResource { config },
            image: ImageResource { config },
            chat: ChatResource { config },
            chat_members: ChatMembersResource { config },
            chat_managers: ChatManagersResource { config },
            chat_announcement: ChatAnnouncementResource { config },
            chat_moderation: ChatModerationResource { config },
            chat_tab: ChatTabResource { config },
            chat_top_notice: ChatTopNoticeResource { config },
            chat_menu_item: ChatMenuItemResource { config },
            chat_menu_tree: ChatMenuTreeResource { config },
            thread: ThreadResource { config },
        }
    }
}
