use serde::{Deserialize, Serialize};

use crate::config::Config;
use crate::constants::AccessTokenType;
use crate::error::LarkError;
use crate::req::RequestOption;
use crate::service::common::{
    EmptyResp, PageIteratorState, PageQuery, RestRequest, impl_page_iterator_controls,
};

// ── Domain types ──

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct App {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub app_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub revision: Option<i64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub is_advanced: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub time_zone: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTable {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub table_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub revision: Option<i64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTableView {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub view_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub view_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub view_public_level: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub view_type: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTableField {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none", rename = "type")]
    pub type_: Option<i32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub property: Option<serde_json::Value>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<AppTableFieldDescription>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub is_primary: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub is_hidden: Option<bool>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTableRecord {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub record_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub fields: Option<std::collections::HashMap<String, serde_json::Value>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub created_by: Option<serde_json::Value>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub created_time: Option<i64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub last_modified_by: Option<serde_json::Value>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub last_modified_time: Option<i64>,
}

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

// ── Request body types ──

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateAppReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_advanced: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_zone: Option<String>,
}

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

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

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

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

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

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateFieldReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub field_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
    pub type_: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub property: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<AppTableFieldDescription>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateFieldReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub field_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
    pub type_: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub property: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<AppTableFieldDescription>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateRecordReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fields: Option<std::collections::HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateRecordReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fields: Option<std::collections::HashMap<String, serde_json::Value>>,
}

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

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

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

// ── Additional domain types ──

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRoleTableRole {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub table_perm: Option<i32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub table_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub table_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_perm: Option<std::collections::HashMap<String, i32>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub allow_add_record: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub allow_delete_record: Option<bool>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRoleBlockRole {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub block_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub block_type: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub block_perm: Option<i32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRole {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub role_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub role_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub table_roles: Option<Vec<AppRoleTableRole>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub block_roles: Option<Vec<AppRoleBlockRole>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRoleMemberId {
    #[serde(default, skip_serializing_if = "Option::is_none", rename = "type")]
    pub type_: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRoleMember {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member_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>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub user_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 department_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_department_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member_en_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member_type: Option<String>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTableForm {
    #[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 shared: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub shared_url: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub shared_limit: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub submit_limit_once: Option<bool>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTableFormField {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub required: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub visible: Option<bool>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppTableFormPatchedField {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pre_field_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub required: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub visible: Option<bool>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Condition {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operator: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChildrenFilter {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub conjunction: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub conditions: Option<Vec<Condition>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FilterInfo {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub conjunction: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub conditions: Option<Vec<Condition>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<ChildrenFilter>>,
}

// ── Additional request body types ──

/// Body for `app.copy`
#[derive(Debug, Clone, Default, Serialize)]
pub struct CopyAppReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub folder_token: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub without_content: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_zone: Option<String>,
}

/// Body for `app.create`
#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateAppReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub folder_token: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_zone: Option<String>,
}

/// Body for `app_role.create` / `app_role.update`
#[derive(Debug, Clone, Default, Serialize)]
pub struct AppRoleReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub role_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub table_roles: Option<Vec<AppRoleTableRole>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub block_roles: Option<Vec<AppRoleBlockRole>>,
}

/// Body for `app_role_member.batch_create` and `app_role_member.batch_delete`
#[derive(Debug, Clone, Default, Serialize)]
pub struct BatchAppRoleMemberReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub member_list: Option<Vec<AppRoleMemberId>>,
}

/// Body for `app_role_member.create`
#[derive(Debug, Clone, Default, Serialize)]
pub struct CreateAppRoleMemberReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub member_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
    pub type_: Option<String>,
}

/// Body for `app_workflow.update`
#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateWorkflowReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<String>,
}

/// Body for `app_dashboard.copy`
#[derive(Debug, Clone, Default, Serialize)]
pub struct CopyAppDashboardReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

/// Body for `app_table_record.batch_get`
#[derive(Debug, Clone, Default, Serialize)]
pub struct BatchGetRecordReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub record_ids: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub with_shared_url: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub automatic_fields: Option<bool>,
}

/// Body for `app_table_record.search`
#[derive(Debug, Clone, Default, Serialize)]
pub struct SearchRecordReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub view_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub field_names: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sort: Option<Vec<Sort>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filter: Option<FilterInfo>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub automatic_fields: Option<bool>,
}

/// Body for `app_table_form.patch`
#[derive(Debug, Clone, Default, Serialize)]
pub struct PatchAppTableFormReqBody {
    #[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 shared: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub shared_limit: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub submit_limit_once: Option<bool>,
}

/// Body for `app_table_form_field.patch`
#[derive(Debug, Clone, Default, Serialize)]
pub struct PatchAppTableFormFieldReqBody {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pre_field_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub visible: Option<bool>,
}

// ── Additional data types ──

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRoleListData {
    #[serde(default)]
    pub items: Vec<AppRole>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppRoleMemberListData {
    #[serde(default)]
    pub items: Vec<AppRoleMember>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WorkflowListData {
    #[serde(default)]
    pub workflows: Vec<AppWorkflow>,
}

// ── Response wrappers ──

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TableData {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub table_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub default_view_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_id_list: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchCreateTableData {
    #[serde(default)]
    pub table_ids: Vec<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TableListData {
    #[serde(default)]
    pub items: Vec<AppTable>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ViewListData {
    #[serde(default)]
    pub items: Vec<AppTableView>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FieldListData {
    #[serde(default)]
    pub items: Vec<AppTableField>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RecordListData {
    #[serde(default)]
    pub items: Vec<AppTableRecord>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchCreateRecordData {
    #[serde(default)]
    pub records: Vec<AppTableRecord>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchUpdateRecordData {
    #[serde(default)]
    pub records: Vec<AppTableRecord>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchDeleteRecordData {
    #[serde(default)]
    pub records: Vec<serde_json::Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DashboardListData {
    #[serde(default)]
    pub dashboards: Vec<Dashboard>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BatchGetRecordData {
    #[serde(default)]
    pub records: Vec<AppTableRecord>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub forbidden_record_ids: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub absent_record_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SearchRecordData {
    #[serde(default)]
    pub items: Vec<AppTableRecord>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FormFieldListData {
    #[serde(default)]
    pub items: Vec<AppTableFormField>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub page_token: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub has_more: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub total: Option<i32>,
}

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

impl_resp!(GetAppResp, AppData);
impl_resp!(UpdateAppResp, AppData);
impl_resp!(CreateTableResp, TableData);
impl_resp!(BatchCreateTableResp, BatchCreateTableData);
impl_resp!(ListTableResp, TableListData);
impl_resp!(CreateViewResp, ViewData);
impl_resp!(GetViewResp, ViewData);
impl_resp!(PatchViewResp, ViewData);
impl_resp!(ListViewResp, ViewListData);
impl_resp!(CreateFieldResp, FieldData);
impl_resp!(UpdateFieldResp, FieldData);
impl_resp!(ListFieldResp, FieldListData);
impl_resp!(CreateRecordResp, RecordData);
impl_resp!(UpdateRecordResp, RecordData);
impl_resp!(GetRecordResp, RecordData);
impl_resp!(ListRecordResp, RecordListData);
impl_resp!(BatchCreateRecordResp, BatchCreateRecordData);
impl_resp!(BatchUpdateRecordResp, BatchUpdateRecordData);
impl_resp!(BatchDeleteRecordResp, BatchDeleteRecordData);
impl_resp!(ListDashboardResp, DashboardListData);
impl_resp!(CopyAppDashboardResp, CopyAppDashboardData);
impl_resp!(BatchGetRecordResp, BatchGetRecordData);
impl_resp!(SearchRecordResp, SearchRecordData);
impl_resp!(GetFormResp, FormData);
impl_resp!(PatchFormResp, FormData);
impl_resp!(ListFormFieldResp, FormFieldListData);
impl_resp!(PatchFormFieldResp, FormFieldPatchData);

// ── v2 helpers (Option<CodeError>, used for newer endpoints) ──

// app.copy / app.create
impl_resp_v2!(CopyAppResp, serde_json::Value);
impl_resp_v2!(CreateAppResp, serde_json::Value);

// app_role
impl_resp_v2!(CreateAppRoleResp, AppRoleData);
impl_resp_v2!(DeleteAppRoleResp, ());
impl_resp_v2!(ListAppRoleResp, AppRoleListData);
impl_resp_v2!(UpdateAppRoleResp, AppRoleData);

// app_role_member
impl_resp_v2!(BatchCreateAppRoleMemberResp, ());
impl_resp_v2!(BatchDeleteAppRoleMemberResp, ());
impl_resp_v2!(CreateAppRoleMemberResp, ());
impl_resp_v2!(DeleteAppRoleMemberResp, ());
impl_resp_v2!(ListAppRoleMemberResp, AppRoleMemberListData);

// app_workflow
impl_resp_v2!(ListAppWorkflowResp, WorkflowListData);
impl_resp_v2!(UpdateAppWorkflowResp, ());

// ── Iterators ──

#[derive(Debug, Clone)]
pub struct ListTableIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<AppTable>,
    app_token: String,
    page_size: Option<i32>,
}

impl_page_iterator_controls!(ListTableIterator);

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

        let resource = AppTableResource {
            config: self.config,
        };
        let resp = resource
            .list(
                &self.app_token,
                self.page_size,
                self.state.page_token_for_request(),
                option,
            )
            .await?;
        let data = resp.data.unwrap_or_default();
        self.state
            .accept_page(Some(data.items), data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct ListViewIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<AppTableView>,
    app_token: String,
    table_id: String,
    page_size: Option<i32>,
    user_id_type: Option<String>,
}

impl_page_iterator_controls!(ListViewIterator);

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

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

#[derive(Debug, Clone)]
pub struct ListFieldIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<AppTableField>,
    app_token: String,
    table_id: String,
    view_id: Option<String>,
    text_field_as_array: Option<bool>,
    user_id_type: Option<String>,
    page_size: Option<i32>,
}

impl_page_iterator_controls!(ListFieldIterator);

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

        let resource = AppTableFieldResource {
            config: self.config,
        };
        let resp = resource
            .list(
                &self.app_token,
                &self.table_id,
                self.view_id.as_deref(),
                self.text_field_as_array,
                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(Some(data.items), data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct ListRecordIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<AppTableRecord>,
    app_token: String,
    table_id: String,
    view_id: Option<String>,
    filter: Option<String>,
    sort: Option<String>,
    field_names: Option<String>,
    text_field_as_array: Option<bool>,
    user_id_type: Option<String>,
    page_size: Option<i32>,
}

impl_page_iterator_controls!(ListRecordIterator);

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

        let resource = AppTableRecordResource {
            config: self.config,
        };
        let resp = resource
            .list(
                &self.app_token,
                &self.table_id,
                self.view_id.as_deref(),
                self.filter.as_deref(),
                self.sort.as_deref(),
                self.field_names.as_deref(),
                self.text_field_as_array,
                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(Some(data.items), data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

#[derive(Debug, Clone)]
pub struct SearchRecordIterator<'a> {
    config: &'a Config,
    state: PageIteratorState<AppTableRecord>,
    app_token: String,
    table_id: String,
    user_id_type: Option<String>,
    page_size: Option<i32>,
    body: SearchRecordReqBody,
}

impl_page_iterator_controls!(SearchRecordIterator);

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

        let resource = AppTableRecordResource {
            config: self.config,
        };
        let resp = resource
            .search(
                &self.app_token,
                &self.table_id,
                &self.body,
                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(Some(data.items), data.page_token, data.has_more);
        Ok(self.state.pop())
    }
}

// ── Resources ──

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

impl<'a> AppResource<'a> {
    /// Copy a bitable app.
    /// POST /open-apis/bitable/v1/apps/:app_token/copy
    pub async fn copy(
        &self,
        app_token: &str,
        body: &CopyAppReqBody,
        option: &RequestOption,
    ) -> Result<CopyAppResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/copy");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<serde_json::Value, CopyAppResp>()
        .await
    }

    /// Create a new bitable app.
    /// POST /open-apis/bitable/v1/apps
    pub async fn create(
        &self,
        body: &CreateAppReqBody,
        option: &RequestOption,
    ) -> Result<CreateAppResp, LarkError> {
        RestRequest::new(
            self.config,
            http::Method::POST,
            "/open-apis/bitable/v1/apps",
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<serde_json::Value, CreateAppResp>()
        .await
    }

    pub async fn get(
        &self,
        app_token: &str,
        option: &RequestOption,
    ) -> Result<GetAppResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_response::<AppData, GetAppResp>()
        .await
    }

    pub async fn update(
        &self,
        app_token: &str,
        body: &UpdateAppReqBody,
        option: &RequestOption,
    ) -> Result<UpdateAppResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<AppData, UpdateAppResp>()
        .await
    }
}

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListTableQuery<'a> {
    pub app_token: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppTableResource<'a> {
    pub async fn create(
        &self,
        app_token: &str,
        body: &CreateTableReqBody,
        option: &RequestOption,
    ) -> Result<CreateTableResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/tables");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<TableData, CreateTableResp>()
        .await
    }

    pub async fn batch_create(
        &self,
        app_token: &str,
        body: &BatchCreateTableReqBody,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<BatchCreateTableResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/tables/batch_create");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<BatchCreateTableData, BatchCreateTableResp>()
        .await
    }

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

    pub async fn batch_delete(
        &self,
        app_token: &str,
        table_ids: &[&str],
        option: &RequestOption,
    ) -> Result<EmptyResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/tables/batch_delete");
        let body = serde_json::json!({ "table_ids": table_ids });
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(&body)?
        .send_empty()
        .await
    }

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

    pub async fn list(
        &self,
        app_token: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListTableResp, LarkError> {
        let query = ListTableQuery::new(app_token)
            .page_size(page_size)
            .page_token(page_token);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListTableQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListTableResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{}/tables", query.app_token);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .send_response::<TableListData, ListTableResp>()
        .await
    }

    pub fn list_by_iterator(
        &self,
        app_token: &str,
        page_size: Option<i32>,
    ) -> ListTableIterator<'a> {
        let query = ListTableQuery::new(app_token).page_size(page_size);
        self.list_iterator_by_query(&query)
    }

    pub fn list_iterator_by_query(&self, query: &ListTableQuery<'_>) -> ListTableIterator<'a> {
        ListTableIterator {
            config: self.config,
            state: PageIteratorState::default()
                .with_page_token(query.page_token.map(ToOwned::to_owned)),
            app_token: query.app_token.to_owned(),
            page_size: query.page_size,
        }
    }
}

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListViewQuery<'a> {
    pub app_token: &'a str,
    pub table_id: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
    pub user_id_type: Option<&'a str>,
}

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

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

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppTableViewResource<'a> {
    pub async fn create(
        &self,
        app_token: &str,
        table_id: &str,
        body: &CreateViewReqBody,
        option: &RequestOption,
    ) -> Result<CreateViewResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/views");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<ViewData, CreateViewResp>()
        .await
    }

    pub async fn get(
        &self,
        app_token: &str,
        table_id: &str,
        view_id: &str,
        option: &RequestOption,
    ) -> Result<GetViewResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_response::<ViewData, GetViewResp>()
        .await
    }

    pub async fn patch(
        &self,
        app_token: &str,
        table_id: &str,
        view_id: &str,
        body: &PatchViewReqBody,
        option: &RequestOption,
    ) -> Result<PatchViewResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<ViewData, PatchViewResp>()
        .await
    }

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

    pub async fn list(
        &self,
        app_token: &str,
        table_id: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListViewResp, LarkError> {
        let query = ListViewQuery::new(app_token, table_id)
            .page_size(page_size)
            .page_token(page_token)
            .user_id_type(user_id_type);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListViewQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListViewResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{}/tables/{}/views",
            query.app_token, query.table_id
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .query("user_id_type", query.user_id_type)
        .send_response::<ViewListData, ListViewResp>()
        .await
    }

    pub fn list_by_iterator(
        &self,
        app_token: &str,
        table_id: &str,
        page_size: Option<i32>,
        user_id_type: Option<&str>,
    ) -> ListViewIterator<'a> {
        let query = ListViewQuery::new(app_token, table_id)
            .page_size(page_size)
            .user_id_type(user_id_type);
        self.list_iterator_by_query(&query)
    }

    pub fn list_iterator_by_query(&self, query: &ListViewQuery<'_>) -> ListViewIterator<'a> {
        ListViewIterator {
            config: self.config,
            state: PageIteratorState::default()
                .with_page_token(query.page_token.map(ToOwned::to_owned)),
            app_token: query.app_token.to_owned(),
            table_id: query.table_id.to_owned(),
            page_size: query.page_size,
            user_id_type: query.user_id_type.map(ToOwned::to_owned),
        }
    }
}

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListFieldQuery<'a> {
    pub app_token: &'a str,
    pub table_id: &'a str,
    pub view_id: Option<&'a str>,
    pub text_field_as_array: Option<bool>,
    pub user_id_type: Option<&'a str>,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

impl<'a> ListFieldQuery<'a> {
    pub fn new(app_token: &'a str, table_id: &'a str) -> Self {
        Self {
            app_token,
            table_id,
            view_id: None,
            text_field_as_array: None,
            user_id_type: None,
            page_size: None,
            page_token: None,
        }
    }

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

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

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppTableFieldResource<'a> {
    pub async fn create(
        &self,
        app_token: &str,
        table_id: &str,
        body: &CreateFieldReqBody,
        option: &RequestOption,
    ) -> Result<CreateFieldResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<FieldData, CreateFieldResp>()
        .await
    }

    pub async fn update(
        &self,
        app_token: &str,
        table_id: &str,
        field_id: &str,
        body: &UpdateFieldReqBody,
        option: &RequestOption,
    ) -> Result<UpdateFieldResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields/{field_id}");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<FieldData, UpdateFieldResp>()
        .await
    }

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

    #[allow(clippy::too_many_arguments)]
    pub async fn list(
        &self,
        app_token: &str,
        table_id: &str,
        view_id: Option<&str>,
        text_field_as_array: Option<bool>,
        user_id_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i32>,
        option: &RequestOption,
    ) -> Result<ListFieldResp, LarkError> {
        let query = ListFieldQuery::new(app_token, table_id)
            .view_id(view_id)
            .text_field_as_array(text_field_as_array)
            .user_id_type(user_id_type)
            .page_token(page_token)
            .page_size(page_size);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListFieldQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListFieldResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{}/tables/{}/fields",
            query.app_token, query.table_id
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("view_id", query.view_id)
        .query("text_field_as_array", query.text_field_as_array)
        .query("user_id_type", query.user_id_type)
        .page_query(query.page_query())
        .send_response::<FieldListData, ListFieldResp>()
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub fn list_by_iterator(
        &self,
        app_token: &str,
        table_id: &str,
        view_id: Option<&str>,
        text_field_as_array: Option<bool>,
        user_id_type: Option<&str>,
        page_size: Option<i32>,
    ) -> ListFieldIterator<'a> {
        let query = ListFieldQuery::new(app_token, table_id)
            .view_id(view_id)
            .text_field_as_array(text_field_as_array)
            .user_id_type(user_id_type)
            .page_size(page_size);
        self.list_iterator_by_query(&query)
    }

    pub fn list_iterator_by_query(&self, query: &ListFieldQuery<'_>) -> ListFieldIterator<'a> {
        ListFieldIterator {
            config: self.config,
            state: PageIteratorState::default()
                .with_page_token(query.page_token.map(ToOwned::to_owned)),
            app_token: query.app_token.to_owned(),
            table_id: query.table_id.to_owned(),
            view_id: query.view_id.map(ToOwned::to_owned),
            text_field_as_array: query.text_field_as_array,
            user_id_type: query.user_id_type.map(ToOwned::to_owned),
            page_size: query.page_size,
        }
    }
}

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListRecordQuery<'a> {
    pub app_token: &'a str,
    pub table_id: &'a str,
    pub view_id: Option<&'a str>,
    pub filter: Option<&'a str>,
    pub sort: Option<&'a str>,
    pub field_names: Option<&'a str>,
    pub text_field_as_array: Option<bool>,
    pub user_id_type: Option<&'a str>,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

impl<'a> ListRecordQuery<'a> {
    pub fn new(app_token: &'a str, table_id: &'a str) -> Self {
        Self {
            app_token,
            table_id,
            view_id: None,
            filter: None,
            sort: None,
            field_names: None,
            text_field_as_array: None,
            user_id_type: None,
            page_size: None,
            page_token: None,
        }
    }

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

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

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

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

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

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SearchRecordQuery<'a> {
    pub app_token: &'a str,
    pub table_id: &'a str,
    pub user_id_type: Option<&'a str>,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

impl<'a> SearchRecordQuery<'a> {
    pub fn new(app_token: &'a str, table_id: &'a str) -> Self {
        Self {
            app_token,
            table_id,
            user_id_type: None,
            page_size: None,
            page_token: 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_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppTableRecordResource<'a> {
    pub async fn create(
        &self,
        app_token: &str,
        table_id: &str,
        body: &CreateRecordReqBody,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<CreateRecordResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<RecordData, CreateRecordResp>()
        .await
    }

    pub async fn update(
        &self,
        app_token: &str,
        table_id: &str,
        record_id: &str,
        body: &UpdateRecordReqBody,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<UpdateRecordResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_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_response::<RecordData, UpdateRecordResp>()
        .await
    }

    pub async fn get(
        &self,
        app_token: &str,
        table_id: &str,
        record_id: &str,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<GetRecordResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", user_id_type)
        .send_response::<RecordData, GetRecordResp>()
        .await
    }

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

    #[allow(clippy::too_many_arguments)]
    pub async fn list(
        &self,
        app_token: &str,
        table_id: &str,
        view_id: Option<&str>,
        filter: Option<&str>,
        sort: Option<&str>,
        field_names: Option<&str>,
        text_field_as_array: Option<bool>,
        user_id_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i32>,
        option: &RequestOption,
    ) -> Result<ListRecordResp, LarkError> {
        let query = ListRecordQuery::new(app_token, table_id)
            .view_id(view_id)
            .filter(filter)
            .sort(sort)
            .field_names(field_names)
            .text_field_as_array(text_field_as_array)
            .user_id_type(user_id_type)
            .page_token(page_token)
            .page_size(page_size);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListRecordQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListRecordResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{}/tables/{}/records",
            query.app_token, query.table_id
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("view_id", query.view_id)
        .query("filter", query.filter)
        .query("sort", query.sort)
        .query("field_names", query.field_names)
        .query("text_field_as_array", query.text_field_as_array)
        .query("user_id_type", query.user_id_type)
        .page_query(query.page_query())
        .send_response::<RecordListData, ListRecordResp>()
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub fn list_by_iterator(
        &self,
        app_token: &str,
        table_id: &str,
        view_id: Option<&str>,
        filter: Option<&str>,
        sort: Option<&str>,
        field_names: Option<&str>,
        text_field_as_array: Option<bool>,
        user_id_type: Option<&str>,
        page_size: Option<i32>,
    ) -> ListRecordIterator<'a> {
        let query = ListRecordQuery::new(app_token, table_id)
            .view_id(view_id)
            .filter(filter)
            .sort(sort)
            .field_names(field_names)
            .text_field_as_array(text_field_as_array)
            .user_id_type(user_id_type)
            .page_size(page_size);
        self.list_iterator_by_query(&query)
    }

    pub fn list_iterator_by_query(&self, query: &ListRecordQuery<'_>) -> ListRecordIterator<'a> {
        ListRecordIterator {
            config: self.config,
            state: PageIteratorState::default()
                .with_page_token(query.page_token.map(ToOwned::to_owned)),
            app_token: query.app_token.to_owned(),
            table_id: query.table_id.to_owned(),
            view_id: query.view_id.map(ToOwned::to_owned),
            filter: query.filter.map(ToOwned::to_owned),
            sort: query.sort.map(ToOwned::to_owned),
            field_names: query.field_names.map(ToOwned::to_owned),
            text_field_as_array: query.text_field_as_array,
            user_id_type: query.user_id_type.map(ToOwned::to_owned),
            page_size: query.page_size,
        }
    }

    pub async fn batch_create(
        &self,
        app_token: &str,
        table_id: &str,
        body: &BatchCreateRecordReqBody,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<BatchCreateRecordResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create"
        );
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<BatchCreateRecordData, BatchCreateRecordResp>()
        .await
    }

    pub async fn batch_update(
        &self,
        app_token: &str,
        table_id: &str,
        body: &BatchUpdateRecordReqBody,
        user_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<BatchUpdateRecordResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
        );
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", user_id_type)
        .json_body(body)?
        .send_response::<BatchUpdateRecordData, BatchUpdateRecordResp>()
        .await
    }

    pub async fn batch_delete(
        &self,
        app_token: &str,
        table_id: &str,
        body: &BatchDeleteRecordReqBody,
        option: &RequestOption,
    ) -> Result<BatchDeleteRecordResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete"
        );
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<BatchDeleteRecordData, BatchDeleteRecordResp>()
        .await
    }

    /// Batch-get records.
    /// POST /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_get
    pub async fn batch_get(
        &self,
        app_token: &str,
        table_id: &str,
        body: &BatchGetRecordReqBody,
        option: &RequestOption,
    ) -> Result<BatchGetRecordResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_get");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<BatchGetRecordData, BatchGetRecordResp>()
        .await
    }

    /// Search records.
    /// POST /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/search
    #[allow(clippy::too_many_arguments)]
    pub async fn search(
        &self,
        app_token: &str,
        table_id: &str,
        body: &SearchRecordReqBody,
        user_id_type: Option<&str>,
        page_token: Option<&str>,
        page_size: Option<i32>,
        option: &RequestOption,
    ) -> Result<SearchRecordResp, LarkError> {
        let query = SearchRecordQuery::new(app_token, table_id)
            .user_id_type(user_id_type)
            .page_token(page_token)
            .page_size(page_size);
        self.search_by_query(&query, body, option).await
    }

    pub async fn search_by_query(
        &self,
        query: &SearchRecordQuery<'_>,
        body: &SearchRecordReqBody,
        option: &RequestOption,
    ) -> Result<SearchRecordResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{}/tables/{}/records/search",
            query.app_token, query.table_id
        );
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("user_id_type", query.user_id_type)
        .page_query(query.page_query())
        .json_body(body)?
        .send_response::<SearchRecordData, SearchRecordResp>()
        .await
    }

    pub fn search_by_iterator(
        &self,
        app_token: &str,
        table_id: &str,
        body: &SearchRecordReqBody,
        user_id_type: Option<&str>,
        page_size: Option<i32>,
    ) -> SearchRecordIterator<'a> {
        let query = SearchRecordQuery::new(app_token, table_id)
            .user_id_type(user_id_type)
            .page_size(page_size);
        self.search_iterator_by_query(&query, body)
    }

    pub fn search_iterator_by_query(
        &self,
        query: &SearchRecordQuery<'_>,
        body: &SearchRecordReqBody,
    ) -> SearchRecordIterator<'a> {
        SearchRecordIterator {
            config: self.config,
            state: PageIteratorState::default()
                .with_page_token(query.page_token.map(ToOwned::to_owned)),
            app_token: query.app_token.to_owned(),
            table_id: query.table_id.to_owned(),
            user_id_type: query.user_id_type.map(ToOwned::to_owned),
            page_size: query.page_size,
            body: body.clone(),
        }
    }
}

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListDashboardQuery<'a> {
    pub app_token: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppDashboardResource<'a> {
    /// Copy a dashboard.
    /// POST /open-apis/bitable/v1/apps/:app_token/dashboards/:block_id/copy
    pub async fn copy(
        &self,
        app_token: &str,
        block_id: &str,
        body: &CopyAppDashboardReqBody,
        option: &RequestOption,
    ) -> Result<CopyAppDashboardResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/dashboards/{block_id}/copy");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<CopyAppDashboardData, CopyAppDashboardResp>()
        .await
    }

    pub async fn list(
        &self,
        app_token: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListDashboardResp, LarkError> {
        let query = ListDashboardQuery::new(app_token)
            .page_size(page_size)
            .page_token(page_token);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListDashboardQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListDashboardResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{}/dashboards", query.app_token);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .send_response::<DashboardListData, ListDashboardResp>()
        .await
    }
}

// ── AppRoleResource ──

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListAppRoleQuery<'a> {
    pub app_token: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppRoleResource<'a> {
    /// Create a custom role.
    /// POST /open-apis/bitable/v1/apps/:app_token/roles
    pub async fn create(
        &self,
        app_token: &str,
        body: &AppRoleReqBody,
        option: &RequestOption,
    ) -> Result<CreateAppRoleResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/roles");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<AppRoleData, CreateAppRoleResp>()
        .await
    }

    /// Delete a custom role.
    /// DELETE /open-apis/bitable/v1/apps/:app_token/roles/:role_id
    pub async fn delete(
        &self,
        app_token: &str,
        role_id: &str,
        option: &RequestOption,
    ) -> Result<DeleteAppRoleResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/roles/{role_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_v2_response::<(), DeleteAppRoleResp>()
        .await
    }

    /// List custom roles.
    /// GET /open-apis/bitable/v1/apps/:app_token/roles
    pub async fn list(
        &self,
        app_token: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListAppRoleResp, LarkError> {
        let query = ListAppRoleQuery::new(app_token)
            .page_size(page_size)
            .page_token(page_token);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListAppRoleQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListAppRoleResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{}/roles", query.app_token);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .send_v2_response::<AppRoleListData, ListAppRoleResp>()
        .await
    }

    /// Update a custom role (full replacement).
    /// PUT /open-apis/bitable/v1/apps/:app_token/roles/:role_id
    pub async fn update(
        &self,
        app_token: &str,
        role_id: &str,
        body: &AppRoleReqBody,
        option: &RequestOption,
    ) -> Result<UpdateAppRoleResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/roles/{role_id}");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<AppRoleData, UpdateAppRoleResp>()
        .await
    }
}

// ── AppRoleMemberResource ──

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListAppRoleMemberQuery<'a> {
    pub app_token: &'a str,
    pub role_id: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

impl<'a> ListAppRoleMemberQuery<'a> {
    pub fn new(app_token: &'a str, role_id: &'a str) -> Self {
        Self {
            app_token,
            role_id,
            page_size: None,
            page_token: None,
        }
    }

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppRoleMemberResource<'a> {
    /// Batch-create members for a custom role.
    /// POST /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/batch_create
    pub async fn batch_create(
        &self,
        app_token: &str,
        role_id: &str,
        body: &BatchAppRoleMemberReqBody,
        option: &RequestOption,
    ) -> Result<BatchCreateAppRoleMemberResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/roles/{role_id}/members/batch_create");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<(), BatchCreateAppRoleMemberResp>()
        .await
    }

    /// Batch-delete members from a custom role.
    /// POST /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/batch_delete
    pub async fn batch_delete(
        &self,
        app_token: &str,
        role_id: &str,
        body: &BatchAppRoleMemberReqBody,
        option: &RequestOption,
    ) -> Result<BatchDeleteAppRoleMemberResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/roles/{role_id}/members/batch_delete");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<(), BatchDeleteAppRoleMemberResp>()
        .await
    }

    /// Add a single member to a custom role.
    /// POST /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members
    pub async fn create(
        &self,
        app_token: &str,
        role_id: &str,
        body: &CreateAppRoleMemberReqBody,
        member_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<CreateAppRoleMemberResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/roles/{role_id}/members");
        RestRequest::new(
            self.config,
            http::Method::POST,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("member_id_type", member_id_type)
        .json_body(body)?
        .send_v2_response::<(), CreateAppRoleMemberResp>()
        .await
    }

    /// Remove a single member from a custom role.
    /// DELETE /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/:member_id
    pub async fn delete(
        &self,
        app_token: &str,
        role_id: &str,
        member_id: &str,
        member_id_type: Option<&str>,
        option: &RequestOption,
    ) -> Result<DeleteAppRoleMemberResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/roles/{role_id}/members/{member_id}");
        RestRequest::new(
            self.config,
            http::Method::DELETE,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .query("member_id_type", member_id_type)
        .send_v2_response::<(), DeleteAppRoleMemberResp>()
        .await
    }

    /// List members of a custom role.
    /// GET /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members
    pub async fn list(
        &self,
        app_token: &str,
        role_id: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListAppRoleMemberResp, LarkError> {
        let query = ListAppRoleMemberQuery::new(app_token, role_id)
            .page_size(page_size)
            .page_token(page_token);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListAppRoleMemberQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListAppRoleMemberResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{}/roles/{}/members",
            query.app_token, query.role_id
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .send_v2_response::<AppRoleMemberListData, ListAppRoleMemberResp>()
        .await
    }
}

// ── AppWorkflowResource ──

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListAppWorkflowQuery<'a> {
    pub app_token: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

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

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppWorkflowResource<'a> {
    /// List automation workflows for a bitable app.
    /// GET /open-apis/bitable/v1/apps/:app_token/workflows
    pub async fn list(
        &self,
        app_token: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListAppWorkflowResp, LarkError> {
        let query = ListAppWorkflowQuery::new(app_token)
            .page_size(page_size)
            .page_token(page_token);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListAppWorkflowQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListAppWorkflowResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{}/workflows", query.app_token);
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .send_v2_response::<WorkflowListData, ListAppWorkflowResp>()
        .await
    }

    /// Update the enabled/disabled status of an automation workflow.
    /// PUT /open-apis/bitable/v1/apps/:app_token/workflows/:workflow_id
    pub async fn update(
        &self,
        app_token: &str,
        workflow_id: &str,
        body: &UpdateWorkflowReqBody,
        option: &RequestOption,
    ) -> Result<UpdateAppWorkflowResp, LarkError> {
        let path = format!("/open-apis/bitable/v1/apps/{app_token}/workflows/{workflow_id}");
        RestRequest::new(
            self.config,
            http::Method::PUT,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_v2_response::<(), UpdateAppWorkflowResp>()
        .await
    }
}

// ── AppTableFormResource ──

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

impl<'a> AppTableFormResource<'a> {
    /// Get form metadata.
    /// GET /open-apis/bitable/v1/apps/:app_token/tables/:table_id/forms/:form_id
    pub async fn get(
        &self,
        app_token: &str,
        table_id: &str,
        form_id: &str,
        option: &RequestOption,
    ) -> Result<GetFormResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/forms/{form_id}");
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .send_response::<FormData, GetFormResp>()
        .await
    }

    /// Patch form metadata.
    /// PATCH /open-apis/bitable/v1/apps/:app_token/tables/:table_id/forms/:form_id
    pub async fn patch(
        &self,
        app_token: &str,
        table_id: &str,
        form_id: &str,
        body: &PatchAppTableFormReqBody,
        option: &RequestOption,
    ) -> Result<PatchFormResp, LarkError> {
        let path =
            format!("/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/forms/{form_id}");
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<FormData, PatchFormResp>()
        .await
    }
}

// ── AppTableFormFieldResource ──

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

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ListFormFieldQuery<'a> {
    pub app_token: &'a str,
    pub table_id: &'a str,
    pub form_id: &'a str,
    pub page_size: Option<i32>,
    pub page_token: Option<&'a str>,
}

impl<'a> ListFormFieldQuery<'a> {
    pub fn new(app_token: &'a str, table_id: &'a str, form_id: &'a str) -> Self {
        Self {
            app_token,
            table_id,
            form_id,
            page_size: None,
            page_token: None,
        }
    }

    pub fn page_size(mut self, value: impl Into<Option<i32>>) -> 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
    }

    pub fn page(mut self, page: PageQuery<'a>) -> Self {
        self.page_size = page.page_size;
        self.page_token = page.page_token;
        self
    }

    pub(crate) fn page_query(&self) -> PageQuery<'a> {
        PageQuery::from_parts(self.page_size, self.page_token)
    }
}

impl<'a> AppTableFormFieldResource<'a> {
    /// List form fields.
    /// GET /open-apis/bitable/v1/apps/:app_token/tables/:table_id/forms/:form_id/fields
    pub async fn list(
        &self,
        app_token: &str,
        table_id: &str,
        form_id: &str,
        page_size: Option<i32>,
        page_token: Option<&str>,
        option: &RequestOption,
    ) -> Result<ListFormFieldResp, LarkError> {
        let query = ListFormFieldQuery::new(app_token, table_id, form_id)
            .page_size(page_size)
            .page_token(page_token);
        self.list_by_query(&query, option).await
    }

    pub async fn list_by_query(
        &self,
        query: &ListFormFieldQuery<'_>,
        option: &RequestOption,
    ) -> Result<ListFormFieldResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{}/tables/{}/forms/{}/fields",
            query.app_token, query.table_id, query.form_id
        );
        RestRequest::new(
            self.config,
            http::Method::GET,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .page_query(query.page_query())
        .send_response::<FormFieldListData, ListFormFieldResp>()
        .await
    }

    /// Patch a form field.
    /// PATCH /open-apis/bitable/v1/apps/:app_token/tables/:table_id/forms/:form_id/fields/:field_id
    pub async fn patch(
        &self,
        app_token: &str,
        table_id: &str,
        form_id: &str,
        field_id: &str,
        body: &PatchAppTableFormFieldReqBody,
        option: &RequestOption,
    ) -> Result<PatchFormFieldResp, LarkError> {
        let path = format!(
            "/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/forms/{form_id}/fields/{field_id}"
        );
        RestRequest::new(
            self.config,
            http::Method::PATCH,
            path,
            vec![AccessTokenType::Tenant, AccessTokenType::User],
            option,
        )
        .json_body(body)?
        .send_response::<FormFieldPatchData, PatchFormFieldResp>()
        .await
    }
}

// ── Version struct ──

pub struct V1<'a> {
    pub app: AppResource<'a>,
    pub table: AppTableResource<'a>,
    pub view: AppTableViewResource<'a>,
    pub field: AppTableFieldResource<'a>,
    pub record: AppTableRecordResource<'a>,
    pub dashboard: AppDashboardResource<'a>,
    pub role: AppRoleResource<'a>,
    pub role_member: AppRoleMemberResource<'a>,
    pub workflow: AppWorkflowResource<'a>,
    pub form: AppTableFormResource<'a>,
    pub form_field: AppTableFormFieldResource<'a>,
}

impl<'a> V1<'a> {
    pub fn new(config: &'a Config) -> Self {
        Self {
            app: AppResource { config },
            table: AppTableResource { config },
            view: AppTableViewResource { config },
            field: AppTableFieldResource { config },
            record: AppTableRecordResource { config },
            dashboard: AppDashboardResource { config },
            role: AppRoleResource { config },
            role_member: AppRoleMemberResource { config },
            workflow: AppWorkflowResource { config },
            form: AppTableFormResource { config },
            form_field: AppTableFormFieldResource { config },
        }
    }
}
