{"version":3,"sources":["../src/types/common.ts","../src/types/auth.ts","../src/types/oa.ts","../src/types/user.ts","../src/types/zns.ts","../src/types/article.ts","../src/types/broadcast.ts","../src/types/purchase.ts","../src/constants/zns.constants.ts","../src/types/webhook.ts","../src/utils/type-guards.ts","../src/clients/base-client.ts","../src/clients/zalo-client.ts","../src/services/auth.service.ts","../src/services/oa.service.ts","../src/services/user.service.ts","../src/services/zns.service.ts","../src/services/group-message.service.ts","../src/services/group-management.service.ts","../src/services/article.service.ts","../src/services/video-upload.service.ts","../src/services/consultation.service.ts","../src/services/transaction.service.ts","../src/services/promotion.service.ts","../src/services/general-message.service.ts","../src/services/message-management.service.ts","../src/services/broadcast.service.ts","../src/services/purchase.service.ts","../src/services/anonymous-message.service.ts","../src/services/message-reaction.service.ts","../src/services/miniapp-message.service.ts","../src/zalo-sdk.ts"],"sourcesContent":["/**\r\n * Common types and interfaces for Zalo SDK\r\n */\r\n\r\n/**\r\n * Standard Zalo API response wrapper\r\n */\r\nexport interface ZaloResponse<T = any> {\r\n  /**\r\n   * Error code (0 = success)\r\n   */\r\n  error: number;\r\n\r\n  /**\r\n   * Response message\r\n   */\r\n  message: string;\r\n\r\n  /**\r\n   * Response data\r\n   */\r\n  data?: T;\r\n}\r\n\r\n/**\r\n * Zalo API error response\r\n */\r\nexport interface ZaloErrorResponse {\r\n  /**\r\n   * Error code\r\n   */\r\n  error: number;\r\n\r\n  /**\r\n   * Error name\r\n   */\r\n  error_name?: string;\r\n\r\n  /**\r\n   * Error reason\r\n   */\r\n  error_reason?: string;\r\n\r\n  /**\r\n   * Error description\r\n   */\r\n  error_description?: string;\r\n\r\n  /**\r\n   * Reference documentation link\r\n   */\r\n  ref_doc?: string;\r\n}\r\n\r\n/**\r\n * Union type for Zalo API responses\r\n */\r\nexport type ZaloApiResponse<T> = ZaloResponse<T> | (ZaloErrorResponse & Partial<T>);\r\n\r\n/**\r\n * SDK Configuration\r\n */\r\nexport interface ZaloSDKConfig {\r\n  /**\r\n   * Application ID\r\n   */\r\n  appId: string;\r\n\r\n  /**\r\n   * Application Secret\r\n   */\r\n  appSecret: string;\r\n\r\n  /**\r\n   * API timeout in milliseconds (default: 30000)\r\n   */\r\n  timeout?: number;\r\n\r\n  /**\r\n   * Enable debug logging (default: false)\r\n   */\r\n  debug?: boolean;\r\n\r\n  /**\r\n   * Custom API base URL (optional)\r\n   */\r\n  apiBaseUrl?: string;\r\n\r\n  /**\r\n   * Retry configuration\r\n   */\r\n  retry?: {\r\n    /**\r\n     * Number of retry attempts (default: 3)\r\n     */\r\n    attempts?: number;\r\n\r\n    /**\r\n     * Delay between retries in milliseconds (default: 1000)\r\n     */\r\n    delay?: number;\r\n  };\r\n}\r\n\r\n/**\r\n * HTTP method types\r\n */\r\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\r\n\r\n/**\r\n * Request configuration\r\n */\r\nexport interface RequestConfig {\r\n  /**\r\n   * HTTP method\r\n   */\r\n  method: HttpMethod;\r\n\r\n  /**\r\n   * Request URL\r\n   */\r\n  url: string;\r\n\r\n  /**\r\n   * Request headers\r\n   */\r\n  headers?: Record<string, string>;\r\n\r\n  /**\r\n   * Query parameters\r\n   */\r\n  params?: Record<string, any>;\r\n\r\n  /**\r\n   * Request body data\r\n   */\r\n  data?: any;\r\n\r\n  /**\r\n   * Request timeout\r\n   */\r\n  timeout?: number;\r\n}\r\n\r\n/**\r\n * Pagination parameters\r\n */\r\nexport interface PaginationParams {\r\n  /**\r\n   * Offset for pagination\r\n   */\r\n  offset?: number;\r\n\r\n  /**\r\n   * Number of items per page\r\n   */\r\n  count?: number;\r\n\r\n  /**\r\n   * Maximum items per page (default: 50)\r\n   */\r\n  limit?: number;\r\n}\r\n\r\n/**\r\n * Paginated response\r\n */\r\nexport interface PaginatedResponse<T> {\r\n  /**\r\n   * Array of items\r\n   */\r\n  items: T[];\r\n\r\n  /**\r\n   * Total number of items\r\n   */\r\n  total: number;\r\n\r\n  /**\r\n   * Current offset\r\n   */\r\n  offset: number;\r\n\r\n  /**\r\n   * Number of items in current page\r\n   */\r\n  count: number;\r\n\r\n  /**\r\n   * Whether there are more items\r\n   */\r\n  hasMore: boolean;\r\n\r\n  /**\r\n   * Next offset for pagination\r\n   */\r\n  nextOffset?: number;\r\n}\r\n\r\n/**\r\n * File upload information\r\n */\r\nexport interface FileUpload {\r\n  /**\r\n   * File buffer or stream\r\n   */\r\n  file: Buffer | NodeJS.ReadableStream;\r\n\r\n  /**\r\n   * Original filename\r\n   */\r\n  filename: string;\r\n\r\n  /**\r\n   * MIME type\r\n   */\r\n  mimetype: string;\r\n\r\n  /**\r\n   * File size in bytes\r\n   */\r\n  size?: number;\r\n}\r\n\r\n/**\r\n * Attachment information\r\n */\r\nexport interface Attachment {\r\n  /**\r\n   * Attachment ID from Zalo\r\n   */\r\n  attachment_id: string;\r\n\r\n  /**\r\n   * File URL\r\n   */\r\n  url: string;\r\n\r\n  /**\r\n   * File type\r\n   */\r\n  type?: string;\r\n\r\n  /**\r\n   * File size in bytes\r\n   */\r\n  size?: number;\r\n}\r\n\r\n/**\r\n * SDK Error class\r\n */\r\nexport class ZaloSDKError extends Error {\r\n  public readonly code: number;\r\n  public readonly details?: any;\r\n\r\n  constructor(message: string, code: number = -1, details?: any) {\r\n    super(message);\r\n    this.name = 'ZaloSDKError';\r\n    this.code = code;\r\n    this.details = details;\r\n  }\r\n}\r\n\r\n/**\r\n * Logger interface\r\n */\r\nexport interface Logger {\r\n  debug(message: string, ...args: any[]): void;\r\n  info(message: string, ...args: any[]): void;\r\n  warn(message: string, ...args: any[]): void;\r\n  error(message: string, ...args: any[]): void;\r\n}\r\n\r\n/**\r\n * Default console logger implementation\r\n */\r\nexport class ConsoleLogger implements Logger {\r\n  constructor(private readonly enableDebug: boolean = false) {}\r\n\r\n  debug(message: string, ...args: any[]): void {\r\n    if (this.enableDebug) {\r\n      console.debug(`[ZALO-SDK:DEBUG] ${message}`, ...args);\r\n    }\r\n  }\r\n\r\n  info(message: string, ...args: any[]): void {\r\n    console.info(`[ZALO-SDK:INFO] ${message}`, ...args);\r\n  }\r\n\r\n  warn(message: string, ...args: any[]): void {\r\n    console.warn(`[ZALO-SDK:WARN] ${message}`, ...args);\r\n  }\r\n\r\n  error(message: string, ...args: any[]): void {\r\n    console.error(`[ZALO-SDK:ERROR] ${message}`, ...args);\r\n  }\r\n}\r\n","/**\r\n * Authentication related types and interfaces\r\n */\r\n\r\n/**\r\n * Access token information\r\n */\r\nexport interface AccessToken {\r\n  /**\r\n   * Access token string\r\n   */\r\n  access_token: string;\r\n\r\n  /**\r\n   * Token expiration time in seconds\r\n   */\r\n  expires_in: number;\r\n\r\n  /**\r\n   * Refresh token (if available)\r\n   */\r\n  refresh_token?: string;\r\n}\r\n\r\n/**\r\n * Refresh token response\r\n */\r\nexport interface RefreshTokenResponse {\r\n  /**\r\n   * New access token\r\n   */\r\n  access_token: string;\r\n\r\n  /**\r\n   * Token expiration time in seconds\r\n   */\r\n  expires_in: number;\r\n\r\n  /**\r\n   * New refresh token (if available)\r\n   */\r\n  refresh_token?: string;\r\n}\r\n\r\n/**\r\n * OAuth authorization parameters\r\n */\r\nexport interface OAuthParams {\r\n  /**\r\n   * Application ID\r\n   */\r\n  app_id: string;\r\n\r\n  /**\r\n   * Redirect URI\r\n   */\r\n  redirect_uri: string;\r\n\r\n  /**\r\n   * OAuth state parameter\r\n   */\r\n  state?: string;\r\n\r\n  /**\r\n   * Code verifier for PKCE (Social API)\r\n   */\r\n  code_verifier?: string;\r\n\r\n  /**\r\n   * Code challenge for PKCE (Social API)\r\n   */\r\n  code_challenge?: string;\r\n\r\n  /**\r\n   * Code challenge method for PKCE (Social API)\r\n   */\r\n  code_challenge_method?: 'S256' | 'plain';\r\n}\r\n\r\n/**\r\n * Authorization code exchange parameters\r\n */\r\nexport interface AuthCodeParams {\r\n  /**\r\n   * Application ID\r\n   */\r\n  app_id: string;\r\n\r\n  /**\r\n   * Application secret\r\n   */\r\n  app_secret: string;\r\n\r\n  /**\r\n   * Authorization code\r\n   */\r\n  code: string;\r\n\r\n  /**\r\n   * Redirect URI (must match the one used in authorization)\r\n   */\r\n  redirect_uri: string;\r\n\r\n  /**\r\n   * Code verifier for PKCE (supports both Social API and Official Account API)\r\n   */\r\n  code_verifier?: string;\r\n}\r\n\r\n/**\r\n * Refresh token parameters\r\n */\r\nexport interface RefreshTokenParams {\r\n  /**\r\n   * Application ID\r\n   */\r\n  app_id: string;\r\n\r\n  /**\r\n   * Application secret\r\n   */\r\n  app_secret: string;\r\n\r\n  /**\r\n   * Refresh token\r\n   */\r\n  refresh_token: string;\r\n}\r\n\r\n/**\r\n * Social user information from Zalo\r\n */\r\nexport interface SocialUserInfo {\r\n  /**\r\n   * User ID\r\n   */\r\n  id: string;\r\n\r\n  /**\r\n   * Display name\r\n   */\r\n  name: string;\r\n\r\n  /**\r\n   * Profile picture\r\n   */\r\n  picture?: {\r\n    data: {\r\n      url: string;\r\n    };\r\n  };\r\n\r\n  /**\r\n   * Gender\r\n   */\r\n  gender?: string;\r\n\r\n  /**\r\n   * Birthday\r\n   */\r\n  birthday?: string;\r\n\r\n  /**\r\n   * Location\r\n   */\r\n  location?: {\r\n    name: string;\r\n  };\r\n\r\n  /**\r\n   * Whether the account is sensitive (under 18)\r\n   */\r\n  is_sensitive?: boolean;\r\n}\r\n\r\n/**\r\n * Token validation result\r\n */\r\nexport interface TokenValidation {\r\n  /**\r\n   * Whether the token is valid\r\n   */\r\n  valid: boolean;\r\n\r\n  /**\r\n   * Token expiration timestamp\r\n   */\r\n  expires_at?: number;\r\n\r\n  /**\r\n   * Associated user information (if available)\r\n   */\r\n  user_info?: SocialUserInfo;\r\n}\r\n\r\n/**\r\n * Authentication scope for different APIs\r\n */\r\nexport enum AuthScope {\r\n  /**\r\n   * Official Account API scope\r\n   */\r\n  OA = 'oa',\r\n\r\n  /**\r\n   * Social API scope\r\n   */\r\n  SOCIAL = 'social',\r\n\r\n  /**\r\n   * ZNS (Zalo Notification Service) scope\r\n   */\r\n  ZNS = 'zns',\r\n}\r\n\r\n/**\r\n * Authentication method\r\n */\r\nexport enum AuthMethod {\r\n  /**\r\n   * OAuth 2.0 Authorization Code flow\r\n   */\r\n  AUTHORIZATION_CODE = 'authorization_code',\r\n\r\n  /**\r\n   * Refresh token flow\r\n   */\r\n  REFRESH_TOKEN = 'refresh_token',\r\n}\r\n\r\n/**\r\n * PKCE (Proof Key for Code Exchange) configuration\r\n */\r\nexport interface PKCEConfig {\r\n  /**\r\n   * Code verifier\r\n   */\r\n  code_verifier: string;\r\n\r\n  /**\r\n   * Code challenge\r\n   */\r\n  code_challenge: string;\r\n\r\n  /**\r\n   * Code challenge method\r\n   */\r\n  code_challenge_method: 'S256' | 'plain';\r\n}\r\n\r\n/**\r\n * Authentication URLs\r\n */\r\nexport interface AuthUrls {\r\n  /**\r\n   * Official Account authorization URL\r\n   */\r\n  oa_auth_url: string;\r\n\r\n  /**\r\n   * Social API authorization URL\r\n   */\r\n  social_auth_url: string;\r\n\r\n  /**\r\n   * Token exchange URL\r\n   */\r\n  token_url: string;\r\n\r\n  /**\r\n   * Token refresh URL\r\n   */\r\n  refresh_url: string;\r\n}\r\n\r\n/**\r\n * Official Account authorization result\r\n */\r\nexport interface OAAuthResult {\r\n  /**\r\n   * Authorization URL\r\n   */\r\n  url: string;\r\n\r\n  /**\r\n   * State parameter used (auto-generated if not provided)\r\n   */\r\n  state: string;\r\n\r\n  /**\r\n   * PKCE configuration used (if PKCE was enabled)\r\n   */\r\n  pkce?: PKCEConfig;\r\n}\r\n","/**\r\n * Official Account (OA) related types and interfaces\r\n */\r\n\r\n/**\r\n * Official Account information\r\n */\r\nexport interface OAInfo {\r\n  /**\r\n   * Official Account ID\r\n   */\r\n  oa_id: string;\r\n\r\n  /**\r\n   * OA name\r\n   */\r\n  name: string;\r\n\r\n  /**\r\n   * OA description\r\n   */\r\n  description: string;\r\n\r\n  /**\r\n   * OA alias\r\n   */\r\n  oa_alias: string;\r\n\r\n  /**\r\n   * Verification status\r\n   */\r\n  is_verified: boolean;\r\n\r\n  /**\r\n   * OA type (number)\r\n   */\r\n  oa_type: number;\r\n\r\n  /**\r\n   * Category name\r\n   */\r\n  cate_name: string;\r\n\r\n  /**\r\n   * Number of followers\r\n   */\r\n  num_follower: number;\r\n\r\n  /**\r\n   * Avatar URL\r\n   */\r\n  avatar: string;\r\n\r\n  /**\r\n   * Cover image URL\r\n   */\r\n  cover: string;\r\n\r\n  /**\r\n   * Package name\r\n   */\r\n  package_name: string;\r\n\r\n  /**\r\n   * Package expiration date\r\n   */\r\n  package_valid_through_date: string;\r\n\r\n  /**\r\n   * Package auto-renewal date\r\n   */\r\n  package_auto_renew_date: string;\r\n\r\n  /**\r\n   * Linked Zalo Cloud Account\r\n   */\r\n  linked_ZCA: string;\r\n}\r\n\r\n/**\r\n * Message quota information\r\n */\r\nexport interface MessageQuota {\r\n  /**\r\n   * Daily quota limit\r\n   */\r\n  daily_quota: number;\r\n\r\n  /**\r\n   * Remaining quota for today\r\n   */\r\n  remaining_quota: number;\r\n\r\n  /**\r\n   * Quota type (e.g., \"free\", \"paid\")\r\n   */\r\n  quota_type: string;\r\n\r\n  /**\r\n   * Quota reset time (Unix timestamp)\r\n   */\r\n  reset_time: number;\r\n}\r\n\r\n/**\r\n * Detailed quota information request\r\n */\r\nexport interface QuotaMessageRequest {\r\n  /**\r\n   * Quota owner (OA or APP)\r\n   */\r\n  quota_owner: string;\r\n\r\n  /**\r\n   * Product type\r\n   */\r\n  product_type?: 'cs' | 'transaction' | 'gmf10' | 'gmf50' | 'gmf100';\r\n\r\n  /**\r\n   * Quota type\r\n   */\r\n  quota_type?: 'sub_quota' | 'purchase_quota' | 'reward_quota';\r\n}\r\n\r\n/**\r\n * Quota asset information\r\n */\r\nexport interface QuotaAsset {\r\n  /**\r\n   * Asset ID\r\n   */\r\n  asset_id: string;\r\n\r\n  /**\r\n   * Product type\r\n   */\r\n  product_type: string;\r\n\r\n  /**\r\n   * Quota type\r\n   */\r\n  quota_type: string;\r\n\r\n  /**\r\n   * Expiration date\r\n   */\r\n  valid_through: string;\r\n\r\n  /**\r\n   * Auto-renewal status\r\n   */\r\n  auto_renew: boolean;\r\n\r\n  /**\r\n   * Asset status\r\n   */\r\n  status: 'available' | 'used';\r\n\r\n  /**\r\n   * Used ID (for GMF, this is group_id)\r\n   */\r\n  used_id: string | null;\r\n\r\n  /**\r\n   * Total quota (legacy)\r\n   */\r\n  total?: number;\r\n\r\n  /**\r\n   * Remaining quota (legacy)\r\n   */\r\n  remain?: number;\r\n}\r\n\r\n/**\r\n * Quota message response\r\n */\r\nexport interface QuotaMessageResponse {\r\n  /**\r\n   * List of quota assets\r\n   */\r\n  data: QuotaAsset[];\r\n\r\n  /**\r\n   * Error code\r\n   */\r\n  error: number;\r\n\r\n  /**\r\n   * Response message\r\n   */\r\n  message: string;\r\n}\r\n\r\n/**\r\n * GMF product types\r\n */\r\nexport enum GMFProductType {\r\n  GMF10 = 'gmf10',\r\n  GMF50 = 'gmf50',\r\n  GMF100 = 'gmf100',\r\n}\r\n\r\n/**\r\n * Quota types\r\n */\r\nexport enum QuotaType {\r\n  SUB_QUOTA = 'sub_quota',\r\n  PURCHASE_QUOTA = 'purchase_quota',\r\n  REWARD_QUOTA = 'reward_quota',\r\n}\r\n\r\n/**\r\n * OA settings\r\n */\r\nexport interface OASettings {\r\n  /**\r\n   * Auto-reply settings\r\n   */\r\n  auto_reply?: {\r\n    enabled: boolean;\r\n    message?: string;\r\n  };\r\n\r\n  /**\r\n   * Welcome message settings\r\n   */\r\n  welcome_message?: {\r\n    enabled: boolean;\r\n    message?: string;\r\n  };\r\n\r\n  /**\r\n   * Business hours\r\n   */\r\n  business_hours?: {\r\n    enabled: boolean;\r\n    schedule?: Array<{\r\n      day: number; // 0-6 (Sunday-Saturday)\r\n      start_time: string; // HH:mm format\r\n      end_time: string; // HH:mm format\r\n    }>;\r\n  };\r\n}\r\n\r\n/**\r\n * OA statistics\r\n */\r\nexport interface OAStatistics {\r\n  /**\r\n   * Total followers\r\n   */\r\n  total_followers: number;\r\n\r\n  /**\r\n   * New followers today\r\n   */\r\n  new_followers_today: number;\r\n\r\n  /**\r\n   * Messages sent today\r\n   */\r\n  messages_sent_today: number;\r\n\r\n  /**\r\n   * Messages received today\r\n   */\r\n  messages_received_today: number;\r\n\r\n  /**\r\n   * Engagement rate\r\n   */\r\n  engagement_rate?: number;\r\n}\r\n\r\n/**\r\n * OA profile update request\r\n */\r\nexport interface UpdateOAProfileRequest {\r\n  /**\r\n   * OA name\r\n   */\r\n  name?: string;\r\n\r\n  /**\r\n   * OA description\r\n   */\r\n  description?: string;\r\n\r\n  /**\r\n   * Avatar image (base64 or URL)\r\n   */\r\n  avatar?: string;\r\n\r\n  /**\r\n   * Cover image (base64 or URL)\r\n   */\r\n  cover?: string;\r\n}\r\n\r\n/**\r\n * OA verification request\r\n */\r\nexport interface OAVerificationRequest {\r\n  /**\r\n   * Business license number\r\n   */\r\n  business_license: string;\r\n\r\n  /**\r\n   * Business name\r\n   */\r\n  business_name: string;\r\n\r\n  /**\r\n   * Contact person name\r\n   */\r\n  contact_name: string;\r\n\r\n  /**\r\n   * Contact phone number\r\n   */\r\n  contact_phone: string;\r\n\r\n  /**\r\n   * Contact email\r\n   */\r\n  contact_email: string;\r\n\r\n  /**\r\n   * Additional documents\r\n   */\r\n  documents?: Array<{\r\n    type: string;\r\n    url: string;\r\n  }>;\r\n}\r\n","/**\r\n * User management related types and interfaces\r\n */\r\n\r\n/**\r\n * User information from Zalo API v3.0\r\n */\r\nexport interface UserInfo {\r\n  /**\r\n   * User ID\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * User ID by app\r\n   */\r\n  user_id_by_app: string;\r\n\r\n  /**\r\n   * External user ID in business system\r\n   */\r\n  user_external_id: string;\r\n\r\n  /**\r\n   * Display name\r\n   */\r\n  display_name: string;\r\n\r\n  /**\r\n   * User alias\r\n   */\r\n  user_alias: string;\r\n\r\n  /**\r\n   * Whether user is under 18\r\n   */\r\n  is_sensitive: boolean;\r\n\r\n  /**\r\n   * Last interaction date (dd/MM/yyyy)\r\n   */\r\n  user_last_interaction_date: string;\r\n\r\n  /**\r\n   * Whether user is following OA\r\n   */\r\n  user_is_follower: boolean;\r\n\r\n  /**\r\n   * Avatar URL\r\n   */\r\n  avatar: string;\r\n\r\n  /**\r\n   * Avatar URLs with different sizes\r\n   */\r\n  avatars: {\r\n    \"120\": string;\r\n    \"240\": string;\r\n  };\r\n\r\n  /**\r\n   * Dynamic param from last access\r\n   */\r\n  dynamic_param?: string;\r\n\r\n  /**\r\n   * Tags and notes information\r\n   */\r\n  tags_and_notes_info: {\r\n    notes: string[];\r\n    tag_names: string[];\r\n  };\r\n\r\n  /**\r\n   * Shared information from user\r\n   */\r\n  shared_info?: {\r\n    address?: string;\r\n    city?: string;\r\n    district?: string;\r\n    phone?: string;\r\n    name?: string;\r\n    user_dob?: string;\r\n  };\r\n}\r\n\r\n/**\r\n * User list request parameters\r\n */\r\nexport interface UserListRequest {\r\n  /**\r\n   * Offset for pagination\r\n   */\r\n  offset: number;\r\n\r\n  /**\r\n   * Number of users to fetch (max 50)\r\n   */\r\n  count: number;\r\n\r\n  /**\r\n   * Filter by tag name\r\n   */\r\n  tag_name?: string;\r\n\r\n  /**\r\n   * Filter by last interaction period\r\n   */\r\n  last_interaction_period?: \"TODAY\" | \"YESTERDAY\" | \"L7D\" | \"L30D\" | string;\r\n\r\n  /**\r\n   * Filter by follower status\r\n   */\r\n  is_follower?: boolean;\r\n}\r\n\r\n/**\r\n * User list response\r\n */\r\nexport interface UserListResponse {\r\n  /**\r\n   * Total number of users\r\n   */\r\n  total: number;\r\n\r\n  /**\r\n   * Number of users returned\r\n   */\r\n  count: number;\r\n\r\n  /**\r\n   * Current offset\r\n   */\r\n  offset: number;\r\n\r\n  /**\r\n   * List of user IDs\r\n   */\r\n  users: Array<{\r\n    user_id: string;\r\n  }>;\r\n}\r\n\r\n/**\r\n * User label/tag information\r\n */\r\nexport interface UserLabel {\r\n  /**\r\n   * Label ID\r\n   */\r\n  label_id: string;\r\n\r\n  /**\r\n   * Label name\r\n   */\r\n  label_name: string;\r\n\r\n  /**\r\n   * Label description\r\n   */\r\n  description?: string;\r\n\r\n  /**\r\n   * Label color (hex)\r\n   */\r\n  color?: string;\r\n\r\n  /**\r\n   * Creation time (Unix timestamp)\r\n   */\r\n  created_time?: number;\r\n\r\n  /**\r\n   * Number of users with this label\r\n   */\r\n  user_count?: number;\r\n}\r\n\r\n/**\r\n * Create label request\r\n */\r\nexport interface CreateLabelRequest {\r\n  /**\r\n   * Label name\r\n   */\r\n  label_name: string;\r\n\r\n  /**\r\n   * Label description\r\n   */\r\n  description?: string;\r\n\r\n  /**\r\n   * Label color (hex)\r\n   */\r\n  color?: string;\r\n}\r\n\r\n/**\r\n * User label operation request (legacy - for label_id based operations)\r\n */\r\nexport interface UserLabelRequest {\r\n  /**\r\n   * User ID\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Label ID\r\n   */\r\n  label_id: string;\r\n}\r\n\r\n/**\r\n * Tag user request - for Zalo API v2.0/oa/tag/tagfollower\r\n */\r\nexport interface TagUserRequest {\r\n  /**\r\n   * User ID (long in API spec, but sent as string)\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Tag name to assign to user\r\n   * Note: If tag doesn't exist, API will create it automatically\r\n   */\r\n  tag_name: string;\r\n}\r\n\r\n/**\r\n * Remove tag from user request - for Zalo API v2.0/oa/tag/rmfollowerfromtag\r\n */\r\nexport interface RemoveTagFromUserRequest {\r\n  /**\r\n   * User ID\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Tag name to remove from user\r\n   */\r\n  tag_name: string;\r\n}\r\n\r\n/**\r\n * Get tags list response - for Zalo API v2.0/oa/tag/gettagsofoa\r\n */\r\nexport interface GetTagsResponse {\r\n  /**\r\n   * Error code (0 = success)\r\n   */\r\n  error: number;\r\n\r\n  /**\r\n   * Response message\r\n   */\r\n  message: string;\r\n\r\n  /**\r\n   * Array of tag names\r\n   */\r\n  data: string[];\r\n}\r\n\r\n/**\r\n * Delete tag request - for Zalo API v2.0/oa/tag/rmtag\r\n */\r\nexport interface DeleteTagRequest {\r\n  /**\r\n   * Tag name to delete\r\n   */\r\n  tag_name: string;\r\n}\r\n\r\n/**\r\n * Update user request - for Zalo API v3.0/oa/user/update\r\n */\r\nexport interface UpdateUserRequest {\r\n  /**\r\n   * User ID (required)\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Shared information to update\r\n   */\r\n  shared_info?: {\r\n    /**\r\n     * Display name\r\n     */\r\n    name?: string;\r\n\r\n    /**\r\n     * Phone number\r\n     */\r\n    phone?: string;\r\n\r\n    /**\r\n     * Address\r\n     */\r\n    address?: string;\r\n\r\n    /**\r\n     * City ID (see Zalo documentation for city codes)\r\n     */\r\n    city_id?: number;\r\n\r\n    /**\r\n     * District ID (see Zalo documentation for district codes)\r\n     */\r\n    district_id?: number;\r\n\r\n    /**\r\n     * Date of birth (dd/MM/yyyy format, from 1/1/1970)\r\n     */\r\n    user_dob?: string;\r\n  };\r\n\r\n  /**\r\n   * User alias name\r\n   */\r\n  user_alias?: string;\r\n\r\n  /**\r\n   * External user ID in business system (unique per customer)\r\n   */\r\n  user_external_id?: string;\r\n}\r\n\r\n/**\r\n * Delete user info request - for Zalo API v2.0/oa/deletefollowerinfo\r\n */\r\nexport interface DeleteUserInfoRequest {\r\n  /**\r\n   * User ID to delete info for\r\n   */\r\n  user_id: string;\r\n}\r\n\r\n/**\r\n * Get user custom info request - for Zalo API v3.0/oa/user/detail/custominfo\r\n */\r\nexport interface GetUserCustomInfoRequest {\r\n  /**\r\n   * User ID to get custom info for\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * List of custom fields to export (optional)\r\n   * If not specified, API returns all available custom info\r\n   * For table type fields, only parent field is accepted\r\n   */\r\n  fields_to_export?: string[];\r\n}\r\n\r\n/**\r\n * User custom info response - for Zalo API v3.0/oa/user/detail/custominfo\r\n */\r\nexport interface UserCustomInfoResponse {\r\n  /**\r\n   * Error code (0 = success)\r\n   */\r\n  error: number;\r\n\r\n  /**\r\n   * Response message\r\n   */\r\n  message: string;\r\n\r\n  /**\r\n   * Custom info data\r\n   */\r\n  data: {\r\n    /**\r\n     * User ID\r\n     */\r\n    user_id: string;\r\n\r\n    /**\r\n     * Custom information fields\r\n     * All values are returned as strings for consistency\r\n     */\r\n    custom_info: Record<string, any>;\r\n  };\r\n}\r\n\r\n/**\r\n * Update user custom info request - for Zalo API v3.0/oa/user/update/custominfo\r\n */\r\nexport interface UpdateUserCustomInfoRequest {\r\n  /**\r\n   * User ID to update custom info for\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Custom information to update\r\n   * Structure depends on OA's custom field configuration\r\n   */\r\n  custom_info: Record<string, any>;\r\n}\r\n\r\n/**\r\n * User custom information\r\n */\r\nexport interface UserCustomInfo {\r\n  /**\r\n   * User ID\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Custom fields (key-value pairs)\r\n   */\r\n  custom_fields: Record<string, any>;\r\n\r\n  /**\r\n   * Last updated timestamp\r\n   */\r\n  last_updated?: number;\r\n}\r\n\r\n/**\r\n * Update custom info request\r\n */\r\nexport interface UpdateCustomInfoRequest {\r\n  /**\r\n   * User ID\r\n   */\r\n  user_id: string;\r\n\r\n  /**\r\n   * Custom fields to update\r\n   */\r\n  custom_fields: Record<string, any>;\r\n}\r\n\r\n/**\r\n * User info field types\r\n */\r\nexport enum UserInfoFieldType {\r\n  TEXT = \"text\",\r\n  NUMBER = \"number\",\r\n  DATE = \"date\",\r\n  SELECT = \"select\",\r\n}\r\n\r\n/**\r\n * Field option for select type\r\n */\r\nexport interface FieldOption {\r\n  /**\r\n   * Option value\r\n   */\r\n  value: string;\r\n\r\n  /**\r\n   * Option label\r\n   */\r\n  label: string;\r\n}\r\n\r\n/**\r\n * User info field definition\r\n */\r\nexport interface UserInfoField {\r\n  /**\r\n   * Field ID\r\n   */\r\n  field_id: string;\r\n\r\n  /**\r\n   * Field name\r\n   */\r\n  field_name: string;\r\n\r\n  /**\r\n   * Field type\r\n   */\r\n  field_type: UserInfoFieldType;\r\n\r\n  /**\r\n   * Field description\r\n   */\r\n  description?: string;\r\n\r\n  /**\r\n   * Whether field is required\r\n   */\r\n  required?: boolean;\r\n\r\n  /**\r\n   * Options for select type\r\n   */\r\n  options?: FieldOption[];\r\n\r\n  /**\r\n   * Default value\r\n   */\r\n  default_value?: string;\r\n\r\n  /**\r\n   * Display order\r\n   */\r\n  display_order?: number;\r\n\r\n  /**\r\n   * Whether it's a system field\r\n   */\r\n  is_system_field?: boolean;\r\n\r\n  /**\r\n   * Creation time\r\n   */\r\n  created_time?: number;\r\n\r\n  /**\r\n   * Last update time\r\n   */\r\n  updated_time?: number;\r\n}\r\n\r\n/**\r\n * Create user info field request\r\n */\r\nexport interface CreateUserInfoFieldRequest {\r\n  /**\r\n   * Field name\r\n   */\r\n  field_name: string;\r\n\r\n  /**\r\n   * Field type\r\n   */\r\n  field_type: UserInfoFieldType;\r\n\r\n  /**\r\n   * Field description\r\n   */\r\n  description?: string;\r\n\r\n  /**\r\n   * Whether field is required\r\n   */\r\n  required?: boolean;\r\n\r\n  /**\r\n   * Options for select type\r\n   */\r\n  options?: FieldOption[];\r\n\r\n  /**\r\n   * Default value\r\n   */\r\n  default_value?: string;\r\n\r\n  /**\r\n   * Display order\r\n   */\r\n  display_order?: number;\r\n}\r\n\r\n/**\r\n * Update user info field request\r\n */\r\nexport interface UpdateUserInfoFieldRequest {\r\n  /**\r\n   * Field name\r\n   */\r\n  field_name?: string;\r\n\r\n  /**\r\n   * Field description\r\n   */\r\n  description?: string;\r\n\r\n  /**\r\n   * Whether field is required\r\n   */\r\n  required?: boolean;\r\n\r\n  /**\r\n   * Options for select type\r\n   */\r\n  options?: FieldOption[];\r\n\r\n  /**\r\n   * Default value\r\n   */\r\n  default_value?: string;\r\n\r\n  /**\r\n   * Display order\r\n   */\r\n  display_order?: number;\r\n}\r\n\r\n/**\r\n * Advanced filter options for getAllUsersWithAdvancedFilters\r\n * Dựa trên các trường trong UserInfo\r\n */\r\nexport interface AdvancedUserFilters {\r\n  /**\r\n   * Filter by specific user IDs (exact match)\r\n   * Chỉ lấy những users có ID trong danh sách này\r\n   */\r\n  userIds?: string[];\r\n\r\n  /**\r\n   * Filter by tag name (existing from basic filters)\r\n   */\r\n  tag_name?: string;\r\n\r\n  /**\r\n   * Filter by last interaction period (existing from basic filters)\r\n   */\r\n  last_interaction_period?: \"TODAY\" | \"YESTERDAY\" | \"L7D\" | \"L30D\" | string;\r\n\r\n  /**\r\n   * Filter by follower status (existing from basic filters)\r\n   */\r\n  is_follower?: boolean;\r\n\r\n  /**\r\n   * Filter by display name (contains search)\r\n   */\r\n  display_name_contains?: string;\r\n\r\n  /**\r\n   * Filter by user alias (contains search)\r\n   */\r\n  user_alias_contains?: string;\r\n\r\n  /**\r\n   * Filter by whether user is sensitive (under 18)\r\n   */\r\n  is_sensitive?: boolean;\r\n\r\n  /**\r\n   * Filter by specific interaction date range\r\n   */\r\n  last_interaction_date_range?: {\r\n    from: string; // dd/MM/yyyy format\r\n    to: string; // dd/MM/yyyy format\r\n  };\r\n\r\n  /**\r\n   * Filter by specific tags (multiple tags - OR operation)\r\n   */\r\n  tag_names?: string[];\r\n\r\n  /**\r\n   * Filter by tags (ALL tags must match - AND operation)\r\n   */\r\n  require_all_tags?: string[];\r\n\r\n  /**\r\n   * Filter by notes content (contains search)\r\n   */\r\n  notes_contains?: string;\r\n\r\n  /**\r\n   * Filter by shared info - city\r\n   */\r\n  city?: string;\r\n\r\n  /**\r\n   * Filter by shared info - district\r\n   */\r\n  district?: string;\r\n\r\n  /**\r\n   * Filter by shared info - phone (contains search)\r\n   */\r\n  phone_contains?: string;\r\n\r\n  /**\r\n   * Filter by shared info - name (contains search)\r\n   */\r\n  name_contains?: string;\r\n\r\n  /**\r\n   * Filter by shared info - address (contains search)\r\n   */\r\n  address_contains?: string;\r\n\r\n  /**\r\n   * Filter by date of birth range\r\n   */\r\n  dob_range?: {\r\n    from: string; // dd/MM/yyyy format\r\n    to: string; // dd/MM/yyyy format\r\n  };\r\n\r\n  /**\r\n   * Filter by age range (calculated from dob)\r\n   */\r\n  age_range?: {\r\n    min: number;\r\n    max: number;\r\n  };\r\n\r\n  /**\r\n   * Filter by external user ID (contains search)\r\n   */\r\n  external_id_contains?: string;\r\n\r\n  /**\r\n   * Exclude users with specific tags\r\n   */\r\n  exclude_tags?: string[];\r\n\r\n  /**\r\n   * Filter by users who have shared info vs those who don't\r\n   */\r\n  has_shared_info?: boolean;\r\n\r\n  /**\r\n   * Filter by users who have phone number\r\n   */\r\n  has_phone?: boolean;\r\n\r\n  /**\r\n   * Filter by users who have address\r\n   */\r\n  has_address?: boolean;\r\n}\r\n","/**\r\n * ZNS (Zalo Notification Service) Type Definitions\r\n */\r\n\r\n// ZNS Template Types\r\nexport enum ZNSTemplateType {\r\n  /** ZNS tùy chỉnh */\r\n  CUSTOM = 1,\r\n  /** ZNS xác thực */\r\n  AUTHENTICATION = 2,\r\n  /** ZNS yêu cầu thanh toán */\r\n  PAYMENT_REQUEST = 3,\r\n  /** ZNS voucher */\r\n  VOUCHER = 4,\r\n  /** ZNS Đánh giá dịch vụ */\r\n  SERVICE_RATING = 5\r\n}\r\n\r\n// ZNS Template Tags\r\nexport enum ZNSTemplateTag {\r\n  /** Transaction */\r\n  TRANSACTION = \"1\",\r\n  /** Customer care */\r\n  CUSTOMER_CARE = \"2\",\r\n  /** Promotion */\r\n  PROMOTION = \"3\"\r\n}\r\n\r\n// ZNS Button Types\r\n// ZNS Button Types\r\nexport enum ZNSButtonType {\r\n  /** Trang doanh nghiệp */\r\n  ENTERPRISE_PAGE = 1,\r\n  /** Gọi điện */\r\n  PHONE = 2,\r\n  /** Trang OA */\r\n  OA_PAGE = 3,\r\n  /** Zalo Mini App */\r\n  ZALO_MINI_APP = 4,\r\n  /** Tải App */\r\n  DOWNLOAD_APP = 5,\r\n  /** Phân phối sản phẩm */\r\n  PRODUCT_DISTRIBUTION = 6,\r\n  /** Web/Zalo Mini App khác */\r\n  OTHER_WEB_APP = 7,\r\n  /** App khác */\r\n  OTHER_APP = 8,\r\n  /** Bài viết OA */\r\n  OA_ARTICLE = 9,\r\n  /** Sao chép */\r\n  COPY = 10,\r\n  /** Thanh toán ngay */\r\n  PAY_NOW = 11,\r\n  /** Xem chi tiết */\r\n  VIEW_DETAILS = 12,\r\n}\r\n\r\n// ZNS Parameter Types\r\nexport enum ZNSParamType {\r\n  /** Tên khách hàng (30) */\r\n  CUSTOMER_NAME = \"1\",\r\n  /** Số điện thoại (15) */\r\n  PHONE_NUMBER = \"2\",\r\n  /** Địa chỉ (200) */\r\n  ADDRESS = \"3\",\r\n  /** Mã số (30) */\r\n  CODE = \"4\",\r\n  /** Nhãn tùy chỉnh (30) */\r\n  CUSTOM_LABEL = \"5\",\r\n  /** Trạng thái giao dịch (30) */\r\n  TRANSACTION_STATUS = \"6\",\r\n  /** Thông tin liên hệ (50) */\r\n  CONTACT_INFO = \"7\",\r\n  /** Giới tính / Danh xưng (5) */\r\n  GENDER_TITLE = \"8\",\r\n  /** Tên sản phẩm / Thương hiệu (200) */\r\n  PRODUCT_BRAND = \"9\",\r\n  /** Số lượng / Số tiền (20) */\r\n  QUANTITY_AMOUNT = \"10\",\r\n  /** Thời gian (20) */\r\n  TIME = \"11\",\r\n  /** OTP (10) */\r\n  OTP = \"12\",\r\n  /** URL (200) */\r\n  URL = \"13\",\r\n  /** Tiền tệ (VNĐ) (12) */\r\n  CURRENCY = \"14\",\r\n  /** Bank transfer note (90) */\r\n  BANK_TRANSFER_NOTE = \"15\"\r\n}\r\n\r\nexport interface ZNSMessage {\r\n  phone: string;\r\n  template_id: string;\r\n  template_data: Record<string, any>;\r\n  tracking_id?: string;\r\n}\r\n\r\nexport interface ZNSHashPhoneMessage {\r\n  phone_hash: string;\r\n  template_id: string;\r\n  template_data: Record<string, any>;\r\n  tracking_id?: string;\r\n}\r\n\r\nexport interface ZNSDevModeMessage {\r\n  phone: string;\r\n  template_id: string;\r\n  template_data: Record<string, any>;\r\n  tracking_id?: string;\r\n  mode: \"development\";\r\n}\r\n\r\nexport interface ZNSRsaMessage {\r\n  phone_rsa: string;\r\n  template_id: string;\r\n  template_data: Record<string, any>;\r\n  tracking_id?: string;\r\n}\r\n\r\nexport interface ZNSJourneyMessage {\r\n  phone: string;\r\n  template_id: string;\r\n  template_data: Record<string, any>;\r\n  tracking_id?: string;\r\n  journey_id: string;\r\n}\r\n\r\n/**\r\n * ZBS/ZNS Template Message by UID\r\n * API: POST https://openapi.zalo.me/v3.0/oa/message/template\r\n * Send template message using user_id instead of phone number\r\n */\r\nexport interface ZNSUidMessage {\r\n  /** ID của người nhận - Định danh người dùng theo OA */\r\n  user_id: string;\r\n  /** ID của template muốn sử dụng */\r\n  template_id: string;\r\n  /** Các thuộc tính của template mà đối tác đã đăng ký với Zalo */\r\n  template_data: Record<string, any>;\r\n}\r\n\r\n/**\r\n * Response from sending ZBS/ZNS template message by UID\r\n */\r\nexport interface ZNSUidSendResult {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    /** ID của tin nhắn */\r\n    message_id: string;\r\n    /** ID của người dùng */\r\n    user_id: string;\r\n  };\r\n  /** Thời gian gửi tin (timestamp) */\r\n  sent_time?: string;\r\n}\r\n\r\nexport interface ZNSSendResult {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    msg_id: string;\r\n    sent_time: string;\r\n    quota?: {\r\n      remainingQuota: number;\r\n      dailyQuota: number;\r\n    };\r\n  };\r\n}\r\n\r\nexport interface ZNSTemplate {\r\n  templateId: string;\r\n  templateName: string;\r\n  status: \"PENDING_REVIEW\" | \"ENABLE\" | \"REJECT\" | \"DISABLE\";\r\n  listParams: Array<{\r\n    name: string;\r\n    require: boolean;\r\n    type: \"STRING\" | \"NUMBER\" | \"DATE\";\r\n    maxLength?: number;\r\n    minLength?: number;\r\n  }>;\r\n  timeout?: number;\r\n  previewUrl?: string;\r\n  templateQuality?: \"HIGH\" | \"MEDIUM\" | \"LOW\";\r\n  templateTag?: string;\r\n  price?: number;\r\n  applyTemplateQuota?: boolean;\r\n  templateDailyQuota?: number;\r\n  templateRemainingQuota?: number;\r\n}\r\n\r\nexport interface ZNSTemplateDetails {\r\n  error: number;\r\n  message: string;\r\n  data: {\r\n    templateId: string; // ID của template\r\n    templateName: string; // Tên của template\r\n    status: \"ENABLE\" | \"PENDING_REVIEW\" | \"DELETE\" | \"REJECT\" | \"DISABLE\"; // Trạng thái template\r\n    reason?: string; // Lý do template có trạng thái hiện tại\r\n    listParams: Array<{\r\n      name: string; // Tên thuộc tính\r\n      require: boolean; // Tính bắt buộc của thuộc tính\r\n      type: string; // Định dạng validate của thuộc tính\r\n      maxLength: number; // Số ký tự tối đa\r\n      minLength: number; // Số ký tự tối thiểu\r\n      acceptNull: boolean; // Thuộc tính có thể nhận giá trị rỗng hay không\r\n    }>;\r\n    listButtons: Array<{\r\n      type: number; // Button type (1-12)\r\n      title: string; // Nội dung Button\r\n      content: string; // Đường dẫn liên kết/số điện thoại\r\n    }>;\r\n    timeout: number; // Thời gian timeout của template\r\n    previewUrl: string; // Đường dẫn đến bản xem trước của template\r\n    templateQuality: string; // Chất lượng template (null từ 10/12)\r\n    templateTag: \"TRANSACTION\" | \"CUSTOMER_CARE\" | \"PROMOTION\"; // Loại nội dung template\r\n    price_sdt: string; // Đơn giá gửi tin qua SĐT\r\n    price_uid: string; // Đơn giá gửi tin qua UID\r\n    price: string; // [DEPRECATED] Đơn giá gửi tin qua SĐT (sử dụng price_sdt thay thế)\r\n  };\r\n}\r\n\r\nexport interface ZNSTemplateList {\r\n  error: number;\r\n  message: string;\r\n  data: Array<{\r\n    templateId: string; // ID của template\r\n    templateName: string; // Tên của template\r\n    createdTime: number; // Thời gian tạo template\r\n    status: \"PENDING_REVIEW\" | \"DISABLE\" | \"ENABLE\" | \"REJECT\"; // Trạng thái template\r\n    templateQuality: \"HIGH\" | \"MEDIUM\" | \"LOW\" | \"UNDEFINED\"; // Chất lượng gửi tin\r\n  }>;\r\n  metadata: {\r\n    total: number; // Tổng số lượng template\r\n  };\r\n}\r\n\r\n/**\r\n * ZNS Template Create Request - theo chuẩn Zalo API\r\n * API: POST https://business.openapi.zalo.me/template/create\r\n */\r\nexport interface ZNSCreateTemplateRequest {\r\n  /** Tên mẫu tin (10-60 ký tự) */\r\n  template_name: string;\r\n\r\n  /** Loại mẫu tin */\r\n  template_type: 1 | 2 | 3 | 4 | 5; // 1: ZNS tùy chỉnh, 2: ZNS xác thực, 3: ZNS yêu cầu thanh toán, 4: ZNS voucher, 5: ZNS Đánh giá dịch vụ\r\n\r\n  /** Tag mẫu tin */\r\n  tag: \"1\" | \"2\" | \"3\"; // 1: Transaction, 2: Customer care, 3: Promotion\r\n\r\n  /** Layout của template */\r\n  layout: ZNSTemplateLayout;\r\n\r\n  /** Thông tin về param (optional) */\r\n  params?: ZNSTemplateParam[];\r\n\r\n  /** Ghi chú kiểm duyệt (1-400 ký tự, optional) */\r\n  note?: string;\r\n\r\n  /** Mã tracking do đối tác tự định nghĩa */\r\n  tracking_id: string;\r\n}\r\n\r\n/**\r\n * ZNS Template Edit Request - theo chuẩn Zalo API\r\n * API: POST https://business.openapi.zalo.me/template/edit\r\n * Chỉ có thể chỉnh sửa template có trạng thái REJECT\r\n */\r\nexport interface ZNSUpdateTemplateRequest {\r\n  /** Template ID được hệ thống tự tạo */\r\n  template_id: string;\r\n\r\n  /** Tên mẫu tin (10-60 ký tự) */\r\n  template_name: string;\r\n\r\n  /** Loại mẫu tin */\r\n  template_type: 1 | 2 | 3 | 4 | 5; // 1: ZNS tùy chỉnh, 2: ZNS xác thực, 3: ZNS yêu cầu thanh toán, 4: ZNS voucher, 5: ZNS Đánh giá dịch vụ\r\n\r\n  /** Tag mẫu tin */\r\n  tag: \"1\" | \"2\" | \"3\"; // 1: Transaction, 2: Customer care, 3: Promotion\r\n\r\n  /** Layout của template */\r\n  layout: ZNSTemplateLayout;\r\n\r\n  /** Thông tin về param (optional) */\r\n  params?: ZNSTemplateParam[];\r\n\r\n  /** Ghi chú kiểm duyệt (1-400 ký tự, optional) */\r\n  note?: string;\r\n\r\n  /** Mã tracking do đối tác tự định nghĩa */\r\n  tracking_id: string;\r\n}\r\n\r\n/**\r\n * Layout của ZNS Template\r\n */\r\nexport interface ZNSTemplateLayout {\r\n  /** Header components (optional) */\r\n  header?: {\r\n    components: ZNSValidationComponent[];\r\n  };\r\n\r\n  /** Body components (required) */\r\n  body: {\r\n    components: ZNSValidationComponent[];\r\n  };\r\n\r\n  /** Footer components (optional) */\r\n  footer?: {\r\n    components: ZNSValidationComponent[];\r\n  };\r\n}\r\n\r\n\r\n\r\n/**\r\n * Template parameter theo chuẩn Zalo API\r\n */\r\nexport interface ZNSTemplateParam {\r\n  /** Loại param */\r\n  type: \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\" | \"10\" | \"11\" | \"12\" | \"13\" | \"14\" | \"15\"; // Xem danh sách param type trong docs\r\n\r\n  /** Tên của param */\r\n  name: string;\r\n\r\n  /** Dữ liệu mẫu của param */\r\n  sample_value: string;\r\n}\r\n\r\n/**\r\n * Response từ API create/edit template\r\n */\r\nexport interface ZNSTemplateCreateEditResponse {\r\n  error: number;\r\n  message: string;\r\n  data: {\r\n    /** Template ID */\r\n    template_id: string;\r\n    /** Tên template */\r\n    template_name: string;\r\n    /** Loại template */\r\n    template_type: number;\r\n    /** Trạng thái template sau khi submit */\r\n    status: \"PENDING_REVIEW\";\r\n    /** Template tag */\r\n    tag: number;\r\n    /** App ID của template */\r\n    app_id: string;\r\n    /** OA ID của template */\r\n    oa_id: string;\r\n    /** Đơn giá gửi tin qua SĐT */\r\n    price_sdt: string;\r\n    /** Đơn giá gửi tin qua UID */\r\n    price_uid: string;\r\n    /** Timeout của template */\r\n    timeout: number;\r\n    /** Link preview template */\r\n    preview_url: string;\r\n  };\r\n}\r\n\r\nexport interface ZNSUploadImageResult {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    media_id: string;\r\n  };\r\n}\r\n\r\nexport interface ZNSStatusInfo {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    status: \"PENDING\" | \"SENT\" | \"RECEIVED\" | \"FAILED\";\r\n    sent_time?: string;\r\n    received_time?: string;\r\n    fail_reason?: string;\r\n  };\r\n}\r\n\r\nexport interface ZNSMessageStatusInfo {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    delivery_time: string; // Thời gian thiết bị nhận được thông báo ZNS\r\n    message: string; // Mô tả trạng thái thông báo\r\n    status: number; // Trạng thái: -1 (không tồn tại), 0 (đã gửi lên server), 1 (đã giao đến thiết bị)\r\n  };\r\n}\r\n\r\nexport interface ZNSBatchMessageStatusRequest {\r\n  message_ids: string[]; // Danh sách message IDs cần kiểm tra trạng thái\r\n}\r\n\r\nexport interface ZNSBatchMessageStatusResponse {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    [messageId: string]: {\r\n      delivery_time: string; // Thời gian thiết bị nhận được thông báo ZNS\r\n      message: string; // Mô tả trạng thái thông báo\r\n      status: number; // Trạng thái: -1 (không tồn tại), 0 (đã gửi lên server), 1 (đã giao đến thiết bị)\r\n    };\r\n  };\r\n}\r\n\r\nexport interface ZNSQuotaInfo {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    dailyQuota: string; // Số thông báo ZNS OA được gửi trong 1 ngày\r\n    remainingQuota: string; // Số thông báo ZNS OA được gửi trong ngày còn lại\r\n    dailyQuotaPromotion: string | null; // Số tin ZNS hậu mãi OA được gửi trong ngày (null từ 1/11)\r\n    remainingQuotaPromotion: string | null; // Số tin ZNS hậu mãi còn lại OA được gửi trong ngày (null từ 1/11)\r\n    monthlyPromotionQuota: number; // Số tin ZNS hậu mãi OA được gửi trong tháng\r\n    remainingMonthlyPromotionQuota: number; // Số tin ZNS hậu mãi còn lại OA được gửi trong tháng\r\n    estimatedNextMonthPromotionQuota: number; // Số tin ZNS hậu mãi dự kiến mà OA có thể gửi trong tháng tiếp theo\r\n  };\r\n}\r\n\r\nexport interface ZNSAllowedContentTypes {\r\n  error: number;\r\n  message: string;\r\n  data?: string[]; // Mảng các loại nội dung: [\"TRANSACTION\", \"CUSTOMER_CARE\", \"PROMOTION\"]\r\n}\r\n\r\nexport interface ZNSTemplateSampleData {\r\n  error: number;\r\n  message: string;\r\n  data: Record<string, any>; // Chứa tham số và dữ liệu mẫu của template\r\n}\r\n\r\nexport interface ZNSCustomerRatingResponse {\r\n  error: number;\r\n  data: {\r\n    total: number; // Tổng số lượng đánh giá được trả về\r\n    data: Array<{\r\n      note: string; // Phần ghi chú thêm của khách hàng\r\n      rate: number; // Số sao được khách hàng đánh giá\r\n      submitDate: string; // Thời điểm khách hàng submit đánh giá (timestamp millisecond)\r\n      msgId: string; // ID của thông tin đánh giá\r\n      feedbacks: string[]; // Phần nhận xét từ khách hàng\r\n      trackingId: string; // Tracking ID từ phía đối tác\r\n    }>;\r\n  };\r\n}\r\n\r\nexport interface ZNSOAQualityInfo {\r\n  error: number;\r\n  message: string;\r\n  data: {\r\n    oaCurrentQuality: \"HIGH\" | \"MEDIUM\" | \"LOW\" | \"UNDEFINED\"; // Chất lượng gửi ZNS trong 48 giờ gần nhất\r\n    oa7dayQuality: \"HIGH\" | \"MEDIUM\" | \"LOW\" | \"UNDEFINED\"; // Chất lượng gửi ZNS trong 7 ngày gần nhất\r\n  };\r\n}\r\n\r\nexport interface ZNSQualityHistoryList {\r\n  error: number;\r\n  message: string;\r\n  data?: {\r\n    history: Array<{\r\n      date: string;\r\n      quality: \"HIGH\" | \"MEDIUM\" | \"LOW\";\r\n      qualityScore: number;\r\n      metrics: {\r\n        sent: number;\r\n        delivered: number;\r\n        read: number;\r\n        responded: number;\r\n        complained: number;\r\n      };\r\n    }>;\r\n  };\r\n}\r\n\r\nexport interface ZNSTemplateStatus {\r\n  templateId: string;\r\n  status: \"PENDING_REVIEW\" | \"ENABLE\" | \"REJECT\" | \"DISABLE\";\r\n  rejectReason?: string;\r\n  enabledTime?: string;\r\n  disabledTime?: string;\r\n}\r\n\r\n// ZNS Component Types for Template Creation\r\nexport interface ZNSComponent {\r\n  type: \"HEADER\" | \"BODY\" | \"FOOTER\";\r\n  format?: \"TEXT\" | \"IMAGE\" | \"VIDEO\";\r\n  text?: string;\r\n  url?: string;\r\n  example?: {\r\n    header_text?: string[];\r\n    body_text?: string[][];\r\n    header_handle?: string[];\r\n  };\r\n}\r\n\r\nexport interface ZNSTemplateComponent {\r\n  type: \"TITLE\" | \"PARAGRAPH\" | \"TABLE\" | \"LOGO\" | \"IMAGE\" | \"BUTTONS\";\r\n  content?: string;\r\n  url?: string;\r\n  buttons?: Array<{\r\n    title: string;\r\n    type: \"PHONE_NUMBER\" | \"URL\";\r\n    payload: string;\r\n  }>;\r\n  table?: {\r\n    headers: string[];\r\n    rows: string[][];\r\n  };\r\n}\r\n\r\n// ZNS Error Types\r\nexport interface ZNSError {\r\n  error: number;\r\n  message: string;\r\n  details?: {\r\n    field?: string;\r\n    code?: string;\r\n    description?: string;\r\n  };\r\n}\r\n\r\n// ZNS Analytics Types\r\nexport interface ZNSAnalytics {\r\n  templateId: string;\r\n  period: {\r\n    from: string;\r\n    to: string;\r\n  };\r\n  metrics: {\r\n    sent: number;\r\n    delivered: number;\r\n    read: number;\r\n    clicked: number;\r\n    failed: number;\r\n    deliveryRate: number;\r\n    readRate: number;\r\n    clickRate: number;\r\n  };\r\n  breakdown?: {\r\n    daily?: Array<{\r\n      date: string;\r\n      sent: number;\r\n      delivered: number;\r\n      read: number;\r\n      clicked: number;\r\n      failed: number;\r\n    }>;\r\n    hourly?: Array<{\r\n      hour: number;\r\n      sent: number;\r\n      delivered: number;\r\n      read: number;\r\n      clicked: number;\r\n      failed: number;\r\n    }>;\r\n  };\r\n}\r\n\r\n// ===== ZNS Component Validation Types =====\r\n// Các interface cụ thể cho validation từng component\r\n\r\n/** Attachment object cho LOGO và IMAGES */\r\nexport interface ZNSAttachment {\r\n  type: \"IMAGE\";\r\n  media_id: string;\r\n}\r\n\r\n/** TITLE component */\r\nexport interface ZNSTitleComponent {\r\n  value: string; // 9-65 ký tự, tối đa 4 params\r\n}\r\n\r\n/** PARAGRAPH component */\r\nexport interface ZNSParagraphComponent {\r\n  value: string; // 9-400 ký tự, tối đa 10 params\r\n}\r\n\r\n/** OTP component */\r\nexport interface ZNSOTPComponent {\r\n  value: string; // 1-10 ký tự, tham số cần define type OTP(10)\r\n}\r\n\r\n/** TABLE component */\r\nexport interface ZNSTableComponent {\r\n  rows: ZNSTableRow[]; // 2-8 rows\r\n}\r\n\r\nexport interface ZNSTableRow {\r\n  title: string; // 3-36 ký tự, chỉ text cố định\r\n  value: string; // 3-90 ký tự, có thể có params\r\n  row_type?: 0 | 1 | 2 | 3 | 4 | 5; // Hiệu ứng row\r\n}\r\n\r\n/** LOGO component */\r\nexport interface ZNSLogoComponent {\r\n  light: ZNSAttachment;\r\n  dark: ZNSAttachment;\r\n}\r\n\r\n/** IMAGES component */\r\nexport interface ZNSImagesComponent {\r\n  items: ZNSAttachment[]; // 1-3 attachments\r\n}\r\n\r\n/** BUTTONS component */\r\nexport interface ZNSButtonsComponent {\r\n  items: ZNSButtonItem[]; // 1-2 buttons\r\n}\r\n\r\nexport interface ZNSButtonItem {\r\n  type: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; // Button types\r\n  title: string; // 5-30 ký tự, chỉ text cố định\r\n  content: string; // URL/phone, có thể có params (URL(200) sub domain)\r\n}\r\n\r\n/** PAYMENT component */\r\nexport interface ZNSPaymentComponent {\r\n  bank_code: string; // Bank code cố định\r\n  account_name: string; // 1-100 ký tự, text cố định\r\n  bank_account: string; // 1-100 ký tự, text cố định (Doc: bank_account)\r\n  amount: string; // 2,000 - 500,000,000 hoặc param (VNĐ 12)\r\n  note?: string; // 1-90 ký tự, có thể có params (Bank transfer note 90)\r\n}\r\n\r\n/** VOUCHER component */\r\nexport interface ZNSVoucherComponent {\r\n  name: string; // 1-30 ký tự, có thể có params\r\n  condition: string; // 1-40 ký tự, có thể có params\r\n  start_date?: string; // Có thể có params/datetime\r\n  end_date: string; // Có thể có params/datetime\r\n  voucher_code: string; // 1-25 ký tự, có thể có params (Mã số 30)\r\n}\r\n\r\n/** RATING component */\r\nexport interface ZNSRatingComponent {\r\n  items: ZNSRatingItem[]; // Đúng 5 items\r\n}\r\n\r\nexport interface ZNSRatingItem {\r\n  star: 1 | 2 | 3 | 4 | 5; // Số sao\r\n  title: string; // 1-50 ký tự\r\n  question?: string; // 1-100 ký tự\r\n  answers?: string[]; // Danh sách câu trả lời (Doc: answers List string)\r\n  thanks: string; // 1-100 ký tự\r\n  description: string; // 1-200 ký tự\r\n}\r\n\r\n/** Union type cho tất cả các component */\r\nexport type ZNSValidationComponent =\r\n  | { TITLE: ZNSTitleComponent }\r\n  | { PARAGRAPH: ZNSParagraphComponent }\r\n  | { OTP: ZNSOTPComponent }\r\n  | { TABLE: ZNSTableComponent }\r\n  | { LOGO: ZNSLogoComponent }\r\n  | { IMAGES: ZNSImagesComponent }\r\n  | { BUTTONS: ZNSButtonsComponent }\r\n  | { PAYMENT: ZNSPaymentComponent }\r\n  | { VOUCHER: ZNSVoucherComponent }\r\n  | { RATING: ZNSRatingComponent };\r\n","/**\r\n * Types for Zalo Official Account Article Management\r\n * Based on Zalo Article API v2.0\r\n */\r\n\r\nimport { ZaloApiResponse, ZaloResponse } from \"./common\";\r\n\r\n// ==================== ARTICLE COVER TYPES ====================\r\n\r\n/**\r\n * Article cover configuration\r\n */\r\nexport interface ArticleCover {\r\n  /** Cover type: photo or video */\r\n  cover_type: 'photo' | 'video';\r\n  /** Photo URL (required when cover_type is 'photo') */\r\n  photo_url?: string;\r\n  /** Video ID (required when cover_type is 'video') */\r\n  video_id?: string;\r\n  /** Cover view orientation for video */\r\n  cover_view?: 'horizontal' | 'vertical' | 'square';\r\n  /** Cover status */\r\n  status: 'show' | 'hide';\r\n}\r\n\r\n// ==================== ARTICLE BODY TYPES ====================\r\n\r\n/**\r\n * Article body content item\r\n */\r\nexport interface ArticleBodyItem {\r\n  /** Content type */\r\n  type: 'text' | 'image' | 'video' | 'product';\r\n  /** Text content (for type: text) */\r\n  content?: string;\r\n  /** Image URL (for type: image) */\r\n  url?: string;\r\n  /** Video ID (for type: video) */\r\n  video_id?: string;\r\n  /** Product ID (for type: product) */\r\n  id?: string;\r\n  /** Additional properties for video */\r\n  width?: number;\r\n  height?: number;\r\n  thumbnail?: string;\r\n}\r\n\r\n// ==================== ARTICLE REQUEST TYPES ====================\r\n\r\n/**\r\n * Normal article creation request\r\n */\r\nexport interface ArticleNormalRequest {\r\n  type: 'normal';\r\n  title: string;\r\n  author: string;\r\n  cover: ArticleCover;\r\n  description: string;\r\n  body: ArticleBodyItem[];\r\n  related_medias?: string[];\r\n  tracking_link?: string;\r\n  status?: 'show' | 'hide';\r\n  comment?: 'show' | 'hide';\r\n}\r\n\r\n/**\r\n * Video article creation request\r\n */\r\nexport interface ArticleVideoRequest {\r\n  type: 'video';\r\n  title: string;\r\n  description: string;\r\n  video_id: string;\r\n  avatar: string;\r\n  status?: 'show' | 'hide';\r\n  comment?: 'show' | 'hide';\r\n}\r\n\r\n/**\r\n * Article creation request (union type)\r\n */\r\nexport type ArticleRequest = ArticleNormalRequest | ArticleVideoRequest;\r\n\r\n// ==================== ARTICLE RESPONSE TYPES ====================\r\n\r\n/**\r\n * Article creation response\r\n */\r\n// Phản hồi tạo bài viết theo đúng cấu trúc API: { error, message, data: { token } }\r\nexport type ArticleResponse = ZaloResponse<{ token: string }>;\r\n\r\n/**\r\n * Article process check response\r\n */\r\nexport interface ArticleProcessResponse {\r\n  /** Article ID if creation is successful */\r\n  article_id?: string;\r\n  /** Process status */\r\n  status: 'processing' | 'success' | 'failed';\r\n  /** Error message if failed */\r\n  error_message?: string;\r\n  /** Creation timestamp */\r\n  created_time: number;\r\n  /** Last update timestamp */\r\n  updated_time: number;\r\n}\r\n\r\n/**\r\n * Article verification response\r\n */\r\nexport interface ArticleVerifyResponse {\r\n  /** Article ID */\r\n  id: string;\r\n}\r\n\r\n// ==================== ARTICLE DETAIL TYPES ====================\r\n\r\n/**\r\n * Article cite information\r\n */\r\nexport interface ArticleCite {\r\n  url: string;\r\n  description: string;\r\n}\r\n\r\n/**\r\n * Article detail (normal type)\r\n */\r\nexport interface ArticleDetailNormal {\r\n  id: string;\r\n  type: 'normal';\r\n  title: string;\r\n  author: string;\r\n  cover: ArticleCover;\r\n  description: string;\r\n  body: ArticleBodyItem[];\r\n  related_medias: string[];\r\n  tracking_link: string;\r\n  status: 'show' | 'hide';\r\n  comment: 'show' | 'hide';\r\n  cite?: ArticleCite;\r\n  link_view: string;\r\n  total_view: number;\r\n  total_share: number;\r\n  total_like: number;\r\n  total_comment: number;\r\n}\r\n\r\n/**\r\n * Article detail (video type)\r\n */\r\nexport interface ArticleDetailVideo {\r\n  id: string;\r\n  type: 'video';\r\n  title: string;\r\n  description: string;\r\n  video_id: string;\r\n  avatar: string;\r\n  status: 'show' | 'hide';\r\n  comment: 'show' | 'hide';\r\n  link_view: string;\r\n  total_view: number;\r\n  total_share: number;\r\n  total_like: number;\r\n  total_comment: number;\r\n}\r\n\r\n/**\r\n * Article detail (union type)\r\n */\r\nexport type ArticleDetail = ArticleDetailNormal | ArticleDetailVideo;\r\n\r\n// ==================== ARTICLE LIST TYPES ====================\r\n\r\n/**\r\n * Article list request\r\n */\r\nexport interface ArticleListRequest {\r\n  /** Starting position */\r\n  offset: number;\r\n  /** Number of articles to return (max 10) */\r\n  limit: number;\r\n  /** Article type filter */\r\n  type: 'normal' | 'video';\r\n}\r\n\r\n/**\r\n * Article list item - matches Zalo API response structure exactly\r\n */\r\nexport interface ArticleListItem {\r\n  /** Article ID */\r\n  id: string;\r\n  /** Article type */\r\n  type: 'normal' | 'video';\r\n  /** Article title */\r\n  title: string;\r\n  /** Article status */\r\n  status: 'show' | 'hide';\r\n  /** Total view count */\r\n  total_view: number;\r\n  /** Total share count */\r\n  total_share: number;\r\n  /** Article creation date (timestamp) */\r\n  create_date: number;\r\n  /** Article update date (timestamp) */\r\n  update_date: number;\r\n  /** Article thumbnail URL */\r\n  thumb: string;\r\n  /** Article view link */\r\n  link_view: string;\r\n}\r\n\r\n/**\r\n * Article list response data - matches Zalo API response structure exactly\r\n */\r\nexport interface ArticleListData {\r\n  /** Array of articles (called \"medias\" in Zalo API) */\r\n  medias: ArticleListItem[];\r\n  /** Total number of articles */\r\n  total: number;\r\n}\r\n\r\n/**\r\n * Article list response\r\n */\r\nexport type ArticleListResponse = ZaloApiResponse<ArticleListData>;\r\n\r\n// ==================== ARTICLE UPDATE TYPES ====================\r\n\r\n/**\r\n * Normal article update request\r\n */\r\nexport interface ArticleUpdateNormalRequest {\r\n  id: string;\r\n  type: 'normal';\r\n  title: string;\r\n  author: string;\r\n  cover: ArticleCover;\r\n  description: string;\r\n  body: ArticleBodyItem[];\r\n  related_medias?: string[];\r\n  tracking_link?: string;\r\n  status?: 'show' | 'hide';\r\n  comment?: 'show' | 'hide';\r\n}\r\n\r\n/**\r\n * Video article update request\r\n */\r\nexport interface ArticleUpdateVideoRequest {\r\n  id: string;\r\n  type: 'video';\r\n  title: string;\r\n  description: string;\r\n  video_id: string;\r\n  avatar: string;\r\n  status?: 'show' | 'hide';\r\n  comment?: 'show' | 'hide';\r\n}\r\n\r\n/**\r\n * Article update request (union type)\r\n */\r\nexport type ArticleUpdateRequest = ArticleUpdateNormalRequest | ArticleUpdateVideoRequest;\r\n\r\n/**\r\n * Article update response\r\n */\r\nexport interface ArticleUpdateResponse {\r\n  /** Token for tracking article update progress */\r\n  token: string;\r\n}\r\n\r\n// ==================== ARTICLE REMOVAL TYPES ====================\r\n\r\n/**\r\n * Article removal request\r\n */\r\nexport interface ArticleRemoveRequest {\r\n  /** Article ID to remove */\r\n  id: string;\r\n}\r\n\r\n/**\r\n * Article removal response\r\n */\r\nexport interface ArticleRemoveResponse {\r\n  /** Success message */\r\n  message: string;\r\n}\r\n\r\n/**\r\n * Bulk article removal result for individual item\r\n */\r\nexport interface BulkRemoveItemResult {\r\n  /** Article ID that was processed */\r\n  article_id: string;\r\n\r\n  /** Whether the removal was successful */\r\n  success: boolean;\r\n\r\n  /** Success message if removal succeeded */\r\n  message?: string;\r\n\r\n  /** Error message if removal failed */\r\n  error?: string;\r\n\r\n  /** Processing time in milliseconds */\r\n  processing_time?: number;\r\n}\r\n\r\n/**\r\n * Progress information for bulk article removal\r\n */\r\nexport interface BulkRemoveProgress {\r\n  /** Current number of articles processed */\r\n  current_count: number;\r\n\r\n  /** Total number of articles to process */\r\n  total_count: number;\r\n\r\n  /** Progress percentage (0-100) */\r\n  percentage: number;\r\n\r\n  /** Number of successful removals so far */\r\n  successful_count: number;\r\n\r\n  /** Number of failed removals so far */\r\n  failed_count: number;\r\n\r\n  /** Whether the operation is complete */\r\n  is_complete: boolean;\r\n\r\n  /** Current batch being processed */\r\n  current_batch?: number;\r\n\r\n  /** Total number of batches */\r\n  total_batches?: number;\r\n}\r\n\r\n/**\r\n * Options for bulk article removal\r\n */\r\nexport interface BulkRemoveOptions {\r\n  /** Number of articles to process in each batch (default: 10) */\r\n  batch_size?: number;\r\n\r\n  /** Maximum number of concurrent requests (default: 3) */\r\n  max_concurrency?: number;\r\n\r\n  /** Whether to continue processing if some articles fail (default: true) */\r\n  continue_on_error?: boolean;\r\n\r\n  /** Delay between batches in milliseconds (default: 100) */\r\n  batch_delay?: number;\r\n\r\n  /** Timeout for each removal request in milliseconds (default: 10000) */\r\n  request_timeout?: number;\r\n}\r\n\r\n/**\r\n * Complete result for bulk article removal operation\r\n */\r\nexport interface BulkRemoveResult {\r\n  /** Total number of articles processed */\r\n  total_processed: number;\r\n\r\n  /** Number of successful removals */\r\n  successful_count: number;\r\n\r\n  /** Number of failed removals */\r\n  failed_count: number;\r\n\r\n  /** Success rate as percentage (0-100) */\r\n  success_rate: number;\r\n\r\n  /** Total processing time in milliseconds */\r\n  total_time: number;\r\n\r\n  /** Detailed results for each article */\r\n  results: BulkRemoveItemResult[];\r\n\r\n  /** Summary of errors encountered */\r\n  error_summary?: {\r\n    [errorType: string]: number;\r\n  };\r\n}\r\n\r\n// ==================== BULK ARTICLE DETAILS FETCH TYPES ====================\r\n\r\n/**\r\n * Result for individual article detail fetch in bulk operation\r\n */\r\nexport interface BulkDetailItemResult {\r\n  /** Article ID that was processed */\r\n  article_id: string;\r\n\r\n  /** Whether the detail fetch was successful */\r\n  success: boolean;\r\n\r\n  /** Article detail if fetch succeeded */\r\n  detail?: ArticleDetail;\r\n\r\n  /** Error message if fetch failed */\r\n  error?: string;\r\n\r\n  /** Processing time in milliseconds */\r\n  processing_time?: number;\r\n}\r\n\r\n/**\r\n * Progress information for bulk article details fetch\r\n */\r\nexport interface BulkDetailProgress {\r\n  /** Current number of articles processed */\r\n  current_count: number;\r\n\r\n  /** Total number of articles to process */\r\n  total_count: number;\r\n\r\n  /** Progress percentage (0-100) */\r\n  percentage: number;\r\n\r\n  /** Number of successful detail fetches so far */\r\n  successful_count: number;\r\n\r\n  /** Number of failed detail fetches so far */\r\n  failed_count: number;\r\n\r\n  /** Whether the operation is complete */\r\n  is_complete: boolean;\r\n\r\n  /** Current phase of operation */\r\n  phase: 'fetching_list' | 'fetching_details' | 'complete';\r\n\r\n  /** Current batch being processed (for details phase) */\r\n  current_batch?: number;\r\n\r\n  /** Total number of batches (for details phase) */\r\n  total_batches?: number;\r\n}\r\n\r\n/**\r\n * Options for bulk article details fetch\r\n */\r\nexport interface BulkDetailOptions {\r\n  /** Number of articles to fetch details for in each batch (default: 5) */\r\n  detail_batch_size?: number;\r\n\r\n  /** Maximum number of concurrent detail requests (default: 3) */\r\n  max_concurrency?: number;\r\n\r\n  /** Whether to continue processing if some details fail (default: true) */\r\n  continue_on_error?: boolean;\r\n\r\n  /** Delay between detail batches in milliseconds (default: 200) */\r\n  batch_delay?: number;\r\n\r\n  /** Timeout for each detail request in milliseconds (default: 15000) */\r\n  request_timeout?: number;\r\n\r\n  /** Maximum number of articles to process (default: unlimited) */\r\n  max_articles?: number;\r\n\r\n  /** Options for the initial article list fetch */\r\n  list_options?: {\r\n    batch_size?: number;\r\n    max_articles?: number;\r\n  };\r\n}\r\n\r\n/**\r\n * Complete result for bulk article details fetch operation\r\n */\r\nexport interface BulkDetailResult {\r\n  /** Total number of articles processed */\r\n  total_processed: number;\r\n\r\n  /** Number of successful detail fetches */\r\n  successful_count: number;\r\n\r\n  /** Number of failed detail fetches */\r\n  failed_count: number;\r\n\r\n  /** Success rate as percentage (0-100) */\r\n  success_rate: number;\r\n\r\n  /** Total processing time in milliseconds */\r\n  total_time: number;\r\n\r\n  /** Detailed results for each article */\r\n  results: BulkDetailItemResult[];\r\n\r\n  /** Successfully fetched article details */\r\n  articles_with_details: ArticleDetail[];\r\n\r\n  /** Summary of errors encountered */\r\n  error_summary?: {\r\n    [errorType: string]: number;\r\n  };\r\n\r\n  /** Statistics from the initial list fetch */\r\n  list_fetch_stats: {\r\n    total_pages_fetched: number;\r\n    list_fetch_time: number;\r\n  };\r\n}\r\n\r\n// ==================== VIDEO UPLOAD TYPES ====================\r\n\r\n/**\r\n * Video upload response\r\n */\r\nexport interface VideoUploadResponse {\r\n  /** Token for tracking video upload progress */\r\n  token: string;\r\n}\r\n\r\n/**\r\n * Video status response\r\n */\r\nexport interface VideoStatusResponse {\r\n  /** Video ID (available when processing is complete) */\r\n  video_id?: string;\r\n  /** Video name */\r\n  video_name?: string;\r\n  /** Video size in bytes */\r\n  video_size?: number;\r\n  /** Processing status */\r\n  status: number;\r\n  /** Status message */\r\n  status_message: string;\r\n  /** Conversion progress percentage */\r\n  convert_percent: number;\r\n  /** Conversion error code */\r\n  convert_error_code: number;\r\n}\r\n\r\n// ==================== VALIDATION TYPES ====================\r\n\r\n/**\r\n * Article validation error\r\n */\r\nexport interface ArticleValidationError {\r\n  field: string;\r\n  message: string;\r\n  code: string;\r\n}\r\n\r\n/**\r\n * Video file validation constraints\r\n */\r\nexport interface VideoFileConstraints {\r\n  /** Maximum file size in bytes (50MB) */\r\n  maxSize: number;\r\n  /** Allowed file extensions */\r\n  allowedExtensions: string[];\r\n  /** Allowed MIME types */\r\n  allowedMimeTypes: string[];\r\n}\r\n\r\n// ==================== CONSTANTS ====================\r\n\r\n/**\r\n * Article validation constants\r\n */\r\nexport const ARTICLE_CONSTRAINTS = {\r\n  TITLE_MAX_LENGTH: 150,\r\n  AUTHOR_MAX_LENGTH: 50,\r\n  DESCRIPTION_MAX_LENGTH: 300,\r\n  IMAGE_MAX_SIZE: 1024 * 1024, // 1MB\r\n  VIDEO_MAX_SIZE: 50 * 1024 * 1024, // 50MB\r\n  LIST_MAX_LIMIT: 10, // Zalo API maximum limit is 10\r\n} as const;\r\n\r\n/**\r\n * Video upload constraints\r\n */\r\nexport const VIDEO_CONSTRAINTS: VideoFileConstraints = {\r\n  maxSize: 50 * 1024 * 1024, // 50MB\r\n  allowedExtensions: ['.mp4', '.avi'],\r\n  allowedMimeTypes: ['video/mp4', 'video/avi', 'video/x-msvideo'],\r\n} as const;\r\n\r\n/**\r\n * Article status enum\r\n */\r\nexport enum ArticleStatus {\r\n  SHOW = 'show',\r\n  HIDE = 'hide',\r\n}\r\n\r\n/**\r\n * Comment status enum\r\n */\r\nexport enum CommentStatus {\r\n  SHOW = 'show',\r\n  HIDE = 'hide',\r\n}\r\n\r\n/**\r\n * Article type enum\r\n */\r\nexport enum ArticleType {\r\n  NORMAL = 'normal',\r\n  VIDEO = 'video',\r\n}\r\n\r\n/**\r\n * Cover type enum\r\n */\r\nexport enum CoverType {\r\n  PHOTO = 'photo',\r\n  VIDEO = 'video',\r\n}\r\n\r\n/**\r\n * Body item type enum\r\n */\r\nexport enum BodyItemType {\r\n  TEXT = 'text',\r\n  IMAGE = 'image',\r\n  VIDEO = 'video',\r\n  PRODUCT = 'product',\r\n}\r\n\r\n/**\r\n * Video upload status enum\r\n */\r\nexport enum VideoUploadStatus {\r\n  UNKNOWN = 0,           // Trạng thái không xác định\r\n  SUCCESS = 1,           // Video đã được xử lý thành công và có thể sử dụng\r\n  LOCKED = 2,            // Video đã bị khóa\r\n  PROCESSING = 3,        // Video đang được xử lý\r\n  FAILED = 4,            // Video xử lý thất bại\r\n  DELETED = 5,           // Video đã bị xóa\r\n}\r\n","/**\r\n * Broadcast Message Type Definitions for Zalo OA\r\n * API: https://openapi.zalo.me/v2.0/oa/message\r\n */\r\n\r\n/**\r\n * Broadcast recipient target criteria\r\n */\r\nexport interface BroadcastTarget {\r\n  /**\r\n   * Danh sách các nhóm tuổi sẽ nhận thông báo. Các giá trị phân tách nhau bởi dấu \",\"\r\n   * 0: Tuổi từ 0-12\r\n   * 1: Tuổi từ 13-17\r\n   * 2: Tuổi từ 18-24\r\n   * 3: Tuổi từ 25-34\r\n   * 4: Tuổi từ 35-44\r\n   * 5: Tuổi từ 45-54\r\n   * 6: Tuổi từ 55-64\r\n   * 7: Tuổi lớn hơn hay bằng 65\r\n   */\r\n  ages?: string;\r\n\r\n  /**\r\n   * Danh sách nhóm giới tính sẽ nhận thông báo. Các giá trị phân tách nhau bởi dấu \",\"\r\n   * 0: Tất cả các giới tính\r\n   * 1: Nam\r\n   * 2: Nữ\r\n   */\r\n  gender?: string;\r\n\r\n  /**\r\n   * Danh sách các địa điểm sẽ nhận thông báo. Các giá trị phân tách nhau bởi dấu \",\"\r\n   * 0: Miền Bắc Việt Nam\r\n   * 1: Miền Trung Việt Nam\r\n   * 2: Miền Nam Việt Nam\r\n   */\r\n  locations?: string;\r\n\r\n  /**\r\n   * Danh sách các tỉnh, thành phố sẽ nhận thông báo. Các giá trị phân tách nhau bởi dấu \",\"\r\n   * Nếu được thiết lập, thuộc tính này sẽ thay thế thuộc tính \"locations\"\r\n   */\r\n  cities?: string;\r\n\r\n  /**\r\n   * Danh sách các hệ điều hành di động sẽ nhận thông báo. Các giá trị phân tách nhau bởi dấu \",\"\r\n   * 1: IOS\r\n   * 2: Android\r\n   * 3: Window Phone\r\n   */\r\n  platform?: string;\r\n}\r\n\r\n/**\r\n * Broadcast recipient\r\n */\r\nexport interface BroadcastRecipient {\r\n  target: BroadcastTarget;\r\n}\r\n\r\n/**\r\n * Broadcast message attachment payload element\r\n */\r\nexport interface BroadcastMessageElement {\r\n  /**\r\n   * Loại media - bắt buộc phải là \"article\"\r\n   */\r\n  media_type: \"article\";\r\n\r\n  /**\r\n   * ID của bài viết muốn broadcast\r\n   */\r\n  attachment_id: string;\r\n}\r\n\r\n/**\r\n * Broadcast message attachment payload\r\n */\r\nexport interface BroadcastMessagePayload {\r\n  /**\r\n   * Loại template - bắt buộc phải là \"media\"\r\n   */\r\n  template_type: \"media\";\r\n\r\n  /**\r\n   * Chứa các đối tượng của template\r\n   */\r\n  elements: BroadcastMessageElement[];\r\n}\r\n\r\n/**\r\n * Broadcast message attachment\r\n */\r\nexport interface BroadcastMessageAttachment {\r\n  /**\r\n   * Loại attachment - bắt buộc phải là \"template\"\r\n   */\r\n  type: \"template\";\r\n\r\n  /**\r\n   * Chứa payload của attachment muốn gửi\r\n   */\r\n  payload: BroadcastMessagePayload;\r\n}\r\n\r\n/**\r\n * Broadcast message\r\n */\r\nexport interface BroadcastMessage {\r\n  attachment: BroadcastMessageAttachment;\r\n}\r\n\r\n/**\r\n * Broadcast request\r\n */\r\nexport interface BroadcastRequest {\r\n  /**\r\n   * Thông tin người nhận\r\n   */\r\n  recipient: BroadcastRecipient;\r\n\r\n  /**\r\n   * Nội dung và các attachment cần gửi\r\n   */\r\n  message: BroadcastMessage;\r\n}\r\n\r\n/**\r\n * Broadcast response data\r\n */\r\nexport interface BroadcastResponseData {\r\n  /**\r\n   * ID của tin broadcast\r\n   */\r\n  message_id: string;\r\n}\r\n\r\n/**\r\n * Broadcast response\r\n */\r\nexport interface BroadcastResponse {\r\n  /**\r\n   * Dữ liệu phản hồi\r\n   */\r\n  data: BroadcastResponseData;\r\n\r\n  /**\r\n   * Mã lỗi (0 = thành công)\r\n   */\r\n  error: number;\r\n\r\n  /**\r\n   * Thông báo\r\n   */\r\n  message: string;\r\n}\r\n\r\n/**\r\n * City codes mapping for broadcast targeting\r\n */\r\nexport const BROADCAST_CITY_CODES = {\r\n  \"Đồng Tháp\": \"0\",\r\n  \"Bình Phước\": \"1\",\r\n  \"Ninh Bình\": \"2\",\r\n  \"Bạc Liêu\": \"3\",\r\n  \"Hồ Chí Minh\": \"4\",\r\n  \"Vĩnh Long\": \"5\",\r\n  \"Lâm Đồng\": \"6\",\r\n  \"Yên Bái\": \"7\",\r\n  \"Hà Nam\": \"8\",\r\n  \"Hà Nội\": \"9\",\r\n  \"Hải Dương\": \"10\",\r\n  \"Hậu Giang\": \"11\",\r\n  \"An Giang\": \"12\",\r\n  \"Trà Vinh\": \"13\",\r\n  \"Tiền Giang\": \"14\",\r\n  \"Tây Ninh\": \"15\",\r\n  \"Đồng Nai\": \"16\",\r\n  \"Đắk Lắk\": \"17\",\r\n  \"Bình Định\": \"18\",\r\n  \"Kon Tum\": \"19\",\r\n  \"Đà Nẵng\": \"20\",\r\n  \"Bắc Giang\": \"21\",\r\n  \"Bắc Kạn\": \"22\",\r\n  \"Điện Biên\": \"23\",\r\n  \"Hòa Bình\": \"24\",\r\n  \"Thái Bình\": \"25\",\r\n  \"Vĩnh Phúc\": \"26\",\r\n  \"Hà Giang\": \"27\",\r\n  \"Kiên Giang\": \"28\",\r\n  \"Bình Dương\": \"29\",\r\n  \"Bình Thuận\": \"30\",\r\n  \"Đắk Nông\": \"31\",\r\n  \"Khánh Hòa\": \"32\",\r\n  \"Gia Lai\": \"33\",\r\n  \"Quảng Nam\": \"34\",\r\n  \"Quảng Trị\": \"35\",\r\n  \"Hà Tĩnh\": \"36\",\r\n  \"Hưng Yên\": \"37\",\r\n  \"Quảng Ninh\": \"38\",\r\n  \"Thanh Hóa\": \"39\",\r\n  \"Phú Thọ\": \"40\",\r\n  \"Lai Châu\": \"41\",\r\n  \"Thái Nguyên\": \"42\",\r\n  \"Cao Bằng\": \"43\",\r\n  \"Cà Mau\": \"44\",\r\n  \"Cần Thơ\": \"45\",\r\n  \"Sóc Trăng\": \"46\",\r\n  \"Bến Tre\": \"47\",\r\n  \"Long An\": \"48\",\r\n  \"Bà Rịa Vũng Tàu\": \"49\",\r\n  \"Ninh Thuận\": \"50\",\r\n  \"Phú Yên\": \"51\",\r\n  \"Quãng Ngãi\": \"52\",\r\n  \"Thừa Thiên Huế\": \"53\",\r\n  \"Quảng Bình\": \"54\",\r\n  \"Nghệ An\": \"55\",\r\n  \"Nam Định\": \"56\",\r\n  \"Hải Phòng\": \"57\",\r\n  \"Lạng Sơn\": \"58\",\r\n  \"Lào Cai\": \"59\",\r\n  \"Sơn La\": \"60\",\r\n  \"Bắc Ninh\": \"61\",\r\n  \"Tuyên Quang\": \"62\",\r\n  \"Không Thuộc Việt Nam\": \"63\"\r\n} as const;\r\n\r\n/**\r\n * Age group codes for broadcast targeting\r\n */\r\nexport const BROADCAST_AGE_CODES = {\r\n  \"0-12\": \"0\",\r\n  \"13-17\": \"1\", \r\n  \"18-24\": \"2\",\r\n  \"25-34\": \"3\",\r\n  \"35-44\": \"4\",\r\n  \"45-54\": \"5\",\r\n  \"55-64\": \"6\",\r\n  \"65+\": \"7\"\r\n} as const;\r\n\r\n/**\r\n * Gender codes for broadcast targeting\r\n */\r\nexport const BROADCAST_GENDER_CODES = {\r\n  \"ALL\": \"0\",\r\n  \"MALE\": \"1\",\r\n  \"FEMALE\": \"2\"\r\n} as const;\r\n\r\n/**\r\n * Location codes for broadcast targeting\r\n */\r\nexport const BROADCAST_LOCATION_CODES = {\r\n  \"NORTH\": \"0\",\r\n  \"CENTRAL\": \"1\", \r\n  \"SOUTH\": \"2\"\r\n} as const;\r\n\r\n/**\r\n * Platform codes for broadcast targeting\r\n */\r\nexport const BROADCAST_PLATFORM_CODES = {\r\n  \"IOS\": \"1\",\r\n  \"ANDROID\": \"2\",\r\n  \"WINDOWS_PHONE\": \"3\"\r\n} as const;\r\n\r\n/**\r\n * Result for individual broadcast message\r\n */\r\nexport interface BroadcastMessageResult {\r\n  /**\r\n   * Article attachment ID\r\n   */\r\n  attachmentId: string;\r\n\r\n  /**\r\n   * Whether the broadcast was successful\r\n   */\r\n  success: boolean;\r\n\r\n  /**\r\n   * Message ID if successful\r\n   */\r\n  messageId: string | null;\r\n\r\n  /**\r\n   * Error if failed\r\n   */\r\n  error: Error | null;\r\n\r\n  /**\r\n   * Timestamp when sent\r\n   */\r\n  sentAt: Date;\r\n}\r\n\r\n/**\r\n * Progress information for multiple broadcast\r\n */\r\nexport interface MultipleBroadcastProgress {\r\n  /**\r\n   * Total number of messages to send\r\n   */\r\n  total: number;\r\n\r\n  /**\r\n   * Number of messages completed\r\n   */\r\n  completed: number;\r\n\r\n  /**\r\n   * Number of successful messages\r\n   */\r\n  successful: number;\r\n\r\n  /**\r\n   * Number of failed messages\r\n   */\r\n  failed: number;\r\n\r\n  /**\r\n   * Current attachment ID being processed\r\n   */\r\n  currentAttachmentId: string;\r\n\r\n  /**\r\n   * Whether all messages are completed\r\n   */\r\n  isCompleted: boolean;\r\n}\r\n\r\n/**\r\n * Result for multiple broadcast messages\r\n */\r\nexport interface MultipleBroadcastResult {\r\n  /**\r\n   * Total number of messages sent\r\n   */\r\n  totalMessages: number;\r\n\r\n  /**\r\n   * Number of successful messages\r\n   */\r\n  successfulMessages: number;\r\n\r\n  /**\r\n   * Number of failed messages\r\n   */\r\n  failedMessages: number;\r\n\r\n  /**\r\n   * Individual message results\r\n   */\r\n  results: BroadcastMessageResult[];\r\n\r\n  /**\r\n   * Total duration in milliseconds\r\n   */\r\n  totalDuration: number;\r\n\r\n  /**\r\n   * Sending mode used\r\n   */\r\n  mode: 'parallel' | 'sequential';\r\n\r\n  /**\r\n   * Target criteria used\r\n   */\r\n  targetCriteria: BroadcastTarget;\r\n}\r\n","/**\r\n * Purchase API types for Zalo Official Account\r\n * \r\n * API để tạo đơn hàng mua sản phẩm/dịch vụ OA\r\n * Yêu cầu: Ứng dụng cần được cấp quyền quản lý \"Mua sản phẩm dịch vụ OA\"\r\n */\r\n\r\n/**\r\n * Beneficiary types - Đối tượng thụ hưởng\r\n */\r\nexport type BeneficiaryType = \"OA\" | \"APP\";\r\n\r\n/**\r\n * Request để tạo đơn hàng với product_id\r\n */\r\nexport interface CreateOrderWithProductRequest {\r\n  /**\r\n   * Đối tượng được thụ hưởng khi mua đơn hàng\r\n   * - OA: Official Account\r\n   * - APP: Application\r\n   * Lưu ý: Mỗi sản phẩm sẽ quy định đối tượng khả dụng riêng\r\n   */\r\n  beneficiary: BeneficiaryType;\r\n\r\n  /**\r\n   * ID của sản phẩm muốn mua\r\n   * Chỉ sử dụng product_id HOẶC redeem_code, không được dùng cả hai\r\n   */\r\n  product_id: number;\r\n\r\n  /**\r\n   * Mã giảm giá (tùy chọn)\r\n   * VD: Giảm 100K, Giảm 10%, ...\r\n   */\r\n  voucher_code?: string;\r\n}\r\n\r\n/**\r\n * Request để tạo đơn hàng với redeem_code\r\n */\r\nexport interface CreateOrderWithRedeemRequest {\r\n  /**\r\n   * Đối tượng được thụ hưởng khi mua đơn hàng\r\n   */\r\n  beneficiary: BeneficiaryType;\r\n\r\n  /**\r\n   * Mã quà tặng\r\n   * Chỉ sử dụng product_id HOẶC redeem_code, không được dùng cả hai\r\n   */\r\n  redeem_code: string;\r\n\r\n  /**\r\n   * Mã giảm giá (tùy chọn)\r\n   */\r\n  voucher_code?: string;\r\n}\r\n\r\n/**\r\n * Union type cho request tạo đơn hàng\r\n */\r\nexport type CreateOrderRequest = CreateOrderWithProductRequest | CreateOrderWithRedeemRequest;\r\n\r\n/**\r\n * Response data khi tạo đơn hàng thành công\r\n */\r\nexport interface OrderData {\r\n  /**\r\n   * ID đơn hàng\r\n   * Lưu ý: Order chỉ được tạo từ 00:01 đến 23h54\r\n   */\r\n  order_id: string;\r\n\r\n  /**\r\n   * Loại đối tượng thụ hưởng\r\n   */\r\n  beneficiary_type: BeneficiaryType;\r\n\r\n  /**\r\n   * ID của OA (nếu beneficiary_type = OA) hoặc ID của App (nếu beneficiary_type = APP)\r\n   */\r\n  beneficiary_id: number;\r\n\r\n  /**\r\n   * ID sản phẩm\r\n   */\r\n  product_id: number;\r\n\r\n  /**\r\n   * Tên sản phẩm\r\n   */\r\n  product_name: string;\r\n\r\n  /**\r\n   * Tài khoản ZCA thanh toán đơn hàng\r\n   */\r\n  zca_id: number;\r\n\r\n  /**\r\n   * Voucher code đã được sử dụng (nếu có)\r\n   */\r\n  voucher_code?: string;\r\n\r\n  /**\r\n   * Mã quà tặng (nếu có)\r\n   */\r\n  redeem_code?: string;\r\n\r\n  /**\r\n   * Giá trị của voucher_code\r\n   * VD: Giảm 10% hay Giảm 100K, ...\r\n   */\r\n  discount?: number;\r\n\r\n  /**\r\n   * Giá tiền của đơn hàng (chưa áp dụng mã giảm giá)\r\n   */\r\n  amount: number;\r\n\r\n  /**\r\n   * Giá tiền chính thức sau khi đã sử dụng mã giảm giá\r\n   */\r\n  final_amount: number;\r\n\r\n  /**\r\n   * Thời điểm tạo đơn hàng, tính bằng millisecond\r\n   */\r\n  created_time: number;\r\n\r\n  /**\r\n   * OTT - One Time Token\r\n   * Mã xác thực dùng để xác nhận thanh toán đơn hàng\r\n   * Sử dụng tại API xác nhận thanh toán đơn hàng\r\n   * OTT sẽ có hiệu lực trong vòng 5 phút kể từ khi Order được khởi tạo\r\n   */\r\n  verified_token: string;\r\n}\r\n\r\n/**\r\n * Response khi tạo đơn hàng thành công\r\n */\r\nexport interface CreateOrderResponse {\r\n  error: 0;\r\n  message: \"Success\";\r\n  data: OrderData;\r\n}\r\n\r\n/**\r\n * Helper type để kiểm tra request type\r\n */\r\nexport function isProductOrderRequest(\r\n  request: CreateOrderRequest\r\n): request is CreateOrderWithProductRequest {\r\n  return 'product_id' in request;\r\n}\r\n\r\n/**\r\n * Helper type để kiểm tra request type\r\n */\r\nexport function isRedeemOrderRequest(\r\n  request: CreateOrderRequest\r\n): request is CreateOrderWithRedeemRequest {\r\n  return 'redeem_code' in request;\r\n}\r\n\r\n/**\r\n * Validation errors cho Purchase API\r\n */\r\nexport interface PurchaseValidationError {\r\n  field: string;\r\n  message: string;\r\n}\r\n\r\n/**\r\n * Request để xác nhận thanh toán đơn hàng\r\n */\r\nexport interface ConfirmOrderRequest {\r\n  /**\r\n   * ID đơn hàng\r\n   * Lấy từ response của API tạo đơn hàng\r\n   */\r\n  order_id: string;\r\n\r\n  /**\r\n   * OTT - One Time Token\r\n   * Mã xác thực dùng để xác nhận thanh toán đơn hàng\r\n   * Lấy từ response của API tạo đơn hàng\r\n   * OTT sẽ có hiệu lực trong vòng 5 phút kể từ khi Order được khởi tạo\r\n   */\r\n  verified_token: string;\r\n}\r\n\r\n/**\r\n * Response data khi xác nhận thanh toán đơn hàng thành công\r\n * Lưu ý: Response không có verified_token như khi tạo đơn hàng\r\n */\r\nexport interface ConfirmedOrderData {\r\n  /**\r\n   * ID đơn hàng\r\n   */\r\n  order_id: string;\r\n\r\n  /**\r\n   * Loại đối tượng thụ hưởng\r\n   */\r\n  beneficiary_type: BeneficiaryType;\r\n\r\n  /**\r\n   * ID của OA (nếu beneficiary_type = OA) hoặc ID của App (nếu beneficiary_type = APP)\r\n   */\r\n  beneficiary_id: number;\r\n\r\n  /**\r\n   * ID sản phẩm\r\n   */\r\n  product_id: number;\r\n\r\n  /**\r\n   * Tên sản phẩm\r\n   */\r\n  product_name: string;\r\n\r\n  /**\r\n   * Tài khoản ZCA thanh toán đơn hàng\r\n   */\r\n  zca_id: number;\r\n\r\n  /**\r\n   * Voucher code đã được sử dụng (nếu có)\r\n   */\r\n  voucher_code?: string;\r\n\r\n  /**\r\n   * Mã quà tặng (nếu có)\r\n   */\r\n  redeem_code?: string;\r\n\r\n  /**\r\n   * Giá trị của voucher_code\r\n   * VD: Giảm 10% hay Giảm 100K, ...\r\n   */\r\n  discount?: number;\r\n\r\n  /**\r\n   * Giá tiền của đơn hàng (chưa áp dụng mã giảm giá)\r\n   */\r\n  amount: number;\r\n\r\n  /**\r\n   * Giá tiền chính thức sau khi đã sử dụng mã giảm giá\r\n   */\r\n  final_amount: number;\r\n\r\n  /**\r\n   * Thời điểm tạo đơn hàng, tính bằng millisecond\r\n   */\r\n  created_time: number;\r\n}\r\n\r\n/**\r\n * Response khi xác nhận thanh toán đơn hàng thành công\r\n */\r\nexport interface ConfirmOrderResponse {\r\n  error: 0;\r\n  message: \"Success\";\r\n  data: ConfirmedOrderData;\r\n}\r\n\r\n/**\r\n * Danh sách sản phẩm OA có sẵn\r\n */\r\nexport const OA_PRODUCTS = {\r\n  // Sản phẩm OA Subscription\r\n  OA_ADVANCED_6M: 866836109767958135,\r\n  OA_ADVANCED_12M: 1302963828004138542,\r\n  OA_PREMIUM_6M: 757295129578622372,\r\n  OA_PREMIUM_12M: 3071996459978068910,\r\n\r\n  // Sản phẩm Quota khởi tạo GMF\r\n  GMF_10_MEMBERS: 739448264820568793,\r\n  GMF_50_MEMBERS: 2405469629611791306,\r\n  GMF_100_MEMBERS: 2275350247265190619,\r\n  GMF_1000_MEMBERS: 3678557233392100095,\r\n\r\n  // Sản phẩm Gói quota tin nhắn OA\r\n  TRANSACTION_5K: 4609774892111011697,\r\n  TRANSACTION_50K: 2038298593847789538,\r\n  TRANSACTION_500K: 2544831667685731909,\r\n} as const;\r\n\r\n/**\r\n * Thông tin chi tiết sản phẩm\r\n */\r\nexport interface ProductInfo {\r\n  id: number;\r\n  name: string;\r\n  beneficiary: BeneficiaryType[];\r\n  category: 'subscription' | 'gmf' | 'quota';\r\n}\r\n\r\n/**\r\n * Danh sách thông tin chi tiết sản phẩm\r\n */\r\nexport const PRODUCT_INFO: Record<number, ProductInfo> = {\r\n  // OA Subscription\r\n  [OA_PRODUCTS.OA_ADVANCED_6M]: {\r\n    id: OA_PRODUCTS.OA_ADVANCED_6M,\r\n    name: \"Gói OA Nâng cao 6 tháng\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"subscription\"\r\n  },\r\n  [OA_PRODUCTS.OA_ADVANCED_12M]: {\r\n    id: OA_PRODUCTS.OA_ADVANCED_12M,\r\n    name: \"Gói OA Nâng cao 12 tháng\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"subscription\"\r\n  },\r\n  [OA_PRODUCTS.OA_PREMIUM_6M]: {\r\n    id: OA_PRODUCTS.OA_PREMIUM_6M,\r\n    name: \"Gói OA Premium 6 tháng\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"subscription\"\r\n  },\r\n  [OA_PRODUCTS.OA_PREMIUM_12M]: {\r\n    id: OA_PRODUCTS.OA_PREMIUM_12M,\r\n    name: \"Gói OA Premium 12 tháng\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"subscription\"\r\n  },\r\n\r\n  // GMF Products\r\n  [OA_PRODUCTS.GMF_10_MEMBERS]: {\r\n    id: OA_PRODUCTS.GMF_10_MEMBERS,\r\n    name: \"GMF tối đa 10 thành viên\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"gmf\"\r\n  },\r\n  [OA_PRODUCTS.GMF_50_MEMBERS]: {\r\n    id: OA_PRODUCTS.GMF_50_MEMBERS,\r\n    name: \"GMF tối đa 50 thành viên\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"gmf\"\r\n  },\r\n  [OA_PRODUCTS.GMF_100_MEMBERS]: {\r\n    id: OA_PRODUCTS.GMF_100_MEMBERS,\r\n    name: \"GMF tối đa 100 thành viên\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"gmf\"\r\n  },\r\n  [OA_PRODUCTS.GMF_1000_MEMBERS]: {\r\n    id: OA_PRODUCTS.GMF_1000_MEMBERS,\r\n    name: \"GMF tối đa 1000 thành viên\",\r\n    beneficiary: [\"OA\"],\r\n    category: \"gmf\"\r\n  },\r\n\r\n  // Transaction Quota\r\n  [OA_PRODUCTS.TRANSACTION_5K]: {\r\n    id: OA_PRODUCTS.TRANSACTION_5K,\r\n    name: \"Gói 5k tin Giao dịch (1 tháng)\",\r\n    beneficiary: [\"APP\", \"OA\"],\r\n    category: \"quota\"\r\n  },\r\n  [OA_PRODUCTS.TRANSACTION_50K]: {\r\n    id: OA_PRODUCTS.TRANSACTION_50K,\r\n    name: \"Gói 50k tin Giao dịch (1 tháng)\",\r\n    beneficiary: [\"APP\", \"OA\"],\r\n    category: \"quota\"\r\n  },\r\n  [OA_PRODUCTS.TRANSACTION_500K]: {\r\n    id: OA_PRODUCTS.TRANSACTION_500K,\r\n    name: \"Gói 500k tin Giao dịch (1 tháng)\",\r\n    beneficiary: [\"APP\", \"OA\"],\r\n    category: \"quota\"\r\n  },\r\n};\r\n\r\n/**\r\n * Purchase API error codes\r\n */\r\nexport enum PurchaseErrorCode {\r\n  INVALID_BENEFICIARY = 1001,\r\n  INVALID_PRODUCT_ID = 1002,\r\n  INVALID_REDEEM_CODE = 1003,\r\n  INVALID_VOUCHER_CODE = 1004,\r\n  PRODUCT_NOT_AVAILABLE = 1005,\r\n  INSUFFICIENT_BALANCE = 1006,\r\n  ORDER_TIME_RESTRICTED = 1007,\r\n  DUPLICATE_ORDER = 1008,\r\n  PERMISSION_DENIED = 1009,\r\n  INVALID_ORDER_ID = 1010,\r\n  INVALID_VERIFIED_TOKEN = 1011,\r\n  ORDER_EXPIRED = 1012,\r\n  ORDER_ALREADY_CONFIRMED = 1013,\r\n  SYSTEM_ERROR = 9999,\r\n}\r\n","/**\r\n * ZNS Constants - Các hằng số theo chuẩn Zalo API\r\n */\r\n\r\nimport {\r\n  ZNSTemplateType,\r\n  ZNSTemplateTag,\r\n  ZNSButtonType,\r\n  ZNSParamType\r\n} from '../types/zns';\r\n\r\n/**\r\n * Template Type mapping với description\r\n */\r\nexport const ZNS_TEMPLATE_TYPES = {\r\n  [ZNSTemplateType.CUSTOM]: {\r\n    value: ZNSTemplateType.CUSTOM,\r\n    name: 'ZNS tùy chỉnh',\r\n    description: 'Template tùy chỉnh cho các mục đích khác nhau'\r\n  },\r\n  [ZNSTemplateType.AUTHENTICATION]: {\r\n    value: ZNSTemplateType.AUTHENTICATION,\r\n    name: 'ZNS xác thực',\r\n    description: 'Template cho việc xác thực OTP, mã PIN'\r\n  },\r\n  [ZNSTemplateType.PAYMENT_REQUEST]: {\r\n    value: ZNSTemplateType.PAYMENT_REQUEST,\r\n    name: 'ZNS yêu cầu thanh toán',\r\n    description: 'Template yêu cầu thanh toán'\r\n  },\r\n  [ZNSTemplateType.VOUCHER]: {\r\n    value: ZNSTemplateType.VOUCHER,\r\n    name: 'ZNS voucher',\r\n    description: 'Template gửi voucher, khuyến mãi'\r\n  },\r\n  [ZNSTemplateType.SERVICE_RATING]: {\r\n    value: ZNSTemplateType.SERVICE_RATING,\r\n    name: 'ZNS đánh giá dịch vụ',\r\n    description: 'Template yêu cầu đánh giá dịch vụ'\r\n  }\r\n} as const;\r\n\r\n/**\r\n * Template Tag mapping với description\r\n */\r\nexport const ZNS_TEMPLATE_TAGS = {\r\n  [ZNSTemplateTag.TRANSACTION]: {\r\n    value: ZNSTemplateTag.TRANSACTION,\r\n    name: 'Transaction',\r\n    description: 'Giao dịch, thanh toán'\r\n  },\r\n  [ZNSTemplateTag.CUSTOMER_CARE]: {\r\n    value: ZNSTemplateTag.CUSTOMER_CARE,\r\n    name: 'Customer Care',\r\n    description: 'Chăm sóc khách hàng'\r\n  },\r\n  [ZNSTemplateTag.PROMOTION]: {\r\n    value: ZNSTemplateTag.PROMOTION,\r\n    name: 'Promotion',\r\n    description: 'Khuyến mãi, quảng cáo'\r\n  }\r\n} as const;\r\n\r\n/**\r\n * Button Type mapping với description\r\n */\r\nexport const ZNS_BUTTON_TYPES = {\r\n  [ZNSButtonType.ENTERPRISE_PAGE]: {\r\n    value: ZNSButtonType.ENTERPRISE_PAGE,\r\n    name: 'Trang doanh nghiệp',\r\n    description: 'Đến trang của doanh nghiệp hoặc trang tra cứu hoá đơn điện tử'\r\n  },\r\n  [ZNSButtonType.PHONE]: {\r\n    value: ZNSButtonType.PHONE,\r\n    name: 'Phone',\r\n    description: 'Gọi điện thoại'\r\n  },\r\n  [ZNSButtonType.OA_PAGE]: {\r\n    value: ZNSButtonType.OA_PAGE,\r\n    name: 'Trang OA',\r\n    description: 'Đến trang thông tin OA'\r\n  },\r\n  [ZNSButtonType.ZALO_MINI_APP]: {\r\n    value: ZNSButtonType.ZALO_MINI_APP,\r\n    name: 'Zalo Mini App',\r\n    description: 'Đến ứng dụng Zalo Mini App của doanh nghiệp'\r\n  },\r\n  [ZNSButtonType.DOWNLOAD_APP]: {\r\n    value: ZNSButtonType.DOWNLOAD_APP,\r\n    name: 'Tải App',\r\n    description: 'Đến trang tải ứng dụng'\r\n  },\r\n  [ZNSButtonType.PRODUCT_DISTRIBUTION]: {\r\n    value: ZNSButtonType.PRODUCT_DISTRIBUTION,\r\n    name: 'Phân phối sản phẩm',\r\n    description: 'Đến trang phân phối sản phẩm'\r\n  },\r\n  [ZNSButtonType.OTHER_WEB_APP]: {\r\n    value: ZNSButtonType.OTHER_WEB_APP,\r\n    name: 'Web/App khác',\r\n    description: 'Đến trang web/Zalo Mini App khác'\r\n  },\r\n  [ZNSButtonType.OTHER_APP]: {\r\n    value: ZNSButtonType.OTHER_APP,\r\n    name: 'App khác',\r\n    description: 'Đến ứng dụng khác'\r\n  },\r\n  [ZNSButtonType.OA_ARTICLE]: {\r\n    value: ZNSButtonType.OA_ARTICLE,\r\n    name: 'Bài viết OA',\r\n    description: 'Đến bài viết của OA'\r\n  },\r\n  [ZNSButtonType.COPY]: {\r\n    value: ZNSButtonType.COPY,\r\n    name: 'Copy',\r\n    description: 'Sao chép nội dung'\r\n  },\r\n  [ZNSButtonType.PAY_NOW]: {\r\n    value: ZNSButtonType.PAY_NOW,\r\n    name: 'Thanh toán ngay',\r\n    description: 'Thanh toán ngay'\r\n  },\r\n  [ZNSButtonType.VIEW_DETAILS]: {\r\n    value: ZNSButtonType.VIEW_DETAILS,\r\n    name: 'Xem chi tiết',\r\n    description: 'Xem chi tiết'\r\n  }\r\n} as const;\r\n\r\n/**\r\n * Parameter Type mapping với description và max length\r\n */\r\nexport const ZNS_PARAM_TYPES = {\r\n  [ZNSParamType.CUSTOMER_NAME]: {\r\n    value: ZNSParamType.CUSTOMER_NAME,\r\n    name: 'Tên khách hàng',\r\n    maxLength: 30\r\n  },\r\n  [ZNSParamType.PHONE_NUMBER]: {\r\n    value: ZNSParamType.PHONE_NUMBER,\r\n    name: 'Số điện thoại',\r\n    maxLength: 15\r\n  },\r\n  [ZNSParamType.ADDRESS]: {\r\n    value: ZNSParamType.ADDRESS,\r\n    name: 'Địa chỉ',\r\n    maxLength: 200\r\n  },\r\n  [ZNSParamType.CODE]: {\r\n    value: ZNSParamType.CODE,\r\n    name: 'Mã số',\r\n    maxLength: 30\r\n  },\r\n  [ZNSParamType.CUSTOM_LABEL]: {\r\n    value: ZNSParamType.CUSTOM_LABEL,\r\n    name: 'Nhãn tùy chỉnh',\r\n    maxLength: 30\r\n  },\r\n  [ZNSParamType.TRANSACTION_STATUS]: {\r\n    value: ZNSParamType.TRANSACTION_STATUS,\r\n    name: 'Trạng thái giao dịch',\r\n    maxLength: 30\r\n  },\r\n  [ZNSParamType.CONTACT_INFO]: {\r\n    value: ZNSParamType.CONTACT_INFO,\r\n    name: 'Thông tin liên hệ',\r\n    maxLength: 50\r\n  },\r\n  [ZNSParamType.GENDER_TITLE]: {\r\n    value: ZNSParamType.GENDER_TITLE,\r\n    name: 'Giới tính / Danh xưng',\r\n    maxLength: 5\r\n  },\r\n  [ZNSParamType.PRODUCT_BRAND]: {\r\n    value: ZNSParamType.PRODUCT_BRAND,\r\n    name: 'Tên sản phẩm / Thương hiệu',\r\n    maxLength: 200\r\n  },\r\n  [ZNSParamType.QUANTITY_AMOUNT]: {\r\n    value: ZNSParamType.QUANTITY_AMOUNT,\r\n    name: 'Số lượng / Số tiền',\r\n    maxLength: 20\r\n  },\r\n  [ZNSParamType.TIME]: {\r\n    value: ZNSParamType.TIME,\r\n    name: 'Thời gian',\r\n    maxLength: 20\r\n  },\r\n  [ZNSParamType.OTP]: {\r\n    value: ZNSParamType.OTP,\r\n    name: 'OTP',\r\n    maxLength: 10\r\n  },\r\n  [ZNSParamType.URL]: {\r\n    value: ZNSParamType.URL,\r\n    name: 'URL',\r\n    maxLength: 200\r\n  },\r\n  [ZNSParamType.CURRENCY]: {\r\n    value: ZNSParamType.CURRENCY,\r\n    name: 'Tiền tệ (VNĐ)',\r\n    maxLength: 12\r\n  },\r\n  [ZNSParamType.BANK_TRANSFER_NOTE]: {\r\n    value: ZNSParamType.BANK_TRANSFER_NOTE,\r\n    name: 'Bank transfer note',\r\n    maxLength: 90\r\n  }\r\n} as const;\r\n\r\n/**\r\n * Template Type và Tag compatibility matrix\r\n */\r\nexport const ZNS_TEMPLATE_TAG_COMPATIBILITY: Record<ZNSTemplateType, ZNSTemplateTag[]> = {\r\n  [ZNSTemplateType.CUSTOM]: [\r\n    ZNSTemplateTag.TRANSACTION,\r\n    ZNSTemplateTag.CUSTOMER_CARE,\r\n    ZNSTemplateTag.PROMOTION\r\n  ],\r\n  [ZNSTemplateType.AUTHENTICATION]: [\r\n    ZNSTemplateTag.TRANSACTION\r\n  ],\r\n  [ZNSTemplateType.PAYMENT_REQUEST]: [\r\n    ZNSTemplateTag.TRANSACTION\r\n  ],\r\n  [ZNSTemplateType.VOUCHER]: [\r\n    ZNSTemplateTag.TRANSACTION,\r\n    ZNSTemplateTag.CUSTOMER_CARE,\r\n    ZNSTemplateTag.PROMOTION\r\n  ],\r\n  [ZNSTemplateType.SERVICE_RATING]: [\r\n    ZNSTemplateTag.CUSTOMER_CARE\r\n  ]\r\n};\r\n\r\n/**\r\n * Validation functions\r\n */\r\nexport const ZNSValidation = {\r\n  /**\r\n   * Kiểm tra template name hợp lệ\r\n   */\r\n  isValidTemplateName: (name: string): boolean => {\r\n    return name.length >= 10 && name.length <= 60;\r\n  },\r\n\r\n  /**\r\n   * Kiểm tra note hợp lệ\r\n   */\r\n  isValidNote: (note: string): boolean => {\r\n    return note.length >= 1 && note.length <= 400;\r\n  },\r\n\r\n  /**\r\n   * Kiểm tra tag có tương thích với template type không\r\n   */\r\n  isTagCompatibleWithType: (templateType: ZNSTemplateType, tag: ZNSTemplateTag): boolean => {\r\n    const compatibleTags = ZNS_TEMPLATE_TAG_COMPATIBILITY[templateType];\r\n    return compatibleTags ? compatibleTags.includes(tag) : false;\r\n  },\r\n\r\n  /**\r\n   * Kiểm tra param value có hợp lệ với type không\r\n   */\r\n  isValidParamValue: (paramType: ZNSParamType, value: string): boolean => {\r\n    const typeInfo = ZNS_PARAM_TYPES[paramType];\r\n    return typeInfo ? value.length <= typeInfo.maxLength : false;\r\n  }\r\n} as const;\r\n","/**\r\n * Types for Zalo Webhook Events\r\n * Based on Zalo Official Account Webhook API\r\n */\r\n\r\n// ==================== BASE WEBHOOK TYPES ====================\r\n\r\n/**\r\n * Base webhook event structure\r\n */\r\nexport interface BaseWebhookEvent {\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User ID for Social API */\r\n  user_id_by_app: string;\r\n  /** Event name */\r\n  event_name: string;\r\n  /** Event timestamp */\r\n  timestamp: string;\r\n}\r\n\r\n/**\r\n * Webhook sender information\r\n */\r\nexport interface WebhookSender {\r\n  /** Sender ID */\r\n  id: string;\r\n}\r\n\r\n/**\r\n * Webhook recipient information\r\n */\r\nexport interface WebhookRecipient {\r\n  /** Recipient ID */\r\n  id: string;\r\n}\r\n\r\n/**\r\n * Webhook follower information (for follow events)\r\n */\r\nexport interface WebhookFollower {\r\n  /** Follower ID */\r\n  id: string;\r\n}\r\n\r\n/**\r\n * Webhook customer information (for shop events)\r\n */\r\nexport interface WebhookCustomer {\r\n  /** Customer ID */\r\n  id: string;\r\n}\r\n\r\n/**\r\n * User submitted information\r\n */\r\nexport interface UserSubmittedInfo {\r\n  /** User address */\r\n  address?: string;\r\n  /** User phone number */\r\n  phone?: string;\r\n  /** User city */\r\n  city?: string;\r\n  /** User district */\r\n  district?: string;\r\n  /** User name */\r\n  name?: string;\r\n  /** User ward */\r\n  ward?: string;\r\n}\r\n\r\n/**\r\n * Webhook user information (for click events)\r\n */\r\nexport interface WebhookUser {\r\n  /** User ID */\r\n  id?: string;\r\n}\r\n\r\n/**\r\n * Message with reaction\r\n */\r\nexport interface ReactionMessage {\r\n  /** Message ID */\r\n  msg_id: string;\r\n  /** Reaction icon */\r\n  react_icon: string;\r\n}\r\n\r\n/**\r\n * Anonymous message with conversation ID\r\n */\r\nexport interface AnonymousMessage extends WebhookBaseMessage {\r\n  /** Conversation ID for anonymous chat */\r\n  conversation_id: string;\r\n}\r\n\r\n/**\r\n * Call information for call events\r\n */\r\nexport interface CallInfo {\r\n  /** Call ID */\r\n  call_id: string;\r\n  /** Call type (AUDIO, VIDEO) */\r\n  call_type: string;\r\n  /** Call initialization time */\r\n  init_time: string;\r\n  /** Total call duration in milliseconds */\r\n  call_duration: string;\r\n  /** Waiting time before call answered */\r\n  waiting_time: string;\r\n  /** Actual talk time */\r\n  talk_time: string;\r\n  /** Call status code */\r\n  status_code: string;\r\n  /** Phone number */\r\n  phone?: string;\r\n}\r\n\r\n/**\r\n * Template message payload\r\n */\r\nexport interface TemplatePayload {\r\n  /** Template checksum */\r\n  checksum: string;\r\n  /** Template link URL */\r\n  link_url: string;\r\n  /** Zalo instant ID */\r\n  zinstant_id: string;\r\n  /** Template text content */\r\n  text: string;\r\n}\r\n\r\n/**\r\n * Template message source\r\n */\r\nexport interface TemplateSource {\r\n  /** Service name */\r\n  service: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * Template message\r\n */\r\nexport interface TemplateMessage {\r\n  /** Client message ID */\r\n  client_msg_id: string;\r\n  /** Template payload */\r\n  payload: TemplatePayload;\r\n  /** Message source */\r\n  source: TemplateSource;\r\n  /** Message ID */\r\n  msg_id: string;\r\n  /** Is reply flag */\r\n  is_reply: boolean;\r\n}\r\n\r\n/**\r\n * Business card payload\r\n */\r\nexport interface BusinessCardPayload {\r\n  /** Business card thumbnail */\r\n  thumbnail: string;\r\n  /** Business card description (JSON string) */\r\n  description: string;\r\n  /** Business card URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * Business card message\r\n */\r\nexport interface BusinessCardMessage extends WebhookBaseMessage {\r\n  /** Client message ID */\r\n  client_msg_id: string;\r\n  /** Business card attachments */\r\n  attachments: Array<\r\n    MessageAttachment & { type: \"link\"; payload: BusinessCardPayload }\r\n  >;\r\n}\r\n\r\n/**\r\n * User feedback message\r\n */\r\nexport interface FeedbackMessage {\r\n  /** Feedback note */\r\n  note: string;\r\n  /** Rating score */\r\n  rate: number;\r\n  /** Submit time */\r\n  submit_time: string;\r\n  /** Feedback options */\r\n  feedbacks: string[];\r\n  /** Message ID */\r\n  msg_id: string;\r\n  /** Tracking ID */\r\n  tracking_id: string;\r\n}\r\n\r\n/**\r\n * Quota change information\r\n */\r\nexport interface QuotaChange {\r\n  /** Previous quota value */\r\n  prev_value: number;\r\n  /** New quota value */\r\n  new_value: number;\r\n}\r\n\r\n/**\r\n * Tag level change information\r\n */\r\nexport interface TagLevelChange {\r\n  /** Previous tag level */\r\n  prev_value: number;\r\n  /** New tag level */\r\n  new_value: number;\r\n}\r\n\r\n/**\r\n * ZNS delivery message\r\n */\r\nexport interface ZNSDeliveryMessage {\r\n  /** Delivery time */\r\n  delivery_time: string;\r\n  /** Message ID */\r\n  msg_id: string;\r\n  /** Tracking ID */\r\n  tracking_id: string;\r\n}\r\n\r\n/**\r\n * Group information\r\n */\r\nexport interface GroupInfo {\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list (for group events) */\r\n  users?: string[];\r\n}\r\n\r\n/**\r\n * Widget interaction data\r\n */\r\nexport interface WidgetInteractionData {\r\n  /** User ID by OA */\r\n  user_id_by_oa: string;\r\n  /** User external ID */\r\n  user_external_id: string;\r\n  /** Widget URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * Widget sync failure data\r\n */\r\nexport interface WidgetSyncFailureData {\r\n  /** Error message */\r\n  message: string;\r\n  /** User ID by OA */\r\n  user_id_by_oa: string;\r\n  /** User external ID */\r\n  user_external_id: string;\r\n}\r\n\r\n/**\r\n * Template status change\r\n */\r\nexport interface TemplateStatusChange {\r\n  /** Previous status */\r\n  prev_status: string;\r\n  /** New status */\r\n  new_status: string;\r\n}\r\n\r\n/**\r\n * Permission revoked data\r\n */\r\nexport interface PermissionRevokedData {\r\n  /** Action performed by (APP or OA) */\r\n  action_by: string;\r\n  /** Revoked timestamp */\r\n  revoked_at: string;\r\n}\r\n\r\n/**\r\n * Extension subscription info\r\n */\r\nexport interface ExtensionSubscriptionInfo {\r\n  /** Valid through date */\r\n  valid_through_date: string;\r\n  /** Duration in months */\r\n  duration_month: string;\r\n  /** Valid start date */\r\n  valid_start_date: string;\r\n}\r\n\r\n/**\r\n * Update user info data\r\n */\r\nexport interface UpdateUserInfoData {\r\n  /** Update method */\r\n  method: string;\r\n}\r\n\r\n/**\r\n * Anonymous message with attachments and conversation ID\r\n */\r\nexport interface AnonymousMessageWithAttachments\r\n  extends WebhookMessageWithAttachments {\r\n  /** Conversation ID for anonymous chat */\r\n  conversation_id: string;\r\n}\r\n\r\n// ==================== MESSAGE ATTACHMENT TYPES ====================\r\n\r\n/**\r\n * Location coordinates\r\n */\r\nexport interface LocationCoordinates {\r\n  /** Latitude */\r\n  latitude: string;\r\n  /** Longitude */\r\n  longitude: string;\r\n}\r\n\r\n/**\r\n * Location attachment payload\r\n */\r\nexport interface LocationPayload {\r\n  /** Location coordinates */\r\n  coordinates: LocationCoordinates;\r\n}\r\n\r\n/**\r\n * Image attachment payload\r\n */\r\nexport interface ImagePayload {\r\n  /** Thumbnail URL */\r\n  thumbnail: string;\r\n  /** Full image URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * Link attachment payload\r\n */\r\nexport interface LinkPayload {\r\n  /** Link thumbnail URL */\r\n  thumbnail: string;\r\n  /** Link description */\r\n  description: string;\r\n  /** Link URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * Sticker attachment payload\r\n */\r\nexport interface StickerPayload {\r\n  /** Sticker ID */\r\n  id: string;\r\n  /** Sticker URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * GIF attachment payload\r\n */\r\nexport interface GifPayload {\r\n  /** GIF thumbnail URL */\r\n  thumbnail: string;\r\n  /** GIF URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * Audio attachment payload\r\n */\r\nexport interface AudioPayload {\r\n  /** Audio file URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * Video attachment payload\r\n */\r\nexport interface VideoPayload {\r\n  /** Video thumbnail URL */\r\n  thumbnail: string;\r\n  /** Video description */\r\n  description: string;\r\n  /** Video file URL */\r\n  url: string;\r\n}\r\n\r\n/**\r\n * File attachment payload\r\n */\r\nexport interface FilePayload {\r\n  /** File download URL */\r\n  url: string;\r\n  /** File size in bytes */\r\n  size: string;\r\n  /** File name */\r\n  name: string;\r\n  /** File checksum */\r\n  checksum: string;\r\n  /** File type/MIME type */\r\n  type: string;\r\n}\r\n\r\n/**\r\n * Enhanced link payload for OA list messages\r\n */\r\nexport interface EnhancedLinkPayload extends LinkPayload {\r\n  /** Link title */\r\n  title: string;\r\n}\r\n\r\n/**\r\n * Union type for all attachment payloads\r\n */\r\nexport type AttachmentPayload =\r\n  | LocationPayload\r\n  | ImagePayload\r\n  | LinkPayload\r\n  | StickerPayload\r\n  | GifPayload\r\n  | AudioPayload\r\n  | VideoPayload\r\n  | FilePayload\r\n  | EnhancedLinkPayload;\r\n\r\n/**\r\n * Message attachment\r\n */\r\nexport interface MessageAttachment {\r\n  /** Attachment payload */\r\n  payload: AttachmentPayload;\r\n  /** Attachment type */\r\n  type:\r\n    | \"location\"\r\n    | \"image\"\r\n    | \"link\"\r\n    | \"sticker\"\r\n    | \"gif\"\r\n    | \"audio\"\r\n    | \"video\"\r\n    | \"file\";\r\n}\r\n\r\n// ==================== MESSAGE TYPES ====================\r\n\r\n/**\r\n * Base webhook message structure\r\n */\r\nexport interface WebhookBaseMessage {\r\n  /** Message ID */\r\n  msg_id: string;\r\n  /** Quoted message ID when the message is a reply */\r\n  quote_msg_id?: string;\r\n  /** Message text content */\r\n  text?: string;\r\n}\r\n\r\n/**\r\n * Webhook message with attachments\r\n */\r\nexport interface WebhookMessageWithAttachments extends WebhookBaseMessage {\r\n  /** Message attachments */\r\n  attachments: MessageAttachment[];\r\n}\r\n\r\n/**\r\n * Message for seen event (contains multiple message IDs)\r\n */\r\nexport interface SeenMessage {\r\n  /** Array of message IDs that were seen */\r\n  msg_ids: string[];\r\n}\r\n\r\n/**\r\n * User follows Official Account event\r\n */\r\nexport interface UserFollowEvent extends BaseWebhookEvent {\r\n  event_name: \"follow\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Event source */\r\n  source: string;\r\n  /** Follower information */\r\n  follower: WebhookFollower;\r\n}\r\n\r\n/**\r\n * User unfollows Official Account event\r\n */\r\nexport interface UserUnfollowEvent extends BaseWebhookEvent {\r\n  event_name: \"unfollow\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Event source */\r\n  source: string;\r\n  /** Follower information */\r\n  follower: WebhookFollower;\r\n}\r\n\r\n/**\r\n * Shop has order event\r\n */\r\nexport interface ShopHasOrderEvent extends BaseWebhookEvent {\r\n  event_name: \"shop_has_order\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Customer information */\r\n  customer: WebhookCustomer;\r\n}\r\n\r\n/**\r\n * Add user to tag event\r\n */\r\nexport interface AddUserToTagEvent extends BaseWebhookEvent {\r\n  event_name: \"add_user_to_tag\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n}\r\n\r\n/**\r\n * OA sends text message event\r\n */\r\nexport interface OASendTextEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_text\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookBaseMessage;\r\n}\r\n\r\n/**\r\n * OA sends image message event\r\n */\r\nexport interface OASendImageEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_image\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"image\"; payload: ImagePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends GIF message event\r\n */\r\nexport interface OASendGifEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_gif\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"gif\"; payload: GifPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends list message event (interactive message with multiple links)\r\n */\r\nexport interface OASendListEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_list\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"link\"; payload: EnhancedLinkPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends file message event\r\n */\r\nexport interface OASendFileEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_file\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"file\"; payload: FilePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends sticker message event\r\n */\r\nexport interface OASendStickerEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_sticker\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"sticker\"; payload: StickerPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User clicks \"Chat Now\" button event\r\n */\r\nexport interface UserClickChatNowEvent extends BaseWebhookEvent {\r\n  event_name: \"user_click_chatnow\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** User ID (for OA API) */\r\n  user_id: string;\r\n}\r\n\r\n/**\r\n * User reacts to message event\r\n */\r\nexport interface UserReactedMessageEvent extends BaseWebhookEvent {\r\n  event_name: \"user_reacted_message\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: ReactionMessage;\r\n}\r\n\r\n/**\r\n * OA reacts to message event\r\n */\r\nexport interface OAReactedMessageEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_reacted_message\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: ReactionMessage;\r\n}\r\n\r\n/**\r\n * OA sends consent request event\r\n */\r\nexport interface OASendConsentEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_consent\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Request type */\r\n  request_type: string;\r\n  /** Create time */\r\n  create_time: string;\r\n  /** Expired time */\r\n  expired_time: string;\r\n  /** Phone number */\r\n  phone: string;\r\n}\r\n\r\n/**\r\n * User replies to consent request event\r\n */\r\nexport interface UserReplyConsentEvent extends BaseWebhookEvent {\r\n  event_name: \"user_reply_consent\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Expired time */\r\n  expired_time: string;\r\n  /** Confirmed time */\r\n  confirmed_time: string;\r\n  /** Phone number */\r\n  phone: string;\r\n}\r\n\r\n/**\r\n * Anonymous user sends text message event\r\n */\r\nexport interface AnonymousSendTextEvent extends BaseWebhookEvent {\r\n  event_name: \"anonymous_send_text\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessage;\r\n}\r\n\r\n/**\r\n * Anonymous user sends image message event\r\n */\r\nexport interface AnonymousSendImageEvent extends BaseWebhookEvent {\r\n  event_name: \"anonymous_send_image\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"image\"; payload: ImagePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * Anonymous user sends file message event\r\n */\r\nexport interface AnonymousSendFileEvent extends BaseWebhookEvent {\r\n  event_name: \"anonymous_send_file\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"file\"; payload: FilePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * Anonymous user sends sticker message event\r\n */\r\nexport interface AnonymousSendStickerEvent extends BaseWebhookEvent {\r\n  event_name: \"anonymous_send_sticker\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"sticker\"; payload: StickerPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends anonymous text message event\r\n */\r\nexport interface OASendAnonymousTextEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_anonymous_text\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessage;\r\n}\r\n\r\n/**\r\n * OA sends anonymous image message event\r\n */\r\nexport interface OASendAnonymousImageEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_anonymous_image\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"image\"; payload: ImagePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends anonymous file message event\r\n */\r\nexport interface OASendAnonymousFileEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_anonymous_file\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"file\"; payload: FilePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA sends anonymous sticker message event\r\n */\r\nexport interface OASendAnonymousStickerEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_anonymous_sticker\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: AnonymousMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"sticker\"; payload: StickerPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA call user event\r\n */\r\nexport interface OACallUserEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_call_user\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Call information */\r\n  call_id: string;\r\n  call_type: string;\r\n  init_time: string;\r\n  call_duration: string;\r\n  waiting_time: string;\r\n  talk_time: string;\r\n  status_code: string;\r\n  phone: string;\r\n}\r\n\r\n/**\r\n * User call OA event\r\n */\r\nexport interface UserCallOAEvent extends BaseWebhookEvent {\r\n  event_name: \"user_call_oa\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** User ID (for OA API) */\r\n  user_id: string;\r\n  /** Call information */\r\n  call_id: string;\r\n  call_type: string;\r\n  init_time: string;\r\n  call_duration: string;\r\n  waiting_time: string;\r\n  talk_time: string;\r\n  status_code: string;\r\n}\r\n\r\n/**\r\n * OA sends template message event\r\n */\r\nexport interface OASendTemplateEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_template\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: TemplateMessage;\r\n}\r\n\r\n/**\r\n * User sends business card event\r\n */\r\nexport interface UserSendBusinessCardEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_business_card\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: BusinessCardMessage;\r\n}\r\n\r\n/**\r\n * User feedback event\r\n */\r\nexport interface UserFeedbackEvent extends BaseWebhookEvent {\r\n  event_name: \"user_feedback\";\r\n  message: FeedbackMessage;\r\n}\r\n\r\n/**\r\n * Change OA daily quota event\r\n */\r\nexport interface ChangeOADailyQuotaEvent extends BaseWebhookEvent {\r\n  event_name: \"change_oa_daily_quota\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Quota change information */\r\n  quota: QuotaChange;\r\n}\r\n\r\n/**\r\n * Change OA template tags event\r\n */\r\nexport interface ChangeOATemplateTagsEvent extends BaseWebhookEvent {\r\n  event_name: \"change_oa_template_tags\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Tag level change information */\r\n  tag_level: TagLevelChange;\r\n}\r\n\r\n/**\r\n * Change template quality event\r\n */\r\nexport interface ChangeTemplateQualityEvent extends BaseWebhookEvent {\r\n  event_name: \"change_template_quality\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Template ID */\r\n  template_id: string;\r\n  /** Template quality */\r\n  quality: string;\r\n}\r\n\r\n/**\r\n * Change template quota event\r\n */\r\nexport interface ChangeTemplateQuotaEvent extends BaseWebhookEvent {\r\n  event_name: \"change_template_quota\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Template ID */\r\n  template_id: string;\r\n  /** Quota change information */\r\n  quota: QuotaChange;\r\n}\r\n\r\n/**\r\n * Journey timeout event\r\n */\r\nexport interface JourneyTimeoutEvent extends BaseWebhookEvent {\r\n  event_name: \"event_journey_time_out\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Journey ID */\r\n  journey_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * Journey acknowledged event\r\n */\r\nexport interface JourneyAcknowledgedEvent extends BaseWebhookEvent {\r\n  event_name: \"event_journey_acknowledged\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Journey ID */\r\n  journey_id: string;\r\n  /** Message ID */\r\n  msg_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * ZNS user received message event\r\n */\r\nexport interface ZNSUserReceivedMessageEvent extends BaseWebhookEvent {\r\n  event_name: \"user_received_message\";\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** ZNS delivery message */\r\n  message: ZNSDeliveryMessage;\r\n}\r\n\r\n/**\r\n * Create group event\r\n */\r\nexport interface CreateGroupEvent extends BaseWebhookEvent {\r\n  event_name: \"create_group\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * User join group event\r\n */\r\nexport interface UserJoinGroupEvent extends BaseWebhookEvent {\r\n  event_name: \"user_join_group\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * User request join group event\r\n */\r\nexport interface UserRequestJoinGroupEvent extends BaseWebhookEvent {\r\n  event_name: \"user_request_join_group\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * React request join group event\r\n */\r\nexport interface ReactRequestJoinGroupEvent extends BaseWebhookEvent {\r\n  event_name: \"react_request_join_group\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * Reject request join group event\r\n */\r\nexport interface RejectRequestJoinGroupEvent extends BaseWebhookEvent {\r\n  event_name: \"reject_request_join_group\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * Add group admin event\r\n */\r\nexport interface AddGroupAdminEvent extends BaseWebhookEvent {\r\n  event_name: \"add_group_admin\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * Remove group admin event\r\n */\r\nexport interface RemoveGroupAdminEvent extends BaseWebhookEvent {\r\n  event_name: \"remove_group_admin\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * Update group info event\r\n */\r\nexport interface UpdateGroupInfoEvent extends BaseWebhookEvent {\r\n  event_name: \"update_group_info\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * User out group event\r\n */\r\nexport interface UserOutGroupEvent extends BaseWebhookEvent {\r\n  event_name: \"user_out_group\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Group ID */\r\n  group_id: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n  /** User list */\r\n  users: string[];\r\n}\r\n\r\n/**\r\n * OA send group text event\r\n */\r\nexport interface OASendGroupTextEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_text\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookBaseMessage;\r\n}\r\n\r\n/**\r\n * OA send group image event\r\n */\r\nexport interface OASendGroupImageEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_image\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"image\"; payload: ImagePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group link event\r\n */\r\nexport interface OASendGroupLinkEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_link\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"link\"; payload: LinkPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group audio event\r\n */\r\nexport interface OASendGroupAudioEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_audio\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"audio\"; payload: AudioPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group location event\r\n */\r\nexport interface OASendGroupLocationEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_location\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"location\"; payload: LocationPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group video event\r\n */\r\nexport interface OASendGroupVideoEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_video\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"video\"; payload: VideoPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group business card event\r\n */\r\nexport interface OASendGroupBusinessCardEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_business_card\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: BusinessCardMessage;\r\n}\r\n\r\n/**\r\n * OA send group sticker event\r\n */\r\nexport interface OASendGroupStickerEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_sticker\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"sticker\"; payload: StickerPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group GIF event\r\n */\r\nexport interface OASendGroupGifEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_gif\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"gif\"; payload: GifPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send group file event\r\n */\r\nexport interface OASendGroupFileEvent extends BaseWebhookEvent {\r\n  event_name: \"oa_send_group_file\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"file\"; payload: FilePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group text event\r\n */\r\nexport interface UserSendGroupTextEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_text\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookBaseMessage;\r\n}\r\n\r\n/**\r\n * User send group image event\r\n */\r\nexport interface UserSendGroupImageEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_image\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"image\"; payload: ImagePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group link event\r\n */\r\nexport interface UserSendGroupLinkEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_link\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"link\"; payload: LinkPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group audio event\r\n */\r\nexport interface UserSendGroupAudioEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_audio\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"audio\"; payload: AudioPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group location event\r\n */\r\nexport interface UserSendGroupLocationEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_location\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"location\"; payload: LocationPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group video event\r\n */\r\nexport interface UserSendGroupVideoEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_video\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"video\"; payload: VideoPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group business card event\r\n */\r\nexport interface UserSendGroupBusinessCardEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_business_card\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: BusinessCardMessage;\r\n}\r\n\r\n/**\r\n * User send group sticker event\r\n */\r\nexport interface UserSendGroupStickerEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_sticker\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"sticker\"; payload: StickerPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group GIF event\r\n */\r\nexport interface UserSendGroupGifEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_gif\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"gif\"; payload: GifPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User send group file event\r\n */\r\nexport interface UserSendGroupFileEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_group_file\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"file\"; payload: FilePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * Widget interaction accepted event\r\n */\r\nexport interface WidgetInteractionAcceptedEvent extends BaseWebhookEvent {\r\n  event_name: \"widget_interaction_accepted\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Widget interaction data */\r\n  data: WidgetInteractionData;\r\n}\r\n\r\n/**\r\n * Widget failed to sync user external ID event\r\n */\r\nexport interface WidgetFailedToSyncUserExternalIdEvent\r\n  extends BaseWebhookEvent {\r\n  event_name: \"widget_failed_to_sync_user_external_id\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Widget sync failure data */\r\n  data: WidgetSyncFailureData;\r\n}\r\n\r\n/**\r\n * Change template status event\r\n */\r\nexport interface ChangeTemplateStatusEvent extends BaseWebhookEvent {\r\n  event_name: \"change_template_status\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Template ID */\r\n  template_id: string;\r\n  /** Status change information */\r\n  status: TemplateStatusChange;\r\n  /** Reason for status change */\r\n  reason: string;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * Permission revoked event\r\n */\r\nexport interface PermissionRevokedEvent extends BaseWebhookEvent {\r\n  event_name: \"permission_revoked\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Permission revoked data */\r\n  data: PermissionRevokedData;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n/**\r\n * Extension purchased event\r\n */\r\nexport interface ExtensionPurchasedEvent extends BaseWebhookEvent {\r\n  event_name: \"extension_purchased\";\r\n  /** Extension ID */\r\n  extension_id: string;\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** Extension subscription info */\r\n  extension_sub_info: ExtensionSubscriptionInfo;\r\n}\r\n\r\n/**\r\n * Update user info event\r\n */\r\nexport interface UpdateUserInfoEvent extends BaseWebhookEvent {\r\n  event_name: \"update_user_info\";\r\n  /** Official Account ID */\r\n  oa_id: string;\r\n  /** User ID (for OA API) */\r\n  user_id: string;\r\n  /** Update user info data */\r\n  data: UpdateUserInfoData;\r\n  /** Application ID */\r\n  app_id: string;\r\n}\r\n\r\n// ==================== SPECIFIC WEBHOOK EVENT TYPES ====================\r\n\r\n/**\r\n * User sends location event\r\n */\r\nexport interface UserSendLocationEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_location\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"location\"; payload: LocationPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User sends image event\r\n */\r\nexport interface UserSendImageEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_image\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"image\"; payload: ImagePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User sends link event\r\n */\r\nexport interface UserSendLinkEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_link\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"link\"; payload: LinkPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User sends text event\r\n */\r\nexport interface UserSendTextEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_text\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookBaseMessage;\r\n}\r\n\r\n/**\r\n * User sends sticker event\r\n */\r\nexport interface UserSendStickerEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_sticker\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"sticker\"; payload: StickerPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User sends GIF event\r\n */\r\nexport interface UserSendGifEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_gif\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"gif\"; payload: GifPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User sends audio event\r\n */\r\nexport interface UserSendAudioEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_audio\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"audio\"; payload: AudioPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User received message event\r\n */\r\nexport interface UserReceivedMessageEvent extends BaseWebhookEvent {\r\n  event_name: \"user_received_message\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookBaseMessage;\r\n}\r\n\r\n/**\r\n * User seen message event\r\n */\r\nexport interface UserSeenMessageEvent extends BaseWebhookEvent {\r\n  event_name: \"user_seen_message\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: SeenMessage;\r\n}\r\n\r\n/**\r\n * User sends video event\r\n */\r\nexport interface UserSendVideoEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_video\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"video\"; payload: VideoPayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User sends file event\r\n */\r\nexport interface UserSendFileEvent extends BaseWebhookEvent {\r\n  event_name: \"user_send_file\";\r\n  sender: WebhookSender;\r\n  recipient: WebhookRecipient;\r\n  message: WebhookMessageWithAttachments & {\r\n    attachments: Array<\r\n      MessageAttachment & { type: \"file\"; payload: FilePayload }\r\n    >;\r\n  };\r\n}\r\n\r\n/**\r\n * User submit info event\r\n */\r\nexport interface UserSubmitInfoEvent extends BaseWebhookEvent {\r\n  event_name: \"user_submit_info\";\r\n  sender: {\r\n    id: string;\r\n  };\r\n  recipient: {\r\n    id: string;\r\n  };\r\n  info: {\r\n    name?: string;\r\n    phone?: string;\r\n    email?: string;\r\n    address?: string;\r\n    city?: string;\r\n    district?: string;\r\n    ward?: string;\r\n    date_of_birth?: string;\r\n  };\r\n}\r\n\r\n/**\r\n * User click button event\r\n */\r\nexport interface UserClickButtonEvent extends BaseWebhookEvent {\r\n  event_name: \"user_click_chatnow\" | \"user_click_button\";\r\n  sender: {\r\n    id: string;\r\n  };\r\n  recipient: {\r\n    id: string;\r\n  };\r\n  button?: {\r\n    title: string;\r\n    payload: string;\r\n  };\r\n}\r\n\r\n/**\r\n * OA send message event\r\n */\r\nexport interface OASendMessageEvent extends BaseWebhookEvent {\r\n  event_name:\r\n    | \"oa_send_text\"\r\n    | \"oa_send_image\"\r\n    | \"oa_send_file\"\r\n    | \"oa_send_sticker\"\r\n    | \"oa_send_gif\"\r\n    | \"oa_send_audio\"\r\n    | \"oa_send_video\"\r\n    | \"oa_send_list\";\r\n  sender: {\r\n    id: string;\r\n  };\r\n  recipient: {\r\n    id: string;\r\n  };\r\n  message: {\r\n    text?: string;\r\n    msg_id: string;\r\n    /** Quoted message ID when the message is a reply */\r\n    quote_msg_id?: string;\r\n    attachments?: Array<{\r\n      type: string;\r\n      payload: {\r\n        url?: string;\r\n        title?: string;\r\n        description?: string;\r\n      };\r\n    }>;\r\n  };\r\n}\r\n\r\n/**\r\n * Anonymous user send message event\r\n */\r\nexport interface AnonymousUserSendMessageEvent extends BaseWebhookEvent {\r\n  event_name:\r\n    | \"anonymous_send_text\"\r\n    | \"anonymous_send_image\"\r\n    | \"anonymous_send_sticker\"\r\n    | \"anonymous_send_file\";\r\n  sender: {\r\n    id: string;\r\n  };\r\n  recipient: {\r\n    id: string;\r\n  };\r\n  message: {\r\n    text?: string;\r\n    msg_id: string;\r\n    /** Quoted message ID when the message is a reply */\r\n    quote_msg_id?: string;\r\n    attachments?: Array<{\r\n      type: string;\r\n      payload: {\r\n        url?: string;\r\n      };\r\n    }>;\r\n  };\r\n}\r\n\r\n/**\r\n * ZNS message status event\r\n */\r\nexport interface ZNSMessageStatusEvent extends BaseWebhookEvent {\r\n  event_name: \"zns_message_status\";\r\n  message_id: string;\r\n  phone: string;\r\n  status: \"sent\" | \"delivered\" | \"read\" | \"failed\";\r\n  error_code?: number;\r\n  error_message?: string;\r\n  timestamp: string;\r\n}\r\n\r\n/**\r\n * Template status event\r\n */\r\nexport interface TemplateStatusEvent extends BaseWebhookEvent {\r\n  event_name: \"template_status_update\";\r\n  template_id: string;\r\n  template_name: string;\r\n  status: \"ENABLE\" | \"PENDING_REVIEW\" | \"REJECT\" | \"DISABLE\" | \"DELETE\";\r\n  reason?: string;\r\n  quality?: \"HIGH\" | \"MEDIUM\" | \"LOW\" | \"UNDEFINED\";\r\n}\r\n\r\n/**\r\n * Group message events\r\n */\r\nexport interface GroupUserJoinEvent extends BaseWebhookEvent {\r\n  event_name: \"group_user_join\";\r\n  group_id: string;\r\n  user: {\r\n    id: string;\r\n    name: string;\r\n  };\r\n}\r\n\r\nexport interface GroupUserLeaveEvent extends BaseWebhookEvent {\r\n  event_name: \"group_user_leave\";\r\n  group_id: string;\r\n  user: {\r\n    id: string;\r\n    name: string;\r\n  };\r\n}\r\n\r\nexport interface GroupSendMessageEvent extends BaseWebhookEvent {\r\n  event_name:\r\n    | \"group_send_text\"\r\n    | \"group_send_image\"\r\n    | \"group_send_sticker\"\r\n    | \"group_send_file\";\r\n  group_id: string;\r\n  sender: {\r\n    id: string;\r\n    name: string;\r\n  };\r\n  message: {\r\n    text?: string;\r\n    msg_id: string;\r\n    /** Quoted message ID when the message is a reply */\r\n    quote_msg_id?: string;\r\n    attachments?: Array<{\r\n      type: string;\r\n      payload: {\r\n        url?: string;\r\n      };\r\n    }>;\r\n  };\r\n}\r\n\r\n// ==================== UNION TYPES ====================\r\n\r\n/**\r\n * All user message-related webhook events\r\n */\r\nexport type UserMessageWebhookEvent =\r\n  | UserSendLocationEvent\r\n  | UserSendImageEvent\r\n  | UserSendLinkEvent\r\n  | UserSendTextEvent\r\n  | UserSendStickerEvent\r\n  | UserSendGifEvent\r\n  | UserSendAudioEvent\r\n  | UserSendVideoEvent\r\n  | UserSendFileEvent\r\n  | UserReceivedMessageEvent\r\n  | UserSeenMessageEvent;\r\n\r\n/**\r\n * All OA message-related webhook events\r\n */\r\nexport type OAMessageWebhookEvent =\r\n  | OASendTextEvent\r\n  | OASendImageEvent\r\n  | OASendGifEvent\r\n  | OASendListEvent\r\n  | OASendFileEvent\r\n  | OASendStickerEvent\r\n  | OASendAnonymousTextEvent\r\n  | OASendAnonymousImageEvent\r\n  | OASendAnonymousFileEvent\r\n  | OASendAnonymousStickerEvent\r\n  | OASendTemplateEvent;\r\n\r\n/**\r\n * All message-related webhook events\r\n */\r\nexport type MessageWebhookEvent =\r\n  | UserMessageWebhookEvent\r\n  | OAMessageWebhookEvent;\r\n\r\n/**\r\n * All user action webhook events\r\n */\r\nexport type UserActionWebhookEvent =\r\n  | UserFollowEvent\r\n  | UserUnfollowEvent\r\n  | UserSubmitInfoEvent\r\n  | UserClickChatNowEvent\r\n  | UserReactedMessageEvent\r\n  | UserReplyConsentEvent\r\n  | UserSendBusinessCardEvent\r\n  | UserFeedbackEvent;\r\n\r\n/**\r\n * All shop/business webhook events\r\n */\r\nexport type ShopWebhookEvent = ShopHasOrderEvent;\r\n\r\n/**\r\n * All call-related webhook events\r\n */\r\nexport type CallWebhookEvent = OACallUserEvent | UserCallOAEvent;\r\n\r\n/**\r\n * All ZNS-related webhook events\r\n */\r\nexport type ZNSWebhookEvent =\r\n  | ChangeOADailyQuotaEvent\r\n  | ChangeOATemplateTagsEvent\r\n  | ChangeTemplateQualityEvent\r\n  | ChangeTemplateQuotaEvent\r\n  | JourneyTimeoutEvent\r\n  | JourneyAcknowledgedEvent\r\n  | ZNSUserReceivedMessageEvent\r\n  | ChangeTemplateStatusEvent;\r\n\r\n/**\r\n * All widget-related webhook events\r\n */\r\nexport type WidgetWebhookEvent =\r\n  | WidgetInteractionAcceptedEvent\r\n  | WidgetFailedToSyncUserExternalIdEvent;\r\n\r\n/**\r\n * All group-related webhook events\r\n */\r\nexport type GroupWebhookEvent =\r\n  | CreateGroupEvent\r\n  | UserJoinGroupEvent\r\n  | UserRequestJoinGroupEvent\r\n  | ReactRequestJoinGroupEvent\r\n  | RejectRequestJoinGroupEvent\r\n  | AddGroupAdminEvent\r\n  | RemoveGroupAdminEvent\r\n  | UpdateGroupInfoEvent\r\n  | UserOutGroupEvent\r\n  | OASendGroupTextEvent\r\n  | OASendGroupImageEvent\r\n  | OASendGroupLinkEvent\r\n  | OASendGroupAudioEvent\r\n  | OASendGroupLocationEvent\r\n  | OASendGroupVideoEvent\r\n  | OASendGroupBusinessCardEvent\r\n  | OASendGroupStickerEvent\r\n  | OASendGroupGifEvent\r\n  | OASendGroupFileEvent\r\n  | UserSendGroupTextEvent\r\n  | UserSendGroupImageEvent\r\n  | UserSendGroupLinkEvent\r\n  | UserSendGroupAudioEvent\r\n  | UserSendGroupLocationEvent\r\n  | UserSendGroupVideoEvent\r\n  | UserSendGroupBusinessCardEvent\r\n  | UserSendGroupStickerEvent\r\n  | UserSendGroupGifEvent\r\n  | UserSendGroupFileEvent;\r\n\r\n/**\r\n * All system/admin webhook events\r\n */\r\nexport type SystemWebhookEvent =\r\n  | AddUserToTagEvent\r\n  | OAReactedMessageEvent\r\n  | OASendConsentEvent\r\n  | PermissionRevokedEvent\r\n  | ExtensionPurchasedEvent\r\n  | UpdateUserInfoEvent;\r\n\r\n/**\r\n * All anonymous user webhook events\r\n */\r\nexport type AnonymousWebhookEvent =\r\n  | AnonymousSendTextEvent\r\n  | AnonymousSendImageEvent\r\n  | AnonymousSendFileEvent\r\n  | AnonymousSendStickerEvent;\r\n\r\n/**\r\n * Legacy webhook events (keeping for backward compatibility)\r\n */\r\nexport type LegacyWebhookEvent =\r\n  | UserSubmitInfoEvent\r\n  | UserClickButtonEvent\r\n  | OASendMessageEvent\r\n  | AnonymousUserSendMessageEvent\r\n  | ZNSMessageStatusEvent\r\n  | TemplateStatusEvent\r\n  | GroupUserJoinEvent\r\n  | GroupUserLeaveEvent\r\n  | GroupSendMessageEvent;\r\n\r\n/**\r\n * All webhook events union type\r\n */\r\nexport type WebhookEvent =\r\n  | MessageWebhookEvent\r\n  | UserActionWebhookEvent\r\n  | ShopWebhookEvent\r\n  | SystemWebhookEvent\r\n  | AnonymousWebhookEvent\r\n  | CallWebhookEvent\r\n  | ZNSWebhookEvent\r\n  | GroupWebhookEvent\r\n  | WidgetWebhookEvent\r\n  | LegacyWebhookEvent;\r\n\r\n/**\r\n * Webhook payload wrapper\r\n */\r\nexport interface WebhookPayload {\r\n  /**\r\n   * Event data\r\n   */\r\n  data: WebhookEvent;\r\n\r\n  /**\r\n   * Event signature for verification\r\n   */\r\n  signature?: string;\r\n}\r\n\r\n/**\r\n * Webhook verification result\r\n */\r\nexport interface WebhookVerification {\r\n  /**\r\n   * Whether the webhook is valid\r\n   */\r\n  valid: boolean;\r\n\r\n  /**\r\n   * Error message if invalid\r\n   */\r\n  error?: string;\r\n}\r\n\r\n/**\r\n * Webhook handler function type\r\n */\r\nexport type WebhookHandler<T extends WebhookEvent = WebhookEvent> = (\r\n  event: T\r\n) => Promise<void> | void;\r\n\r\n/**\r\n * Webhook event handlers map\r\n */\r\nexport interface WebhookHandlers {\r\n  // User message event handlers\r\n  user_send_text?: WebhookHandler<UserSendTextEvent>;\r\n  user_send_image?: WebhookHandler<UserSendImageEvent>;\r\n  user_send_location?: WebhookHandler<UserSendLocationEvent>;\r\n  user_send_link?: WebhookHandler<UserSendLinkEvent>;\r\n  user_send_sticker?: WebhookHandler<UserSendStickerEvent>;\r\n  user_send_gif?: WebhookHandler<UserSendGifEvent>;\r\n  user_send_audio?: WebhookHandler<UserSendAudioEvent>;\r\n  user_send_video?: WebhookHandler<UserSendVideoEvent>;\r\n  user_send_file?: WebhookHandler<UserSendFileEvent>;\r\n  user_received_message?: WebhookHandler<UserReceivedMessageEvent>;\r\n  user_seen_message?: WebhookHandler<UserSeenMessageEvent>;\r\n\r\n  // User action event handlers\r\n  follow?: WebhookHandler<UserFollowEvent>;\r\n  unfollow?: WebhookHandler<UserUnfollowEvent>;\r\n  user_submit_info?: WebhookHandler<UserSubmitInfoEvent>;\r\n\r\n  // OA message event handlers\r\n  oa_send_text?: WebhookHandler<OASendTextEvent>;\r\n  oa_send_image?: WebhookHandler<OASendImageEvent>;\r\n  oa_send_gif?: WebhookHandler<OASendGifEvent>;\r\n  oa_send_list?: WebhookHandler<OASendListEvent>;\r\n  oa_send_file?: WebhookHandler<OASendFileEvent>;\r\n  oa_send_sticker?: WebhookHandler<OASendStickerEvent>;\r\n\r\n  // User interaction event handlers\r\n  user_click_chatnow?: WebhookHandler<UserClickChatNowEvent>;\r\n  user_reacted_message?: WebhookHandler<UserReactedMessageEvent>;\r\n  user_reply_consent?: WebhookHandler<UserReplyConsentEvent>;\r\n\r\n  // OA interaction event handlers\r\n  oa_reacted_message?: WebhookHandler<OAReactedMessageEvent>;\r\n  oa_send_consent?: WebhookHandler<OASendConsentEvent>;\r\n\r\n  // Anonymous user event handlers\r\n  anonymous_send_text?: WebhookHandler<AnonymousSendTextEvent>;\r\n  anonymous_send_image?: WebhookHandler<AnonymousSendImageEvent>;\r\n  anonymous_send_file?: WebhookHandler<AnonymousSendFileEvent>;\r\n  anonymous_send_sticker?: WebhookHandler<AnonymousSendStickerEvent>;\r\n\r\n  // OA anonymous event handlers\r\n  oa_send_anonymous_text?: WebhookHandler<OASendAnonymousTextEvent>;\r\n  oa_send_anonymous_image?: WebhookHandler<OASendAnonymousImageEvent>;\r\n  oa_send_anonymous_file?: WebhookHandler<OASendAnonymousFileEvent>;\r\n  oa_send_anonymous_sticker?: WebhookHandler<OASendAnonymousStickerEvent>;\r\n\r\n  // Call event handlers\r\n  oa_call_user?: WebhookHandler<OACallUserEvent>;\r\n  user_call_oa?: WebhookHandler<UserCallOAEvent>;\r\n\r\n  // Template and business card event handlers\r\n  oa_send_template?: WebhookHandler<OASendTemplateEvent>;\r\n  user_send_business_card?: WebhookHandler<UserSendBusinessCardEvent>;\r\n\r\n  // Feedback event handlers\r\n  user_feedback?: WebhookHandler<UserFeedbackEvent>;\r\n\r\n  // ZNS event handlers\r\n  change_oa_daily_quota?: WebhookHandler<ChangeOADailyQuotaEvent>;\r\n  change_oa_template_tags?: WebhookHandler<ChangeOATemplateTagsEvent>;\r\n  change_template_quality?: WebhookHandler<ChangeTemplateQualityEvent>;\r\n  change_template_quota?: WebhookHandler<ChangeTemplateQuotaEvent>;\r\n  change_template_status?: WebhookHandler<ChangeTemplateStatusEvent>;\r\n  event_journey_time_out?: WebhookHandler<JourneyTimeoutEvent>;\r\n  event_journey_acknowledged?: WebhookHandler<JourneyAcknowledgedEvent>;\r\n  zns_user_received_message?: WebhookHandler<ZNSUserReceivedMessageEvent>;\r\n\r\n  // Group event handlers\r\n  create_group?: WebhookHandler<CreateGroupEvent>;\r\n  user_join_group?: WebhookHandler<UserJoinGroupEvent>;\r\n  user_request_join_group?: WebhookHandler<UserRequestJoinGroupEvent>;\r\n  react_request_join_group?: WebhookHandler<ReactRequestJoinGroupEvent>;\r\n  reject_request_join_group?: WebhookHandler<RejectRequestJoinGroupEvent>;\r\n  add_group_admin?: WebhookHandler<AddGroupAdminEvent>;\r\n  remove_group_admin?: WebhookHandler<RemoveGroupAdminEvent>;\r\n  update_group_info?: WebhookHandler<UpdateGroupInfoEvent>;\r\n  user_out_group?: WebhookHandler<UserOutGroupEvent>;\r\n  oa_send_group_text?: WebhookHandler<OASendGroupTextEvent>;\r\n  oa_send_group_image?: WebhookHandler<OASendGroupImageEvent>;\r\n  oa_send_group_link?: WebhookHandler<OASendGroupLinkEvent>;\r\n  oa_send_group_audio?: WebhookHandler<OASendGroupAudioEvent>;\r\n  oa_send_group_location?: WebhookHandler<OASendGroupLocationEvent>;\r\n  oa_send_group_video?: WebhookHandler<OASendGroupVideoEvent>;\r\n  oa_send_group_business_card?: WebhookHandler<OASendGroupBusinessCardEvent>;\r\n  oa_send_group_sticker?: WebhookHandler<OASendGroupStickerEvent>;\r\n  oa_send_group_gif?: WebhookHandler<OASendGroupGifEvent>;\r\n  oa_send_group_file?: WebhookHandler<OASendGroupFileEvent>;\r\n  user_send_group_text?: WebhookHandler<UserSendGroupTextEvent>;\r\n  user_send_group_image?: WebhookHandler<UserSendGroupImageEvent>;\r\n  user_send_group_link?: WebhookHandler<UserSendGroupLinkEvent>;\r\n  user_send_group_audio?: WebhookHandler<UserSendGroupAudioEvent>;\r\n  user_send_group_location?: WebhookHandler<UserSendGroupLocationEvent>;\r\n  user_send_group_video?: WebhookHandler<UserSendGroupVideoEvent>;\r\n  user_send_group_business_card?: WebhookHandler<UserSendGroupBusinessCardEvent>;\r\n  user_send_group_sticker?: WebhookHandler<UserSendGroupStickerEvent>;\r\n  user_send_group_gif?: WebhookHandler<UserSendGroupGifEvent>;\r\n  user_send_group_file?: WebhookHandler<UserSendGroupFileEvent>;\r\n\r\n  // Widget event handlers\r\n  widget_interaction_accepted?: WebhookHandler<WidgetInteractionAcceptedEvent>;\r\n  widget_failed_to_sync_user_external_id?: WebhookHandler<WidgetFailedToSyncUserExternalIdEvent>;\r\n\r\n  // System event handlers\r\n  permission_revoked?: WebhookHandler<PermissionRevokedEvent>;\r\n  extension_purchased?: WebhookHandler<ExtensionPurchasedEvent>;\r\n  update_user_info?: WebhookHandler<UpdateUserInfoEvent>;\r\n\r\n  // Shop event handlers\r\n  shop_has_order?: WebhookHandler<ShopHasOrderEvent>;\r\n\r\n  // Legacy system event handlers\r\n  add_user_to_tag?: WebhookHandler<AddUserToTagEvent>;\r\n\r\n  // Legacy handlers (for backward compatibility)\r\n  user_click_button?: WebhookHandler<UserClickButtonEvent>;\r\n  oa_send_audio?: WebhookHandler<OASendMessageEvent>;\r\n  oa_send_video?: WebhookHandler<OASendMessageEvent>;\r\n  zns_message_status?: WebhookHandler<ZNSMessageStatusEvent>;\r\n  template_status_update?: WebhookHandler<TemplateStatusEvent>;\r\n  group_user_join?: WebhookHandler<GroupUserJoinEvent>;\r\n  group_user_leave?: WebhookHandler<GroupUserLeaveEvent>;\r\n  group_send_text?: WebhookHandler<GroupSendMessageEvent>;\r\n  group_send_image?: WebhookHandler<GroupSendMessageEvent>;\r\n  group_send_sticker?: WebhookHandler<GroupSendMessageEvent>;\r\n  group_send_file?: WebhookHandler<GroupSendMessageEvent>;\r\n\r\n  // Catch-all handler\r\n  \"*\"?: WebhookHandler<WebhookEvent>;\r\n}\r\n\r\n// ==================== EVENT NAME ENUMS ====================\r\n\r\n/**\r\n * Webhook event names enum\r\n */\r\nexport enum WebhookEventName {\r\n  // User message events\r\n  USER_SEND_LOCATION = \"user_send_location\",\r\n  USER_SEND_IMAGE = \"user_send_image\",\r\n  USER_SEND_LINK = \"user_send_link\",\r\n  USER_SEND_TEXT = \"user_send_text\",\r\n  USER_SEND_STICKER = \"user_send_sticker\",\r\n  USER_SEND_GIF = \"user_send_gif\",\r\n  USER_SEND_AUDIO = \"user_send_audio\",\r\n  USER_SEND_VIDEO = \"user_send_video\",\r\n  USER_SEND_FILE = \"user_send_file\",\r\n  USER_RECEIVED_MESSAGE = \"user_received_message\",\r\n  USER_SEEN_MESSAGE = \"user_seen_message\",\r\n\r\n  // User action events\r\n  FOLLOW = \"follow\",\r\n  UNFOLLOW = \"unfollow\",\r\n  USER_SUBMIT_INFO = \"user_submit_info\",\r\n\r\n  // OA message events\r\n  OA_SEND_TEXT = \"oa_send_text\",\r\n  OA_SEND_IMAGE = \"oa_send_image\",\r\n  OA_SEND_GIF = \"oa_send_gif\",\r\n  OA_SEND_LIST = \"oa_send_list\",\r\n  OA_SEND_FILE = \"oa_send_file\",\r\n  OA_SEND_STICKER = \"oa_send_sticker\",\r\n\r\n  // User interaction events\r\n  USER_CLICK_CHATNOW = \"user_click_chatnow\",\r\n  USER_REACTED_MESSAGE = \"user_reacted_message\",\r\n  USER_REPLY_CONSENT = \"user_reply_consent\",\r\n\r\n  // OA interaction events\r\n  OA_REACTED_MESSAGE = \"oa_reacted_message\",\r\n  OA_SEND_CONSENT = \"oa_send_consent\",\r\n\r\n  // Anonymous user events\r\n  ANONYMOUS_SEND_TEXT = \"anonymous_send_text\",\r\n  ANONYMOUS_SEND_IMAGE = \"anonymous_send_image\",\r\n  ANONYMOUS_SEND_FILE = \"anonymous_send_file\",\r\n  ANONYMOUS_SEND_STICKER = \"anonymous_send_sticker\",\r\n\r\n  // OA anonymous events\r\n  OA_SEND_ANONYMOUS_TEXT = \"oa_send_anonymous_text\",\r\n  OA_SEND_ANONYMOUS_IMAGE = \"oa_send_anonymous_image\",\r\n  OA_SEND_ANONYMOUS_FILE = \"oa_send_anonymous_file\",\r\n  OA_SEND_ANONYMOUS_STICKER = \"oa_send_anonymous_sticker\",\r\n\r\n  // Call events\r\n  OA_CALL_USER = \"oa_call_user\",\r\n  USER_CALL_OA = \"user_call_oa\",\r\n\r\n  // Template and business card events\r\n  OA_SEND_TEMPLATE = \"oa_send_template\",\r\n  USER_SEND_BUSINESS_CARD = \"user_send_business_card\",\r\n\r\n  // Feedback events\r\n  USER_FEEDBACK = \"user_feedback\",\r\n\r\n  // ZNS events\r\n  CHANGE_OA_DAILY_QUOTA = \"change_oa_daily_quota\",\r\n  CHANGE_OA_TEMPLATE_TAGS = \"change_oa_template_tags\",\r\n  CHANGE_TEMPLATE_QUALITY = \"change_template_quality\",\r\n  CHANGE_TEMPLATE_QUOTA = \"change_template_quota\",\r\n  CHANGE_TEMPLATE_STATUS = \"change_template_status\",\r\n  EVENT_JOURNEY_TIME_OUT = \"event_journey_time_out\",\r\n  EVENT_JOURNEY_ACKNOWLEDGED = \"event_journey_acknowledged\",\r\n  ZNS_USER_RECEIVED_MESSAGE = \"user_received_message\",\r\n\r\n  // Group events\r\n  CREATE_GROUP = \"create_group\",\r\n  USER_JOIN_GROUP = \"user_join_group\",\r\n  USER_REQUEST_JOIN_GROUP = \"user_request_join_group\",\r\n  REACT_REQUEST_JOIN_GROUP = \"react_request_join_group\",\r\n  REJECT_REQUEST_JOIN_GROUP = \"reject_request_join_group\",\r\n  ADD_GROUP_ADMIN = \"add_group_admin\",\r\n  REMOVE_GROUP_ADMIN = \"remove_group_admin\",\r\n  UPDATE_GROUP_INFO = \"update_group_info\",\r\n  USER_OUT_GROUP = \"user_out_group\",\r\n  OA_SEND_GROUP_TEXT = \"oa_send_group_text\",\r\n  OA_SEND_GROUP_IMAGE = \"oa_send_group_image\",\r\n  OA_SEND_GROUP_LINK = \"oa_send_group_link\",\r\n  OA_SEND_GROUP_AUDIO = \"oa_send_group_audio\",\r\n  OA_SEND_GROUP_LOCATION = \"oa_send_group_location\",\r\n  OA_SEND_GROUP_VIDEO = \"oa_send_group_video\",\r\n  OA_SEND_GROUP_BUSINESS_CARD = \"oa_send_group_business_card\",\r\n  OA_SEND_GROUP_STICKER = \"oa_send_group_sticker\",\r\n  OA_SEND_GROUP_GIF = \"oa_send_group_gif\",\r\n  OA_SEND_GROUP_FILE = \"oa_send_group_file\",\r\n  USER_SEND_GROUP_TEXT = \"user_send_group_text\",\r\n  USER_SEND_GROUP_IMAGE = \"user_send_group_image\",\r\n  USER_SEND_GROUP_LINK = \"user_send_group_link\",\r\n  USER_SEND_GROUP_AUDIO = \"user_send_group_audio\",\r\n  USER_SEND_GROUP_LOCATION = \"user_send_group_location\",\r\n  USER_SEND_GROUP_VIDEO = \"user_send_group_video\",\r\n  USER_SEND_GROUP_BUSINESS_CARD = \"user_send_group_business_card\",\r\n  USER_SEND_GROUP_STICKER = \"user_send_group_sticker\",\r\n  USER_SEND_GROUP_GIF = \"user_send_group_gif\",\r\n  USER_SEND_GROUP_FILE = \"user_send_group_file\",\r\n\r\n  // Widget events\r\n  WIDGET_INTERACTION_ACCEPTED = \"widget_interaction_accepted\",\r\n  WIDGET_FAILED_TO_SYNC_USER_EXTERNAL_ID = \"widget_failed_to_sync_user_external_id\",\r\n\r\n  // System events\r\n  PERMISSION_REVOKED = \"permission_revoked\",\r\n  EXTENSION_PURCHASED = \"extension_purchased\",\r\n  UPDATE_USER_INFO = \"update_user_info\",\r\n\r\n  // Shop events\r\n  SHOP_HAS_ORDER = \"shop_has_order\",\r\n\r\n  // Legacy system events\r\n  ADD_USER_TO_TAG = \"add_user_to_tag\",\r\n}\r\n\r\n/**\r\n * Attachment types enum\r\n */\r\nexport enum AttachmentType {\r\n  LOCATION = \"location\",\r\n  IMAGE = \"image\",\r\n  LINK = \"link\",\r\n  STICKER = \"sticker\",\r\n  GIF = \"gif\",\r\n  AUDIO = \"audio\",\r\n  VIDEO = \"video\",\r\n  FILE = \"file\",\r\n}\r\n\r\n// ==================== UTILITY TYPES ====================\r\n\r\n/**\r\n * Type guard to check if event is a message event\r\n */\r\nexport function isMessageEvent(\r\n  event: WebhookEvent\r\n): event is MessageWebhookEvent {\r\n  return [\r\n    WebhookEventName.USER_SEND_LOCATION,\r\n    WebhookEventName.USER_SEND_IMAGE,\r\n    WebhookEventName.USER_SEND_LINK,\r\n    WebhookEventName.USER_SEND_TEXT,\r\n    WebhookEventName.USER_SEND_STICKER,\r\n    WebhookEventName.USER_SEND_GIF,\r\n    WebhookEventName.USER_SEND_AUDIO,\r\n    WebhookEventName.USER_SEND_VIDEO,\r\n    WebhookEventName.USER_SEND_FILE,\r\n    WebhookEventName.USER_RECEIVED_MESSAGE,\r\n    WebhookEventName.USER_SEEN_MESSAGE,\r\n    WebhookEventName.OA_SEND_TEXT,\r\n    WebhookEventName.OA_SEND_IMAGE,\r\n    WebhookEventName.OA_SEND_GIF,\r\n    WebhookEventName.OA_SEND_LIST,\r\n    WebhookEventName.OA_SEND_FILE,\r\n    WebhookEventName.OA_SEND_STICKER,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_TEXT,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_IMAGE,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_FILE,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_STICKER,\r\n    WebhookEventName.OA_SEND_TEMPLATE,\r\n    WebhookEventName.ANONYMOUS_SEND_TEXT,\r\n    WebhookEventName.ANONYMOUS_SEND_IMAGE,\r\n    WebhookEventName.ANONYMOUS_SEND_FILE,\r\n    WebhookEventName.ANONYMOUS_SEND_STICKER,\r\n    WebhookEventName.USER_SEND_BUSINESS_CARD,\r\n    WebhookEventName.OA_SEND_GROUP_TEXT,\r\n    WebhookEventName.OA_SEND_GROUP_IMAGE,\r\n    WebhookEventName.OA_SEND_GROUP_LINK,\r\n    WebhookEventName.OA_SEND_GROUP_AUDIO,\r\n    WebhookEventName.OA_SEND_GROUP_LOCATION,\r\n    WebhookEventName.OA_SEND_GROUP_VIDEO,\r\n    WebhookEventName.OA_SEND_GROUP_BUSINESS_CARD,\r\n    WebhookEventName.OA_SEND_GROUP_STICKER,\r\n    WebhookEventName.OA_SEND_GROUP_GIF,\r\n    WebhookEventName.OA_SEND_GROUP_FILE,\r\n    WebhookEventName.USER_SEND_GROUP_TEXT,\r\n    WebhookEventName.USER_SEND_GROUP_LINK,\r\n    WebhookEventName.USER_SEND_GROUP_AUDIO,\r\n    WebhookEventName.USER_SEND_GROUP_LOCATION,\r\n    WebhookEventName.USER_SEND_GROUP_VIDEO,\r\n    WebhookEventName.USER_SEND_GROUP_BUSINESS_CARD,\r\n    WebhookEventName.USER_SEND_GROUP_STICKER,\r\n    WebhookEventName.USER_SEND_GROUP_GIF,\r\n    WebhookEventName.USER_SEND_GROUP_FILE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a follow/unfollow event\r\n */\r\nexport function isFollowEvent(\r\n  event: WebhookEvent\r\n): event is UserFollowEvent | UserUnfollowEvent {\r\n  return [WebhookEventName.FOLLOW, WebhookEventName.UNFOLLOW].includes(\r\n    event.event_name as WebhookEventName\r\n  );\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a user action event\r\n */\r\nexport function isUserActionEvent(\r\n  event: WebhookEvent\r\n): event is UserActionWebhookEvent {\r\n  return [\r\n    WebhookEventName.FOLLOW,\r\n    WebhookEventName.UNFOLLOW,\r\n    WebhookEventName.USER_SUBMIT_INFO,\r\n    WebhookEventName.USER_CLICK_CHATNOW,\r\n    WebhookEventName.USER_REACTED_MESSAGE,\r\n    WebhookEventName.USER_REPLY_CONSENT,\r\n    WebhookEventName.USER_SEND_BUSINESS_CARD,\r\n    WebhookEventName.USER_FEEDBACK,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a shop event\r\n */\r\nexport function isShopEvent(event: WebhookEvent): event is ShopWebhookEvent {\r\n  return event.event_name === WebhookEventName.SHOP_HAS_ORDER;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is an OA message event\r\n */\r\nexport function isOAMessageEvent(\r\n  event: WebhookEvent\r\n): event is OAMessageWebhookEvent {\r\n  return [\r\n    WebhookEventName.OA_SEND_TEXT,\r\n    WebhookEventName.OA_SEND_IMAGE,\r\n    WebhookEventName.OA_SEND_GIF,\r\n    WebhookEventName.OA_SEND_LIST,\r\n    WebhookEventName.OA_SEND_FILE,\r\n    WebhookEventName.OA_SEND_STICKER,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_TEXT,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_IMAGE,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_FILE,\r\n    WebhookEventName.OA_SEND_ANONYMOUS_STICKER,\r\n    WebhookEventName.OA_SEND_TEMPLATE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is an anonymous user event\r\n */\r\nexport function isAnonymousEvent(\r\n  event: WebhookEvent\r\n): event is AnonymousWebhookEvent {\r\n  return [\r\n    WebhookEventName.ANONYMOUS_SEND_TEXT,\r\n    WebhookEventName.ANONYMOUS_SEND_IMAGE,\r\n    WebhookEventName.ANONYMOUS_SEND_FILE,\r\n    WebhookEventName.ANONYMOUS_SEND_STICKER,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a call event\r\n */\r\nexport function isCallEvent(event: WebhookEvent): event is CallWebhookEvent {\r\n  return [\r\n    WebhookEventName.OA_CALL_USER,\r\n    WebhookEventName.USER_CALL_OA,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a template event\r\n */\r\nexport function isTemplateEvent(\r\n  event: WebhookEvent\r\n): event is OASendTemplateEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_TEMPLATE;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a business card event\r\n */\r\nexport function isBusinessCardEvent(\r\n  event: WebhookEvent\r\n): event is UserSendBusinessCardEvent {\r\n  return event.event_name === WebhookEventName.USER_SEND_BUSINESS_CARD;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a feedback event\r\n */\r\nexport function isFeedbackEvent(\r\n  event: WebhookEvent\r\n): event is UserFeedbackEvent {\r\n  return event.event_name === WebhookEventName.USER_FEEDBACK;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a ZNS event\r\n */\r\nexport function isZNSEvent(event: WebhookEvent): event is ZNSWebhookEvent {\r\n  return [\r\n    WebhookEventName.CHANGE_OA_DAILY_QUOTA,\r\n    WebhookEventName.CHANGE_OA_TEMPLATE_TAGS,\r\n    WebhookEventName.CHANGE_TEMPLATE_QUALITY,\r\n    WebhookEventName.CHANGE_TEMPLATE_QUOTA,\r\n    WebhookEventName.CHANGE_TEMPLATE_STATUS,\r\n    WebhookEventName.EVENT_JOURNEY_TIME_OUT,\r\n    WebhookEventName.EVENT_JOURNEY_ACKNOWLEDGED,\r\n    WebhookEventName.ZNS_USER_RECEIVED_MESSAGE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a group event\r\n */\r\nexport function isGroupEvent(event: WebhookEvent): event is GroupWebhookEvent {\r\n  return [\r\n    WebhookEventName.CREATE_GROUP,\r\n    WebhookEventName.USER_JOIN_GROUP,\r\n    WebhookEventName.USER_REQUEST_JOIN_GROUP,\r\n    WebhookEventName.REACT_REQUEST_JOIN_GROUP,\r\n    WebhookEventName.REJECT_REQUEST_JOIN_GROUP,\r\n    WebhookEventName.ADD_GROUP_ADMIN,\r\n    WebhookEventName.REMOVE_GROUP_ADMIN,\r\n    WebhookEventName.UPDATE_GROUP_INFO,\r\n    WebhookEventName.USER_OUT_GROUP,\r\n    WebhookEventName.OA_SEND_GROUP_TEXT,\r\n    WebhookEventName.OA_SEND_GROUP_IMAGE,\r\n    WebhookEventName.OA_SEND_GROUP_LINK,\r\n    WebhookEventName.OA_SEND_GROUP_AUDIO,\r\n    WebhookEventName.OA_SEND_GROUP_LOCATION,\r\n    WebhookEventName.OA_SEND_GROUP_VIDEO,\r\n    WebhookEventName.OA_SEND_GROUP_BUSINESS_CARD,\r\n    WebhookEventName.OA_SEND_GROUP_STICKER,\r\n    WebhookEventName.OA_SEND_GROUP_GIF,\r\n    WebhookEventName.OA_SEND_GROUP_FILE,\r\n    WebhookEventName.USER_SEND_GROUP_TEXT,\r\n    WebhookEventName.USER_SEND_GROUP_LINK,\r\n    WebhookEventName.USER_SEND_GROUP_AUDIO,\r\n    WebhookEventName.USER_SEND_GROUP_LOCATION,\r\n    WebhookEventName.USER_SEND_GROUP_VIDEO,\r\n    WebhookEventName.USER_SEND_GROUP_BUSINESS_CARD,\r\n    WebhookEventName.USER_SEND_GROUP_STICKER,\r\n    WebhookEventName.USER_SEND_GROUP_GIF,\r\n    WebhookEventName.USER_SEND_GROUP_FILE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a journey event\r\n */\r\nexport function isJourneyEvent(\r\n  event: WebhookEvent\r\n): event is JourneyTimeoutEvent | JourneyAcknowledgedEvent {\r\n  return [\r\n    WebhookEventName.EVENT_JOURNEY_TIME_OUT,\r\n    WebhookEventName.EVENT_JOURNEY_ACKNOWLEDGED,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a quota/template change event\r\n */\r\nexport function isQuotaTemplateEvent(\r\n  event: WebhookEvent\r\n): event is\r\n  | ChangeOADailyQuotaEvent\r\n  | ChangeOATemplateTagsEvent\r\n  | ChangeTemplateQualityEvent\r\n  | ChangeTemplateQuotaEvent\r\n  | ChangeTemplateStatusEvent {\r\n  return [\r\n    WebhookEventName.CHANGE_OA_DAILY_QUOTA,\r\n    WebhookEventName.CHANGE_OA_TEMPLATE_TAGS,\r\n    WebhookEventName.CHANGE_TEMPLATE_QUALITY,\r\n    WebhookEventName.CHANGE_TEMPLATE_QUOTA,\r\n    WebhookEventName.CHANGE_TEMPLATE_STATUS,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a widget event\r\n */\r\nexport function isWidgetEvent(\r\n  event: WebhookEvent\r\n): event is WidgetWebhookEvent {\r\n  return [\r\n    WebhookEventName.WIDGET_INTERACTION_ACCEPTED,\r\n    WebhookEventName.WIDGET_FAILED_TO_SYNC_USER_EXTERNAL_ID,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a system event\r\n */\r\nexport function isSystemEvent(\r\n  event: WebhookEvent\r\n): event is SystemWebhookEvent {\r\n  return [\r\n    WebhookEventName.ADD_USER_TO_TAG,\r\n    WebhookEventName.PERMISSION_REVOKED,\r\n    WebhookEventName.EXTENSION_PURCHASED,\r\n    WebhookEventName.UPDATE_USER_INFO,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a permission event\r\n */\r\nexport function isPermissionEvent(\r\n  event: WebhookEvent\r\n): event is PermissionRevokedEvent {\r\n  return event.event_name === WebhookEventName.PERMISSION_REVOKED;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is an extension event\r\n */\r\nexport function isExtensionEvent(\r\n  event: WebhookEvent\r\n): event is ExtensionPurchasedEvent {\r\n  return event.event_name === WebhookEventName.EXTENSION_PURCHASED;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is an update user info event\r\n */\r\nexport function isUpdateUserInfoEvent(\r\n  event: WebhookEvent\r\n): event is UpdateUserInfoEvent {\r\n  return event.event_name === WebhookEventName.UPDATE_USER_INFO;\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a reaction event\r\n */\r\nexport function isReactionEvent(\r\n  event: WebhookEvent\r\n): event is UserReactedMessageEvent | OAReactedMessageEvent {\r\n  return [\r\n    WebhookEventName.USER_REACTED_MESSAGE,\r\n    WebhookEventName.OA_REACTED_MESSAGE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a consent event\r\n */\r\nexport function isConsentEvent(\r\n  event: WebhookEvent\r\n): event is OASendConsentEvent | UserReplyConsentEvent {\r\n  return [\r\n    WebhookEventName.OA_SEND_CONSENT,\r\n    WebhookEventName.USER_REPLY_CONSENT,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if message has attachments\r\n */\r\nexport function hasAttachments(\r\n  message: WebhookBaseMessage\r\n): message is WebhookMessageWithAttachments {\r\n  return (\r\n    \"attachments\" in message &&\r\n    Array.isArray((message as WebhookMessageWithAttachments).attachments)\r\n  );\r\n}\r\n\r\n// ==================== MESSAGE DIRECTION & TARGET TYPE GUARDS ====================\r\n\r\n/**\r\n * Type guard to check if event is a user message (from individual user to OA)\r\n */\r\nexport function isUserMessageEvent(\r\n  event: WebhookEvent\r\n): event is UserMessageWebhookEvent {\r\n  return [\r\n    WebhookEventName.USER_SEND_LOCATION,\r\n    WebhookEventName.USER_SEND_IMAGE,\r\n    WebhookEventName.USER_SEND_LINK,\r\n    WebhookEventName.USER_SEND_TEXT,\r\n    WebhookEventName.USER_SEND_STICKER,\r\n    WebhookEventName.USER_SEND_GIF,\r\n    WebhookEventName.USER_SEND_AUDIO,\r\n    WebhookEventName.USER_SEND_VIDEO,\r\n    WebhookEventName.USER_SEND_FILE,\r\n    WebhookEventName.USER_RECEIVED_MESSAGE,\r\n    WebhookEventName.USER_SEEN_MESSAGE,\r\n    WebhookEventName.USER_SEND_BUSINESS_CARD,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is a group message (from user in group)\r\n */\r\nexport function isGroupMessageEvent(\r\n  event: WebhookEvent\r\n): event is GroupWebhookEvent {\r\n  return [\r\n    WebhookEventName.USER_SEND_GROUP_TEXT,\r\n    WebhookEventName.USER_SEND_GROUP_LINK,\r\n    WebhookEventName.USER_SEND_GROUP_AUDIO,\r\n    WebhookEventName.USER_SEND_GROUP_LOCATION,\r\n    WebhookEventName.USER_SEND_GROUP_VIDEO,\r\n    WebhookEventName.USER_SEND_GROUP_BUSINESS_CARD,\r\n    WebhookEventName.USER_SEND_GROUP_STICKER,\r\n    WebhookEventName.USER_SEND_GROUP_GIF,\r\n    WebhookEventName.USER_SEND_GROUP_FILE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is OA sending message to individual user\r\n */\r\nexport function isOAToUserMessageEvent(\r\n  event: WebhookEvent\r\n): event is OAMessageWebhookEvent {\r\n  return [\r\n    WebhookEventName.OA_SEND_TEXT,\r\n    WebhookEventName.OA_SEND_IMAGE,\r\n    WebhookEventName.OA_SEND_GIF,\r\n    WebhookEventName.OA_SEND_LIST,\r\n    WebhookEventName.OA_SEND_FILE,\r\n    WebhookEventName.OA_SEND_STICKER,\r\n    WebhookEventName.OA_SEND_TEMPLATE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Type guard to check if event is OA sending message to group\r\n */\r\nexport function isOAToGroupMessageEvent(\r\n  event: WebhookEvent\r\n): event is GroupWebhookEvent {\r\n  return [\r\n    WebhookEventName.OA_SEND_GROUP_TEXT,\r\n    WebhookEventName.OA_SEND_GROUP_IMAGE,\r\n    WebhookEventName.OA_SEND_GROUP_LINK,\r\n    WebhookEventName.OA_SEND_GROUP_AUDIO,\r\n    WebhookEventName.OA_SEND_GROUP_LOCATION,\r\n    WebhookEventName.OA_SEND_GROUP_VIDEO,\r\n    WebhookEventName.OA_SEND_GROUP_BUSINESS_CARD,\r\n    WebhookEventName.OA_SEND_GROUP_STICKER,\r\n    WebhookEventName.OA_SEND_GROUP_GIF,\r\n    WebhookEventName.OA_SEND_GROUP_FILE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Helper function to get message direction and target type\r\n */\r\nexport function getMessageDirection(event: WebhookEvent): {\r\n  direction: 'incoming' | 'outgoing' | 'unknown';\r\n  target: 'user' | 'group' | 'unknown';\r\n  description: string;\r\n} {\r\n  if (isUserMessageEvent(event)) {\r\n    return {\r\n      direction: 'incoming',\r\n      target: 'user',\r\n      description: 'Tin nhắn từ người dùng cá nhân gửi tới OA'\r\n    };\r\n  }\r\n\r\n  if (isGroupMessageEvent(event)) {\r\n    return {\r\n      direction: 'incoming',\r\n      target: 'group',\r\n      description: 'Tin nhắn từ người dùng trong group'\r\n    };\r\n  }\r\n\r\n  if (isOAToUserMessageEvent(event)) {\r\n    return {\r\n      direction: 'outgoing',\r\n      target: 'user',\r\n      description: 'Tin nhắn từ OA gửi tới người dùng cá nhân'\r\n    };\r\n  }\r\n\r\n  if (isOAToGroupMessageEvent(event)) {\r\n    return {\r\n      direction: 'outgoing',\r\n      target: 'group',\r\n      description: 'Tin nhắn từ OA gửi tới group'\r\n    };\r\n  }\r\n\r\n  return {\r\n    direction: 'unknown',\r\n    target: 'unknown',\r\n    description: 'Loại sự kiện không xác định'\r\n  };\r\n}\r\n\r\nexport interface WebhookEventIdentifiers {\r\n  oaId: string | null;\r\n  appId: string | null;\r\n  senderId: string | null;\r\n  recipientId: string | null;\r\n}\r\n\r\nfunction readWebhookString(value: unknown): string | null {\r\n  if (typeof value !== \"string\") {\r\n    return null;\r\n  }\r\n\r\n  const normalizedValue = value.trim();\r\n  return normalizedValue.length > 0 ? normalizedValue : null;\r\n}\r\n\r\nfunction readWebhookObjectId(value: unknown): string | null {\r\n  if (!value || typeof value !== \"object\") {\r\n    return null;\r\n  }\r\n\r\n  return readWebhookString((value as { id?: unknown }).id);\r\n}\r\n\r\nfunction hasWebhookEventPrefix(event: WebhookEvent, prefix: string): boolean {\r\n  return event.event_name.startsWith(prefix);\r\n}\r\n\r\nexport function getWebhookEventAppId(event: WebhookEvent): string | null {\r\n  const directAppId = readWebhookString((event as { app_id?: unknown }).app_id);\r\n\r\n  if (directAppId) {\r\n    return directAppId;\r\n  }\r\n\r\n  if (\"message\" in event && event.message && typeof event.message === \"object\") {\r\n    const messageSource = (event.message as { source?: unknown }).source;\r\n\r\n    if (messageSource && typeof messageSource === \"object\") {\r\n      return readWebhookString(\r\n        (messageSource as { app_id?: unknown }).app_id\r\n      );\r\n    }\r\n  }\r\n\r\n  return null;\r\n}\r\n\r\nexport function getWebhookEventOaId(event: WebhookEvent): string | null {\r\n  const directOaId = readWebhookString((event as { oa_id?: unknown }).oa_id);\r\n\r\n  if (directOaId) {\r\n    return directOaId;\r\n  }\r\n\r\n  if (hasWebhookEventPrefix(event, \"oa_\")) {\r\n    return readWebhookObjectId((event as { sender?: unknown }).sender);\r\n  }\r\n\r\n  if (\r\n    hasWebhookEventPrefix(event, \"user_\") ||\r\n    hasWebhookEventPrefix(event, \"anonymous_\")\r\n  ) {\r\n    return readWebhookObjectId((event as { recipient?: unknown }).recipient);\r\n  }\r\n\r\n  return null;\r\n}\r\n\r\nexport function getWebhookEventSenderId(event: WebhookEvent): string | null {\r\n  const directSenderId = readWebhookObjectId(\r\n    (event as { sender?: unknown }).sender\r\n  );\r\n\r\n  if (directSenderId) {\r\n    return directSenderId;\r\n  }\r\n\r\n  const fallbackSenderId =\r\n    readWebhookString((event as { user_id?: unknown }).user_id) ??\r\n    readWebhookObjectId((event as { follower?: unknown }).follower) ??\r\n    readWebhookObjectId((event as { customer?: unknown }).customer) ??\r\n    readWebhookObjectId((event as { user?: unknown }).user);\r\n\r\n  if (fallbackSenderId) {\r\n    return fallbackSenderId;\r\n  }\r\n\r\n  if (hasWebhookEventPrefix(event, \"oa_\")) {\r\n    return getWebhookEventOaId(event);\r\n  }\r\n\r\n  return null;\r\n}\r\n\r\nexport function getWebhookEventRecipientId(\r\n  event: WebhookEvent\r\n): string | null {\r\n  const directRecipientId = readWebhookObjectId(\r\n    (event as { recipient?: unknown }).recipient\r\n  );\r\n\r\n  if (directRecipientId) {\r\n    return directRecipientId;\r\n  }\r\n\r\n  if (hasWebhookEventPrefix(event, \"oa_\")) {\r\n    return readWebhookString((event as { user_id?: unknown }).user_id);\r\n  }\r\n\r\n  return getWebhookEventOaId(event);\r\n}\r\n\r\nexport function getWebhookEventIdentifiers(\r\n  event: WebhookEvent\r\n): WebhookEventIdentifiers {\r\n  return {\r\n    oaId: getWebhookEventOaId(event),\r\n    appId: getWebhookEventAppId(event),\r\n    senderId: getWebhookEventSenderId(event),\r\n    recipientId: getWebhookEventRecipientId(event),\r\n  };\r\n}\r\n","/**\r\n * Type guard utilities for Zalo SDK webhook events\r\n * \r\n * This file provides type-safe functions to check webhook event types\r\n * and narrow down TypeScript types for better development experience.\r\n */\r\n\r\nimport {\r\n  WebhookEvent,\r\n  GroupWebhookEvent,\r\n  OAMessageWebhookEvent,\r\n  UserMessageWebhookEvent,\r\n  UserSendGroupTextEvent,\r\n  UserSendGroupImageEvent,\r\n  UserSendGroupVideoEvent,\r\n  UserSendGroupAudioEvent,\r\n  UserSendGroupFileEvent,\r\n  OASendTextEvent,\r\n  OASendImageEvent,\r\n  OASendFileEvent,\r\n  OASendStickerEvent,\r\n  OASendGifEvent,\r\n  OASendGroupTextEvent,\r\n  OASendGroupImageEvent,\r\n  OASendGroupFileEvent,\r\n  OASendGroupStickerEvent,\r\n  OASendGroupGifEvent,\r\n  WebhookEventName,\r\n} from '../types/webhook';\r\n\r\n// ===== USER GROUP MESSAGE TYPE GUARDS =====\r\n\r\n/**\r\n * Type union for user message events in groups\r\n */\r\nexport type UserGroupMessageEvent =\r\n  | UserSendGroupTextEvent\r\n  | UserSendGroupImageEvent\r\n  | UserSendGroupVideoEvent\r\n  | UserSendGroupAudioEvent\r\n  | UserSendGroupFileEvent;\r\n\r\n/**\r\n * Check if event is a user sending text message to group\r\n */\r\nexport function isUserSendGroupTextEvent(\r\n  event: WebhookEvent\r\n): event is UserSendGroupTextEvent {\r\n  return event.event_name === WebhookEventName.USER_SEND_GROUP_TEXT;\r\n}\r\n\r\n/**\r\n * Check if event is a user sending image message to group\r\n */\r\nexport function isUserSendGroupImageEvent(\r\n  event: WebhookEvent\r\n): event is UserSendGroupImageEvent {\r\n  return event.event_name === WebhookEventName.USER_SEND_GROUP_IMAGE;\r\n}\r\n\r\n/**\r\n * Check if event is a user sending video message to group\r\n */\r\nexport function isUserSendGroupVideoEvent(\r\n  event: WebhookEvent\r\n): event is UserSendGroupVideoEvent {\r\n  return event.event_name === WebhookEventName.USER_SEND_GROUP_VIDEO;\r\n}\r\n\r\n/**\r\n * Check if event is a user sending audio message to group\r\n */\r\nexport function isUserSendGroupAudioEvent(\r\n  event: WebhookEvent\r\n): event is UserSendGroupAudioEvent {\r\n  return event.event_name === WebhookEventName.USER_SEND_GROUP_AUDIO;\r\n}\r\n\r\n/**\r\n * Check if event is a user sending file to group\r\n */\r\nexport function isUserSendGroupFileEvent(\r\n  event: WebhookEvent\r\n): event is UserSendGroupFileEvent {\r\n  return event.event_name === WebhookEventName.USER_SEND_GROUP_FILE;\r\n}\r\n\r\n/**\r\n * Check if event is any user group message event\r\n */\r\nexport function isUserGroupMessageEvent(\r\n  event: WebhookEvent\r\n): event is UserGroupMessageEvent {\r\n  return [\r\n    WebhookEventName.USER_SEND_GROUP_TEXT,\r\n    WebhookEventName.USER_SEND_GROUP_IMAGE,\r\n    WebhookEventName.USER_SEND_GROUP_VIDEO,\r\n    WebhookEventName.USER_SEND_GROUP_AUDIO,\r\n    WebhookEventName.USER_SEND_GROUP_FILE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n// ===== OA MESSAGE TYPE GUARDS =====\r\n\r\n/**\r\n * Check if event is OA sending text message to user\r\n */\r\nexport function isOASendTextEvent(\r\n  event: WebhookEvent\r\n): event is OASendTextEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_TEXT;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending image message to user\r\n */\r\nexport function isOASendImageEvent(\r\n  event: WebhookEvent\r\n): event is OASendImageEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_IMAGE;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending file to user\r\n */\r\nexport function isOASendFileEvent(\r\n  event: WebhookEvent\r\n): event is OASendFileEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_FILE;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending sticker to user\r\n */\r\nexport function isOASendStickerEvent(\r\n  event: WebhookEvent\r\n): event is OASendStickerEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_STICKER;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending GIF to user\r\n */\r\nexport function isOASendGifEvent(\r\n  event: WebhookEvent\r\n): event is OASendGifEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_GIF;\r\n}\r\n\r\n// ===== OA GROUP MESSAGE TYPE GUARDS =====\r\n\r\n/**\r\n * Check if event is OA sending text message to group\r\n */\r\nexport function isOASendGroupTextEvent(\r\n  event: WebhookEvent\r\n): event is OASendGroupTextEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_GROUP_TEXT;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending image to group\r\n */\r\nexport function isOASendGroupImageEvent(\r\n  event: WebhookEvent\r\n): event is OASendGroupImageEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_GROUP_IMAGE;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending file to group\r\n */\r\nexport function isOASendGroupFileEvent(\r\n  event: WebhookEvent\r\n): event is OASendGroupFileEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_GROUP_FILE;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending sticker to group\r\n */\r\nexport function isOASendGroupStickerEvent(\r\n  event: WebhookEvent\r\n): event is OASendGroupStickerEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_GROUP_STICKER;\r\n}\r\n\r\n/**\r\n * Check if event is OA sending GIF to group\r\n */\r\nexport function isOASendGroupGifEvent(\r\n  event: WebhookEvent\r\n): event is OASendGroupGifEvent {\r\n  return event.event_name === WebhookEventName.OA_SEND_GROUP_GIF;\r\n}\r\n\r\n// ===== COMBINED TYPE GUARDS =====\r\n\r\n/**\r\n * Check if event is any OA message event (personal or group)\r\n */\r\nexport function isOAMessageEvent(\r\n  event: WebhookEvent\r\n): event is OAMessageWebhookEvent {\r\n  return [\r\n    WebhookEventName.OA_SEND_TEXT,\r\n    WebhookEventName.OA_SEND_IMAGE,\r\n    WebhookEventName.OA_SEND_FILE,\r\n    WebhookEventName.OA_SEND_STICKER,\r\n    WebhookEventName.OA_SEND_GIF,\r\n    WebhookEventName.OA_SEND_GROUP_TEXT,\r\n    WebhookEventName.OA_SEND_GROUP_IMAGE,\r\n    WebhookEventName.OA_SEND_GROUP_FILE,\r\n    WebhookEventName.OA_SEND_GROUP_STICKER,\r\n    WebhookEventName.OA_SEND_GROUP_GIF,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Check if event is any user message event (personal or group)\r\n */\r\nexport function isUserMessageEvent(\r\n  event: WebhookEvent\r\n): event is UserMessageWebhookEvent {\r\n  return [\r\n    WebhookEventName.USER_SEND_TEXT,\r\n    WebhookEventName.USER_SEND_IMAGE,\r\n    WebhookEventName.USER_SEND_AUDIO,\r\n    WebhookEventName.USER_SEND_VIDEO,\r\n    WebhookEventName.USER_SEND_FILE,\r\n    WebhookEventName.USER_SEND_STICKER,\r\n    WebhookEventName.USER_SEND_LOCATION,\r\n    WebhookEventName.USER_SEND_GROUP_TEXT,\r\n    WebhookEventName.USER_SEND_GROUP_VIDEO,\r\n    WebhookEventName.USER_SEND_GROUP_AUDIO,\r\n    WebhookEventName.USER_SEND_GROUP_FILE,\r\n  ].includes(event.event_name as WebhookEventName);\r\n}\r\n\r\n/**\r\n * Check if event has message attachments\r\n */\r\nexport function hasAttachments(event: WebhookEvent): boolean {\r\n  if ('message' in event && event.message && 'attachments' in event.message) {\r\n    return Array.isArray(event.message.attachments) && event.message.attachments.length > 0;\r\n  }\r\n  return false;\r\n}\r\n\r\n/**\r\n * Check if event is from a group context\r\n */\r\nexport function isFromGroup(event: WebhookEvent): boolean {\r\n  return 'group_id' in event || event.event_name.includes('group');\r\n}\r\n\r\n/**\r\n * Check if event is from a personal context\r\n */\r\nexport function isFromPersonal(event: WebhookEvent): boolean {\r\n  return !isFromGroup(event);\r\n}\r\n","/**\r\n * Base HTTP client for Zalo API\r\n */\r\n\r\nimport axios, {\r\n  AxiosInstance,\r\n  AxiosRequestConfig,\r\n  AxiosResponse,\r\n  AxiosError,\r\n} from \"axios\";\r\nimport FormData from \"form-data\";\r\nimport {\r\n  ZaloSDKConfig,\r\n  ZaloSDKError,\r\n  Logger,\r\n  ConsoleLogger,\r\n  RequestConfig,\r\n} from \"../types/common\";\r\n\r\n/**\r\n * Base client for making HTTP requests to Zalo API\r\n */\r\nexport class BaseClient {\r\n  protected readonly axios: AxiosInstance;\r\n  protected readonly logger: Logger;\r\n  protected readonly config: Required<ZaloSDKConfig>;\r\n\r\n  constructor(config: ZaloSDKConfig) {\r\n    // Set default configuration\r\n    this.config = {\r\n      appId: config.appId,\r\n      appSecret: config.appSecret,\r\n      timeout: config.timeout || 30000,\r\n      debug: config.debug || false,\r\n      apiBaseUrl: config.apiBaseUrl || \"https://openapi.zalo.me\",\r\n      retry: {\r\n        attempts: config.retry?.attempts || 3,\r\n        delay: config.retry?.delay || 1000,\r\n        ...config.retry,\r\n      },\r\n    };\r\n\r\n    this.logger = new ConsoleLogger(this.config.debug);\r\n\r\n    // Create axios instance\r\n    this.axios = axios.create({\r\n      baseURL: this.config.apiBaseUrl,\r\n      timeout: this.config.timeout,\r\n      headers: {\r\n        \"Content-Type\": \"application/json\",\r\n        \"User-Agent\": \"RedAI-Zalo-SDK/1.0.0\",\r\n      },\r\n    });\r\n\r\n    this.setupInterceptors();\r\n  }\r\n\r\n  /**\r\n   * Setup axios interceptors for logging and error handling\r\n   */\r\n  private setupInterceptors(): void {\r\n    // Request interceptor\r\n    this.axios.interceptors.request.use(\r\n      (config: any) => {\r\n        this.logger.debug(\r\n          `Making ${config.method?.toUpperCase()} request to ${config.url}`,\r\n          {\r\n            headers: this.sanitizeHeaders(config.headers || {}),\r\n            params: config.params,\r\n          }\r\n        );\r\n        return config;\r\n      },\r\n      (error: any) => {\r\n        this.logger.error(\"Request setup error:\", error.message);\r\n        return Promise.reject(error);\r\n      }\r\n    );\r\n\r\n    // Response interceptor\r\n    this.axios.interceptors.response.use(\r\n      (response: AxiosResponse) => {\r\n        this.logger.debug(`Received response from ${response.config.url}`, {\r\n          status: response.status,\r\n          data: response.data,\r\n        });\r\n        return response;\r\n      },\r\n      (error: AxiosError) => {\r\n        this.handleAxiosError(error);\r\n        return Promise.reject(error);\r\n      }\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Sanitize headers for logging (remove sensitive information)\r\n   */\r\n  private sanitizeHeaders(headers: Record<string, any>): Record<string, any> {\r\n    const sanitized = { ...headers };\r\n    if (sanitized.access_token) {\r\n      sanitized.access_token = \"***REDACTED***\";\r\n    }\r\n    if (sanitized.secret_key) {\r\n      sanitized.secret_key = \"***REDACTED***\";\r\n    }\r\n    return sanitized;\r\n  }\r\n\r\n  /**\r\n   * Handle axios errors and log them\r\n   */\r\n  private handleAxiosError(error: AxiosError): void {\r\n    if (error.response) {\r\n      this.logger.error(`HTTP ${error.response.status} error:`, {\r\n        url: error.config?.url,\r\n        status: error.response.status,\r\n        statusText: error.response.statusText,\r\n        data: error.response.data,\r\n      });\r\n    } else if (error.request) {\r\n      this.logger.error(\"No response received:\", {\r\n        url: error.config?.url,\r\n        message: error.message,\r\n      });\r\n    } else {\r\n      this.logger.error(\"Request setup error:\", error.message);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Make a GET request\r\n   */\r\n  protected async get<T = any>(\r\n    url: string,\r\n    accessToken?: string,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"GET\",\r\n      url,\r\n      headers: accessToken ? { access_token: accessToken } : {},\r\n      params,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make a POST request\r\n   */\r\n  protected async post<T = any>(\r\n    url: string,\r\n    accessToken?: string,\r\n    data?: any,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"POST\",\r\n      url,\r\n      headers: accessToken ? { access_token: accessToken } : {},\r\n      data,\r\n      params,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make a PUT request\r\n   */\r\n  protected async put<T = any>(\r\n    url: string,\r\n    accessToken?: string,\r\n    data?: any,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"PUT\",\r\n      url,\r\n      headers: accessToken ? { access_token: accessToken } : {},\r\n      data,\r\n      params,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make a DELETE request\r\n   */\r\n  protected async delete<T = any>(\r\n    url: string,\r\n    accessToken?: string,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"DELETE\",\r\n      url,\r\n      headers: accessToken ? { access_token: accessToken } : {},\r\n      params,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Upload file using FormData\r\n   */\r\n  protected async uploadFile<T = any>(\r\n    url: string,\r\n    accessToken: string,\r\n    file: Buffer | NodeJS.ReadableStream,\r\n    filename: string,\r\n    additionalFields?: Record<string, any>\r\n  ): Promise<T> {\r\n    const formData = new FormData();\r\n    formData.append(\"file\", file, filename);\r\n\r\n    if (additionalFields) {\r\n      Object.entries(additionalFields).forEach(([key, value]) => {\r\n        formData.append(key, value);\r\n      });\r\n    }\r\n\r\n    return this.request<T>({\r\n      method: \"POST\",\r\n      url,\r\n      headers: {\r\n        access_token: accessToken,\r\n        ...formData.getHeaders(), // Let form-data package set Content-Type with proper boundary\r\n      },\r\n      data: formData,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make a generic HTTP request with retry logic\r\n   */\r\n  protected async request<T = any>(config: RequestConfig): Promise<T> {\r\n    let lastError: Error;\r\n\r\n    for (\r\n      let attempt = 1;\r\n      attempt <= (this.config.retry?.attempts || 3);\r\n      attempt++\r\n    ) {\r\n      try {\r\n        const axiosConfig: AxiosRequestConfig = {\r\n          method: config.method,\r\n          url: config.url,\r\n          headers: config.headers,\r\n          params: config.params,\r\n          data: config.data,\r\n          timeout: config.timeout || this.config.timeout,\r\n        };\r\n\r\n        const response: AxiosResponse<T> = await this.axios.request(\r\n          axiosConfig\r\n        );\r\n\r\n        // Check for Zalo API errors in response\r\n        this.validateZaloResponse(response.data);\r\n\r\n        return response.data;\r\n      } catch (error) {\r\n        lastError = error as Error;\r\n\r\n        if (\r\n          attempt < (this.config.retry?.attempts || 3) &&\r\n          this.shouldRetry(error as AxiosError)\r\n        ) {\r\n          this.logger.warn(\r\n            `Request failed, retrying (${attempt}/${\r\n              this.config.retry?.attempts || 3\r\n            })...`\r\n          );\r\n          await this.delay((this.config.retry?.delay || 1000) * attempt);\r\n          continue;\r\n        }\r\n\r\n        break;\r\n      }\r\n    }\r\n\r\n    throw this.createSDKError(lastError!);\r\n  }\r\n\r\n  /**\r\n   * Validate Zalo API response for errors\r\n   */\r\n  private validateZaloResponse(data: any): void {\r\n    if (data && typeof data === \"object\") {\r\n      // Check for standard Zalo error format\r\n      if (\"error\" in data && data.error !== 0) {\r\n        const errorMessage =\r\n          data.error_description || data.message || \"Unknown Zalo API error\";\r\n        throw new ZaloSDKError(errorMessage, data.error, data);\r\n      }\r\n\r\n      // Check for nested result error format\r\n      if (\r\n        \"result\" in data &&\r\n        data.result &&\r\n        typeof data.result === \"object\" &&\r\n        \"error\" in data.result &&\r\n        data.result.error !== 0\r\n      ) {\r\n        const errorMessage =\r\n          data.result.error_description ||\r\n          data.result.message ||\r\n          \"Unknown Zalo API error\";\r\n        throw new ZaloSDKError(errorMessage, data.result.error, data.result);\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Determine if request should be retried\r\n   */\r\n  private shouldRetry(error: AxiosError): boolean {\r\n    // Retry on network errors or 5xx server errors\r\n    return (\r\n      !error.response ||\r\n      (error.response.status >= 500 && error.response.status < 600)\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Create SDK error from axios error\r\n   */\r\n  private createSDKError(error: Error): ZaloSDKError {\r\n    if (error instanceof ZaloSDKError) {\r\n      return error;\r\n    }\r\n\r\n    if (error instanceof AxiosError) {\r\n      const response = (error as any).response;\r\n      if (response?.data) {\r\n        const errorCode = response.data.error || response.status;\r\n        const errorMessage =\r\n          response.data.error_description ||\r\n          response.data.message ||\r\n          error.message;\r\n        return new ZaloSDKError(errorMessage, errorCode, response.data);\r\n      }\r\n    }\r\n\r\n    return new ZaloSDKError(error.message, -1, error);\r\n  }\r\n\r\n  /**\r\n   * Delay execution for specified milliseconds\r\n   */\r\n  private delay(ms: number): Promise<void> {\r\n    return new Promise((resolve) => setTimeout(resolve, ms));\r\n  }\r\n}\r\n","/**\r\n * Main Zalo API client\r\n */\r\n\r\nimport { BaseClient } from \"./base-client\";\r\nimport { ZaloSDKConfig } from \"../types/common\";\r\n\r\n/**\r\n * Zalo API client for making HTTP requests\r\n */\r\nexport class ZaloClient extends BaseClient {\r\n  constructor(config: ZaloSDKConfig) {\r\n    super(config);\r\n  }\r\n\r\n  /**\r\n   * Make authenticated GET request to Zalo API\r\n   */\r\n  public async apiGet<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.get<T>(endpoint, accessToken, params);\r\n  }\r\n\r\n  /**\r\n   * Make GET request with custom headers\r\n   */\r\n  public async apiGetWithHeaders<T = any>(\r\n    endpoint: string,\r\n    headers: Record<string, string>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"GET\",\r\n      url: endpoint,\r\n      headers,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make authenticated POST request to Zalo API\r\n   */\r\n  public async apiPost<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    data?: any,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.post<T>(endpoint, accessToken, data, params);\r\n  }\r\n\r\n  /**\r\n   * Make authenticated POST request to Zalo API with custom headers\r\n   */\r\n  public async apiPostWithHeaders<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    data?: any,\r\n    customHeaders?: Record<string, string>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"POST\",\r\n      url: endpoint,\r\n      headers: {\r\n        access_token: accessToken,\r\n        ...customHeaders,\r\n      },\r\n      data,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make authenticated PUT request to Zalo API\r\n   */\r\n  public async apiPut<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    data?: any,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.put<T>(endpoint, accessToken, data, params);\r\n  }\r\n\r\n  /**\r\n   * Make authenticated DELETE request to Zalo API\r\n   */\r\n  public async apiDelete<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.delete<T>(endpoint, accessToken, params);\r\n  }\r\n\r\n  /**\r\n   * Upload file to Zalo API\r\n   */\r\n  public async apiUploadFile<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    file: Buffer | NodeJS.ReadableStream,\r\n    filename: string,\r\n    additionalFields?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.uploadFile<T>(\r\n      endpoint,\r\n      accessToken,\r\n      file,\r\n      filename,\r\n      additionalFields\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Make POST request with FormData (for file uploads)\r\n   */\r\n  public async apiPostFormData<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    formData: FormData\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method: \"POST\",\r\n      url: endpoint,\r\n      headers: {\r\n        access_token: accessToken,\r\n        // Don't set Content-Type header, let the browser set it with boundary\r\n      },\r\n      data: formData,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Make request to OAuth endpoints (without access token)\r\n   */\r\n  public async oauthRequest<T = any>(\r\n    method: \"GET\" | \"POST\",\r\n    endpoint: string,\r\n    data?: any,\r\n    headers?: Record<string, string>\r\n  ): Promise<T> {\r\n    const config = {\r\n      method,\r\n      url: endpoint,\r\n      headers,\r\n      data,\r\n    };\r\n\r\n    if (method === \"GET\") {\r\n      config.data = undefined;\r\n      return this.get<T>(endpoint, undefined, data);\r\n    } else {\r\n      return this.post<T>(endpoint, undefined, data);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Make request to ZNS API (different base URL)\r\n   */\r\n  public async znsRequest<T = any>(\r\n    method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\",\r\n    endpoint: string,\r\n    accessToken: string,\r\n    data?: any,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    const znsBaseUrl = \"https://business.openapi.zalo.me\";\r\n    const fullUrl = `${znsBaseUrl}${endpoint}`;\r\n\r\n    switch (method) {\r\n      case \"GET\":\r\n        return this.get<T>(fullUrl, accessToken, params);\r\n      case \"POST\":\r\n        return this.post<T>(fullUrl, accessToken, data, params);\r\n      case \"PUT\":\r\n        return this.put<T>(fullUrl, accessToken, data, params);\r\n      case \"DELETE\":\r\n        return this.delete<T>(fullUrl, accessToken, params);\r\n      default:\r\n        throw new Error(`Unsupported HTTP method: ${method}`);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Make request to OAuth endpoints with custom base URL\r\n   */\r\n  public async oauthRequestWithUrl<T = any>(\r\n    method: \"GET\" | \"POST\",\r\n    url: string,\r\n    data?: any,\r\n    headers?: Record<string, string>\r\n  ): Promise<T> {\r\n    return this.request<T>({\r\n      method,\r\n      url,\r\n      headers: headers || {},\r\n      data: method === \"POST\" ? data : undefined,\r\n      params: method === \"GET\" ? data : undefined,\r\n    });\r\n  }\r\n}\r\n","/**\r\n * Authentication service for Zalo API\r\n */\r\n\r\nimport { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  AccessToken,\r\n  RefreshTokenResponse,\r\n  AuthCodeParams,\r\n  RefreshTokenParams,\r\n  SocialUserInfo,\r\n  TokenValidation,\r\n  AuthScope,\r\n  PKCEConfig,\r\n  AuthUrls,\r\n  OAAuthResult,\r\n} from \"../types/auth\";\r\nimport { ZaloSDKError } from \"../types/common\";\r\nimport { createHash, randomBytes } from \"crypto\";\r\n\r\n/**\r\n * Authentication service for handling OAuth flows and token management\r\n */\r\nexport class AuthService {\r\n  // Zalo OAuth & Graph endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    auth: {\r\n      oaPermission: \"https://oauth.zaloapp.com/v4/oa/permission\",\r\n      socialPermission: \"https://oauth.zaloapp.com/v4/permission\",\r\n      oaToken: \"https://oauth.zaloapp.com/v4/oa/access_token\",\r\n      socialToken: \"https://oauth.zaloapp.com/v4/access_token\",\r\n      refreshOaToken: \"https://oauth.zaloapp.com/v4/oa/access_token\",\r\n      refreshSocialToken: \"https://openapi.zalo.me/v2.0/oauth/access_token\",\r\n    },\r\n    social: {\r\n      me: \"https://graph.zalo.me/v2.0/me\",\r\n    },\r\n  } as const;\r\n\r\n  constructor(\r\n    private readonly client: ZaloClient,\r\n    private readonly appId: string,\r\n    private readonly appSecret: string\r\n  ) {}\r\n\r\n  /**\r\n   * Generate PKCE code verifier and challenge for Social API\r\n   */\r\n  public generatePKCE(): PKCEConfig {\r\n    const codeVerifier = randomBytes(32).toString(\"base64url\");\r\n    const codeChallenge = createHash(\"sha256\")\r\n      .update(codeVerifier)\r\n      .digest(\"base64url\");\r\n\r\n    return {\r\n      code_verifier: codeVerifier,\r\n      code_challenge: codeChallenge,\r\n      code_challenge_method: \"S256\",\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Create OAuth authorization URL for Official Account with PKCE support\r\n   *\r\n   * @param redirectUri - The redirect URI after authorization\r\n   * @param state - Optional state parameter for security. If not provided, auto-generates with 'zalo_oa_' prefix\r\n   * @param usePkce - Whether to use PKCE for enhanced security. If true and pkce not provided, will auto-generate\r\n   * @param pkce - Optional PKCE configuration for enhanced security. If usePkce=true and this is not provided, will be auto-generated\r\n   * @returns Object containing the authorization URL, state, and PKCE config (if used)\r\n   */\r\n  public createOAAuthUrl(\r\n    redirectUri: string,\r\n    state?: string,\r\n    pkce?: PKCEConfig,\r\n    usePkce: boolean = false\r\n  ): OAAuthResult {\r\n    // Generate state with zalo_oa_ prefix if not provided\r\n    const finalState = state || `zalo_oa_${randomBytes(16).toString(\"hex\")}`;\r\n\r\n    // Auto-generate PKCE if usePkce is true but pkce is not provided\r\n    let finalPkce: PKCEConfig | undefined = pkce;\r\n    if (usePkce && !pkce) {\r\n      finalPkce = this.generatePKCE();\r\n    }\r\n\r\n    const params = new URLSearchParams({\r\n      app_id: this.appId,\r\n      redirect_uri: redirectUri,\r\n      state: finalState,\r\n    });\r\n\r\n    // Add PKCE parameters if PKCE is being used\r\n    if (usePkce && finalPkce) {\r\n      params.append(\"code_challenge\", finalPkce.code_challenge);\r\n      params.append(\"code_challenge_method\", finalPkce.code_challenge_method);\r\n    }\r\n\r\n    const url = `${this.endpoints.auth.oaPermission}?${params.toString()}`;\r\n\r\n    return {\r\n      url,\r\n      state: finalState,\r\n      pkce: usePkce ? finalPkce : undefined,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Create OAuth authorization URL for Social API\r\n   */\r\n  public createSocialAuthUrl(\r\n    redirectUri: string,\r\n    state?: string,\r\n    pkce?: PKCEConfig\r\n  ): string {\r\n    const params = new URLSearchParams({\r\n      app_id: this.appId,\r\n      redirect_uri: redirectUri,\r\n      state: state || \"social_auth\",\r\n    });\r\n\r\n    if (pkce) {\r\n      params.append(\"code_challenge\", pkce.code_challenge);\r\n      params.append(\"code_challenge_method\", pkce.code_challenge_method);\r\n    }\r\n\r\n    return `${this.endpoints.auth.socialPermission}?${params.toString()}`;\r\n  }\r\n\r\n  /**\r\n   * Exchange authorization code for Official Account access token\r\n   * Now supports PKCE code_verifier for enhanced security\r\n   */\r\n  public async getOAAccessToken(params: AuthCodeParams): Promise<AccessToken> {\r\n    try {\r\n      const url = this.endpoints.auth.oaToken;\r\n\r\n      // Prepare form data according to API docs\r\n      const formData = new URLSearchParams();\r\n      formData.append(\"code\", params.code);\r\n      formData.append(\"app_id\", params.app_id);\r\n      formData.append(\"grant_type\", \"authorization_code\");\r\n\r\n      // Add code_verifier if provided (for PKCE)\r\n      if (params.code_verifier) {\r\n        formData.append(\"code_verifier\", params.code_verifier);\r\n      }\r\n\r\n      let result: any;\r\n      try {\r\n        result = await this.client.oauthRequestWithUrl(\r\n          \"POST\",\r\n          url,\r\n          formData.toString(),\r\n          {\r\n            \"Content-Type\": \"application/x-www-form-urlencoded\",\r\n            \"secret_key\": params.app_secret,\r\n          }\r\n        );\r\n      } catch (error) {\r\n        // If the error contains access_token, it means the request was successful\r\n        // but Zalo API returned a warning/error code along with valid tokens\r\n        if (error instanceof ZaloSDKError && error.details?.access_token) {\r\n          result = error.details;\r\n        } else {\r\n          throw error;\r\n        }\r\n      }\r\n\r\n      // Check if we have access_token (prioritize token presence over error codes)\r\n      if (result.access_token) {\r\n        return {\r\n          access_token: result.access_token,\r\n          expires_in: parseInt(result.expires_in) || result.expires_in,\r\n          refresh_token: result.refresh_token,\r\n        };\r\n      }\r\n\r\n      // If no access_token but we have data wrapper, check inside\r\n      if (result.data?.access_token) {\r\n        return {\r\n          access_token: result.data.access_token,\r\n          expires_in: parseInt(result.data.expires_in) || result.data.expires_in,\r\n          refresh_token: result.data.refresh_token,\r\n        };\r\n      }\r\n\r\n      // Only check error codes if we don't have access_token\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.error_description ||\r\n            result.message ||\r\n            \"Failed to get OA access token\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      throw new ZaloSDKError(\"No access token received from Zalo API\", -1, result);\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get OA access token: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Exchange authorization code for Social API access token\r\n   */\r\n  public async getSocialAccessToken(\r\n    params: AuthCodeParams\r\n  ): Promise<AccessToken> {\r\n    try {\r\n      const url = this.endpoints.auth.socialToken;\r\n\r\n      const formData = new URLSearchParams();\r\n      formData.append(\"code\", params.code);\r\n      formData.append(\"app_id\", params.app_id);\r\n      formData.append(\"grant_type\", \"authorization_code\");\r\n\r\n      if (params.code_verifier) {\r\n        formData.append(\"code_verifier\", params.code_verifier);\r\n      }\r\n\r\n      let result: any;\r\n      try {\r\n        result = await this.client.oauthRequestWithUrl(\r\n          \"POST\",\r\n          url,\r\n          formData.toString(),\r\n          {\r\n            \"Content-Type\": \"application/x-www-form-urlencoded\",\r\n            secret_key: params.app_secret,\r\n          }\r\n        );\r\n      } catch (error) {\r\n        // If the error contains access_token, it means the request was successful\r\n        // but Zalo API returned a warning/error code along with valid tokens\r\n        if (error instanceof ZaloSDKError && error.details?.access_token) {\r\n          result = error.details;\r\n        } else {\r\n          throw error;\r\n        }\r\n      }\r\n\r\n      // Check if we have access_token (prioritize token presence over error codes)\r\n      if (result.access_token) {\r\n        return {\r\n          access_token: result.access_token,\r\n          expires_in: result.expires_in,\r\n          refresh_token: result.refresh_token,\r\n        };\r\n      }\r\n\r\n      // If no access_token but we have data wrapper, check inside\r\n      if (result.data?.access_token) {\r\n        return {\r\n          access_token: result.data.access_token,\r\n          expires_in: result.data.expires_in,\r\n          refresh_token: result.data.refresh_token,\r\n        };\r\n      }\r\n\r\n      // Only check error codes if we don't have access_token\r\n      if (result.error && result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.error_description ||\r\n            result.message ||\r\n            \"Failed to get Social access token\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      throw new ZaloSDKError(\"No access token received from Zalo API\", -1, result);\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get Social access token: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Refresh Official Account access token\r\n   */\r\n  public async refreshOAAccessToken(\r\n    params: RefreshTokenParams\r\n  ): Promise<AccessToken> {\r\n    try {\r\n      const url = this.endpoints.auth.refreshOaToken;\r\n\r\n      const formData = new URLSearchParams();\r\n      formData.append(\"refresh_token\", params.refresh_token);\r\n      formData.append(\"app_id\", params.app_id);\r\n      formData.append(\"grant_type\", \"refresh_token\");\r\n\r\n      const result = await this.client.oauthRequestWithUrl(\r\n        \"POST\",\r\n        url,\r\n        formData.toString(),\r\n        {\r\n          \"Content-Type\": \"application/x-www-form-urlencoded\",\r\n          secret_key: params.app_secret,\r\n        }\r\n      );\r\n\r\n      if (!result.access_token) {\r\n        throw new ZaloSDKError(\r\n          \"No access token received when refreshing OA token\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      return {\r\n        access_token: result.access_token,\r\n        refresh_token: result.refresh_token,\r\n        expires_in: parseInt(result.expires_in),\r\n      };\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to refresh OA access token: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Refresh Social API access token\r\n   */\r\n  public async refreshSocialAccessToken(\r\n    params: RefreshTokenParams\r\n  ): Promise<RefreshTokenResponse> {\r\n    try {\r\n      const endpoint = this.endpoints.auth.refreshSocialToken;\r\n      const requestParams = {\r\n        app_id: params.app_id,\r\n        grant_type: \"refresh_token\",\r\n        refresh_token: params.refresh_token,\r\n      };\r\n\r\n      const result = await this.client.apiGet(endpoint, \"\", requestParams);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.error_description ||\r\n            result.message ||\r\n            \"Failed to refresh Social access token\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\r\n          \"No data received when refreshing Social token\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to refresh Social access token: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get Social user information\r\n   */\r\n  public async getSocialUserInfo(\r\n    accessToken: string,\r\n    fields: string = \"id,name,picture\"\r\n  ): Promise<SocialUserInfo> {\r\n    try {\r\n      const url = this.endpoints.social.me;\r\n      const params = { fields };\r\n\r\n      const result = await this.client.oauthRequestWithUrl(\"GET\", url, params, {\r\n        access_token: accessToken,\r\n      });\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get Social user info\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.id) {\r\n        throw new ZaloSDKError(\"No user data received from Social API\", -1);\r\n      }\r\n\r\n      return {\r\n        id: result.id,\r\n        name: result.name,\r\n        picture: result.picture,\r\n        is_sensitive: result.is_sensitive,\r\n      };\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get Social user info: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate access token by attempting to get user info\r\n   */\r\n  public async validateAccessToken(\r\n    accessToken: string,\r\n    scope: AuthScope = AuthScope.SOCIAL\r\n  ): Promise<TokenValidation> {\r\n    try {\r\n      if (scope === AuthScope.SOCIAL) {\r\n        const userInfo = await this.getSocialUserInfo(accessToken, \"id\");\r\n        return {\r\n          valid: true,\r\n          user_info: userInfo,\r\n        };\r\n      } else {\r\n        // For OA tokens, we could try to get OA info\r\n        // This is a simplified validation\r\n        return { valid: true };\r\n      }\r\n    } catch (error) {\r\n      return { valid: false };\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all authentication URLs with optional PKCE support\r\n   */\r\n  public getAuthUrls(\r\n    redirectUri: string,\r\n    usePkce: boolean = false,\r\n    pkce?: PKCEConfig\r\n  ): AuthUrls {\r\n    const oaAuthResult = this.createOAAuthUrl(\r\n      redirectUri,\r\n      undefined,\r\n      pkce,\r\n      usePkce\r\n    );\r\n\r\n    return {\r\n      oa_auth_url: oaAuthResult.url,\r\n      social_auth_url: this.createSocialAuthUrl(redirectUri, undefined, pkce),\r\n      token_url: this.endpoints.auth.socialToken,\r\n      refresh_url: this.endpoints.auth.refreshOaToken,\r\n    };\r\n  }\r\n}\r\n","/**\r\n * Official Account (OA) service for Zalo API\r\n */\r\n\r\nimport { ZaloClient } from '../clients/zalo-client';\r\nimport {\r\n  OAInfo,\r\n  MessageQuota,\r\n  QuotaMessageRequest,\r\n  QuotaMessageResponse,\r\n  QuotaAsset,\r\n  OASettings,\r\n  OAStatistics,\r\n  UpdateOAProfileRequest,\r\n  OAVerificationRequest,\r\n  GMFProductType,\r\n  QuotaType,\r\n} from '../types/oa';\r\nimport { ZaloResponse, ZaloSDKError } from '../types/common';\r\n\r\n/**\r\n * Official Account service for managing OA information and settings\r\n */\r\nexport class OAService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    // OA information\r\n    info: \"https://openapi.zalo.me/v2.0/oa/getoa\",\r\n    // Quota endpoints\r\n    quota: {\r\n      message: \"https://openapi.zalo.me/v3.0/oa/quota/message\",\r\n    },\r\n    // Profile management\r\n    profile: {\r\n      update: \"https://openapi.zalo.me/v2.0/oa/update\",\r\n    },\r\n    // Statistics\r\n    statistics: {\r\n      overview: \"https://openapi.zalo.me/v2.0/oa/statistics\",\r\n    },\r\n  };\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Get Official Account information\r\n   */\r\n  public async getOAInfo(accessToken: string): Promise<OAInfo> {\r\n    try {\r\n      const endpoint = this.endpoints.info;\r\n      const result: ZaloResponse<OAInfo> = await this.client.apiGet(endpoint, accessToken);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || 'Failed to get OA information',\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError('No OA data received', -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(`Failed to get OA info: ${error.message}`, -1, error);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get detailed quota message information\r\n   */\r\n  public async getDetailedQuotaMessage(\r\n    accessToken: string,\r\n    request: QuotaMessageRequest\r\n  ): Promise<QuotaAsset[]> {\r\n    try {\r\n      const endpoint = this.endpoints.quota.message;\r\n      const result: QuotaMessageResponse = await this.client.apiPost(endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || 'Failed to get detailed quota message',\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return result.data || [];\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(`Failed to get detailed quota message: ${error.message}`, -1, error);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get quota for specific product type\r\n   */\r\n  public async getQuotaByProductType(\r\n    accessToken: string,\r\n    productType: 'cs' | 'transaction' | 'gmf10' | 'gmf50' | 'gmf100',\r\n    quotaType?: 'sub_quota' | 'purchase_quota' | 'reward_quota'\r\n  ): Promise<QuotaAsset[]> {\r\n    const request: QuotaMessageRequest = {\r\n      quota_owner: 'OA',\r\n      product_type: productType,\r\n      quota_type: quotaType,\r\n    };\r\n\r\n    return this.getDetailedQuotaMessage(accessToken, request);\r\n  }\r\n\r\n  /**\r\n   * Get available GMF quota\r\n   */\r\n  public async getGMFQuota(\r\n    accessToken: string,\r\n    gmfType: GMFProductType = GMFProductType.GMF10\r\n  ): Promise<QuotaAsset[]> {\r\n    return this.getQuotaByProductType(accessToken, gmfType);\r\n  }\r\n\r\n  /**\r\n   * Get consultation message quota\r\n   */\r\n  public async getConsultationQuota(accessToken: string): Promise<QuotaAsset[]> {\r\n    return this.getQuotaByProductType(accessToken, 'cs');\r\n  }\r\n\r\n  /**\r\n   * Get transaction message quota\r\n   */\r\n  public async getTransactionQuota(accessToken: string): Promise<QuotaAsset[]> {\r\n    return this.getQuotaByProductType(accessToken, 'transaction');\r\n  }\r\n\r\n  /**\r\n   * Update OA profile information\r\n   */\r\n  public async updateOAProfile(\r\n    accessToken: string,\r\n    profileData: UpdateOAProfileRequest\r\n  ): Promise<boolean> {\r\n    try {\r\n      // Note: This endpoint might not be available in all Zalo API versions\r\n      // This is a placeholder implementation\r\n      const endpoint = this.endpoints.profile.update;\r\n      const result = await this.client.apiPost(endpoint, accessToken, profileData);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || 'Failed to update OA profile',\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(`Failed to update OA profile: ${error.message}`, -1, error);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get OA statistics (if available)\r\n   */\r\n  public async getOAStatistics(accessToken: string): Promise<OAStatistics> {\r\n    try {\r\n      // Note: This endpoint might not be available in all Zalo API versions\r\n      // This is a placeholder implementation\r\n      const endpoint = this.endpoints.statistics.overview;\r\n      const result = await this.client.apiGet(endpoint, accessToken);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || 'Failed to get OA statistics',\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return result.data || {\r\n        total_followers: 0,\r\n        new_followers_today: 0,\r\n        messages_sent_today: 0,\r\n        messages_received_today: 0,\r\n      };\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(`Failed to get OA statistics: ${error.message}`, -1, error);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Lấy thông tin quota tổng quan cho tin nhắn (tổng số asset và số còn khả dụng)\r\n   * Lưu ý: Zalo hiện cung cấp API chi tiết qua endpoint quota/message theo asset.\r\n   * Hàm này tổng hợp đơn giản để trả về cấu trúc MessageQuota phục vụ SDK.\r\n   */\r\n  public async getMessageQuota(accessToken: string): Promise<MessageQuota> {\r\n    try {\r\n      // Gọi endpoint quota/message không chỉ định product_type để lấy toàn bộ asset\r\n      const request: QuotaMessageRequest = {\r\n        quota_owner: 'OA',\r\n      };\r\n      const assets = await this.getDetailedQuotaMessage(accessToken, request);\r\n\r\n      // Đếm tổng asset và số asset còn khả dụng\r\n      const totalAssets = assets.length;\r\n      const availableAssets = assets.filter((a) => a.status === 'available')\r\n        .length;\r\n\r\n      // Trả về dạng MessageQuota tối giản\r\n      const quota: MessageQuota = {\r\n        // Sử dụng tổng số asset làm daily_quota (ước lượng theo asset)\r\n        daily_quota: totalAssets,\r\n        // Số còn lại là số asset đang ở trạng thái available\r\n        remaining_quota: availableAssets,\r\n        // Quota type tổng hợp vì có thể gồm nhiều loại\r\n        quota_type: 'mixed',\r\n        // Không có reset_time từ API asset -> trả 0\r\n        reset_time: 0,\r\n      };\r\n\r\n      return quota;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get message quota: ${((error as unknown) as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Check if OA has sufficient quota for message type\r\n   */\r\n  public async hasQuotaForMessageType(\r\n    accessToken: string,\r\n    messageType: 'cs' | 'transaction' | 'promotion',\r\n    requiredCount: number = 1\r\n  ): Promise<boolean> {\r\n    try {\r\n      let productType: 'cs' | 'transaction';\r\n      \r\n      if (messageType === 'promotion') {\r\n        // Promotion messages might use different quota logic\r\n        return true; // Placeholder\r\n      } else {\r\n        productType = messageType;\r\n      }\r\n\r\n      const quotaAssets = await this.getQuotaByProductType(accessToken, productType);\r\n      \r\n      // Check if there are available assets\r\n      const availableAssets = quotaAssets.filter(asset => asset.status === 'available');\r\n      \r\n      if (availableAssets.length === 0) {\r\n        return false;\r\n      }\r\n\r\n      // For simplicity, assume each available asset can handle the required count\r\n      return true;\r\n    } catch (error) {\r\n      // If we can't check quota, assume it's available to avoid blocking\r\n      return true;\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get quota summary for all message types\r\n   */\r\n  public async getQuotaSummary(accessToken: string): Promise<{\r\n    consultation: QuotaAsset[];\r\n    transaction: QuotaAsset[];\r\n    gmf10: QuotaAsset[];\r\n    gmf50: QuotaAsset[];\r\n    gmf100: QuotaAsset[];\r\n  }> {\r\n    try {\r\n      const [consultation, transaction, gmf10, gmf50, gmf100] = await Promise.all([\r\n        this.getConsultationQuota(accessToken),\r\n        this.getTransactionQuota(accessToken),\r\n        this.getGMFQuota(accessToken, GMFProductType.GMF10),\r\n        this.getGMFQuota(accessToken, GMFProductType.GMF50),\r\n        this.getGMFQuota(accessToken, GMFProductType.GMF100),\r\n      ]);\r\n\r\n      return {\r\n        consultation,\r\n        transaction,\r\n        gmf10,\r\n        gmf50,\r\n        gmf100,\r\n      };\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(`Failed to get quota summary: ${error.message}`, -1, error);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate OA access token by getting OA info\r\n   */\r\n  public async validateOAToken(accessToken: string): Promise<boolean> {\r\n    try {\r\n      await this.getOAInfo(accessToken);\r\n      return true;\r\n    } catch (error) {\r\n      return false;\r\n    }\r\n  }\r\n}\r\n","/**\r\n * User management service for Zalo API\r\n * Handles all user-related operations with specific endpoints\r\n */\r\n\r\nimport { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  UserInfo,\r\n  UserListRequest,\r\n  UserListResponse,\r\n  UserLabel,\r\n  TagUserRequest,\r\n  RemoveTagFromUserRequest,\r\n  GetTagsResponse,\r\n  DeleteTagRequest,\r\n  UpdateUserRequest,\r\n  DeleteUserInfoRequest,\r\n  GetUserCustomInfoRequest,\r\n  UserCustomInfoResponse,\r\n  UpdateUserCustomInfoRequest,\r\n  AdvancedUserFilters,\r\n} from \"../types/user\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\n\r\n/**\r\n * User management service for handling user operations\r\n * Each method uses specific Zalo API endpoints\r\n */\r\nexport class UserService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    // User management endpoints\r\n    user: {\r\n      getList: \"https://openapi.zalo.me/v3.0/oa/user/getlist\",\r\n      postList: \"https://openapi.zalo.me/v3.0/oa/user/getlist\",\r\n      getDetail: \"https://openapi.zalo.me/v3.0/oa/user/detail\",\r\n      update: \"https://openapi.zalo.me/v3.0/oa/user/update\",\r\n      delete: \"https://openapi.zalo.me/v2.0/oa/deletefollowerinfo\",\r\n      getCustomInfo: \"https://openapi.zalo.me/v3.0/oa/user/detail/custominfo\",\r\n      updateCustomInfo:\r\n        \"https://openapi.zalo.me/v3.0/oa/user/update/custominfo\",\r\n    },\r\n    // Tag management endpoints\r\n    tag: {\r\n      addToUser: \"https://openapi.zalo.me/v2.0/oa/tag/tagfollower\",\r\n      removeFromUser: \"https://openapi.zalo.me/v2.0/oa/tag/rmfollowerfromtag\",\r\n      getList: \"https://openapi.zalo.me/v2.0/oa/tag/gettagsofoa\",\r\n      delete: \"https://openapi.zalo.me/v2.0/oa/tag/rmtag\",\r\n    },\r\n  };\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Truy xuất chi tiết người dùng\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/user/detail\r\n   * Method: GET with data parameter as JSON string\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param userId User ID to get details for\r\n   * @returns Promise<UserInfo> - Detailed user information\r\n   */\r\n  public async getUserInfo(\r\n    accessToken: string,\r\n    userId: string\r\n  ): Promise<UserInfo> {\r\n    try {\r\n      // Build data object according to API spec\r\n      const dataObject = { user_id: userId };\r\n\r\n      // Send data as JSON string parameter according to API spec\r\n      const params = {\r\n        data: JSON.stringify(dataObject),\r\n      };\r\n\r\n      const result: ZaloResponse<UserInfo> = await this.client.apiGet(\r\n        this.endpoints.user.getDetail,\r\n        accessToken,\r\n        params\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get user information\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No user data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      this.handleError(error, \"Failed to get user info\");\r\n    }\r\n  }\r\n\r\n  /**\r\n * Truy xuất chi tiết người dùng (bản dùng POST)\r\n * Endpoint: https://openapi.zalo.me/v3.0/oa/user/detail\r\n * Method: POST với body JSON\r\n *\r\n * @param accessToken OA access token\r\n * @param userId ID người dùng cần lấy thông tin\r\n * @returns Promise<UserInfo> - Thông tin chi tiết người dùng\r\n */\r\npublic async postUserInfo(\r\n  accessToken: string,\r\n  userId: string\r\n): Promise<UserInfo> {\r\n  try {\r\n    // Tạo body JSON theo yêu cầu API\r\n    const body = {\r\n      user_id: userId,\r\n    };\r\n\r\n    // Gửi POST request thay vì GET\r\n    const result: ZaloResponse<UserInfo> = await this.client.apiPost(\r\n      this.endpoints.user.getDetail,\r\n      accessToken,\r\n      body\r\n    );\r\n\r\n    // Kiểm tra lỗi từ API Zalo\r\n    if (result.error !== 0) {\r\n      throw new ZaloSDKError(\r\n        result.message || \"Failed to get user information (POST)\",\r\n        result.error,\r\n        result\r\n      );\r\n    }\r\n\r\n    // Kiểm tra dữ liệu trả về\r\n    if (!result.data) {\r\n      throw new ZaloSDKError(\"No user data received from POST request\", -1);\r\n    }\r\n\r\n    return result.data;\r\n  } catch (error) {\r\n    if (error instanceof ZaloSDKError) {\r\n      throw error;\r\n    }\r\n    this.handleError(error, \"Failed to get user info (POST)\");\r\n  }\r\n}\r\n\r\n\r\n  /**\r\n   * Truy xuất danh sách người dùng\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/user/getlist\r\n   * Method: GET with data parameter as JSON string\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request User list request parameters\r\n   * @returns Promise<UserListResponse> - List of users with pagination info\r\n   */\r\n  public async getUserList(\r\n    accessToken: string,\r\n    request: UserListRequest\r\n  ): Promise<UserListResponse> {\r\n    try {\r\n      // Build data object according to API spec\r\n      const dataObject = {\r\n        offset: request.offset,\r\n        count: Math.min(request.count, 50), // Max 50 users per request\r\n        ...(request.tag_name && { tag_name: request.tag_name }),\r\n        ...(request.last_interaction_period && {\r\n          last_interaction_period: request.last_interaction_period,\r\n        }),\r\n        ...(request.is_follower !== undefined && {\r\n          is_follower: request.is_follower,\r\n        }),\r\n      };\r\n\r\n      // Send data as JSON string parameter according to API spec\r\n      const params = {\r\n        data: JSON.stringify(dataObject),\r\n      };\r\n\r\n      const result: ZaloResponse<UserListResponse> = await this.client.apiGet(\r\n        this.endpoints.user.getList,\r\n        accessToken,\r\n        params\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get user list\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No user list data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      this.handleError(error, \"Failed to get user list\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Truy xuất danh sách người dùng (POST method)\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/user/getlist\r\n   * Method: POST with data in request body\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request User list request parameters\r\n   * @returns Promise<UserListResponse> - List of users with pagination info\r\n   */\r\n  public async postUserList(\r\n    accessToken: string,\r\n    request: UserListRequest\r\n  ): Promise<UserListResponse> {\r\n    try {\r\n      // Build data object according to API spec\r\n      const dataObject = {\r\n        offset: request.offset,\r\n        count: Math.min(request.count, 50), // Max 50 users per request\r\n        ...(request.tag_name && { tag_name: request.tag_name }),\r\n        ...(request.last_interaction_period && {\r\n          last_interaction_period: request.last_interaction_period,\r\n        }),\r\n        ...(request.is_follower !== undefined && {\r\n          is_follower: request.is_follower,\r\n        }),\r\n      };\r\n\r\n      const result: ZaloResponse<UserListResponse> = await this.client.apiPost(\r\n        this.endpoints.user.postList,\r\n        accessToken,\r\n        dataObject\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get user list\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No user list data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      this.handleError(error, \"Failed to get user list\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all users with automatic pagination\r\n   * Lấy tất cả users với phân trang tự động, đảm bảo lấy 100%\r\n   *\r\n   * @param accessToken Access token của OA\r\n   * @param filters Bộ lọc tùy chọn\r\n   * @returns Promise<UserInfo[]> - Danh sách đầy đủ tất cả users\r\n   */\r\n  public async getAllUsers(\r\n    accessToken: string,\r\n    filters?: {\r\n      tag_name?: string;\r\n      last_interaction_period?: string;\r\n      is_follower?: boolean;\r\n    }\r\n  ): Promise<UserInfo[]> {\r\n    console.log('[getAllUsers] Bắt đầu lấy danh sách users với filters:', filters);\r\n    const users: UserInfo[] = [];\r\n    let offset = 0;\r\n    const count = 50; // Max per request\r\n    let totalUsers = 0;\r\n    let fetchedUsers = 0;\r\n\r\n    let failedUsers = 0;\r\n\r\n    try {\r\n      while (true) {\r\n        console.log(`[getAllUsers] Đang lấy batch tại offset=${offset}, count=${count}`);\r\n        const userListResponse = await this.postUserList(accessToken, {\r\n          offset,\r\n          count,\r\n          ...filters,\r\n        });\r\n        console.log(`[getAllUsers] Nhận được ${userListResponse.users.length} user IDs từ postUserList`);\r\n\r\n        // Lần đầu tiên, lưu tổng số users\r\n        if (offset === 0) {\r\n          totalUsers = userListResponse.total;\r\n          console.log(`[getAllUsers] ✓ Total users to fetch: ${totalUsers}`);\r\n\r\n          // Cảnh báo nếu vượt quá giới hạn Zalo API\r\n          if (totalUsers > 10000) {\r\n            console.warn(\r\n              `[getAllUsers] ⚠ Warning: Total users (${totalUsers}) exceeds Zalo API limit (10000). ` +\r\n                `Only first 10000 users can be fetched due to max offset = 9951.`\r\n            );\r\n          }\r\n        }\r\n\r\n        // Get detailed info for each user (sequential to avoid rate limit)\r\n        // Xử lý tuần tự để tránh bị Zalo API rate limit\r\n        const userIds = userListResponse.users.map((u) => u.user_id);\r\n        console.log(`[getAllUsers] Bắt đầu lấy chi tiết cho ${userIds.length} users...`);\r\n        const userInfos: UserInfo[] = [];\r\n        \r\n        for (let i = 0; i < userIds.length; i++) {\r\n          const userId = userIds[i];\r\n          console.log(`[getAllUsers] ├─ [${i + 1}/${userIds.length}] Đang lấy info cho user: ${userId}`);\r\n          \r\n          try {\r\n            const userInfo = await this.postUserInfo(accessToken, userId);\r\n            userInfos.push(userInfo);\r\n            console.log(`[getAllUsers] │  ✓ Thành công: ${userInfo.display_name || 'N/A'}`);\r\n          } catch (error) {\r\n            failedUsers++;\r\n            console.warn(`[getAllUsers] │  ⚠ Skip user ${userId} do lỗi: ${error.message}`);\r\n            // Tiếp tục với user tiếp theo thay vì throw error\r\n          }\r\n          \r\n          // Small delay between requests to avoid rate limiting\r\n          if (i < userIds.length - 1) {\r\n            await new Promise((resolve) => setTimeout(resolve, 100));\r\n          }\r\n        }\r\n        console.log(`[getAllUsers] ✓ Hoàn thành lấy chi tiết ${userInfos.length} users trong batch này`);\r\n\r\n        users.push(...userInfos);\r\n        fetchedUsers += userInfos.length;\r\n\r\n        console.log(`[getAllUsers] 📊 Progress: Đã lấy ${fetchedUsers}/${totalUsers} users (${Math.round((fetchedUsers/totalUsers)*100)}%)`);\r\n\r\n        // Điều kiện dừng ĐÚNG:\r\n        // 1. Không còn users trong response\r\n        // 2. Hoặc đã lấy đủ số lượng theo total\r\n        // 3. Hoặc số users trả về ít hơn count (page cuối)\r\n        if (\r\n          userListResponse.users.length === 0 ||\r\n          fetchedUsers >= totalUsers ||\r\n          userListResponse.users.length < count\r\n        ) {\r\n          console.log(`[getAllUsers] 🛑 Điều kiện dừng:`, {\r\n            noMoreUsers: userListResponse.users.length === 0,\r\n            reachedTotal: fetchedUsers >= totalUsers,\r\n            lastPage: userListResponse.users.length < count\r\n          });\r\n          break;\r\n        }\r\n\r\n        offset += count;\r\n        console.log(`[getAllUsers] ➡ Chuyển sang batch tiếp theo, offset=${offset}`);\r\n\r\n        // Protection: Tránh infinite loop\r\n        // Zalo giới hạn max offset = 9951 (tương ứng 10000 người dùng)\r\n        if (offset > 9951) {\r\n          console.warn(\r\n            `[getAllUsers] ⚠ Reached Zalo maximum offset limit (9951), stopping pagination`\r\n          );\r\n          break;\r\n        }\r\n      }\r\n\r\n      console.log(`[getAllUsers] ✅ HOÀN THÀNH: Successfully fetched ${users.length}/${totalUsers} users` + (failedUsers > 0 ? ` (${failedUsers} failed/skipped)` : ''));\r\n      return users;\r\n    } catch (error) {\r\n      console.error('[getAllUsers] ❌ LỖI:', {\r\n        message: error.message,\r\n        code: error.code || error.error,\r\n        offset: offset,\r\n        fetchedSoFar: fetchedUsers,\r\n        totalTarget: totalUsers\r\n      });\r\n      \r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      this.handleError(error, \"Failed to get all users\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all user IDs only (without detailed info) - Faster version\r\n   * Chỉ lấy danh sách user ID, không lấy thông tin chi tiết - Nhanh hơn\r\n   *\r\n   * @param accessToken Access token của OA\r\n   * @param filters Bộ lọc tùy chọn\r\n   * @returns Promise<string[]> - Danh sách user IDs\r\n   */\r\n  public async getAllUserIds(\r\n    accessToken: string,\r\n    filters?: {\r\n      tag_name?: string;\r\n      last_interaction_period?: string;\r\n      is_follower?: boolean;\r\n    }\r\n  ): Promise<string[]> {\r\n    const userIds: string[] = [];\r\n    let offset = 0;\r\n    const count = 50; // Max per request\r\n    let totalUsers = 0;\r\n\r\n    try {\r\n      while (true) {\r\n        const userListResponse = await this.postUserList(accessToken, {\r\n          offset,\r\n          count,\r\n          ...filters,\r\n        });\r\n\r\n        // Lần đầu tiên, lưu tổng số users\r\n        if (offset === 0) {\r\n          totalUsers = userListResponse.total;\r\n          console.log(`Total user IDs to fetch: ${totalUsers}`);\r\n\r\n          // Cảnh báo nếu vượt quá giới hạn Zalo API\r\n          if (totalUsers > 10000) {\r\n            console.warn(\r\n              `Warning: Total users (${totalUsers}) exceeds Zalo API limit (10000). ` +\r\n                `Only first 10000 users can be fetched due to max offset = 9951.`\r\n            );\r\n          }\r\n        }\r\n\r\n        // Chỉ lấy user IDs, không lấy detailed info\r\n        const currentUserIds = userListResponse.users.map((u) => u.user_id);\r\n        userIds.push(...currentUserIds);\r\n\r\n        console.log(\r\n          `Fetched ${userIds.length}/${Math.min(totalUsers, 10000)} user IDs`\r\n        );\r\n\r\n        // Điều kiện dừng\r\n        if (\r\n          userListResponse.users.length === 0 ||\r\n          userIds.length >= Math.min(totalUsers, 10000) ||\r\n          userListResponse.users.length < count\r\n        ) {\r\n          break;\r\n        }\r\n\r\n        offset += count;\r\n\r\n        // Protection: Tránh infinite loop\r\n        if (offset > 9951) {\r\n          console.warn(\r\n            \"Reached Zalo maximum offset limit (9951), stopping pagination\"\r\n          );\r\n          break;\r\n        }\r\n      }\r\n\r\n      console.log(`Successfully fetched ${userIds.length} user IDs`);\r\n      return userIds;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      this.handleError(error, \"Failed to get all user IDs\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all users with advanced filtering based on UserInfo fields\r\n   * Lấy tất cả users với bộ lọc nâng cao dựa trên các trường của UserInfo\r\n   *\r\n   * @param accessToken Access token của OA\r\n   * @param filters Bộ lọc nâng cao\r\n   * @returns Promise<UserInfo[]> - Danh sách users đã được lọc\r\n   */\r\n  public async getAllUsersWithAdvancedFilters(\r\n    accessToken: string,\r\n    filters: AdvancedUserFilters = {}\r\n  ): Promise<UserInfo[]> {\r\n    try {\r\n      // Optimization: If only userIds filter is provided, get users directly\r\n      const hasOnlyUserIdsFilter =\r\n        filters.userIds &&\r\n        filters.userIds.length > 0 &&\r\n        Object.keys(filters).length === 1;\r\n\r\n      if (hasOnlyUserIdsFilter) {\r\n        console.log(`Fetching ${filters.userIds!.length} specific users by IDs`);\r\n        const userInfoPromises = filters.userIds!.map((userId) =>\r\n          this.getUserInfo(accessToken, userId)\r\n        );\r\n        return await Promise.all(userInfoPromises);\r\n      }\r\n\r\n      // First get all users using basic Zalo API filters\r\n      const basicFilters = {\r\n        ...(filters.tag_name && { tag_name: filters.tag_name }),\r\n        ...(filters.last_interaction_period && {\r\n          last_interaction_period: filters.last_interaction_period,\r\n        }),\r\n        ...(filters.is_follower !== undefined && {\r\n          is_follower: filters.is_follower,\r\n        }),\r\n      };\r\n\r\n      console.log(\"Fetching users with basic filters:\", basicFilters);\r\n      const allUsers = await this.getAllUsers(accessToken, basicFilters);\r\n\r\n      // Apply advanced client-side filters\r\n      const filteredUsers = allUsers.filter((user) => {\r\n        // Filter by specific user IDs (exact match) - early return for performance\r\n        if (filters.userIds && filters.userIds.length > 0) {\r\n          if (!filters.userIds.includes(user.user_id)) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Filter by display name (contains)\r\n        if (\r\n          filters.display_name_contains &&\r\n          !user.display_name\r\n            ?.toLowerCase()\r\n            .includes(filters.display_name_contains.toLowerCase())\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        // Filter by user alias (contains)\r\n        if (\r\n          filters.user_alias_contains &&\r\n          !user.user_alias\r\n            ?.toLowerCase()\r\n            .includes(filters.user_alias_contains.toLowerCase())\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        // Filter by sensitive status\r\n        if (\r\n          filters.is_sensitive !== undefined &&\r\n          user.is_sensitive !== filters.is_sensitive\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        // Filter by interaction date range\r\n        if (filters.last_interaction_date_range) {\r\n          const userDate = this.parseDate(user.user_last_interaction_date);\r\n          const fromDate = this.parseDate(\r\n            filters.last_interaction_date_range.from\r\n          );\r\n          const toDate = this.parseDate(filters.last_interaction_date_range.to);\r\n\r\n          if (userDate < fromDate || userDate > toDate) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Filter by multiple tags (OR operation)\r\n        if (filters.tag_names && filters.tag_names.length > 0) {\r\n          const userTags = user.tags_and_notes_info.tag_names || [];\r\n          const hasAnyTag = filters.tag_names.some((tag) =>\r\n            userTags.includes(tag)\r\n          );\r\n          if (!hasAnyTag) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Filter by required all tags (AND operation)\r\n        if (filters.require_all_tags && filters.require_all_tags.length > 0) {\r\n          const userTags = user.tags_and_notes_info.tag_names || [];\r\n          const hasAllTags = filters.require_all_tags.every((tag) =>\r\n            userTags.includes(tag)\r\n          );\r\n          if (!hasAllTags) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Exclude users with specific tags\r\n        if (filters.exclude_tags && filters.exclude_tags.length > 0) {\r\n          const userTags = user.tags_and_notes_info.tag_names || [];\r\n          const hasExcludedTag = filters.exclude_tags.some((tag) =>\r\n            userTags.includes(tag)\r\n          );\r\n          if (hasExcludedTag) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Filter by notes content (contains)\r\n        if (filters.notes_contains) {\r\n          const userNotes = user.tags_and_notes_info.notes || [];\r\n          const hasNote = userNotes.some((note) =>\r\n            note.toLowerCase().includes(filters.notes_contains!.toLowerCase())\r\n          );\r\n          if (!hasNote) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Shared info filters\r\n        if (user.shared_info) {\r\n          // Filter by city\r\n          if (\r\n            filters.city &&\r\n            user.shared_info.city?.toLowerCase() !== filters.city.toLowerCase()\r\n          ) {\r\n            return false;\r\n          }\r\n\r\n          // Filter by district\r\n          if (\r\n            filters.district &&\r\n            user.shared_info.district?.toLowerCase() !==\r\n              filters.district.toLowerCase()\r\n          ) {\r\n            return false;\r\n          }\r\n\r\n          // Filter by phone (contains)\r\n          if (\r\n            filters.phone_contains &&\r\n            !user.shared_info.phone?.includes(filters.phone_contains)\r\n          ) {\r\n            return false;\r\n          }\r\n\r\n          // Filter by name (contains)\r\n          if (\r\n            filters.name_contains &&\r\n            !user.shared_info.name\r\n              ?.toLowerCase()\r\n              .includes(filters.name_contains.toLowerCase())\r\n          ) {\r\n            return false;\r\n          }\r\n\r\n          // Filter by address (contains)\r\n          if (\r\n            filters.address_contains &&\r\n            !user.shared_info.address\r\n              ?.toLowerCase()\r\n              .includes(filters.address_contains.toLowerCase())\r\n          ) {\r\n            return false;\r\n          }\r\n\r\n          // Filter by date of birth range\r\n          if (filters.dob_range && user.shared_info.user_dob) {\r\n            const userDob = this.parseDate(user.shared_info.user_dob);\r\n            const fromDob = this.parseDate(filters.dob_range.from);\r\n            const toDob = this.parseDate(filters.dob_range.to);\r\n\r\n            if (userDob < fromDob || userDob > toDob) {\r\n              return false;\r\n            }\r\n          }\r\n\r\n          // Filter by age range (calculated from dob)\r\n          if (filters.age_range && user.shared_info.user_dob) {\r\n            const userAge = this.calculateAge(user.shared_info.user_dob);\r\n            if (\r\n              userAge < filters.age_range.min ||\r\n              userAge > filters.age_range.max\r\n            ) {\r\n              return false;\r\n            }\r\n          }\r\n        } else {\r\n          // If user has no shared_info, filter out based on shared_info requirements\r\n          if (\r\n            filters.city ||\r\n            filters.district ||\r\n            filters.phone_contains ||\r\n            filters.name_contains ||\r\n            filters.address_contains ||\r\n            filters.dob_range ||\r\n            filters.age_range\r\n          ) {\r\n            return false;\r\n          }\r\n        }\r\n\r\n        // Filter by external user ID (contains)\r\n        if (\r\n          filters.external_id_contains &&\r\n          !user.user_external_id?.includes(filters.external_id_contains)\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        // Filter by has shared info\r\n        if (\r\n          filters.has_shared_info !== undefined &&\r\n          !!user.shared_info !== filters.has_shared_info\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        // Filter by has phone\r\n        if (\r\n          filters.has_phone !== undefined &&\r\n          !!user.shared_info?.phone !== filters.has_phone\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        // Filter by has address\r\n        if (\r\n          filters.has_address !== undefined &&\r\n          !!user.shared_info?.address !== filters.has_address\r\n        ) {\r\n          return false;\r\n        }\r\n\r\n        return true;\r\n      });\r\n\r\n      console.log(\r\n        `Applied advanced filters: ${allUsers.length} -> ${filteredUsers.length} users`\r\n      );\r\n      return filteredUsers;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      this.handleError(error, \"Failed to get users with advanced filters\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Parse date from dd/MM/yyyy format to Date object\r\n   * Helper method cho các filter date\r\n   */\r\n  private parseDate(dateString: string): Date {\r\n    if (!dateString) return new Date(0);\r\n    const [day, month, year] = dateString.split(\"/\").map(Number);\r\n    return new Date(year, month - 1, day);\r\n  }\r\n\r\n  /**\r\n   * Calculate age from date of birth\r\n   * Helper method cho age filter\r\n   */\r\n  private calculateAge(dobString: string): number {\r\n    if (!dobString) return 0;\r\n    const dob = this.parseDate(dobString);\r\n    const today = new Date();\r\n    let age = today.getFullYear() - dob.getFullYear();\r\n    const monthDiff = today.getMonth() - dob.getMonth();\r\n\r\n    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {\r\n      age--;\r\n    }\r\n\r\n    return age;\r\n  }\r\n\r\n  /**\r\n   * Cập nhật chi tiết người dùng\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/user/update\r\n   * Method: POST\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request Update user request with user_id and optional fields\r\n   * @returns Promise<boolean> - true if successful\r\n   */\r\n  public async updateUser(\r\n    accessToken: string,\r\n    request: UpdateUserRequest\r\n  ): Promise<boolean> {\r\n    try {\r\n      const result = await this.client.apiPost(\r\n        this.endpoints.user.update,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to update user\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to update user: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Xóa thông tin người dùng\r\n   * Endpoint: https://openapi.zalo.me/v2.0/oa/deletefollowerinfo\r\n   * Method: POST\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param userId User ID to delete info for\r\n   * @returns Promise<boolean> - true if successful\r\n   *\r\n   * Note: Chỉ xóa thông tin lưu trữ ở phía OA, không thay đổi tài khoản Zalo\r\n   */\r\n  public async deleteUserInfo(\r\n    accessToken: string,\r\n    userId: string\r\n  ): Promise<boolean> {\r\n    try {\r\n      const request: DeleteUserInfoRequest = { user_id: userId };\r\n      const result = await this.client.apiPost(\r\n        this.endpoints.user.delete,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to delete user info\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to delete user info: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Truy xuất thông tin tùy biến của người dùng\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/user/detail/custominfo\r\n   * Method: GET with data parameter as JSON string\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request Get custom info request with user_id and optional fields_to_export\r\n   * @returns Promise<UserCustomInfoResponse> - Custom info data\r\n   *\r\n   * Note: Tất cả giá trị trong custom_info đều được trả về dưới dạng string\r\n   */\r\n  public async getUserCustomInfo(\r\n    accessToken: string,\r\n    request: GetUserCustomInfoRequest\r\n  ): Promise<UserCustomInfoResponse> {\r\n    try {\r\n      // Build data object according to API spec\r\n      const dataObject = {\r\n        user_id: request.user_id,\r\n        ...(request.fields_to_export && {\r\n          fields_to_export: request.fields_to_export,\r\n        }),\r\n      };\r\n\r\n      // Send data as JSON string parameter according to API spec\r\n      const params = {\r\n        data: JSON.stringify(dataObject),\r\n      };\r\n\r\n      const result: UserCustomInfoResponse = await this.client.apiGet(\r\n        this.endpoints.user.getCustomInfo,\r\n        accessToken,\r\n        params\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get user custom info\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return result;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get user custom info: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Cập nhật thông tin tùy biến của người dùng\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/user/update/custominfo\r\n   * Method: POST\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request Update custom info request with user_id and custom_info\r\n   * @returns Promise<boolean> - true if successful\r\n   *\r\n   * Note: Cấu trúc custom_info phụ thuộc vào thiết lập OA\r\n   */\r\n  public async updateUserCustomInfo(\r\n    accessToken: string,\r\n    request: UpdateUserCustomInfoRequest\r\n  ): Promise<boolean> {\r\n    try {\r\n      const result = await this.client.apiPost(\r\n        this.endpoints.user.updateCustomInfo,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to update user custom info\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to update user custom info: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Lấy danh sách nhãn\r\n   * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/gettagsofoa\r\n   * Method: GET\r\n   *\r\n   * @param accessToken OA access token\r\n   * @returns Promise<string[]> - Array of tag names\r\n   */\r\n  public async getLabels(accessToken: string): Promise<string[]> {\r\n    try {\r\n      const result: GetTagsResponse = await this.client.apiGet(\r\n        this.endpoints.tag.getList,\r\n        accessToken\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get labels\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return result.data || [];\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get labels: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gắn nhãn người dùng\r\n   * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/tagfollower\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request Tag user request with user_id and tag_name\r\n   * @returns Promise<boolean> - true if successful\r\n   *\r\n   * Note: Nếu tag_name không tồn tại, API sẽ tự động tạo nhãn mới\r\n   */\r\n  public async addTagToUser(\r\n    accessToken: string,\r\n    request: TagUserRequest\r\n  ): Promise<boolean> {\r\n    try {\r\n      const result = await this.client.apiPost(\r\n        this.endpoints.tag.addToUser,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to add tag to user\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to add tag to user: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gỡ nhãn người dùng\r\n   * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/rmfollowerfromtag\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param request Remove tag request with user_id and tag_name\r\n   * @returns Promise<boolean> - true if successful\r\n   */\r\n  public async removeTagFromUser(\r\n    accessToken: string,\r\n    request: RemoveTagFromUserRequest\r\n  ): Promise<boolean> {\r\n    try {\r\n      const result = await this.client.apiPost(\r\n        this.endpoints.tag.removeFromUser,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to remove tag from user\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to remove tag from user: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Xóa nhãn\r\n   * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/rmtag\r\n   * Method: POST\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param tagName Tag name to delete\r\n   * @returns Promise<boolean> - true if successful\r\n   */\r\n  public async deleteLabel(\r\n    accessToken: string,\r\n    tagName: string\r\n  ): Promise<boolean> {\r\n    try {\r\n      const request: DeleteTagRequest = { tag_name: tagName };\r\n\r\n      const result = await this.client.apiPost(\r\n        this.endpoints.tag.delete,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to delete label\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return true;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to delete label: ${error.message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get users by tag\r\n   */\r\n  public async getUsersByTag(\r\n    accessToken: string,\r\n    tagName: string,\r\n    offset: number = 0,\r\n    count: number = 50\r\n  ): Promise<UserListResponse> {\r\n    return this.getUserList(accessToken, {\r\n      offset,\r\n      count,\r\n      tag_name: tagName,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Get followers only\r\n   */\r\n  public async getFollowers(\r\n    accessToken: string,\r\n    offset: number = 0,\r\n    count: number = 50\r\n  ): Promise<UserListResponse> {\r\n    return this.getUserList(accessToken, {\r\n      offset,\r\n      count,\r\n      is_follower: true,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Get users by interaction period\r\n   */\r\n  public async getUsersByInteraction(\r\n    accessToken: string,\r\n    period: \"TODAY\" | \"YESTERDAY\" | \"L7D\" | \"L30D\",\r\n    offset: number = 0,\r\n    count: number = 50\r\n  ): Promise<UserListResponse> {\r\n    return this.getUserList(accessToken, {\r\n      offset,\r\n      count,\r\n      last_interaction_period: period,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Bulk tag operations\r\n   */\r\n  public async bulkAddTag(\r\n    accessToken: string,\r\n    userIds: string[],\r\n    tagName: string\r\n  ): Promise<{ success: string[]; failed: string[] }> {\r\n    const results: { success: string[]; failed: string[] } = {\r\n      success: [],\r\n      failed: [],\r\n    };\r\n\r\n    for (const userId of userIds) {\r\n      try {\r\n        await this.addTagToUser(accessToken, {\r\n          user_id: userId,\r\n          tag_name: tagName,\r\n        });\r\n        results.success.push(userId);\r\n      } catch (error) {\r\n        results.failed.push(userId);\r\n      }\r\n    }\r\n\r\n    return results;\r\n  }\r\n\r\n  /**\r\n   * Bulk remove tag operations\r\n   */\r\n  public async bulkRemoveTag(\r\n    accessToken: string,\r\n    userIds: string[],\r\n    tagName: string\r\n  ): Promise<{ success: string[]; failed: string[] }> {\r\n    const results: { success: string[]; failed: string[] } = {\r\n      success: [],\r\n      failed: [],\r\n    };\r\n\r\n    for (const userId of userIds) {\r\n      try {\r\n        await this.removeTagFromUser(accessToken, {\r\n          user_id: userId,\r\n          tag_name: tagName,\r\n        });\r\n        results.success.push(userId);\r\n      } catch (error) {\r\n        results.failed.push(userId);\r\n      }\r\n    }\r\n\r\n    return results;\r\n  }\r\n\r\n  private handleError(error: unknown, message: string): never {\r\n    const errorMessage =\r\n      error instanceof Error ? error.message : \"Unknown error\";\r\n    throw new ZaloSDKError(`${message}: ${errorMessage}`, -1, error);\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport { ZaloSDKError } from \"../types/common\";\r\nimport {\r\n  ZNSMessage,\r\n  ZNSSendResult,\r\n  ZNSUidMessage,\r\n  ZNSUidSendResult,\r\n  ZNSTemplate,\r\n  ZNSTemplateList,\r\n  ZNSTemplateDetails,\r\n  ZNSCreateTemplateRequest,\r\n  ZNSUpdateTemplateRequest,\r\n  ZNSTemplateCreateEditResponse,\r\n  ZNSUploadImageResult,\r\n  ZNSHashPhoneMessage,\r\n  ZNSDevModeMessage,\r\n  ZNSRsaMessage,\r\n  ZNSJourneyMessage,\r\n  ZNSMessageStatusInfo,\r\n  ZNSBatchMessageStatusRequest,\r\n  ZNSBatchMessageStatusResponse,\r\n  ZNSQuotaInfo,\r\n  ZNSAllowedContentTypes,\r\n  ZNSTemplateSampleData,\r\n  ZNSCustomerRatingResponse,\r\n  ZNSOAQualityInfo,\r\n  // Import các type validation components\r\n  ZNSValidationComponent,\r\n  ZNSTitleComponent,\r\n  ZNSParagraphComponent,\r\n  ZNSOTPComponent,\r\n  ZNSTableComponent,\r\n  ZNSTableRow,\r\n  ZNSLogoComponent,\r\n  ZNSImagesComponent,\r\n  ZNSButtonsComponent,\r\n  ZNSButtonItem,\r\n  ZNSPaymentComponent,\r\n  ZNSVoucherComponent,\r\n  ZNSRatingComponent,\r\n  ZNSRatingItem,\r\n  ZNSAttachment,\r\n} from \"../types/zns\";\r\n\r\n// ===== Custom aggregated result types =====\r\nexport interface ZNSAllTemplateDetailsResult {\r\n  error: number;\r\n  message: string;\r\n  data: Array<ZNSTemplateDetails[\"data\"]>;\r\n  metadata: {\r\n    total: number;\r\n  };\r\n}\r\n\r\nexport interface ZNSOAGroupedTemplateDetails {\r\n  accessToken: string;\r\n  templates: Array<ZNSTemplateDetails[\"data\"]>;\r\n  total: number;\r\n}\r\n\r\nexport interface ZNSMultiOAAllTemplateDetailsResult {\r\n  error: number;\r\n  message: string;\r\n  data: ZNSOAGroupedTemplateDetails[];\r\n}\r\n\r\n/**\r\n * Service for Zalo Notification Service (ZNS)\r\n *\r\n * Requirements for using ZNS API:\r\n * - Official Account must be approved and have ZNS sending permission\r\n * - Valid Official Account access token\r\n * - ZNS template must be approved before use\r\n * - Recipient phone number must be in correct format and valid\r\n * - Template data must match defined parameters\r\n * - Comply with message quantity limits according to service package\r\n *\r\n * Reference: https://developers.zalo.me/docs/zalo-notification-service/\r\n */\r\nexport class ZNSService {\r\n  // Zalo ZNS endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    message: {\r\n      sendTemplate: \"https://business.openapi.zalo.me/message/template\",\r\n      sendHashPhone:\r\n        \"https://business.openapi.zalo.me/message/template/hash_phone\",\r\n      sendDev: \"https://business.openapi.zalo.me/message/template/dev\",\r\n      sendRsa: \"https://business.openapi.zalo.me/message/template/rsa\",\r\n      sendJourney: \"https://business.openapi.zalo.me/message/template/journey\",\r\n      sendByUid: \"https://openapi.zalo.me/v3.0/oa/message/template\",\r\n      status: \"https://business.openapi.zalo.me/message/status\",\r\n      quota: \"https://business.openapi.zalo.me/message/quota\",\r\n      templateTag: \"https://business.openapi.zalo.me/message/template-tag\",\r\n    },\r\n    template: {\r\n      list: \"https://business.openapi.zalo.me/template/all\",\r\n      detail: \"https://business.openapi.zalo.me/template/info/v2\",\r\n      create: \"https://business.openapi.zalo.me/template/create\",\r\n      edit: \"https://business.openapi.zalo.me/template/edit\",\r\n      uploadImage: \"https://business.openapi.zalo.me/upload/image\",\r\n      quality: \"https://business.openapi.zalo.me/template/quality\",\r\n      sampleData: \"https://business.openapi.zalo.me/template/sample-data\",\r\n    },\r\n    rating: {\r\n      get: \"https://business.openapi.zalo.me/rating/get\",\r\n    },\r\n    quality: {\r\n      oa: \"https://business.openapi.zalo.me/quality\",\r\n    },\r\n  } as const;\r\n\r\n  constructor(private readonly client: ZaloClient) { }\r\n\r\n  /**\r\n   * Send ZNS message\r\n   * @param accessToken OA access token\r\n   * @param message ZNS message data\r\n   * @returns Send result\r\n   */\r\n  async sendMessage(\r\n    accessToken: string,\r\n    message: ZNSMessage\r\n  ): Promise<ZNSSendResult> {\r\n    try {\r\n      const response = await this.client.apiPost<ZNSSendResult>(\r\n        this.endpoints.message.sendTemplate,\r\n        accessToken,\r\n        message\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to send ZNS message\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send ZNS message with phone hash\r\n   * @param accessToken OA access token\r\n   * @param message ZNS message with hashed phone\r\n   * @returns Send result\r\n   */\r\n  async sendHashPhoneMessage(\r\n    accessToken: string,\r\n    message: ZNSHashPhoneMessage\r\n  ): Promise<ZNSSendResult> {\r\n    try {\r\n      const response = await this.client.apiPost<ZNSSendResult>(\r\n        this.endpoints.message.sendHashPhone,\r\n        accessToken,\r\n        message\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to send ZNS hash phone message\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send ZNS message in development mode\r\n   * @param accessToken OA access token\r\n   * @param message ZNS dev mode message\r\n   * @returns Send result\r\n   */\r\n  async sendDevModeMessage(\r\n    accessToken: string,\r\n    message: ZNSDevModeMessage\r\n  ): Promise<ZNSSendResult> {\r\n    try {\r\n      const response = await this.client.apiPost<ZNSSendResult>(\r\n        this.endpoints.message.sendDev,\r\n        accessToken,\r\n        message\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to send ZNS dev mode message\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send ZNS message with RSA encryption\r\n   * @param accessToken OA access token\r\n   * @param message ZNS RSA message\r\n   * @returns Send result\r\n   */\r\n  async sendRsaMessage(\r\n    accessToken: string,\r\n    message: ZNSRsaMessage\r\n  ): Promise<ZNSSendResult> {\r\n    try {\r\n      const response = await this.client.apiPost<ZNSSendResult>(\r\n        this.endpoints.message.sendRsa,\r\n        accessToken,\r\n        message\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to send ZNS RSA message\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send ZNS journey message\r\n   * @param accessToken OA access token\r\n   * @param message ZNS journey message\r\n   * @returns Send result\r\n   */\r\n  async sendJourneyMessage(\r\n    accessToken: string,\r\n    message: ZNSJourneyMessage\r\n  ): Promise<ZNSSendResult> {\r\n    try {\r\n      const response = await this.client.apiPost<ZNSSendResult>(\r\n        this.endpoints.message.sendJourney,\r\n        accessToken,\r\n        message\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to send ZNS journey message\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send ZBS/ZNS Template Message by UID\r\n   * Gửi tin nhắn template theo UID người dùng thay vì số điện thoại\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param message ZNS UID message\r\n   * @returns Send result với message_id và user_id\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/message/template\r\n   *\r\n   * Lưu ý:\r\n   * - App phải đã bật quyền gửi tin và thông báo qua OA\r\n   * - Tin Giao dịch: Sử dụng template Tag 1 (TRANSACTION) hoặc Tag 2 (CUSTOMER_CARE)\r\n   * - Tin Hậu mãi: Sử dụng template Tag 3 (PROMOTION)\r\n   * - Có hạn mức nhận tin theo chính sách của Zalo\r\n   *\r\n   * Example:\r\n   * ```typescript\r\n   * const result = await znsService.sendMessageByUid(accessToken, {\r\n   *   user_id: \"3485904339058348590\",\r\n   *   template_id: \"433555\",\r\n   *   template_data: {\r\n   *     customer: \"Nguyễn Thị Hoàng Anh\",\r\n   *     amount: \"100000\",\r\n   *     // ... các params khác theo template\r\n   *   }\r\n   * });\r\n   * ```\r\n   */\r\n  async sendMessageByUid(\r\n    accessToken: string,\r\n    message: ZNSUidMessage\r\n  ): Promise<ZNSUidSendResult> {\r\n    try {\r\n      // Validate input\r\n      if (!message.user_id) {\r\n        throw new ZaloSDKError(\"user_id là bắt buộc\", 400);\r\n      }\r\n      if (!message.template_id) {\r\n        throw new ZaloSDKError(\"template_id là bắt buộc\", 400);\r\n      }\r\n      if (!message.template_data || typeof message.template_data !== 'object') {\r\n        throw new ZaloSDKError(\"template_data phải là object\", 400);\r\n      }\r\n\r\n      const response = await this.client.apiPost<ZNSUidSendResult>(\r\n        this.endpoints.message.sendByUid,\r\n        accessToken,\r\n        message\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to send ZNS message by UID\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS message status\r\n   * @param accessToken OA access token\r\n   * @param messageId Message ID\r\n   * @returns Message status info\r\n   *\r\n   * Response data:\r\n   * - delivery_time: Thời gian thiết bị nhận được thông báo\r\n   * - status: Trạng thái thông báo\r\n   *   * -1: The message does not exist\r\n   *   * 0: The message is pushed successfully to Zalo server but has not yet delivered to user's phone\r\n   *   * 1: The message was delivered to the user's phone\r\n   * - message: Mô tả trạng thái thông báo\r\n   */\r\n  async getMessageStatus(\r\n    accessToken: string,\r\n    messageId: string\r\n  ): Promise<ZNSMessageStatusInfo> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSMessageStatusInfo>(\r\n        this.endpoints.message.status,\r\n        accessToken,\r\n        { message_id: messageId }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get ZNS message status\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS batch message status (custom implementation)\r\n   * @param accessToken OA access token\r\n   * @param messageIds Array of message IDs\r\n   * @returns Batch message status info\r\n   *\r\n   * This is a custom implementation that calls the single message status API\r\n   * multiple times to get status for multiple messages. Since Zalo API doesn't\r\n   * provide a native batch endpoint, this method provides convenience by:\r\n   * - Making concurrent API calls for better performance\r\n   * - Handling errors gracefully (failed requests return error status)\r\n   * - Returning consistent response format\r\n   */\r\n  async getBatchMessageStatus(\r\n    accessToken: string,\r\n    messageIds: string[]\r\n  ): Promise<ZNSBatchMessageStatusResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!Array.isArray(messageIds) || messageIds.length === 0) {\r\n        throw new ZaloSDKError(\"messageIds phải là array và không được rỗng\", 400);\r\n      }\r\n\r\n      if (messageIds.length > 100) {\r\n        throw new ZaloSDKError(\"Không được kiểm tra quá 100 message cùng lúc\", 400);\r\n      }\r\n\r\n      // Remove duplicates\r\n      const uniqueMessageIds = [...new Set(messageIds)];\r\n\r\n      // Make concurrent requests for all message IDs\r\n      const statusPromises = uniqueMessageIds.map(async (messageId) => {\r\n        try {\r\n          const status = await this.getMessageStatus(accessToken, messageId);\r\n          return {\r\n            messageId,\r\n            success: true,\r\n            data: status.data\r\n          };\r\n        } catch (error) {\r\n          return {\r\n            messageId,\r\n            success: false,\r\n            error: error instanceof ZaloSDKError ? error.message : \"Unknown error\"\r\n          };\r\n        }\r\n      });\r\n\r\n      // Wait for all requests to complete\r\n      const results = await Promise.all(statusPromises);\r\n\r\n      // Build response data\r\n      const responseData: { [messageId: string]: any } = {};\r\n      let hasErrors = false;\r\n\r\n      results.forEach(result => {\r\n        if (result.success && result.data) {\r\n          responseData[result.messageId] = result.data;\r\n        } else {\r\n          hasErrors = true;\r\n          responseData[result.messageId] = {\r\n            delivery_time: \"\",\r\n            message: result.error || \"Failed to get status\",\r\n            status: -1\r\n          };\r\n        }\r\n      });\r\n\r\n      return {\r\n        error: hasErrors ? 1 : 0,\r\n        message: hasErrors ? \"Some messages failed to get status\" : \"Success\",\r\n        data: responseData\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get batch ZNS message status\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS quota information\r\n   * @param accessToken OA access token\r\n   * @returns Quota information\r\n   *\r\n   * Response data:\r\n   * - dailyQuota: Số thông báo ZNS OA được gửi trong 1 ngày\r\n   * - remainingQuota: Số thông báo ZNS OA được gửi trong ngày còn lại\r\n   * - dailyQuotaPromotion: Số tin ZNS hậu mãi OA được gửi trong ngày (null từ 1/11)\r\n   * - remainingQuotaPromotion: Số tin ZNS hậu mãi còn lại OA được gửi trong ngày (null từ 1/11)\r\n   * - monthlyPromotionQuota: Số tin ZNS hậu mãi OA được gửi trong tháng\r\n   * - remainingMonthlyPromotionQuota: Số tin ZNS hậu mãi còn lại OA được gửi trong tháng\r\n   * - estimatedNextMonthPromotionQuota: Số tin ZNS hậu mãi dự kiến mà OA có thể gửi trong tháng tiếp theo\r\n   */\r\n  async getQuotaInfo(accessToken: string): Promise<ZNSQuotaInfo> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSQuotaInfo>(\r\n        this.endpoints.message.quota,\r\n        accessToken\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get ZNS quota info\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS allowed content types\r\n   * @param accessToken OA access token\r\n   * @returns Allowed content types\r\n   *\r\n   * Response data:\r\n   * - Mảng các loại nội dung mà OA có thể gửi:\r\n   *   * \"TRANSACTION\": Giao dịch (cấp độ 1)\r\n   *   * \"CUSTOMER_CARE\": Chăm sóc khách hàng (cấp độ 2)\r\n   *   * \"PROMOTION\": Hậu mãi (cấp độ 3)\r\n   * - Dựa theo chất lượng gửi ZNS của OA, Zalo sẽ tự động điều chỉnh loại nội dung OA được gửi\r\n   */\r\n  async getAllowedContentTypes(\r\n    accessToken: string\r\n  ): Promise<ZNSAllowedContentTypes> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSAllowedContentTypes>(\r\n        this.endpoints.message.templateTag,\r\n        accessToken\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(\r\n        error,\r\n        \"Failed to get ZNS allowed content types\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS template list\r\n   * @param accessToken OA access token\r\n   * @param offset Offset for pagination (template được tạo gần nhất có thứ tự 0)\r\n   * @param limit Limit for pagination (tối đa 100)\r\n   * @param status Template status filter (optional)\r\n   * @returns Template list\r\n   *\r\n   * Status values:\r\n   * - 1: ENABLE templates\r\n   * - 2: PENDING_REVIEW templates\r\n   * - 3: REJECT templates\r\n   * - 4: DISABLE templates\r\n   * - undefined: All templates\r\n   */\r\n  async getTemplateList(\r\n    accessToken: string,\r\n    offset: number = 0,\r\n    limit: number = 10,\r\n    status?: 1 | 2 | 3 | 4\r\n  ): Promise<ZNSTemplateList> {\r\n    try {\r\n      // Validate limit (max 100)\r\n      if (limit > 100) {\r\n        throw new ZaloSDKError(\"Limit không được vượt quá 100\", 400);\r\n      }\r\n\r\n      // Prepare query parameters\r\n      const params: any = { offset, limit };\r\n      if (status !== undefined) {\r\n        params.status = status;\r\n      }\r\n\r\n      const response = await this.client.apiGet<ZNSTemplateList>(\r\n        this.endpoints.template.list,\r\n        accessToken,\r\n        params\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get ZNS template list\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS template details\r\n   * @param accessToken OA access token\r\n   * @param templateId Template ID\r\n   * @returns Template details\r\n   *\r\n   * Response data:\r\n   * - templateId: ID của template\r\n   * - templateName: Tên của template\r\n   * - status: Trạng thái template (ENABLE, PENDING_REVIEW, DELETE, REJECT, DISABLE)\r\n   * - reason: Lý do template có trạng thái hiện tại\r\n   * - listParams: Danh sách các thuộc tính của template\r\n   * - listButtons: Danh sách các buttons/CTAs của template\r\n   * - timeout: Thời gian timeout của template\r\n   * - previewUrl: Đường dẫn đến bản xem trước của template\r\n   * - templateQuality: Chất lượng template (null từ 10/12)\r\n   * - templateTag: Loại nội dung (TRANSACTION, CUSTOMER_CARE, PROMOTION)\r\n   * - price: Đơn giá của template\r\n   */\r\n  async getTemplateDetails(\r\n    accessToken: string,\r\n    templateId: string\r\n  ): Promise<ZNSTemplateDetails> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSTemplateDetails>(\r\n        this.endpoints.template.detail,\r\n        accessToken,\r\n        { template_id: templateId }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get ZNS template details\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Custom: Lấy tất cả template kèm thông tin chi tiết cho 1 accessToken\r\n   * - Tự động phân trang để lấy đủ tổng số template\r\n   * - Lấy chi tiết song song theo batch để tối ưu thời gian\r\n   */\r\n  async getAllTemplatesWithDetails(\r\n    accessToken: string,\r\n    status?: 1 | 2 | 3 | 4\r\n  ): Promise<ZNSAllTemplateDetailsResult> {\r\n    try {\r\n      const limitPerPage = 100;\r\n      let offset = 0;\r\n      const templateIds: string[] = [];\r\n      let total = 0;\r\n\r\n      // Paginate through template list\r\n      // eslint-disable-next-line no-constant-condition\r\n      while (true) {\r\n        const listResp = await this.getTemplateList(accessToken, offset, limitPerPage, status);\r\n        const items = listResp.data || [];\r\n        if (listResp.metadata && typeof listResp.metadata.total === \"number\") {\r\n          total = listResp.metadata.total;\r\n        }\r\n\r\n        for (const item of items) {\r\n          if (!templateIds.includes(item.templateId)) {\r\n            templateIds.push(item.templateId);\r\n          }\r\n        }\r\n\r\n        if (items.length < limitPerPage || templateIds.length >= total) {\r\n          break;\r\n        }\r\n        offset += limitPerPage;\r\n      }\r\n\r\n      if (templateIds.length === 0) {\r\n        return { error: 0, message: \"Success\", data: [], metadata: { total } };\r\n      }\r\n\r\n      // Fetch details in batches to avoid burst\r\n      const batchSize = 10;\r\n      const detailResults: Array<ZNSTemplateDetails[\"data\"]> = [];\r\n      let hasErrors = false;\r\n\r\n      for (let i = 0; i < templateIds.length; i += batchSize) {\r\n        const batchIds = templateIds.slice(i, i + batchSize);\r\n        const settled = await Promise.allSettled(\r\n          batchIds.map((id) => this.getTemplateDetails(accessToken, id))\r\n        );\r\n\r\n        for (const res of settled) {\r\n          if (res.status === \"fulfilled\" && res.value && res.value.data) {\r\n            detailResults.push(res.value.data);\r\n          } else {\r\n            hasErrors = true;\r\n          }\r\n        }\r\n      }\r\n\r\n      return {\r\n        error: hasErrors ? 1 : 0,\r\n        message: hasErrors ? \"Some template details failed to fetch\" : \"Success\",\r\n        data: detailResults,\r\n        metadata: { total },\r\n      };\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get all templates with details\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Custom: Lấy tất cả template chi tiết cho nhiều accessToken (nhiều OA)\r\n   * Đầu ra nhóm theo từng accessToken\r\n   */\r\n  async getAllTemplatesWithDetailsByTokens(\r\n    accessTokens: string[],\r\n    status?: 1 | 2 | 3 | 4\r\n  ): Promise<ZNSMultiOAAllTemplateDetailsResult> {\r\n    try {\r\n      const uniqueTokens = Array.from(new Set(accessTokens.filter((t) => t && t.trim().length > 0)));\r\n      if (uniqueTokens.length === 0) {\r\n        return { error: 0, message: \"Success\", data: [] };\r\n      }\r\n\r\n      const settled = await Promise.allSettled(\r\n        uniqueTokens.map((token) => this.getAllTemplatesWithDetails(token, status))\r\n      );\r\n\r\n      const groups: ZNSOAGroupedTemplateDetails[] = [];\r\n      let hasErrors = false;\r\n\r\n      settled.forEach((res, index) => {\r\n        const token = uniqueTokens[index];\r\n        if (res.status === \"fulfilled\") {\r\n          const payload = res.value;\r\n          groups.push({\r\n            accessToken: token,\r\n            templates: payload.data,\r\n            total: payload.metadata.total,\r\n          });\r\n          if (payload.error !== 0) {\r\n            hasErrors = true;\r\n          }\r\n        } else {\r\n          hasErrors = true;\r\n          groups.push({ accessToken: token, templates: [], total: 0 });\r\n        }\r\n      });\r\n\r\n      return {\r\n        error: hasErrors ? 1 : 0,\r\n        message: hasErrors ? \"Some OA groups failed to fetch\" : \"Success\",\r\n        data: groups,\r\n      };\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get templates with details by tokens\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ZNS template sample data\r\n   * @param accessToken OA access token\r\n   * @param templateId Template ID\r\n   * @returns Template sample data\r\n   *\r\n   * Response data:\r\n   * - Chứa tham số và dữ liệu mẫu của template\r\n   * - Ví dụ: { \"balance_debt\": 2000, \"due_date\": \"01/01/1970\", \"customer_name\": \"customer_name_sample\" }\r\n   */\r\n  async getTemplateSampleData(\r\n    accessToken: string,\r\n    templateId: string\r\n  ): Promise<ZNSTemplateSampleData> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSTemplateSampleData>(\r\n        this.endpoints.template.sampleData,\r\n        accessToken,\r\n        { template_id: templateId }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(\r\n        error,\r\n        \"Failed to get ZNS template sample data\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get customer rating information\r\n   * @param accessToken OA access token\r\n   * @param templateId Template ID\r\n   * @param fromTime Start time (timestamp in milliseconds)\r\n   * @param toTime End time (timestamp in milliseconds)\r\n   * @param offset Position of first rating to return\r\n   * @param limit Maximum number of ratings to return\r\n   * @returns Customer rating information\r\n   *\r\n   * Lưu ý:\r\n   * - Chỉ có thể lấy thông tin đánh giá từ template đánh giá dịch vụ được tạo bởi ứng dụng\r\n   * - Access token phải ứng với template ID được tạo bởi app và OA\r\n   * - Thời gian theo định dạng timestamp (millisecond)\r\n   */\r\n  async getCustomerRating(\r\n    accessToken: string,\r\n    templateId: string,\r\n    fromTime: number,\r\n    toTime: number,\r\n    offset: number,\r\n    limit: number\r\n  ): Promise<ZNSCustomerRatingResponse> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSCustomerRatingResponse>(\r\n        this.endpoints.rating.get,\r\n        accessToken,\r\n        {\r\n          template_id: templateId,\r\n          from_time: fromTime,\r\n          to_time: toTime,\r\n          offset,\r\n          limit,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get customer rating\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get OA ZNS sending quality information\r\n   * @param accessToken OA access token\r\n   * @returns OA quality information\r\n   *\r\n   * Response data:\r\n   * - oaCurrentQuality: Chất lượng gửi ZNS trong 48 giờ gần nhất\r\n   * - oa7dayQuality: Chất lượng gửi ZNS trong 7 ngày gần nhất\r\n   *\r\n   * Quality levels:\r\n   * - HIGH: Mức độ chất lượng tốt\r\n   * - MEDIUM: Mức độ chất lượng trung bình\r\n   * - LOW: Mức độ chất lượng kém\r\n   * - UNDEFINED: Chưa được xác định (OA không gửi ZNS trong khung thời gian đánh giá)\r\n   */\r\n  async getOAQuality(accessToken: string): Promise<ZNSOAQualityInfo> {\r\n    try {\r\n      const response = await this.client.apiGet<ZNSOAQualityInfo>(\r\n        this.endpoints.quality.oa,\r\n        accessToken\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to get OA quality information\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Create ZNS template\r\n   * @param accessToken OA access token\r\n   * @param templateData Template creation data theo chuẩn Zalo API\r\n   * @returns Created template response\r\n   *\r\n   * API: POST https://business.openapi.zalo.me/template/create\r\n   */\r\n  async createTemplate(\r\n    accessToken: string,\r\n    templateData: ZNSCreateTemplateRequest\r\n  ): Promise<ZNSTemplateCreateEditResponse> {\r\n    try {\r\n      // Comprehensive validation\r\n      this.validateZNSTemplateRequest(templateData);\r\n\r\n      const response = await this.client.apiPost<ZNSTemplateCreateEditResponse>(\r\n        this.endpoints.template.create,\r\n        accessToken,\r\n        templateData\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to create ZNS template\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Edit ZNS template (chỉnh sửa template có trạng thái REJECT)\r\n   * @param accessToken OA access token\r\n   * @param templateData Template edit data theo chuẩn Zalo API\r\n   * @returns Edited template response\r\n   *\r\n   * Lưu ý:\r\n   * - Chỉ có thể chỉnh sửa template có trạng thái REJECT\r\n   * - Template sau khi chỉnh sửa sẽ chuyển về trạng thái PENDING_REVIEW\r\n   * - Daily quota: 100 requests/ngày\r\n   * - Cần quyền \"Quản lý tài sản\"\r\n   *\r\n   * API: POST https://business.openapi.zalo.me/template/edit\r\n   */\r\n  async updateTemplate(\r\n    accessToken: string,\r\n    templateData: ZNSUpdateTemplateRequest\r\n  ): Promise<ZNSTemplateCreateEditResponse> {\r\n    try {\r\n      // Validate template_id first\r\n      if (!templateData.template_id) {\r\n        throw new ZaloSDKError(\"template_id là bắt buộc\", 400);\r\n      }\r\n\r\n      // Comprehensive validation (same as createTemplate)\r\n      this.validateZNSTemplateRequest(templateData);\r\n\r\n      const response = await this.client.apiPost<ZNSTemplateCreateEditResponse>(\r\n        this.endpoints.template.edit,\r\n        accessToken,\r\n        templateData\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to edit ZNS template\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Upload image for ZNS template\r\n   * @param accessToken OA access token\r\n   * @param imageFile Image file (Buffer or ReadableStream)\r\n   * @param filename Filename with extension\r\n   * @returns Upload result with media_id\r\n   *\r\n   * Lưu ý:\r\n   * - Định dạng hỗ trợ: JPG, PNG\r\n   * - Dung lượng tối đa: 500KB\r\n   * - Hạn mức: 5000 ảnh/tháng/app\r\n   * - Logo: PNG, 400x96px\r\n   * - Hình ảnh: JPG/PNG, tỉ lệ 16:9\r\n   * - Cần quyền \"Quản lý tài sản\"\r\n   */\r\n  async uploadImage(\r\n    accessToken: string,\r\n    imageFile: Buffer | NodeJS.ReadableStream,\r\n    filename: string = \"image.jpg\"\r\n  ): Promise<ZNSUploadImageResult> {\r\n    try {\r\n      // Validate file extension\r\n      const allowedExtensions = [\".jpg\", \".jpeg\", \".png\"];\r\n      const fileExtension = filename\r\n        .toLowerCase()\r\n        .substring(filename.lastIndexOf(\".\"));\r\n\r\n      if (!allowedExtensions.includes(fileExtension)) {\r\n        throw new ZaloSDKError(\"File phải có định dạng JPG hoặc PNG\", 400);\r\n      }\r\n\r\n      // Validate file size if it's a Buffer (max 500KB)\r\n      if (Buffer.isBuffer(imageFile)) {\r\n        const maxSize = 500 * 1024; // 500KB\r\n        if (imageFile.length > maxSize) {\r\n          throw new ZaloSDKError(\"Dung lượng file không được vượt quá 500KB\", 400);\r\n        }\r\n      }\r\n\r\n      const response = await this.client.apiUploadFile<ZNSUploadImageResult>(\r\n        this.endpoints.template.uploadImage,\r\n        accessToken,\r\n        imageFile,\r\n        filename\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleZNSError(error, \"Failed to upload ZNS image\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Comprehensive validation for ZNS template request\r\n   */\r\n  public validateZNSTemplateRequest(templateData: ZNSCreateTemplateRequest): void {\r\n    // 1. Basic required fields validation\r\n    this.validateBasicFields(templateData);\r\n\r\n    // 2. Template type and tag compatibility\r\n    this.validateTemplateTypeTagCompatibility(templateData.template_type, templateData.tag);\r\n\r\n    // 3. Layout structure validation\r\n    this.validateLayoutStructure(templateData.template_type, templateData.layout);\r\n\r\n    // 4. Params validation if provided\r\n    if (templateData.params && templateData.params.length > 0) {\r\n      this.validateParams(templateData.params, templateData.layout);\r\n    }\r\n\r\n    // 5. Note validation if provided\r\n    if (templateData.note) {\r\n      if (templateData.note.length < 1 || templateData.note.length > 400) {\r\n        throw new ZaloSDKError(\"note phải có độ dài từ 1 đến 400 ký tự\", 400);\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate basic required fields\r\n   */\r\n  private validateBasicFields(templateData: ZNSCreateTemplateRequest): void {\r\n    if (!templateData.template_name) {\r\n      throw new ZaloSDKError(\"template_name là bắt buộc\", 400);\r\n    }\r\n    if (templateData.template_name.length < 10 || templateData.template_name.length > 60) {\r\n      throw new ZaloSDKError(\"template_name phải có độ dài từ 10 đến 60 ký tự\", 400);\r\n    }\r\n\r\n    if (!templateData.template_type) {\r\n      throw new ZaloSDKError(\"template_type là bắt buộc\", 400);\r\n    }\r\n    if (![1, 2, 3, 4, 5].includes(templateData.template_type)) {\r\n      throw new ZaloSDKError(\"template_type phải là 1, 2, 3, 4 hoặc 5\", 400);\r\n    }\r\n\r\n    if (!templateData.tag) {\r\n      throw new ZaloSDKError(\"tag là bắt buộc\", 400);\r\n    }\r\n    if (![\"1\", \"2\", \"3\"].includes(templateData.tag)) {\r\n      throw new ZaloSDKError(\"tag phải là '1', '2' hoặc '3'\", 400);\r\n    }\r\n\r\n    if (!templateData.layout) {\r\n      throw new ZaloSDKError(\"layout là bắt buộc\", 400);\r\n    }\r\n    if (!Array.isArray(templateData.layout) && typeof templateData.layout !== 'object') {\r\n      throw new ZaloSDKError(\"layout phải là array hoặc object\", 400);\r\n    }\r\n\r\n    if (!templateData.tracking_id) {\r\n      throw new ZaloSDKError(\"tracking_id là bắt buộc\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate template_type and tag compatibility\r\n   */\r\n  private validateTemplateTypeTagCompatibility(templateType: number, tag: string): void {\r\n    const compatibility: Record<number, string[]> = {\r\n      1: [\"1\", \"2\", \"3\"], // ZNS tùy chỉnh\r\n      2: [\"1\"],           // ZNS xác thực\r\n      3: [\"1\"],           // ZNS yêu cầu thanh toán\r\n      4: [\"1\", \"2\", \"3\"], // ZNS voucher\r\n      5: [\"2\"]            // ZNS đánh giá dịch vụ\r\n    };\r\n\r\n    if (!compatibility[templateType]?.includes(tag)) {\r\n      throw new ZaloSDKError(`template_type ${templateType} không tương thích với tag ${tag}`, 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate layout structure based on template type\r\n   */\r\n  private validateLayoutStructure(templateType: number, layout: ZNSValidationComponent[] | Record<string, any>): void {\r\n    // Handle both array format (Zalo API) and object format (our types)\r\n    let headerComponents: ZNSValidationComponent[] = [];\r\n    let bodyComponents: ZNSValidationComponent[] = [];\r\n    let footerComponents: ZNSValidationComponent[] = [];\r\n    let allComponents: ZNSValidationComponent[] = [];\r\n\r\n    if (Array.isArray(layout)) {\r\n      // Array format - direct from Zalo API docs\r\n      headerComponents = layout.filter(item => this.isHeaderComponent(item));\r\n      bodyComponents = layout.filter(item => this.isBodyComponent(item));\r\n      footerComponents = layout.filter(item => this.isFooterComponent(item));\r\n      allComponents = layout;\r\n    } else if (layout && typeof layout === 'object') {\r\n      // Object format - our structured type\r\n      if (layout.header?.components) {\r\n        headerComponents = layout.header.components;\r\n      }\r\n      if (layout.body?.components) {\r\n        bodyComponents = layout.body.components;\r\n      }\r\n      if (layout.footer?.components) {\r\n        footerComponents = layout.footer.components;\r\n      }\r\n      allComponents = [...headerComponents, ...bodyComponents, ...footerComponents];\r\n    } else {\r\n      throw new ZaloSDKError(\"layout phải là array hoặc object có cấu trúc header/body/footer\", 400);\r\n    }\r\n\r\n    switch (templateType) {\r\n      case 1: // ZNS tùy chỉnh\r\n        this.validateCustomTemplateLayout(headerComponents, bodyComponents, footerComponents);\r\n        break;\r\n      case 2: // ZNS xác thực\r\n        this.validateAuthTemplateLayout(headerComponents, bodyComponents, footerComponents);\r\n        break;\r\n      case 3: // ZNS yêu cầu thanh toán\r\n        this.validatePaymentTemplateLayout(headerComponents, bodyComponents, footerComponents);\r\n        break;\r\n      case 4: // ZNS voucher\r\n        this.validateVoucherTemplateLayout(headerComponents, bodyComponents, footerComponents);\r\n        break;\r\n      case 5: // ZNS đánh giá dịch vụ\r\n        this.validateRatingTemplateLayout(headerComponents, bodyComponents, footerComponents);\r\n        break;\r\n    }\r\n\r\n    // Validate individual components\r\n    allComponents.forEach(component => this.validateComponent(component));\r\n  }\r\n\r\n  /**\r\n   * Check if component belongs to header\r\n   */\r\n  private isHeaderComponent(component: ZNSValidationComponent): boolean {\r\n    return 'LOGO' in component || 'IMAGES' in component;\r\n  }\r\n\r\n  /**\r\n   * Check if component belongs to body\r\n   */\r\n  private isBodyComponent(component: ZNSValidationComponent): boolean {\r\n    return 'TITLE' in component || 'PARAGRAPH' in component || 'TABLE' in component ||\r\n      'OTP' in component || 'PAYMENT' in component || 'VOUCHER' in component || 'RATING' in component;\r\n  }\r\n\r\n  /**\r\n   * Check if component belongs to footer\r\n   */\r\n  private isFooterComponent(component: ZNSValidationComponent): boolean {\r\n    return 'BUTTONS' in component;\r\n  }\r\n\r\n  /**\r\n   * Validate custom template layout (type 1)\r\n   */\r\n  private validateCustomTemplateLayout(header: ZNSValidationComponent[], body: ZNSValidationComponent[], footer: ZNSValidationComponent[]): void {\r\n    // Header: Logo OR Image (exactly 1)\r\n    if (header.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS tùy chỉnh phải có đúng 1 component trong header (Logo hoặc Image)\", 400);\r\n    }\r\n\r\n    // Body: Title (1) + Paragraph (0-4) + Table (0-1)\r\n    const titles = body.filter(c => 'TITLE' in c);\r\n    const paragraphs = body.filter(c => 'PARAGRAPH' in c);\r\n    const tables = body.filter(c => 'TABLE' in c);\r\n\r\n    if (titles.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS tùy chỉnh phải có đúng 1 component TITLE\", 400);\r\n    }\r\n    if (paragraphs.length > 4) {\r\n      throw new ZaloSDKError(\"ZNS tùy chỉnh không được có quá 4 component PARAGRAPH\", 400);\r\n    }\r\n    if (tables.length > 1) {\r\n      throw new ZaloSDKError(\"ZNS tùy chỉnh không được có quá 1 component TABLE\", 400);\r\n    }\r\n\r\n    // Footer: Buttons (0-2)\r\n    if (footer.length > 2) {\r\n      throw new ZaloSDKError(\"ZNS tùy chỉnh không được có quá 2 button\", 400);\r\n    }\r\n\r\n    // If has image, must have at least 1 button\r\n    const hasImage = header.some(c => 'IMAGES' in c);\r\n    if (hasImage && footer.length === 0) {\r\n      throw new ZaloSDKError(\"ZNS có component Image phải có ít nhất 1 button\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate auth template layout (type 2)\r\n   */\r\n  private validateAuthTemplateLayout(header: ZNSValidationComponent[], body: ZNSValidationComponent[], footer: ZNSValidationComponent[]): void {\r\n    // Header: Logo only (1)\r\n    if (header.length !== 1 || !('LOGO' in header[0])) {\r\n      throw new ZaloSDKError(\"ZNS xác thực phải có đúng 1 component LOGO trong header\", 400);\r\n    }\r\n\r\n    // Body: OTP (1) + Paragraph (1)\r\n    const otps = body.filter(c => 'OTP' in c);\r\n    const paragraphs = body.filter(c => 'PARAGRAPH' in c);\r\n\r\n    if (otps.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS xác thực phải có đúng 1 component OTP\", 400);\r\n    }\r\n    if (paragraphs.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS xác thực phải có đúng 1 component PARAGRAPH\", 400);\r\n    }\r\n\r\n    // Footer: None\r\n    if (footer.length > 0) {\r\n      throw new ZaloSDKError(\"ZNS xác thực không được có component trong footer\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate payment template layout (type 3)\r\n   */\r\n  private validatePaymentTemplateLayout(header: ZNSValidationComponent[], body: ZNSValidationComponent[], footer: ZNSValidationComponent[]): void {\r\n    // Header: Logo only (1)\r\n    if (header.length !== 1 || !('LOGO' in header[0])) {\r\n      throw new ZaloSDKError(\"ZNS yêu cầu thanh toán phải có đúng 1 component LOGO trong header\", 400);\r\n    }\r\n\r\n    // Body: Title (1) + Paragraph (0-4) + Table (0-1) + Payment (1)\r\n    const titles = body.filter(c => 'TITLE' in c);\r\n    const paragraphs = body.filter(c => 'PARAGRAPH' in c);\r\n    const tables = body.filter(c => 'TABLE' in c);\r\n    const payments = body.filter(c => 'PAYMENT' in c);\r\n\r\n    if (titles.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS yêu cầu thanh toán phải có đúng 1 component TITLE\", 400);\r\n    }\r\n    if (paragraphs.length > 4) {\r\n      throw new ZaloSDKError(\"ZNS yêu cầu thanh toán không được có quá 4 component PARAGRAPH\", 400);\r\n    }\r\n    if (tables.length > 1) {\r\n      throw new ZaloSDKError(\"ZNS yêu cầu thanh toán không được có quá 1 component TABLE\", 400);\r\n    }\r\n    if (payments.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS yêu cầu thanh toán phải có đúng 1 component PAYMENT\", 400);\r\n    }\r\n\r\n    // Footer: Buttons (0-2)\r\n    if (footer.length > 2) {\r\n      throw new ZaloSDKError(\"ZNS yêu cầu thanh toán không được có quá 2 button\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate voucher template layout (type 4)\r\n   */\r\n  private validateVoucherTemplateLayout(header: ZNSValidationComponent[], body: ZNSValidationComponent[], footer: ZNSValidationComponent[]): void {\r\n    // Header: Logo OR Image (exactly 1)\r\n    if (header.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS voucher phải có đúng 1 component trong header (Logo hoặc Image)\", 400);\r\n    }\r\n\r\n    // Body: Title (1) + Paragraph (0-4) + Table (0-1) + Voucher (1)\r\n    const titles = body.filter(c => 'TITLE' in c);\r\n    const paragraphs = body.filter(c => 'PARAGRAPH' in c);\r\n    const tables = body.filter(c => 'TABLE' in c);\r\n    const vouchers = body.filter(c => 'VOUCHER' in c);\r\n\r\n    if (titles.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS voucher phải có đúng 1 component TITLE\", 400);\r\n    }\r\n    if (paragraphs.length > 4) {\r\n      throw new ZaloSDKError(\"ZNS voucher không được có quá 4 component PARAGRAPH\", 400);\r\n    }\r\n    if (tables.length > 1) {\r\n      throw new ZaloSDKError(\"ZNS voucher không được có quá 1 component TABLE\", 400);\r\n    }\r\n    if (vouchers.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS voucher phải có đúng 1 component VOUCHER\", 400);\r\n    }\r\n\r\n    // Footer: Buttons (0-2)\r\n    if (footer.length > 2) {\r\n      throw new ZaloSDKError(\"ZNS voucher không được có quá 2 button\", 400);\r\n    }\r\n\r\n    // If has image, must have at least 1 button, max 2\r\n    const hasImage = header.some(c => 'IMAGES' in c);\r\n    if (hasImage && footer.length === 0) {\r\n      throw new ZaloSDKError(\"ZNS voucher có component Image phải có ít nhất 1 button\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate rating template layout (type 5)\r\n   */\r\n  private validateRatingTemplateLayout(header: ZNSValidationComponent[], body: ZNSValidationComponent[], footer: ZNSValidationComponent[]): void {\r\n    // Header: Logo only (1)\r\n    if (header.length !== 1 || !('LOGO' in header[0])) {\r\n      throw new ZaloSDKError(\"ZNS đánh giá dịch vụ phải có đúng 1 component LOGO trong header\", 400);\r\n    }\r\n\r\n    // Body: Title (1) + Paragraph (0-1) + Rating (1)\r\n    const titles = body.filter(c => 'TITLE' in c);\r\n    const paragraphs = body.filter(c => 'PARAGRAPH' in c);\r\n    const ratings = body.filter(c => 'RATING' in c);\r\n\r\n    if (titles.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS đánh giá dịch vụ phải có đúng 1 component TITLE\", 400);\r\n    }\r\n    if (paragraphs.length > 1) {\r\n      throw new ZaloSDKError(\"ZNS đánh giá dịch vụ không được có quá 1 component PARAGRAPH\", 400);\r\n    }\r\n    if (ratings.length !== 1) {\r\n      throw new ZaloSDKError(\"ZNS đánh giá dịch vụ phải có đúng 1 component RATING\", 400);\r\n    }\r\n\r\n    // Footer: Buttons (0-2)\r\n    if (footer.length > 2) {\r\n      throw new ZaloSDKError(\"ZNS đánh giá dịch vụ không được có quá 2 button\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate individual component content\r\n   */\r\n  private validateComponent(component: ZNSValidationComponent): void {\r\n    if ('TITLE' in component) {\r\n      this.validateTitleComponent(component.TITLE);\r\n    }\r\n    if ('PARAGRAPH' in component) {\r\n      this.validateParagraphComponent(component.PARAGRAPH);\r\n    }\r\n    if ('OTP' in component) {\r\n      this.validateOTPComponent(component.OTP);\r\n    }\r\n    if ('TABLE' in component) {\r\n      this.validateTableComponent(component.TABLE);\r\n    }\r\n    if ('LOGO' in component) {\r\n      this.validateLogoComponent(component.LOGO);\r\n    }\r\n    if ('IMAGES' in component) {\r\n      this.validateImagesComponent(component.IMAGES);\r\n    }\r\n    if ('BUTTONS' in component) {\r\n      this.validateButtonsComponent(component.BUTTONS);\r\n    }\r\n    if ('PAYMENT' in component) {\r\n      this.validatePaymentComponent(component.PAYMENT);\r\n    }\r\n    if ('VOUCHER' in component) {\r\n      this.validateVoucherComponent(component.VOUCHER);\r\n    }\r\n    if ('RATING' in component) {\r\n      this.validateRatingComponent(component.RATING);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate TITLE component\r\n   */\r\n  private validateTitleComponent(title: ZNSTitleComponent): void {\r\n    if (!title.value || typeof title.value !== 'string') {\r\n      throw new ZaloSDKError(\"TITLE component phải có field value kiểu string\", 400);\r\n    }\r\n    if (title.value.length < 9 || title.value.length > 65) {\r\n      throw new ZaloSDKError(\"TITLE value phải có độ dài từ 9 đến 65 ký tự\", 400);\r\n    }\r\n    // Check max 4 params\r\n    const paramCount = (title.value.match(/\\{\\{[^}]+\\}\\}/g) || []).length;\r\n    if (paramCount > 4) {\r\n      throw new ZaloSDKError(\"TITLE component không được có quá 4 params\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate PARAGRAPH component\r\n   */\r\n  private validateParagraphComponent(paragraph: ZNSParagraphComponent): void {\r\n    if (!paragraph.value || typeof paragraph.value !== 'string') {\r\n      throw new ZaloSDKError(\"PARAGRAPH component phải có field value kiểu string\", 400);\r\n    }\r\n    if (paragraph.value.length < 9 || paragraph.value.length > 400) {\r\n      throw new ZaloSDKError(\"PARAGRAPH value phải có độ dài từ 9 đến 400 ký tự\", 400);\r\n    }\r\n    // Check max 10 params\r\n    const paramCount = (paragraph.value.match(/\\{\\{[^}]+\\}\\}/g) || []).length;\r\n    if (paramCount > 10) {\r\n      throw new ZaloSDKError(\"PARAGRAPH component không được có quá 10 params\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate OTP component\r\n   */\r\n  private validateOTPComponent(otp: ZNSOTPComponent): void {\r\n    if (!otp.value || typeof otp.value !== 'string') {\r\n      throw new ZaloSDKError(\"OTP component phải có field value kiểu string\", 400);\r\n    }\r\n    if (otp.value.length < 1 || otp.value.length > 10) {\r\n      throw new ZaloSDKError(\"OTP value phải có độ dài từ 1 đến 10 ký tự\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate TABLE component\r\n   */\r\n  private validateTableComponent(table: ZNSTableComponent): void {\r\n    if (!table.rows || !Array.isArray(table.rows)) {\r\n      throw new ZaloSDKError(\"TABLE component phải có field rows kiểu array\", 400);\r\n    }\r\n    if (table.rows.length < 2 || table.rows.length > 8) {\r\n      throw new ZaloSDKError(\"TABLE rows phải có từ 2 đến 8 objects\", 400);\r\n    }\r\n\r\n    table.rows.forEach((row: ZNSTableRow, index: number) => {\r\n      if (!row.title || typeof row.title !== 'string') {\r\n        throw new ZaloSDKError(`TABLE row ${index + 1} phải có field title kiểu string`, 400);\r\n      }\r\n      if (row.title.length < 3 || row.title.length > 36) {\r\n        throw new ZaloSDKError(`TABLE row ${index + 1} title phải có độ dài từ 3 đến 36 ký tự`, 400);\r\n      }\r\n      if (!row.value || typeof row.value !== 'string') {\r\n        throw new ZaloSDKError(`TABLE row ${index + 1} phải có field value kiểu string`, 400);\r\n      }\r\n      if (row.value.length < 3 || row.value.length > 90) {\r\n        throw new ZaloSDKError(`TABLE row ${index + 1} value phải có độ dài từ 3 đến 90 ký tự`, 400);\r\n      }\r\n      if (row.row_type !== undefined && ![0, 1, 2, 3, 4, 5].includes(row.row_type)) {\r\n        throw new ZaloSDKError(`TABLE row ${index + 1} row_type phải là 0, 1, 2, 3, 4 hoặc 5`, 400);\r\n      }\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Validate LOGO component\r\n   */\r\n  private validateLogoComponent(logo: ZNSLogoComponent): void {\r\n    if (!logo.light || !logo.dark) {\r\n      throw new ZaloSDKError(\"LOGO component phải có cả light và dark attachment\", 400);\r\n    }\r\n    this.validateAttachment(logo.light, \"LOGO light\");\r\n    this.validateAttachment(logo.dark, \"LOGO dark\");\r\n  }\r\n\r\n  /**\r\n   * Validate IMAGES component\r\n   */\r\n  private validateImagesComponent(images: ZNSImagesComponent): void {\r\n    if (!images.items || !Array.isArray(images.items)) {\r\n      throw new ZaloSDKError(\"IMAGES component phải có field items kiểu array\", 400);\r\n    }\r\n    if (images.items.length < 1 || images.items.length > 3) {\r\n      throw new ZaloSDKError(\"IMAGES items phải có từ 1 đến 3 attachments\", 400);\r\n    }\r\n    images.items.forEach((item: ZNSAttachment, index: number) => {\r\n      this.validateAttachment(item, `IMAGES item ${index + 1}`);\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Validate BUTTONS component\r\n   */\r\n  private validateButtonsComponent(buttons: ZNSButtonsComponent): void {\r\n    if (!buttons.items || !Array.isArray(buttons.items)) {\r\n      throw new ZaloSDKError(\"BUTTONS component phải có field items kiểu array\", 400);\r\n    }\r\n    if (buttons.items.length < 1 || buttons.items.length > 2) {\r\n      throw new ZaloSDKError(\"BUTTONS items phải có từ 1 đến 2 objects\", 400);\r\n    }\r\n\r\n    buttons.items.forEach((button: ZNSButtonItem, index: number) => {\r\n      if (!button.type || ![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].includes(button.type)) {\r\n        throw new ZaloSDKError(`BUTTON ${index + 1} type phải là 1-12`, 400);\r\n      }\r\n      if (!button.title || typeof button.title !== 'string') {\r\n        throw new ZaloSDKError(`BUTTON ${index + 1} phải có field title kiểu string`, 400);\r\n      }\r\n      if (button.title.length < 5 || button.title.length > 30) {\r\n        throw new ZaloSDKError(`BUTTON ${index + 1} title phải có độ dài từ 5 đến 30 ký tự`, 400);\r\n      }\r\n      if (!button.content || typeof button.content !== 'string') {\r\n        throw new ZaloSDKError(`BUTTON ${index + 1} phải có field content kiểu string`, 400);\r\n      }\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Validate PAYMENT component\r\n   */\r\n  private validatePaymentComponent(payment: ZNSPaymentComponent): void {\r\n    if (!payment.bank_code || typeof payment.bank_code !== 'string') {\r\n      throw new ZaloSDKError(\"PAYMENT component phải có field bank_code kiểu string\", 400);\r\n    }\r\n    if (!payment.account_name || typeof payment.account_name !== 'string') {\r\n      throw new ZaloSDKError(\"PAYMENT component phải có field account_name kiểu string\", 400);\r\n    }\r\n    if (payment.account_name.length < 1 || payment.account_name.length > 100) {\r\n      throw new ZaloSDKError(\"PAYMENT account_name phải có độ dài từ 1 đến 100 ký tự\", 400);\r\n    }\r\n    if (!payment.bank_account || typeof payment.bank_account !== 'string') {\r\n      throw new ZaloSDKError(\"PAYMENT component phải có field bank_account kiểu string\", 400);\r\n    }\r\n    if (payment.bank_account.length < 1 || payment.bank_account.length > 100) {\r\n      throw new ZaloSDKError(\"PAYMENT bank_account phải có độ dài từ 1 đến 100 ký tự\", 400);\r\n    }\r\n    if (!payment.amount) {\r\n      throw new ZaloSDKError(\"PAYMENT component phải có field amount\", 400);\r\n    }\r\n    if (payment.note && (payment.note.length < 1 || payment.note.length > 90)) {\r\n      throw new ZaloSDKError(\"PAYMENT note phải có độ dài từ 1 đến 90 ký tự\", 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate VOUCHER component\r\n   */\r\n  private validateVoucherComponent(voucher: ZNSVoucherComponent): void {\r\n    if (!voucher.name || typeof voucher.name !== 'string') {\r\n      throw new ZaloSDKError(\"VOUCHER component phải có field name kiểu string\", 400);\r\n    }\r\n    if (voucher.name.length < 1 || voucher.name.length > 30) {\r\n      throw new ZaloSDKError(\"VOUCHER name phải có độ dài từ 1 đến 30 ký tự\", 400);\r\n    }\r\n    if (!voucher.condition || typeof voucher.condition !== 'string') {\r\n      throw new ZaloSDKError(\"VOUCHER component phải có field condition kiểu string\", 400);\r\n    }\r\n    if (voucher.condition.length < 1 || voucher.condition.length > 40) {\r\n      throw new ZaloSDKError(\"VOUCHER condition phải có độ dài từ 1 đến 40 ký tự\", 400);\r\n    }\r\n    if (!voucher.end_date || typeof voucher.end_date !== 'string') {\r\n      throw new ZaloSDKError(\"VOUCHER component phải có field end_date kiểu string\", 400);\r\n    }\r\n    if (!voucher.voucher_code || typeof voucher.voucher_code !== 'string') {\r\n      throw new ZaloSDKError(\"VOUCHER component phải có field voucher_code kiểu string\", 400);\r\n    }\r\n    if (voucher.voucher_code.length < 1 || voucher.voucher_code.length > 25) {\r\n      throw new ZaloSDKError(\"VOUCHER voucher_code phải có độ dài từ 1 đến 25 ký tự\", 400);\r\n    }\r\n\r\n  }\r\n\r\n  /**\r\n   * Validate RATING component\r\n   */\r\n  private validateRatingComponent(rating: ZNSRatingComponent): void {\r\n    if (!rating.items || !Array.isArray(rating.items)) {\r\n      throw new ZaloSDKError(\"RATING component phải có field items kiểu array\", 400);\r\n    }\r\n    if (rating.items.length !== 5) {\r\n      throw new ZaloSDKError(\"RATING items phải có đúng 5 objects\", 400);\r\n    }\r\n\r\n    rating.items.forEach((item: ZNSRatingItem, index: number) => {\r\n      const starValue = (index + 1) as 1 | 2 | 3 | 4 | 5;\r\n      if (item.star !== starValue) {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} phải có star = ${starValue}`, 400);\r\n      }\r\n      if (!item.title || typeof item.title !== 'string') {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} phải có field title kiểu string`, 400);\r\n      }\r\n      if (item.title.length < 1 || item.title.length > 50) {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} title phải có độ dài từ 1 đến 50 ký tự`, 400);\r\n      }\r\n      if (item.question && (item.question.length < 1 || item.question.length > 100)) {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} question phải có độ dài từ 1 đến 100 ký tự`, 400);\r\n      }\r\n      if (item.answers && Array.isArray(item.answers)) {\r\n        if (item.answers.length < 1 || item.answers.length > 5) {\r\n          throw new ZaloSDKError(`RATING item ${index + 1} answers phải có từ 1 đến 5 câu trả lời`, 400);\r\n        }\r\n        item.answers.forEach((answer: string, answerIndex: number) => {\r\n          if (!answer || answer.length < 1 || answer.length > 50) {\r\n            throw new ZaloSDKError(`RATING item ${index + 1} answer ${answerIndex + 1} phải có độ dài từ 1 đến 50 ký tự`, 400);\r\n          }\r\n        });\r\n      }\r\n      if (!item.thanks || typeof item.thanks !== 'string') {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} phải có field thanks kiểu string`, 400);\r\n      }\r\n      if (item.thanks.length < 1 || item.thanks.length > 100) {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} thanks phải có độ dài từ 1 đến 100 ký tự`, 400);\r\n      }\r\n      if (!item.description || typeof item.description !== 'string') {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} phải có field description kiểu string`, 400);\r\n      }\r\n      if (item.description.length < 1 || item.description.length > 200) {\r\n        throw new ZaloSDKError(`RATING item ${index + 1} description phải có độ dài từ 1 đến 200 ký tự`, 400);\r\n      }\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Validate attachment object\r\n   */\r\n  private validateAttachment(attachment: ZNSAttachment, context: string): void {\r\n    if (!attachment.type || !attachment.media_id) {\r\n      throw new ZaloSDKError(`${context} phải có type và media_id`, 400);\r\n    }\r\n    if (attachment.type !== \"IMAGE\") {\r\n      throw new ZaloSDKError(`${context} type phải là \"IMAGE\"`, 400);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate params array\r\n   */\r\n  private validateParams(params: Array<{\r\n    name: string;\r\n    type: string;\r\n    sample_value: string;\r\n  }>, layout: ZNSValidationComponent[] | Record<string, any>): void {\r\n    const paramTypeConstraints: Record<string, number> = {\r\n      \"1\": 30,  // Tên khách hàng\r\n      \"2\": 15,  // Số điện thoại\r\n      \"3\": 200, // Địa chỉ\r\n      \"4\": 30,  // Mã số\r\n      \"5\": 30,  // Nhãn tùy chỉnh\r\n      \"6\": 30,  // Trạng thái giao dịch\r\n      \"7\": 50,  // Thông tin liên hệ\r\n      \"8\": 5,   // Giới tính / Danh xưng\r\n      \"9\": 200, // Tên sản phẩm / Thương hiệu\r\n      \"10\": 20, // Số lượng / Số tiền\r\n      \"11\": 20, // Thời gian\r\n      \"12\": 10, // OTP\r\n      \"13\": 200, // URL\r\n      \"14\": 12, // Tiền tệ (VNĐ)\r\n      \"15\": 90  // Bank transfer note\r\n    };\r\n\r\n    // Get all params used in layout\r\n    const layoutString = JSON.stringify(layout);\r\n    const usedParams = new Set<string>();\r\n    const paramMatches = layoutString.match(/\\{\\{([^}]+)\\}\\}/g);\r\n    if (paramMatches) {\r\n      paramMatches.forEach(match => {\r\n        const paramName = match.replace(/[{}]/g, '');\r\n        usedParams.add(paramName);\r\n      });\r\n    }\r\n\r\n    // Validate each param\r\n    params.forEach((param: {\r\n      name: string;\r\n      type: string;\r\n      sample_value: string;\r\n    }, index: number) => {\r\n      if (!param.name || typeof param.name !== 'string') {\r\n        throw new ZaloSDKError(`Param ${index + 1} phải có field name kiểu string`, 400);\r\n      }\r\n      if (!param.type || typeof param.type !== 'string') {\r\n        throw new ZaloSDKError(`Param ${index + 1} phải có field type kiểu string`, 400);\r\n      }\r\n      if (!Object.keys(paramTypeConstraints).includes(param.type)) {\r\n        throw new ZaloSDKError(`Param ${index + 1} type phải là 1-15`, 400);\r\n      }\r\n      if (!param.sample_value || typeof param.sample_value !== 'string') {\r\n        throw new ZaloSDKError(`Param ${index + 1} phải có field sample_value kiểu string`, 400);\r\n      }\r\n\r\n      // Validate sample_value length based on type\r\n      const maxLength = paramTypeConstraints[param.type];\r\n      if (param.sample_value.length > maxLength) {\r\n        throw new ZaloSDKError(`Param ${index + 1} sample_value không được vượt quá ${maxLength} ký tự cho type ${param.type}`, 400);\r\n      }\r\n\r\n      // Remove param from used set if found\r\n      usedParams.delete(param.name);\r\n    });\r\n\r\n    // Check if all used params are defined\r\n    if (usedParams.size > 0) {\r\n      throw new ZaloSDKError(`Các param sau được sử dụng trong layout nhưng chưa được định nghĩa: ${Array.from(usedParams).join(', ')}`, 400);\r\n    }\r\n  }\r\n\r\n  private handleZNSError(error: any, defaultMessage: string): ZaloSDKError {\r\n    if (error.response?.data) {\r\n      const errorData = error.response.data;\r\n      return new ZaloSDKError(\r\n        `${defaultMessage}: ${errorData.message || errorData.error || \"Unknown error\"\r\n        }`,\r\n        errorData.error || -1,\r\n        errorData\r\n      );\r\n    }\r\n    return new ZaloSDKError(`${defaultMessage}: ${error.message || \"Unknown error\"}`, -1, error);\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  GroupTextMessage,\r\n  GroupImageMessage,\r\n  GroupFileMessage,\r\n  GroupStickerMessage,\r\n  GroupMentionMessage,\r\n  GroupMessageResult,\r\n  GroupInfo,\r\n  GroupMember,\r\n} from \"../types/group\";\r\nimport { ZaloSDKError } from \"../types/common\";\r\n\r\n/**\r\n * Interface cho từng tin nhắn trong danh sách tin nhắn group\r\n */\r\nexport interface GroupMessageItem {\r\n  /** Loại tin nhắn */\r\n  type: \"text\" | \"image\" | \"file\" | \"sticker\" | \"mention\";\r\n  /** Nội dung tin nhắn (cho text/mention message) */\r\n  text?: string;\r\n  /** URL hình ảnh (cho image message) */\r\n  imageUrl?: string;\r\n  /** Attachment ID (cho image message) */\r\n  attachmentId?: string;\r\n  /** Caption cho hình ảnh (tối đa 2000 ký tự) */\r\n  caption?: string;\r\n  /** Token file (cho file message) */\r\n  fileToken?: string;\r\n  /** Tên file (cho file message) */\r\n  fileName?: string;\r\n  /** Sticker ID (cho sticker message) */\r\n  stickerId?: string;\r\n  /** Danh sách mentions (cho mention message) */\r\n  mentions?: Array<{\r\n    user_id: string;\r\n    offset: number;\r\n    length: number;\r\n  }>;\r\n  /** Delay sau khi gửi tin nhắn này (milliseconds) */\r\n  delay?: number;\r\n}\r\n\r\n/**\r\n * Interface cho request gửi danh sách tin nhắn tới 1 group\r\n */\r\nexport interface SendMessageListToGroupRequest {\r\n  /** Access token của Official Account */\r\n  accessToken: string;\r\n  /** Group ID */\r\n  groupId: string;\r\n  /** Danh sách tin nhắn */\r\n  messages: GroupMessageItem[];\r\n  /** Delay mặc định giữa các tin nhắn (milliseconds) */\r\n  defaultDelay?: number;\r\n  /** Callback function để tracking tiến trình */\r\n  onProgress?: (progress: GroupMessageProgressInfo) => void;\r\n}\r\n\r\n/**\r\n * Interface cho thông tin tiến trình từng tin nhắn\r\n */\r\nexport interface GroupMessageProgressInfo {\r\n  /** Group ID */\r\n  groupId: string;\r\n  /** Index của tin nhắn trong danh sách (bắt đầu từ 0) */\r\n  messageIndex: number;\r\n  /** Tổng số tin nhắn */\r\n  totalMessages: number;\r\n  /** Loại tin nhắn */\r\n  messageType: string;\r\n  /** Trạng thái: 'started' | 'completed' | 'failed' */\r\n  status: 'started' | 'completed' | 'failed';\r\n  /** Kết quả gửi tin nhắn (nếu đã hoàn thành) */\r\n  result?: GroupMessageResult;\r\n  /** Thông tin lỗi (nếu thất bại) */\r\n  error?: string;\r\n  /** Thời gian bắt đầu */\r\n  startTime: number;\r\n  /** Thời gian kết thúc (nếu đã hoàn thành) */\r\n  endTime?: number;\r\n}\r\n\r\n/**\r\n * Interface cho response gửi danh sách tin nhắn tới 1 group\r\n */\r\nexport interface SendMessageListToGroupResponse {\r\n  /** Group ID */\r\n  groupId: string;\r\n  /** Tổng số tin nhắn */\r\n  totalMessages: number;\r\n  /** Số tin nhắn gửi thành công */\r\n  successfulMessages: number;\r\n  /** Số tin nhắn gửi thất bại */\r\n  failedMessages: number;\r\n  /** Chi tiết kết quả từng tin nhắn */\r\n  messageResults: Array<{\r\n    /** Index của tin nhắn trong danh sách */\r\n    messageIndex: number;\r\n    /** Loại tin nhắn */\r\n    messageType: string;\r\n    /** Trạng thái gửi */\r\n    success: boolean;\r\n    /** Kết quả chi tiết */\r\n    result?: GroupMessageResult;\r\n    /** Thông tin lỗi nếu thất bại */\r\n    error?: string;\r\n    /** Thời gian bắt đầu gửi */\r\n    startTime: number;\r\n    /** Thời gian kết thúc */\r\n    endTime: number;\r\n    /** Thời gian thực hiện (milliseconds) */\r\n    duration: number;\r\n  }>;\r\n  /** Tổng thời gian thực hiện (milliseconds) */\r\n  totalDuration: number;\r\n}\r\n\r\n/**\r\n * Interface cho request gửi danh sách tin nhắn tới nhiều groups\r\n */\r\nexport interface SendMessageListToMultipleGroupsRequest {\r\n  /** Access token của Official Account */\r\n  accessToken: string;\r\n  /** Danh sách group IDs */\r\n  groupIds: string[];\r\n  /** Danh sách tin nhắn */\r\n  messages: GroupMessageItem[];\r\n  /** Delay mặc định giữa các tin nhắn (milliseconds) */\r\n  defaultDelay?: number;\r\n  /** Delay giữa các groups (milliseconds) để tránh rate limit */\r\n  delayBetweenGroups?: number;\r\n  /** Callback function để tracking tiến trình */\r\n  onProgress?: (progress: MultipleGroupsProgressInfo) => void;\r\n}\r\n\r\n/**\r\n * Interface cho thông tin tiến trình từng group\r\n */\r\nexport interface MultipleGroupsProgressInfo {\r\n  /** Group ID hiện tại */\r\n  groupId: string;\r\n  /** Index của group trong danh sách (bắt đầu từ 0) */\r\n  groupIndex: number;\r\n  /** Tổng số groups */\r\n  totalGroups: number;\r\n  /** Trạng thái: 'started' | 'completed' | 'failed' */\r\n  status: 'started' | 'completed' | 'failed';\r\n  /** Kết quả gửi tin nhắn cho group này (nếu đã hoàn thành) */\r\n  result?: SendMessageListToGroupResponse;\r\n  /** Thông tin lỗi (nếu thất bại) */\r\n  error?: string;\r\n  /** Thời gian bắt đầu */\r\n  startTime: number;\r\n  /** Thời gian kết thúc (nếu đã hoàn thành) */\r\n  endTime?: number;\r\n}\r\n\r\n/**\r\n * Interface cho response gửi danh sách tin nhắn tới nhiều groups\r\n */\r\nexport interface SendMessageListToMultipleGroupsResponse {\r\n  /** Tổng số groups */\r\n  totalGroups: number;\r\n  /** Số groups gửi thành công */\r\n  successfulGroups: number;\r\n  /** Số groups gửi thất bại */\r\n  failedGroups: number;\r\n  /** Chi tiết kết quả từng group */\r\n  groupResults: Array<{\r\n    /** Group ID */\r\n    groupId: string;\r\n    /** Index của group trong danh sách */\r\n    groupIndex: number;\r\n    /** Trạng thái gửi cho group này */\r\n    success: boolean;\r\n    /** Kết quả chi tiết gửi tin nhắn */\r\n    messageListResult?: SendMessageListToGroupResponse;\r\n    /** Thông tin lỗi nếu thất bại */\r\n    error?: string;\r\n    /** Thời gian bắt đầu gửi */\r\n    startTime: number;\r\n    /** Thời gian kết thúc */\r\n    endTime: number;\r\n    /** Thời gian thực hiện (milliseconds) */\r\n    duration: number;\r\n  }>;\r\n  /** Tổng thời gian thực hiện (milliseconds) */\r\n  totalDuration: number;\r\n  /** Thống kê tin nhắn tổng cộng */\r\n  messageStats: {\r\n    /** Tổng số tin nhắn đã gửi thành công */\r\n    totalSuccessfulMessages: number;\r\n    /** Tổng số tin nhắn thất bại */\r\n    totalFailedMessages: number;\r\n    /** Tổng số tin nhắn */\r\n    totalMessages: number;\r\n  };\r\n}\r\n\r\n/**\r\n * Interface cho tin nhắn cá nhân hóa cho từng group\r\n */\r\nexport interface PersonalizedGroupMessage {\r\n  /** Group ID */\r\n  groupId: string;\r\n  /** Danh sách tin nhắn riêng cho group này */\r\n  messages: GroupMessageItem[];\r\n}\r\n\r\n/**\r\n * Interface cho request gửi tin nhắn cá nhân hóa tới nhiều groups\r\n */\r\nexport interface SendPersonalizedMessageToMultipleGroupsRequest {\r\n  /** Access token của Official Account */\r\n  accessToken: string;\r\n  /** Danh sách tin nhắn cá nhân hóa cho từng group */\r\n  personalizedMessages: PersonalizedGroupMessage[];\r\n  /** Delay mặc định giữa các tin nhắn (milliseconds) */\r\n  defaultDelay?: number;\r\n  /** Delay giữa các groups (milliseconds) để tránh rate limit */\r\n  delayBetweenGroups?: number;\r\n  /** Callback function để tracking tiến trình */\r\n  onProgress?: (progress: PersonalizedGroupsProgressInfo) => void;\r\n}\r\n\r\n/**\r\n * Interface cho thông tin tiến trình gửi tin nhắn cá nhân hóa\r\n */\r\nexport interface PersonalizedGroupsProgressInfo {\r\n  /** Group ID hiện tại */\r\n  groupId: string;\r\n  /** Index của group trong danh sách (bắt đầu từ 0) */\r\n  groupIndex: number;\r\n  /** Tổng số groups */\r\n  totalGroups: number;\r\n  /** Trạng thái: 'started' | 'completed' | 'failed' */\r\n  status: 'started' | 'completed' | 'failed';\r\n  /** Kết quả gửi tin nhắn cho group này (nếu đã hoàn thành) */\r\n  result?: SendMessageListToGroupResponse;\r\n  /** Thông tin lỗi (nếu thất bại) */\r\n  error?: string;\r\n  /** Thời gian bắt đầu */\r\n  startTime: number;\r\n  /** Thời gian kết thúc (nếu đã hoàn thành) */\r\n  endTime?: number;\r\n}\r\n\r\n/**\r\n * Interface cho response gửi tin nhắn cá nhân hóa tới nhiều groups\r\n */\r\nexport interface SendPersonalizedMessageToMultipleGroupsResponse {\r\n  /** Tổng số groups */\r\n  totalGroups: number;\r\n  /** Số groups gửi thành công */\r\n  successfulGroups: number;\r\n  /** Số groups gửi thất bại */\r\n  failedGroups: number;\r\n  /** Chi tiết kết quả từng group */\r\n  groupResults: Array<{\r\n    /** Group ID */\r\n    groupId: string;\r\n    /** Index của group trong danh sách */\r\n    groupIndex: number;\r\n    /** Trạng thái gửi cho group này */\r\n    success: boolean;\r\n    /** Kết quả chi tiết gửi tin nhắn */\r\n    messageListResult?: SendMessageListToGroupResponse;\r\n    /** Thông tin lỗi nếu thất bại */\r\n    error?: string;\r\n    /** Thời gian bắt đầu gửi */\r\n    startTime: number;\r\n    /** Thời gian kết thúc */\r\n    endTime: number;\r\n    /** Thời gian thực hiện (milliseconds) */\r\n    duration: number;\r\n  }>;\r\n  /** Tổng thời gian thực hiện (milliseconds) */\r\n  totalDuration: number;\r\n  /** Thống kê tin nhắn tổng cộng */\r\n  messageStats: {\r\n    /** Tổng số tin nhắn đã gửi thành công */\r\n    totalSuccessfulMessages: number;\r\n    /** Tổng số tin nhắn thất bại */\r\n    totalFailedMessages: number;\r\n    /** Tổng số tin nhắn */\r\n    totalMessages: number;\r\n  };\r\n}\r\n\r\n/**\r\n * Service for handling Zalo Official Account Group Message Framework (GMF) APIs\r\n *\r\n * CONDITIONS FOR USING ZALO GMF:\r\n *\r\n * 1. OPT-IN CONDITIONS FOR SENDING GROUP MESSAGES:\r\n *    - OA must have the group_id of the chat group\r\n *    - OA must be added to the chat group by group admin\r\n *    - OA must have permission to send messages in the group (granted by group admin)\r\n *    - Chat group must be active (not locked or disbanded)\r\n *\r\n * 2. ACCESS PERMISSIONS:\r\n *    - Application needs to be granted group chat management permissions\r\n *    - Access token must have \"manage_group\" scope or equivalent\r\n *    - OA must be authenticated and have active status\r\n *\r\n * 3. LIMITS AND CONSTRAINTS:\r\n *    - Can only send messages to groups that OA has joined\r\n *    - Cannot send messages to private groups that OA hasn't been invited to\r\n *    - Must comply with Zalo's message sending frequency limits\r\n *    - Message content must comply with Zalo's content policy\r\n *\r\n * 4. SUPPORTED MESSAGE TYPES:\r\n *    - Text message: Plain text messages\r\n *    - Image message: Image messages (JPG, PNG, GIF - max 5MB)\r\n *    - File message: File attachments (max 25MB)\r\n *    - Sticker message: Stickers from Zalo collection\r\n *    - Mention message: Tag/mention specific members\r\n *\r\n * 5. TECHNICAL REQUIREMENTS:\r\n *    - Use HTTPS for all API calls\r\n *    - Content-Type: application/json for text/mention messages\r\n *    - Content-Type: multipart/form-data for file/image uploads\r\n */\r\nexport class GroupMessageService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    // Group message endpoints\r\n    message: {\r\n      group: \"https://openapi.zalo.me/v3.0/oa/group/message\",\r\n    },\r\n    // Group management endpoints\r\n    group: {\r\n      getInfo: \"https://openapi.zalo.me/v3.0/oa/group/getinfo\",\r\n      getMembers: \"https://openapi.zalo.me/v3.0/oa/group/getmembers\",\r\n    },\r\n  };\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Send text message to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param message Text message content\r\n   * @returns Send result\r\n   */\r\n  async sendTextMessage(\r\n    accessToken: string,\r\n    groupId: string,\r\n    message: GroupTextMessage\r\n  ): Promise<GroupMessageResult> {\r\n    try {\r\n      const response = await this.client.apiPost<GroupMessageResult>(\r\n        this.endpoints.message.group,\r\n        accessToken,\r\n        {\r\n          recipient: {\r\n            group_id: groupId,\r\n          },\r\n          message: {\r\n            text: message.text,\r\n          },\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(\r\n        error,\r\n        \"Failed to send text message to group\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send image message to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param message Image message content\r\n   * @returns Send result\r\n   */\r\n  async sendImageMessage(\r\n    accessToken: string,\r\n    groupId: string,\r\n    message: GroupImageMessage\r\n  ): Promise<GroupMessageResult> {\r\n    try {\r\n      // Validate that either imageUrl or attachmentId is provided\r\n      if (!message.imageUrl && !message.attachmentId) {\r\n        throw new Error(\"Either imageUrl or attachmentId must be provided\");\r\n      }\r\n\r\n\r\n      // Validate caption length\r\n      if (message.caption && message.caption.length > 2000) {\r\n        throw new Error(\"Caption cannot exceed 2000 characters\");\r\n      }\r\n\r\n      // Prepare attachment payload\r\n      const attachmentPayload = {\r\n        type: \"template\",\r\n        payload: {\r\n          template_type: \"media\",\r\n          elements: [\r\n            {\r\n              media_type: \"image\",\r\n              ...(message.attachmentId\r\n                ? { attachment_id: message.attachmentId }\r\n                : { url: message.imageUrl }),\r\n            },\r\n          ],\r\n        },\r\n      };\r\n\r\n      // Prepare message payload with correct structure\r\n      const messagePayload: {\r\n        attachment: typeof attachmentPayload;\r\n        text?: string;\r\n      } = {\r\n        attachment: attachmentPayload,\r\n      };\r\n\r\n      // Add text caption if provided - must be at same level as attachment\r\n      if (message.caption) {\r\n        messagePayload.text = message.caption;\r\n      }\r\n\r\n      const response = await this.client.apiPost<GroupMessageResult>(\r\n        this.endpoints.message.group,\r\n        accessToken,\r\n        {\r\n          recipient: {\r\n            group_id: groupId,\r\n          },\r\n          message: messagePayload,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(\r\n        error,\r\n        \"Failed to send image message to group\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send file message to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param message File message content\r\n   * @returns Send result\r\n   */\r\n  async sendFileMessage(\r\n    accessToken: string,\r\n    groupId: string,\r\n    message: GroupFileMessage\r\n  ): Promise<GroupMessageResult> {\r\n    try {\r\n      const response = await this.client.apiPost<GroupMessageResult>(\r\n        this.endpoints.message.group,\r\n        accessToken,\r\n        {\r\n          recipient: {\r\n            group_id: groupId,\r\n          },\r\n          message: {\r\n            attachment: {\r\n              type: \"file\",\r\n              payload: {\r\n                token: message.fileToken,\r\n              },\r\n            },\r\n          },\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(\r\n        error,\r\n        \"Failed to send file message to group\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send sticker message to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param message Sticker message content\r\n   * @returns Send result\r\n   */\r\n  async sendStickerMessage(\r\n    accessToken: string,\r\n    groupId: string,\r\n    message: GroupStickerMessage\r\n  ): Promise<GroupMessageResult> {\r\n    try {\r\n      const response = await this.client.apiPost<GroupMessageResult>(\r\n        this.endpoints.message.group,\r\n        accessToken,\r\n        {\r\n          recipient: {\r\n            group_id: groupId,\r\n          },\r\n          message: {\r\n            attachment: {\r\n              type: \"template\",\r\n              payload: {\r\n                template_type: \"media\",\r\n                elements: [\r\n                  {\r\n                    media_type: \"sticker\",\r\n                    attachment_id: message.stickerId,\r\n                  },\r\n                ],\r\n              },\r\n            },\r\n          },\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(\r\n        error,\r\n        \"Failed to send sticker message to group\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Send mention message to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param message Mention message content\r\n   * @returns Send result\r\n   */\r\n  async sendMentionMessage(\r\n    accessToken: string,\r\n    groupId: string,\r\n    message: GroupMentionMessage\r\n  ): Promise<GroupMessageResult> {\r\n    try {\r\n      const response = await this.client.apiPost<GroupMessageResult>(\r\n        this.endpoints.message.group,\r\n        accessToken,\r\n        {\r\n          recipient: {\r\n            group_id: groupId,\r\n          },\r\n          message: {\r\n            text: message.text,\r\n            mention: message.mentions,\r\n          },\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(\r\n        error,\r\n        \"Failed to send mention message to group\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get group information\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @returns Group information\r\n   */\r\n  async getGroupInfo(accessToken: string, groupId: string): Promise<GroupInfo> {\r\n    try {\r\n      const response = await this.client.apiGet<GroupInfo>(\r\n        this.endpoints.group.getInfo,\r\n        accessToken,\r\n        {\r\n          group_id: groupId,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(\r\n        error,\r\n        \"Failed to get group information\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get group members\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param offset Offset for pagination\r\n   * @param count Number of members to retrieve\r\n   * @returns Group members list\r\n   */\r\n  async getGroupMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    offset: number = 0,\r\n    count: number = 50\r\n  ): Promise<{ members: GroupMember[]; total: number }> {\r\n    try {\r\n      const response = await this.client.apiGet<{\r\n        members: GroupMember[];\r\n        total: number;\r\n      }>(this.endpoints.group.getMembers, accessToken, {\r\n        group_id: groupId,\r\n        offset,\r\n        count,\r\n      });\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupMessageError(error, \"Failed to get group members\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi danh sách tin nhắn tới 1 group với delay tùy chỉnh\r\n   * Hỗ trợ tất cả các loại tin nhắn: text, image, file, sticker, mention\r\n   *\r\n   * @param request Thông tin request gửi danh sách tin nhắn\r\n   * @returns Kết quả gửi từng tin nhắn\r\n   */\r\n  public async sendMessageListToGroup(\r\n    request: SendMessageListToGroupRequest\r\n  ): Promise<SendMessageListToGroupResponse> {\r\n    const startTime = Date.now();\r\n    const messageResults: SendMessageListToGroupResponse[\"messageResults\"] = [];\r\n    let successfulMessages = 0;\r\n    let failedMessages = 0;\r\n\r\n    try {\r\n      // Validate input\r\n      if (!request.accessToken || request.accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.groupId || request.groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.messages || request.messages.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách tin nhắn không được để trống\", -1);\r\n      }\r\n\r\n      // Gửi từng tin nhắn theo thứ tự\r\n      for (let i = 0; i < request.messages.length; i++) {\r\n        const message = request.messages[i];\r\n        const messageStartTime = Date.now();\r\n\r\n        // Callback: Bắt đầu gửi tin nhắn\r\n        if (request.onProgress) {\r\n          request.onProgress({\r\n            groupId: request.groupId,\r\n            messageIndex: i,\r\n            totalMessages: request.messages.length,\r\n            messageType: message.type,\r\n            status: 'started',\r\n            startTime: messageStartTime,\r\n          });\r\n        }\r\n\r\n        const messageResult = {\r\n          messageIndex: i,\r\n          messageType: message.type,\r\n          success: false,\r\n          startTime: messageStartTime,\r\n          endTime: 0,\r\n          duration: 0,\r\n        } as SendMessageListToGroupResponse[\"messageResults\"][0];\r\n\r\n        try {\r\n          let result: GroupMessageResult;\r\n\r\n          // Gửi tin nhắn theo loại\r\n          switch (message.type) {\r\n            case \"text\":\r\n              if (!message.text) {\r\n                throw new Error(\"Text không được để trống cho text message\");\r\n              }\r\n              result = await this.sendTextMessage(\r\n                request.accessToken,\r\n                request.groupId,\r\n                { type: \"text\", text: message.text }\r\n              );\r\n              break;\r\n\r\n            case \"image\":\r\n              if (!message.imageUrl && !message.attachmentId) {\r\n                throw new Error(\"imageUrl hoặc attachmentId là bắt buộc cho image message\");\r\n              }\r\n              result = await this.sendImageMessage(\r\n                request.accessToken,\r\n                request.groupId,\r\n                {\r\n                  type: \"image\",\r\n                  imageUrl: message.imageUrl,\r\n                  attachmentId: message.attachmentId,\r\n                  caption: message.caption,\r\n                }\r\n              );\r\n              break;\r\n\r\n            case \"file\":\r\n              if (!message.fileToken) {\r\n                throw new Error(\"fileToken là bắt buộc cho file message\");\r\n              }\r\n              result = await this.sendFileMessage(\r\n                request.accessToken,\r\n                request.groupId,\r\n                {\r\n                  type: \"file\",\r\n                  fileToken: message.fileToken,\r\n                  fileName: message.fileName,\r\n                }\r\n              );\r\n              break;\r\n\r\n            case \"sticker\":\r\n              if (!message.stickerId) {\r\n                throw new Error(\"stickerId là bắt buộc cho sticker message\");\r\n              }\r\n              result = await this.sendStickerMessage(\r\n                request.accessToken,\r\n                request.groupId,\r\n                {\r\n                  type: \"sticker\",\r\n                  stickerId: message.stickerId,\r\n                }\r\n              );\r\n              break;\r\n\r\n            case \"mention\":\r\n              if (!message.text || !message.mentions) {\r\n                throw new Error(\"text và mentions là bắt buộc cho mention message\");\r\n              }\r\n              result = await this.sendMentionMessage(\r\n                request.accessToken,\r\n                request.groupId,\r\n                {\r\n                  type: \"mention\",\r\n                  text: message.text,\r\n                  mentions: message.mentions,\r\n                }\r\n              );\r\n              break;\r\n\r\n            default:\r\n              throw new Error(`Loại tin nhắn không được hỗ trợ: ${message.type}`);\r\n          }\r\n\r\n          // Ghi nhận thành công\r\n          const messageEndTime = Date.now();\r\n          messageResult.success = true;\r\n          messageResult.result = result;\r\n          messageResult.endTime = messageEndTime;\r\n          messageResult.duration = messageEndTime - messageStartTime;\r\n\r\n          successfulMessages++;\r\n\r\n          // Callback: Hoàn thành thành công\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              groupId: request.groupId,\r\n              messageIndex: i,\r\n              totalMessages: request.messages.length,\r\n              messageType: message.type,\r\n              status: 'completed',\r\n              result: result,\r\n              startTime: messageStartTime,\r\n              endTime: messageEndTime,\r\n            });\r\n          }\r\n\r\n        } catch (error) {\r\n          // Ghi nhận thất bại\r\n          const messageEndTime = Date.now();\r\n          const errorMessage = error instanceof Error ? error.message : String(error);\r\n\r\n          messageResult.success = false;\r\n          messageResult.error = errorMessage;\r\n          messageResult.endTime = messageEndTime;\r\n          messageResult.duration = messageEndTime - messageStartTime;\r\n\r\n          failedMessages++;\r\n\r\n          // Callback: Thất bại\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              groupId: request.groupId,\r\n              messageIndex: i,\r\n              totalMessages: request.messages.length,\r\n              messageType: message.type,\r\n              status: 'failed',\r\n              error: errorMessage,\r\n              startTime: messageStartTime,\r\n              endTime: messageEndTime,\r\n            });\r\n          }\r\n        }\r\n\r\n        messageResults.push(messageResult);\r\n\r\n        // Delay trước khi gửi tin nhắn tiếp theo (trừ tin nhắn cuối cùng)\r\n        if (i < request.messages.length - 1) {\r\n          const delayTime = message.delay ?? request.defaultDelay ?? 0;\r\n          if (delayTime > 0) {\r\n            await this.sleep(delayTime);\r\n          }\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      return {\r\n        groupId: request.groupId,\r\n        totalMessages: request.messages.length,\r\n        successfulMessages,\r\n        failedMessages,\r\n        messageResults,\r\n        totalDuration,\r\n      };\r\n\r\n    } catch (error) {\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n\r\n      throw new ZaloSDKError(\r\n        `Failed to send message list to group: ${(error as Error).message}`,\r\n        -1,\r\n        {\r\n          groupId: request.groupId,\r\n          totalMessages: request.messages?.length || 0,\r\n          successfulMessages,\r\n          failedMessages,\r\n          messageResults,\r\n          totalDuration,\r\n        }\r\n      );\r\n    }\r\n  }\r\n\r\n\r\n  private handleGroupMessageError(error: unknown, defaultMessage: string): Error {\r\n    let errorDetails = '';\r\n    \r\n    // Type guard for axios error with response\r\n    if (\r\n      error &&\r\n      typeof error === 'object' &&\r\n      'response' in error &&\r\n      error.response &&\r\n      typeof error.response === 'object' &&\r\n      'data' in error.response\r\n    ) {\r\n      const errorData = error.response.data as Record<string, unknown>;\r\n      const errorCode = errorData.error || (error.response as { status?: number }).status;\r\n      const errorMessage = errorData.message || errorData.error_description || \"Unknown error\";\r\n      \r\n      errorDetails = `Error ${errorCode}: ${errorMessage}`;\r\n      \r\n      // Add additional debug info if available\r\n      if (errorData.error_name) {\r\n        errorDetails += ` (${errorData.error_name})`;\r\n      }\r\n      \r\n      // Log detailed error for debugging\r\n      console.error(`[GroupMessageService] ${defaultMessage}`, {\r\n        errorCode,\r\n        errorMessage,\r\n        errorData,\r\n        url: (error as { config?: { url?: string } }).config?.url,\r\n        method: (error as { config?: { method?: string } }).config?.method,\r\n        requestData: (error as { config?: { data?: unknown } }).config?.data\r\n      });\r\n      \r\n      return new Error(`${defaultMessage}: ${errorDetails}`);\r\n    }\r\n    \r\n    // Handle network or other errors\r\n    const errorObj = error as Error;\r\n    console.error(`[GroupMessageService] ${defaultMessage}`, {\r\n      message: errorObj.message,\r\n      stack: errorObj.stack,\r\n      url: (error as { config?: { url?: string } }).config?.url,\r\n      method: (error as { config?: { method?: string } }).config?.method,\r\n    });\r\n    \r\n    return new Error(`${defaultMessage}: ${errorObj.message || \"Unknown error\"}`);\r\n  }\r\n\r\n  /**\r\n   * Gửi danh sách tin nhắn tới nhiều groups với callback tracking\r\n   *\r\n   * @param request Thông tin request gửi danh sách tin nhắn tới nhiều groups\r\n   * @returns Kết quả gửi tin nhắn cho tất cả groups\r\n   */\r\n  public async sendMessageListToMultipleGroups(\r\n    request: SendMessageListToMultipleGroupsRequest\r\n  ): Promise<SendMessageListToMultipleGroupsResponse> {\r\n    const startTime = Date.now();\r\n    const groupResults: SendMessageListToMultipleGroupsResponse[\"groupResults\"] = [];\r\n    let successfulGroups = 0;\r\n    let failedGroups = 0;\r\n    let totalSuccessfulMessages = 0;\r\n    let totalFailedMessages = 0;\r\n\r\n    try {\r\n      // Validate input\r\n      if (!request.accessToken || request.accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.groupIds || request.groupIds.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách group IDs không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.messages || request.messages.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách tin nhắn không được để trống\", -1);\r\n      }\r\n\r\n      // Loại bỏ group IDs trùng lặp và rỗng\r\n      const uniqueGroupIds = [...new Set(request.groupIds.filter(id => id && id.trim().length > 0))];\r\n\r\n      if (uniqueGroupIds.length === 0) {\r\n        throw new ZaloSDKError(\"Không có group ID hợp lệ nào\", -1);\r\n      }\r\n\r\n      // Gửi tin nhắn cho từng group tuần tự\r\n      for (let i = 0; i < uniqueGroupIds.length; i++) {\r\n        const groupId = uniqueGroupIds[i];\r\n        const groupStartTime = Date.now();\r\n\r\n        // Callback: Bắt đầu gửi cho group\r\n        if (request.onProgress) {\r\n          request.onProgress({\r\n            groupId,\r\n            groupIndex: i,\r\n            totalGroups: uniqueGroupIds.length,\r\n            status: 'started',\r\n            startTime: groupStartTime,\r\n          });\r\n        }\r\n\r\n        const groupResult = {\r\n          groupId,\r\n          groupIndex: i,\r\n          success: false,\r\n          startTime: groupStartTime,\r\n          endTime: 0,\r\n          duration: 0,\r\n        } as SendMessageListToMultipleGroupsResponse[\"groupResults\"][0];\r\n\r\n        try {\r\n          // Gửi danh sách tin nhắn cho group này\r\n          const messageListResult = await this.sendMessageListToGroup({\r\n            accessToken: request.accessToken,\r\n            groupId,\r\n            messages: request.messages,\r\n            defaultDelay: request.defaultDelay,\r\n            // Không truyền onProgress để tránh callback lồng nhau\r\n          });\r\n\r\n          // Ghi nhận thành công\r\n          const groupEndTime = Date.now();\r\n          groupResult.success = true;\r\n          groupResult.messageListResult = messageListResult;\r\n          groupResult.endTime = groupEndTime;\r\n          groupResult.duration = groupEndTime - groupStartTime;\r\n\r\n          successfulGroups++;\r\n          totalSuccessfulMessages += messageListResult.successfulMessages;\r\n          totalFailedMessages += messageListResult.failedMessages;\r\n\r\n          // Callback: Hoàn thành thành công\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              groupId,\r\n              groupIndex: i,\r\n              totalGroups: uniqueGroupIds.length,\r\n              status: 'completed',\r\n              result: messageListResult,\r\n              startTime: groupStartTime,\r\n              endTime: groupEndTime,\r\n            });\r\n          }\r\n\r\n        } catch (error) {\r\n          // Ghi nhận thất bại\r\n          const groupEndTime = Date.now();\r\n          const errorMessage = error instanceof Error ? error.message : String(error);\r\n\r\n          groupResult.success = false;\r\n          groupResult.error = errorMessage;\r\n          groupResult.endTime = groupEndTime;\r\n          groupResult.duration = groupEndTime - groupStartTime;\r\n\r\n          failedGroups++;\r\n          // Với group thất bại, coi như tất cả tin nhắn đều thất bại\r\n          totalFailedMessages += request.messages.length;\r\n\r\n          // Callback: Thất bại\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              groupId,\r\n              groupIndex: i,\r\n              totalGroups: uniqueGroupIds.length,\r\n              status: 'failed',\r\n              error: errorMessage,\r\n              startTime: groupStartTime,\r\n              endTime: groupEndTime,\r\n            });\r\n          }\r\n        }\r\n\r\n        groupResults.push(groupResult);\r\n\r\n        // Delay giữa các groups (trừ group cuối cùng)\r\n        if (i < uniqueGroupIds.length - 1 && request.delayBetweenGroups && request.delayBetweenGroups > 0) {\r\n          await this.sleep(request.delayBetweenGroups);\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n      const totalMessages = uniqueGroupIds.length * request.messages.length;\r\n\r\n      return {\r\n        totalGroups: uniqueGroupIds.length,\r\n        successfulGroups,\r\n        failedGroups,\r\n        groupResults,\r\n        totalDuration,\r\n        messageStats: {\r\n          totalSuccessfulMessages,\r\n          totalFailedMessages,\r\n          totalMessages,\r\n        },\r\n      };\r\n\r\n    } catch (error) {\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n\r\n      throw new ZaloSDKError(\r\n        `Failed to send message list to multiple groups: ${(error as Error).message}`,\r\n        -1,\r\n        {\r\n          totalGroups: request.groupIds?.length || 0,\r\n          successfulGroups,\r\n          failedGroups,\r\n          groupResults,\r\n          totalDuration,\r\n          messageStats: {\r\n            totalSuccessfulMessages,\r\n            totalFailedMessages,\r\n            totalMessages: (request.groupIds?.length || 0) * (request.messages?.length || 0),\r\n          },\r\n        }\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn cá nhân hóa tới nhiều groups - mỗi group có bộ tin nhắn riêng\r\n   *\r\n   * @param request Thông tin request gửi tin nhắn cá nhân hóa tới nhiều groups\r\n   * @returns Kết quả gửi tin nhắn cho tất cả groups\r\n   */\r\n  public async sendPersonalizedMessageToMultipleGroups(\r\n    request: SendPersonalizedMessageToMultipleGroupsRequest\r\n  ): Promise<SendPersonalizedMessageToMultipleGroupsResponse> {\r\n    const startTime = Date.now();\r\n    const groupResults: SendPersonalizedMessageToMultipleGroupsResponse[\"groupResults\"] = [];\r\n    let successfulGroups = 0;\r\n    let failedGroups = 0;\r\n    let totalSuccessfulMessages = 0;\r\n    let totalFailedMessages = 0;\r\n    let totalMessages = 0;\r\n\r\n    try {\r\n      // Validate input\r\n      if (!request.accessToken || request.accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.personalizedMessages || request.personalizedMessages.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách tin nhắn cá nhân hóa không được để trống\", -1);\r\n      }\r\n\r\n      // Validate từng group message\r\n      for (let i = 0; i < request.personalizedMessages.length; i++) {\r\n        const personalizedMessage = request.personalizedMessages[i];\r\n\r\n        if (!personalizedMessage.groupId || personalizedMessage.groupId.trim().length === 0) {\r\n          throw new ZaloSDKError(`Group ID tại index ${i} không được để trống`, -1);\r\n        }\r\n\r\n        if (!personalizedMessage.messages || personalizedMessage.messages.length === 0) {\r\n          throw new ZaloSDKError(`Danh sách tin nhắn cho group ${personalizedMessage.groupId} không được để trống`, -1);\r\n        }\r\n      }\r\n\r\n      // Loại bỏ groups trùng lặp dựa trên groupId\r\n      const uniquePersonalizedMessages = request.personalizedMessages.filter(\r\n        (message, index, self) =>\r\n          self.findIndex(m => m.groupId === message.groupId) === index &&\r\n          message.groupId.trim().length > 0\r\n      );\r\n\r\n      if (uniquePersonalizedMessages.length === 0) {\r\n        throw new ZaloSDKError(\"Không có group message hợp lệ nào\", -1);\r\n      }\r\n\r\n      // Tính tổng số tin nhắn\r\n      totalMessages = uniquePersonalizedMessages.reduce((sum, pm) => sum + pm.messages.length, 0);\r\n\r\n      // Gửi tin nhắn cho từng group tuần tự\r\n      for (let i = 0; i < uniquePersonalizedMessages.length; i++) {\r\n        const personalizedMessage = uniquePersonalizedMessages[i];\r\n        const groupStartTime = Date.now();\r\n\r\n        // Callback: Bắt đầu gửi cho group\r\n        if (request.onProgress) {\r\n          request.onProgress({\r\n            groupId: personalizedMessage.groupId,\r\n            groupIndex: i,\r\n            totalGroups: uniquePersonalizedMessages.length,\r\n            status: 'started',\r\n            startTime: groupStartTime,\r\n          });\r\n        }\r\n\r\n        const groupResult = {\r\n          groupId: personalizedMessage.groupId,\r\n          groupIndex: i,\r\n          success: false,\r\n          startTime: groupStartTime,\r\n          endTime: 0,\r\n          duration: 0,\r\n        } as SendPersonalizedMessageToMultipleGroupsResponse[\"groupResults\"][0];\r\n\r\n        try {\r\n          // Gửi danh sách tin nhắn cá nhân hóa cho group này\r\n          const messageListResult = await this.sendMessageListToGroup({\r\n            accessToken: request.accessToken,\r\n            groupId: personalizedMessage.groupId,\r\n            messages: personalizedMessage.messages,\r\n            defaultDelay: request.defaultDelay,\r\n            // Không truyền onProgress để tránh callback lồng nhau\r\n          });\r\n\r\n          // Ghi nhận thành công\r\n          const groupEndTime = Date.now();\r\n          groupResult.success = true;\r\n          groupResult.messageListResult = messageListResult;\r\n          groupResult.endTime = groupEndTime;\r\n          groupResult.duration = groupEndTime - groupStartTime;\r\n\r\n          successfulGroups++;\r\n          totalSuccessfulMessages += messageListResult.successfulMessages;\r\n          totalFailedMessages += messageListResult.failedMessages;\r\n\r\n          // Callback: Hoàn thành thành công\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              groupId: personalizedMessage.groupId,\r\n              groupIndex: i,\r\n              totalGroups: uniquePersonalizedMessages.length,\r\n              status: 'completed',\r\n              result: messageListResult,\r\n              startTime: groupStartTime,\r\n              endTime: groupEndTime,\r\n            });\r\n          }\r\n\r\n        } catch (error) {\r\n          // Ghi nhận thất bại\r\n          const groupEndTime = Date.now();\r\n          const errorMessage = error instanceof Error ? error.message : String(error);\r\n\r\n          groupResult.success = false;\r\n          groupResult.error = errorMessage;\r\n          groupResult.endTime = groupEndTime;\r\n          groupResult.duration = groupEndTime - groupStartTime;\r\n\r\n          failedGroups++;\r\n          // Với group thất bại, coi như tất cả tin nhắn của group đó đều thất bại\r\n          totalFailedMessages += personalizedMessage.messages.length;\r\n\r\n          // Callback: Thất bại\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              groupId: personalizedMessage.groupId,\r\n              groupIndex: i,\r\n              totalGroups: uniquePersonalizedMessages.length,\r\n              status: 'failed',\r\n              error: errorMessage,\r\n              startTime: groupStartTime,\r\n              endTime: groupEndTime,\r\n            });\r\n          }\r\n        }\r\n\r\n        groupResults.push(groupResult);\r\n\r\n        // Delay giữa các groups (trừ group cuối cùng)\r\n        if (i < uniquePersonalizedMessages.length - 1 && request.delayBetweenGroups && request.delayBetweenGroups > 0) {\r\n          await this.sleep(request.delayBetweenGroups);\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      return {\r\n        totalGroups: uniquePersonalizedMessages.length,\r\n        successfulGroups,\r\n        failedGroups,\r\n        groupResults,\r\n        totalDuration,\r\n        messageStats: {\r\n          totalSuccessfulMessages,\r\n          totalFailedMessages,\r\n          totalMessages,\r\n        },\r\n      };\r\n\r\n    } catch (error) {\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n\r\n      throw new ZaloSDKError(\r\n        `Failed to send personalized messages to multiple groups: ${(error as Error).message}`,\r\n        -1,\r\n        {\r\n          totalGroups: request.personalizedMessages?.length || 0,\r\n          successfulGroups,\r\n          failedGroups,\r\n          groupResults,\r\n          totalDuration,\r\n          messageStats: {\r\n            totalSuccessfulMessages,\r\n            totalFailedMessages,\r\n            totalMessages,\r\n          },\r\n        }\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Utility method để sleep/delay\r\n   * @param ms Thời gian delay tính bằng milliseconds\r\n   */\r\n  private sleep(ms: number): Promise<void> {\r\n    return new Promise(resolve => setTimeout(resolve, ms));\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  GroupCreateRequest,\r\n  GroupCreateResult,\r\n  GroupCreateData,\r\n  GroupUpdateRequest,\r\n  GroupUpdateResult,\r\n  GroupAssetUpdateRequest,\r\n  GroupAvatarUpdateRequest,\r\n  GroupMemberInviteRequest,\r\n  GroupInviteResult,\r\n  GroupMemberActionRequest,\r\n  GroupAdminActionRequest,\r\n  GroupDeleteRequest,\r\n  RecentChatsResponse,\r\n  GroupConversationResponse,\r\n  GroupsOfOAResponse,\r\n  GroupDetailResponse,\r\n  GroupPendingMembersResponse,\r\n  GroupAcceptPendingMembersRequest,\r\n  GroupAcceptPendingMembersResponse,\r\n  GroupRemoveMembersRequest,\r\n  GroupRemoveMembersResponse,\r\n  GroupMembersResponse,\r\n  GroupMemberInfo,\r\n  GroupQuotaMessageRequest,\r\n  GroupQuotaMessageResponse,\r\n  AllGroupMembersResponse,\r\n  GetAllMembersProgress,\r\n  EnhancedGroupMember,\r\n  AllGroupMembersWithDetailsResponse,\r\n  GetAllMembersWithDetailsProgress,\r\n  OAGroupItem,\r\n  AllGroupsOfOAResponse,\r\n} from \"../types/group\";\r\nimport { GMFProductType, QuotaType } from \"../types/oa\";\r\nimport { ZaloSDKError } from \"../types/common\";\r\nimport { UserService } from \"./user.service\";\r\nimport { UserInfo } from \"../types/user\";\r\n\r\n/**\r\n * Service for handling Zalo Official Account Group Management Framework (GMF) APIs\r\n *\r\n * CONDITIONS FOR USING ZALO GMF GROUP MANAGEMENT:\r\n *\r\n * 1. GENERAL CONDITIONS:\r\n *    - OA must be granted permission to use GMF (Group Message Framework) feature\r\n *    - Access token must have \"manage_group\" and \"group_message\" scopes\r\n *    - OA must have active status and be verified\r\n *    - Must comply with limits on number of groups and members\r\n *\r\n * 2. CREATE NEW GROUP:\r\n *    - Group name: required, max 100 characters, no special characters\r\n *    - Description: optional, max 500 characters\r\n *    - Avatar: optional, JPG/PNG format, max 5MB\r\n *    - Initial members: max 200 people, must be users who have interacted with OA\r\n *    - OA automatically becomes admin of the group\r\n *\r\n * 3. MEMBER MANAGEMENT:\r\n *    - Only admins can invite/remove members\r\n *    - Invite members: max 50 people per time, users must have interacted with OA\r\n *    - Remove members: cannot remove other admins, must have at least 1 admin\r\n *    - Members can leave group themselves\r\n *\r\n * 4. ADMIN MANAGEMENT:\r\n *    - Only current admins can add/remove other admins\r\n *    - Must have at least 1 admin in group\r\n *    - OA always has admin rights and cannot be removed\r\n *\r\n * 5. LIMITS AND CONSTRAINTS:\r\n *    - Maximum groups: according to service package (usually 10-100 groups)\r\n *    - Maximum members per group: 200 people\r\n *    - Group creation frequency: max 10 groups/day\r\n *    - Member invitation frequency: max 500 invitations/day\r\n */\r\nexport class GroupManagementService {\r\n\r\n\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    group: {\r\n      create: \"https://openapi.zalo.me/v3.0/oa/group/creategroupwithoa\",\r\n      get: \"https://openapi.zalo.me/v3.0/oa/group/getgroup\",\r\n      updateInfo: \"https://openapi.zalo.me/v3.0/oa/group/updateinfo\",\r\n      updateAsset: \"https://openapi.zalo.me/v3.0/oa/group/updateasset\",\r\n      invite: \"https://openapi.zalo.me/v3.0/oa/group/invite\",\r\n      listPendingInvite: \"https://openapi.zalo.me/v3.0/oa/group/listpendinginvite\",\r\n      acceptPendingInvite: \"https://openapi.zalo.me/v3.0/oa/group/acceptpendinginvite\",\r\n      rejectPendingInvite: \"https://openapi.zalo.me/v3.0/oa/group/rejectpendinginvite\",\r\n      removeMembers: \"https://openapi.zalo.me/v3.0/oa/group/removemembers\",\r\n      getGroupsOfOA: \"https://openapi.zalo.me/v3.0/oa/group/getgroupsofoa\",\r\n      recent: \"https://openapi.zalo.me/v3.0/oa/group/listrecentchat\",\r\n      conversation: \"https://openapi.zalo.me/v3.0/oa/group/conversation\",\r\n      addAdmins: \"https://openapi.zalo.me/v3.0/oa/group/addadmins\",\r\n      removeAdmins: \"https://openapi.zalo.me/v3.0/oa/group/removeadmins\",\r\n      conversationByGroupId: (groupId: string) => `https://openapi.zalo.me/v3.0/oa/group/${groupId}/conversation`,\r\n      members: \"https://openapi.zalo.me/v3.0/oa/group/listmember\",\r\n      delete: \"https://openapi.zalo.me/v3.0/oa/group/delete\",\r\n      quota: \"https://openapi.zalo.me/v3.0/oa/quota/group\",\r\n    },\r\n  } as const;\r\n\r\n  constructor(\r\n    private readonly client: ZaloClient,\r\n    private readonly userService?: UserService\r\n  ) { }\r\n\r\n  /**\r\n   * Create GroupManagementService with UserService for enhanced member details\r\n   * @param client ZaloClient instance\r\n   * @param userService UserService instance for fetching detailed user info\r\n   * @returns GroupManagementService with enhanced capabilities\r\n   */\r\n  static withUserService(client: ZaloClient, userService: UserService): GroupManagementService {\r\n    return new GroupManagementService(client, userService);\r\n  }\r\n\r\n  /**\r\n   * Create basic GroupManagementService without UserService\r\n   * @param client ZaloClient instance\r\n   * @returns GroupManagementService with basic capabilities only\r\n   */\r\n  static basic(client: ZaloClient): GroupManagementService {\r\n    return new GroupManagementService(client);\r\n  }\r\n\r\n  /**\r\n   * Create new group chat with asset_id\r\n   * @param accessToken OA access token\r\n   * @param groupData Group information to create\r\n   * @returns Created group information\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/creategroupwithoa\r\n   */\r\n  async createGroup(\r\n    accessToken: string,\r\n    groupData: GroupCreateRequest\r\n  ): Promise<GroupCreateResult> {\r\n    try {\r\n      // Validate input\r\n      if (!groupData.group_name || groupData.group_name.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group name cannot be empty\", -1);\r\n      }\r\n\r\n      if (groupData.group_name.length > 100) {\r\n        throw new ZaloSDKError(\"Group name cannot exceed 100 characters\", -1);\r\n      }\r\n\r\n      if (!groupData.asset_id || groupData.asset_id.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Asset ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (groupData.member_user_ids.length > 99) {\r\n        throw new ZaloSDKError(\r\n          \"Initial member count cannot exceed 99 people\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (groupData.member_user_ids.length === 0) {\r\n        throw new ZaloSDKError(\"Member list cannot be empty\", -1);\r\n      }\r\n\r\n      const requestData = {\r\n        group_name: groupData.group_name.trim(),\r\n        ...(groupData.group_description && {\r\n          group_description: groupData.group_description.trim(),\r\n        }),\r\n        asset_id: groupData.asset_id,\r\n        member_user_ids: groupData.member_user_ids,\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupCreateResult>(\r\n        this.endpoints.group.create,\r\n        accessToken,\r\n        requestData\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to create group\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Helper method to extract group data from create response\r\n   * @param response Full API response\r\n   * @returns Group data only\r\n   */\r\n  extractGroupData(response: GroupCreateResult): GroupCreateData {\r\n    return response.data;\r\n  }\r\n\r\n  /**\r\n   * Helper method to extract group info from update response\r\n   * @param response Full API response\r\n   * @returns Group info only\r\n   */\r\n  extractGroupInfo(response: GroupUpdateResult) {\r\n    return response.data.group_info;\r\n  }\r\n\r\n  /**\r\n   * Helper method to extract group settings from update response\r\n   * @param response Full API response\r\n   * @returns Group settings only\r\n   */\r\n  extractGroupSettings(response: GroupUpdateResult) {\r\n    return response.data.group_setting;\r\n  }\r\n\r\n  /**\r\n   * Helper method to extract asset info from update response\r\n   * @param response Full API response\r\n   * @returns Asset info only\r\n   */\r\n  extractAssetInfo(response: GroupUpdateResult) {\r\n    return response.data.asset_info;\r\n  }\r\n\r\n  /**\r\n   * Update group asset (upgrade package or extend expiration)\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param assetId New asset ID for the group\r\n   * @returns Update result with full group information\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/updateasset\r\n   *\r\n   * Use cases:\r\n   * - Increase member limit for the group\r\n   * - Extend group expiration when expired\r\n   */\r\n  async updateGroupAsset(\r\n    accessToken: string,\r\n    groupId: string,\r\n    assetId: string\r\n  ): Promise<GroupUpdateResult>;\r\n\r\n  /**\r\n   * Update group asset (upgrade package or extend expiration) - Object parameter version\r\n   * @param accessToken OA access token\r\n   * @param updateData Asset update data\r\n   * @returns Update result with full group information\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/updateasset\r\n   */\r\n  async updateGroupAsset(\r\n    accessToken: string,\r\n    updateData: GroupAssetUpdateRequest\r\n  ): Promise<GroupUpdateResult>;\r\n\r\n  async updateGroupAsset(\r\n    accessToken: string,\r\n    groupIdOrUpdateData: string | GroupAssetUpdateRequest,\r\n    assetId?: string\r\n  ): Promise<GroupUpdateResult> {\r\n    try {\r\n      let requestData: GroupAssetUpdateRequest;\r\n\r\n      // Handle overloaded parameters\r\n      if (typeof groupIdOrUpdateData === 'string') {\r\n        // First overload: (accessToken, groupId, assetId)\r\n        if (!assetId) {\r\n          throw new ZaloSDKError(\"Asset ID is required when using separate parameters\", -1);\r\n        }\r\n\r\n        requestData = {\r\n          group_id: groupIdOrUpdateData,\r\n          asset_id: assetId,\r\n        };\r\n      } else {\r\n        // Second overload: (accessToken, updateData)\r\n        requestData = groupIdOrUpdateData;\r\n      }\r\n\r\n      // Validate input\r\n      if (!requestData.group_id || requestData.group_id.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (!requestData.asset_id || requestData.asset_id.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Asset ID cannot be empty\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiPost<GroupUpdateResult>(\r\n        this.endpoints.group.updateAsset,\r\n        accessToken,\r\n        requestData\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to update group asset\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get detailed group information\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @returns Detailed group information including group_info, asset_info and group_setting\r\n   *\r\n   * API: GET https://openapi.zalo.me/v3.0/oa/group/getgroup\r\n   */\r\n  async getGroupInfo(\r\n    accessToken: string,\r\n    groupId: string\r\n  ): Promise<GroupDetailResponse> {\r\n    try {\r\n      // Validate access token\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      // Validate group ID\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<GroupDetailResponse>(\r\n        this.endpoints.group.get,\r\n        accessToken,\r\n        {\r\n          group_id: groupId.trim(),\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get group information\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Update group information\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param updateData Information to update\r\n   * @returns Update result with full group information\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/updateinfo\r\n   */\r\n  async updateGroupInfo(\r\n    accessToken: string,\r\n    groupId: string,\r\n    updateData: GroupUpdateRequest\r\n  ): Promise<GroupUpdateResult> {\r\n    try {\r\n      // Validate input\r\n      if (updateData.group_name && updateData.group_name.length > 100) {\r\n        throw new ZaloSDKError(\"Group name cannot exceed 100 characters\", -1);\r\n      }\r\n\r\n      if (updateData.group_description && updateData.group_description.length > 500) {\r\n        throw new ZaloSDKError(\r\n          \"Group description cannot exceed 500 characters\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Build request data according to Zalo API\r\n      const requestData = {\r\n        group_id: groupId,\r\n        ...(updateData.group_name && {\r\n          group_name: updateData.group_name.trim(),\r\n        }),\r\n        ...(updateData.group_avatar && {\r\n          group_avatar: updateData.group_avatar,\r\n        }),\r\n        ...(updateData.group_description && {\r\n          group_description: updateData.group_description.trim(),\r\n        }),\r\n        ...(updateData.lock_send_msg !== undefined && {\r\n          lock_send_msg: updateData.lock_send_msg,\r\n        }),\r\n        ...(updateData.join_appr !== undefined && {\r\n          join_appr: updateData.join_appr,\r\n        }),\r\n        ...(updateData.enable_msg_history !== undefined && {\r\n          enable_msg_history: updateData.enable_msg_history,\r\n        }),\r\n        ...(updateData.enable_link_join !== undefined && {\r\n          enable_link_join: updateData.enable_link_join,\r\n        }),\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupUpdateResult>(\r\n        this.endpoints.group.updateInfo,\r\n        accessToken,\r\n        requestData\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to update group information\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Update group avatar\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param avatarData New avatar information\r\n   * @returns Update result\r\n   * @deprecated Use updateGroupInfo() with group_avatar field instead\r\n   */\r\n  async updateGroupAvatar(\r\n    accessToken: string,\r\n    groupId: string,\r\n    avatarData: GroupAvatarUpdateRequest\r\n  ): Promise<{ success: boolean }> {\r\n    try {\r\n      const response = await this.client.apiPost<{ error: number; message: string }>(\r\n        this.endpoints.group.updateInfo,\r\n        accessToken,\r\n        {\r\n          group_id: groupId,\r\n          ...avatarData,\r\n        }\r\n      );\r\n\r\n      return { success: response.error === 0 };\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to update group avatar\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Invite members to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param inviteData List of user IDs to invite\r\n   * @returns Invitation result\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/invite\r\n   */\r\n  async inviteMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    inviteData: GroupMemberInviteRequest\r\n  ): Promise<GroupInviteResult>;\r\n\r\n  /**\r\n   * Invite members to group - Array parameter version\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param memberUserIds Array of user IDs to invite\r\n   * @returns Invitation result\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/invite\r\n   */\r\n  async inviteMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    memberUserIds: string[]\r\n  ): Promise<GroupInviteResult>;\r\n\r\n  async inviteMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    inviteDataOrUserIds: GroupMemberInviteRequest | string[]\r\n  ): Promise<GroupInviteResult> {\r\n    try {\r\n      let memberUserIds: string[];\r\n\r\n      // Handle overloaded parameters\r\n      if (Array.isArray(inviteDataOrUserIds)) {\r\n        // Second overload: (accessToken, groupId, memberUserIds)\r\n        memberUserIds = inviteDataOrUserIds;\r\n      } else {\r\n        // First overload: (accessToken, groupId, inviteData)\r\n        memberUserIds = inviteDataOrUserIds.member_user_ids;\r\n      }\r\n\r\n      // Validate input\r\n      if (memberUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Member list cannot be empty\", -1);\r\n      }\r\n\r\n      if (memberUserIds.length > 50) {\r\n        throw new ZaloSDKError(\"Cannot invite more than 50 people at once\", -1);\r\n      }\r\n\r\n      const requestData = {\r\n        group_id: groupId,\r\n        member_user_ids: memberUserIds,\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupInviteResult>(\r\n        this.endpoints.group.invite,\r\n        accessToken,\r\n        requestData\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to invite members\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get list of pending members\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param offset Offset for pagination (default: 0)\r\n   * @param count Maximum number to return (default: 20, max: 50)\r\n   * @returns List of pending members\r\n   */\r\n  async getPendingMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    offset: number = 0,\r\n    count: number = 5\r\n  ): Promise<GroupPendingMembersResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (offset < 0) {\r\n        throw new ZaloSDKError(\"Offset must be >= 0\", -1);\r\n      }\r\n\r\n      if (count <= 0 || count > 50) {\r\n        throw new ZaloSDKError(\"Count must be from 1 to 50\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<GroupPendingMembersResponse>(\r\n        this.endpoints.group.listPendingInvite,\r\n        accessToken,\r\n        {\r\n          group_id: groupId.trim(),\r\n          offset: offset,\r\n          count: count,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get pending members\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Accept pending members to group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param memberUserIds List of user IDs to accept\r\n   * @returns Accept result\r\n   */\r\n  async acceptPendingMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    memberUserIds: string[]\r\n  ): Promise<GroupAcceptPendingMembersResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (!memberUserIds || memberUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Member user IDs list cannot be empty\", -1);\r\n      }\r\n\r\n      if (memberUserIds.length > 100) {\r\n        throw new ZaloSDKError(\r\n          \"Cannot accept more than 100 members at once\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Validate user IDs\r\n      const validUserIds = memberUserIds.filter(\r\n        (id) => id && id.trim().length > 0\r\n      );\r\n      if (validUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"No valid user IDs found\", -1);\r\n      }\r\n\r\n      const requestBody: GroupAcceptPendingMembersRequest = {\r\n        group_id: groupId.trim(),\r\n        member_user_ids: validUserIds.map((id) => id.trim()),\r\n      };\r\n\r\n      const response =\r\n        await this.client.apiPost<GroupAcceptPendingMembersResponse>(\r\n          this.endpoints.group.acceptPendingInvite,\r\n          accessToken,\r\n          requestBody\r\n        );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to accept pending members\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Reject pending members from group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param memberUserIds List of user IDs to reject\r\n   * @returns Reject result\r\n   */\r\n  async rejectPendingMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    memberUserIds: string[]\r\n  ): Promise<GroupAcceptPendingMembersResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (!memberUserIds || memberUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Member user IDs list cannot be empty\", -1);\r\n      }\r\n\r\n      if (memberUserIds.length > 100) {\r\n        throw new ZaloSDKError(\r\n          \"Cannot reject more than 100 members at once\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Validate user IDs\r\n      const validUserIds = memberUserIds.filter(\r\n        (id) => id && id.trim().length > 0\r\n      );\r\n      if (validUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"No valid user IDs found\", -1);\r\n      }\r\n\r\n      const requestBody: GroupAcceptPendingMembersRequest = {\r\n        group_id: groupId.trim(),\r\n        member_user_ids: validUserIds.map((id) => id.trim()),\r\n      };\r\n\r\n      const response =\r\n        await this.client.apiPost<GroupAcceptPendingMembersResponse>(\r\n          this.endpoints.group.rejectPendingInvite,\r\n          accessToken,\r\n          requestBody\r\n        );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to reject pending members\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Remove members from group\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param memberUserIds List of user IDs to remove\r\n   * @returns Remove result\r\n   */\r\n  async removeMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    memberUserIds: string[]\r\n  ): Promise<GroupRemoveMembersResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (!memberUserIds || memberUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Member user IDs list cannot be empty\", -1);\r\n      }\r\n\r\n      if (memberUserIds.length > 100) {\r\n        throw new ZaloSDKError(\r\n          \"Cannot remove more than 100 members at once\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Validate user IDs\r\n      const validUserIds = memberUserIds.filter(\r\n        (id) => id && id.trim().length > 0\r\n      );\r\n      if (validUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"No valid user IDs found\", -1);\r\n      }\r\n\r\n      const requestBody: GroupRemoveMembersRequest = {\r\n        group_id: groupId.trim(),\r\n        member_user_ids: validUserIds.map((id) => id.trim()),\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupRemoveMembersResponse>(\r\n        this.endpoints.group.removeMembers,\r\n        accessToken,\r\n        requestBody\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to remove members\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Add admin rights to members - Array parameter version\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param memberUserIds Array of user IDs to add admin rights\r\n   * @returns Add admin result\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/addadmins\r\n   */\r\n  async addAdmins(\r\n    accessToken: string,\r\n    groupId: string,\r\n    memberUserIds: string[]\r\n  ): Promise<{ error: number; message: string }>;\r\n\r\n  async addAdmins(\r\n    accessToken: string,\r\n    groupId: string,\r\n    adminDataOrUserIds: GroupAdminActionRequest | string[]\r\n  ): Promise<{ error: number; message: string }> {\r\n    try {\r\n      let memberUserIds: string[];\r\n\r\n      // Handle overloaded parameters\r\n      if (Array.isArray(adminDataOrUserIds)) {\r\n        // Second overload: (accessToken, groupId, memberUserIds)\r\n        memberUserIds = adminDataOrUserIds;\r\n      } else {\r\n        // First overload: (accessToken, groupId, adminData)\r\n        memberUserIds = adminDataOrUserIds.member_user_ids;\r\n      }\r\n\r\n      // Validate input\r\n      if (!memberUserIds || memberUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Member user IDs list cannot be empty\", -1);\r\n      }\r\n\r\n      const requestData = {\r\n        group_id: groupId,\r\n        member_user_ids: memberUserIds,\r\n      };\r\n\r\n      const response = await this.client.apiPost<{\r\n        error: number;\r\n        message: string;\r\n      }>(this.endpoints.group.addAdmins, accessToken, requestData);\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to add admins\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Remove admin rights from members - Array parameter version\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param memberUserIds Array of user IDs to remove admin rights\r\n   * @returns Remove admin result\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/group/removeadmins\r\n   */\r\n  async removeAdmins(\r\n    accessToken: string,\r\n    groupId: string,\r\n    memberUserIds: string[]\r\n  ): Promise<{ error: number; message: string }>;\r\n\r\n  async removeAdmins(\r\n    accessToken: string,\r\n    groupId: string,\r\n    adminDataOrUserIds: GroupAdminActionRequest | string[]\r\n  ): Promise<{ error: number; message: string }> {\r\n    try {\r\n      let memberUserIds: string[];\r\n\r\n      // Handle overloaded parameters\r\n      if (Array.isArray(adminDataOrUserIds)) {\r\n        // Second overload: (accessToken, groupId, memberUserIds)\r\n        memberUserIds = adminDataOrUserIds;\r\n      } else {\r\n        // First overload: (accessToken, groupId, adminData)\r\n        memberUserIds = adminDataOrUserIds.member_user_ids;\r\n      }\r\n\r\n      // Validate input\r\n      if (!memberUserIds || memberUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Member user IDs list cannot be empty\", -1);\r\n      }\r\n\r\n      const requestData = {\r\n        group_id: groupId,\r\n        member_user_ids: memberUserIds,\r\n      };\r\n\r\n      const response = await this.client.apiPost<{\r\n        error: number;\r\n        message: string;\r\n      }>(this.endpoints.group.removeAdmins, accessToken, requestData);\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to remove admins\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Delete group chat (Disband group)\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID to delete\r\n   * @returns Delete result\r\n   */\r\n  async deleteGroup(\r\n    accessToken: string,\r\n    groupId: string\r\n  ): Promise<{ error: number; message: string }> {\r\n    try {\r\n      const requestData: GroupDeleteRequest = {\r\n        group_id: groupId,\r\n      };\r\n\r\n      const response = await this.client.apiPost<{\r\n        error: number;\r\n        message: string;\r\n      }>(this.endpoints.group.delete, accessToken, requestData);\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to delete group\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get list of OA groups\r\n   * @param accessToken OA access token\r\n   * @param offset Offset for pagination (default: 0)\r\n   * @param count Maximum number to return (default: 5, max: 50)\r\n   * @returns List of OA groups\r\n   *\r\n   * API: GET https://openapi.zalo.me/v3.0/oa/group/getgroupsofoa\r\n   */\r\n  async getGroupsOfOA(\r\n    accessToken: string,\r\n    offset: number = 0,\r\n    count: number = 5\r\n  ): Promise<GroupsOfOAResponse> {\r\n    try {\r\n      // Validate access token\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      // Validate parameters\r\n      if (offset < 0) {\r\n        throw new ZaloSDKError(\"Offset must be >= 0\", -1);\r\n      }\r\n\r\n      if (count <= 0 || count > 50) {\r\n        throw new ZaloSDKError(\"Count must be from 1 to 50\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<GroupsOfOAResponse>(\r\n        this.endpoints.group.getGroupsOfOA,\r\n        accessToken,\r\n        {\r\n          offset: offset,\r\n          count: count,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get groups of OA\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ALL groups of OA (custom function with automatic pagination)\r\n   * @param accessToken OA access token\r\n   * @param maxGroups Maximum number of groups to retrieve (default: unlimited)\r\n   * @param progressCallback Optional callback to track progress\r\n   * @returns All OA groups with pagination info\r\n   *\r\n   * This function automatically handles pagination to retrieve all groups,\r\n   * not limited by the 50-group API limit per request.\r\n   */\r\n  async getAllGroupsOfOA(\r\n    accessToken: string,\r\n    maxGroups?: number,\r\n    progressCallback?: (progress: { currentCount: number; totalCount?: number; percentage?: number; isComplete: boolean }) => void\r\n  ): Promise<AllGroupsOfOAResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      if (maxGroups !== undefined && maxGroups <= 0) {\r\n        throw new ZaloSDKError(\"Max groups must be greater than 0\", -1);\r\n      }\r\n\r\n      const allGroups: OAGroupItem[] = [];\r\n      let offset = 0;\r\n      const pageSize = 50; // Maximum allowed by Zalo API\r\n      let totalCount: number | undefined;\r\n      let pagesFetched = 0;\r\n      let hasMore = true;\r\n\r\n      // Progress tracking\r\n      const updateProgress = (currentCount: number, isComplete: boolean = false) => {\r\n        if (progressCallback) {\r\n          const percentage = totalCount ? Math.round((currentCount / totalCount) * 100) : undefined;\r\n          progressCallback({\r\n            currentCount,\r\n            totalCount,\r\n            percentage,\r\n            isComplete\r\n          });\r\n        }\r\n      };\r\n\r\n      // Initial progress\r\n      updateProgress(0);\r\n\r\n      while (hasMore) {\r\n        // Check if we've reached the maximum limit\r\n        if (maxGroups && allGroups.length >= maxGroups) {\r\n          break;\r\n        }\r\n\r\n        // Calculate how many groups to request in this batch\r\n        const remainingLimit = maxGroups ? maxGroups - allGroups.length : pageSize;\r\n        const currentPageSize = Math.min(pageSize, remainingLimit);\r\n\r\n        // Fetch current page\r\n        const response = await this.getGroupsOfOA(\r\n          accessToken,\r\n          offset,\r\n          currentPageSize\r\n        );\r\n\r\n        // Handle API errors\r\n        if (response.error !== 0) {\r\n          throw new ZaloSDKError(\r\n            `Failed to fetch groups at offset ${offset}: ${response.message}`,\r\n            response.error\r\n          );\r\n        }\r\n\r\n        // Check if we have data\r\n        if (!response.data || !response.data.groups) {\r\n          break;\r\n        }\r\n\r\n        // Update total count from first response\r\n        if (totalCount === undefined && response.data.total !== undefined) {\r\n          totalCount = response.data.total;\r\n\r\n          // If maxGroups is set and less than total, use maxGroups as effective total\r\n          if (maxGroups && maxGroups < totalCount) {\r\n            totalCount = maxGroups;\r\n          }\r\n        }\r\n\r\n        // Add groups to our collection\r\n        allGroups.push(...response.data.groups);\r\n        pagesFetched++;\r\n\r\n        // Update progress\r\n        updateProgress(allGroups.length);\r\n\r\n        // Check if we should continue\r\n        const receivedCount = response.data.groups.length;\r\n        hasMore = receivedCount === currentPageSize &&\r\n          (!maxGroups || allGroups.length < maxGroups) &&\r\n          (response.data.total === undefined || allGroups.length < response.data.total);\r\n\r\n        // Move to next page\r\n        offset += receivedCount;\r\n\r\n        // Safety check to prevent infinite loops\r\n        if (pagesFetched > 100) { // Max 5,000 groups (50 * 100)\r\n          console.warn(`getAllGroupsOfOA: Reached maximum page limit (${pagesFetched}) for OA`);\r\n          break;\r\n        }\r\n      }\r\n\r\n      // Final progress update\r\n      updateProgress(allGroups.length, true);\r\n\r\n      // Apply maxGroups limit if specified\r\n      const finalGroups = maxGroups ? allGroups.slice(0, maxGroups) : allGroups;\r\n\r\n      // Return enhanced response\r\n      return {\r\n        error: 0,\r\n        message: \"Success\",\r\n        data: {\r\n          total_groups: totalCount || allGroups.length,\r\n          groups: finalGroups,\r\n          pages_fetched: pagesFetched,\r\n          is_complete: !maxGroups || allGroups.length <= maxGroups\r\n        }\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get all groups of OA\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ALL groups of OA with simple interface (no progress tracking)\r\n   * @param accessToken OA access token\r\n   * @param maxGroups Maximum number of groups to retrieve (optional)\r\n   * @returns Array of all OA groups\r\n   *\r\n   * Simplified version of getAllGroupsOfOA that returns just the groups array\r\n   */\r\n  async getAllGroupsOfOASimple(\r\n    accessToken: string,\r\n    maxGroups?: number\r\n  ): Promise<OAGroupItem[]> {\r\n    const response = await this.getAllGroupsOfOA(accessToken, maxGroups);\r\n    return response.data.groups;\r\n  }\r\n\r\n  /**\r\n   * Get group quota information and asset_id\r\n   * @param accessToken OA access token\r\n   * @param productType Product type (optional)\r\n   * @param quotaType Quota type (optional)\r\n   * @returns Group quota information including asset_id\r\n   *\r\n   * API: POST https://openapi.zalo.me/v3.0/oa/quota/group\r\n   */\r\n  async getGroupQuota(\r\n    accessToken: string,\r\n    productType?: GMFProductType,\r\n    quotaType?: QuotaType\r\n  ): Promise<GroupQuotaMessageResponse> {\r\n    try {\r\n      // Validate access token\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      const quotaRequest: GroupQuotaMessageRequest = {\r\n        quota_owner: \"OA\",\r\n        ...(productType && { product_type: productType }),\r\n        ...(quotaType && { quota_type: quotaType }),\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupQuotaMessageResponse>(\r\n        this.endpoints.group.quota,\r\n        accessToken,\r\n        quotaRequest\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(error, \"Failed to get group quota\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get asset_id for creating GMF group\r\n   * @param accessToken OA access token\r\n   * @returns Asset_id for group creation\r\n   */\r\n  async getAssetId(accessToken: string): Promise<string> {\r\n    try {\r\n      const quotaRequest: GroupQuotaMessageRequest = {\r\n        quota_owner: \"OA\",\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupQuotaMessageResponse>(\r\n        this.endpoints.group.quota,\r\n        accessToken,\r\n        quotaRequest\r\n      );\r\n\r\n      if (response.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          response.message || \"Failed to get asset_id\",\r\n          response.error\r\n        );\r\n      }\r\n\r\n      if (response.data && response.data.length > 0) {\r\n        const activeAsset = response.data.find(\r\n          (asset) => asset.status === \"available\"\r\n        );\r\n\r\n        if (activeAsset) {\r\n          return activeAsset.asset_id;\r\n        }\r\n      }\r\n\r\n      throw new ZaloSDKError(\r\n        \"No valid asset_id found for group creation. Please check your GMF package.\",\r\n        -1\r\n      );\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get asset_id for group creation\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get list of asset_ids available for creating GMF groups\r\n   * @param accessToken OA access token\r\n   * @returns List of asset_ids and quota information\r\n   */\r\n  async getAssetIds(accessToken: string): Promise<GroupQuotaMessageResponse> {\r\n    try {\r\n      const quotaRequest: GroupQuotaMessageRequest = {\r\n        quota_owner: \"OA\",\r\n      };\r\n\r\n      const response = await this.client.apiPost<GroupQuotaMessageResponse>(\r\n        this.endpoints.group.quota,\r\n        accessToken,\r\n        quotaRequest\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get asset_ids list\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get list of recent chats\r\n   * @param accessToken OA access token\r\n   * @param offset Offset for pagination (default: 0)\r\n   * @param count Maximum number to return (default: 5, max: 50)\r\n   * @returns List of recent chats\r\n   *\r\n   * API: GET https://openapi.zalo.me/v3.0/oa/group/listrecentchat\r\n   */\r\n  async getRecentChats(\r\n    accessToken: string,\r\n    offset: number = 0,\r\n    count: number = 5\r\n  ): Promise<RecentChatsResponse> {\r\n    try {\r\n      // Validate access token\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      // Validate parameters\r\n      if (offset < 0) {\r\n        throw new ZaloSDKError(\"Offset must be >= 0\", -1);\r\n      }\r\n\r\n      if (count <= 0 || count > 50) {\r\n        throw new ZaloSDKError(\"Count must be from 1 to 50\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<RecentChatsResponse>(\r\n        this.endpoints.group.recent,\r\n        accessToken,\r\n        {\r\n          offset: offset,\r\n          count: count,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get recent chats\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Lấy thông tin tin nhắn trong một nhóm\r\n   * \r\n   * Lưu ý: Ứng dụng cần được cấp quyền quản lý thông tin nhóm.\r\n   * \r\n   * @param accessToken Access token của Official Account\r\n   * @param groupId ID nhóm muốn query\r\n   * @param offset Offset muốn query (mặc định: 0)\r\n   * @param count Số lượng mong muốn query (mặc định: 5)\r\n   * @returns Lịch sử tin nhắn trong nhóm\r\n   * \r\n   * @example\r\n   * ```typescript\r\n   * const messages = await groupService.getGroupConversation(\r\n   *   accessToken,\r\n   *   'f414c8f76fa586fbdfb4',\r\n   *   0,\r\n   *   2\r\n   * );\r\n   * ```\r\n   * \r\n   * API: GET https://openapi.zalo.me/v3.0/oa/group/conversation\r\n   */\r\n  async getGroupConversation(\r\n    accessToken: string,\r\n    groupId: string,\r\n    offset: number = 0,\r\n    count: number = 5\r\n  ): Promise<GroupConversationResponse> {\r\n    try {\r\n      // Validate access token\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      // Validate group ID\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      // Validate parameters\r\n      if (offset < 0) {\r\n        throw new ZaloSDKError(\"Offset must be >= 0\", -1);\r\n      }\r\n\r\n      if (count <= 0) {\r\n        throw new ZaloSDKError(\"Count must be greater than 0\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<GroupConversationResponse>(\r\n        this.endpoints.group.conversation,\r\n        accessToken,\r\n        {\r\n          group_id: groupId.trim(),\r\n          offset: offset,\r\n          count: count,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get group conversation\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get group members list from Zalo API\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param offset Offset for pagination (default: 0)\r\n   * @param count Maximum number to return (default: 5, max: 50)\r\n   * @returns Group members list\r\n   */\r\n  async getGroupMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    offset: number = 0,\r\n    count: number = 5\r\n  ): Promise<GroupMembersResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (offset < 0) {\r\n        throw new ZaloSDKError(\"Offset must be >= 0\", -1);\r\n      }\r\n\r\n      if (count <= 0 || count > 50) {\r\n        throw new ZaloSDKError(\"Count must be from 1 to 50\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<GroupMembersResponse>(\r\n        this.endpoints.group.members,\r\n        accessToken,\r\n        {\r\n          group_id: groupId.trim(),\r\n          offset: offset,\r\n          count: count,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get group members\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Find a specific group member by user ID\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param userId User ID to search for\r\n   * @returns GroupMemberInfo if found, null otherwise\r\n   * \r\n   * Note: This method iterates through pages of members to find the user.\r\n   */\r\n  async getGroupMemberByUserId(\r\n    accessToken: string,\r\n    groupId: string,\r\n    userId: string\r\n  ): Promise<GroupMemberInfo | null> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (!userId || userId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"User ID cannot be empty\", -1);\r\n      }\r\n\r\n      const targetUserId = userId.trim();\r\n      let offset = 0;\r\n      const pageSize = 50; // Use max page size for efficiency\r\n      let hasMore = true;\r\n      let pagesFetched = 0;\r\n\r\n      while (hasMore) {\r\n        const response = await this.getGroupMembers(\r\n          accessToken,\r\n          groupId,\r\n          offset,\r\n          pageSize\r\n        );\r\n\r\n        if (response.error !== 0 || !response.data || !response.data.members) {\r\n          throw new ZaloSDKError(\r\n            `Failed to fetch members at offset ${offset}: ${response.message}`,\r\n            response.error\r\n          );\r\n        }\r\n\r\n        // Search in current page\r\n        const foundMember = response.data.members.find(\r\n          (member) => member.user_id === targetUserId\r\n        );\r\n\r\n        if (foundMember) {\r\n          return foundMember;\r\n        }\r\n\r\n        // Check if we should continue\r\n        const membersCount = response.data.members.length;\r\n        hasMore = membersCount === pageSize &&\r\n          (response.data.total === undefined || offset + membersCount < response.data.total);\r\n\r\n        offset += membersCount;\r\n        pagesFetched++;\r\n\r\n        // Safety break\r\n        if (pagesFetched > 1000) { // Max 50k members safety limit\r\n          break;\r\n        }\r\n      }\r\n\r\n      return null;\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        `Failed to find member with user ID ${userId}`\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ALL members of a group (custom function with automatic pagination)\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param maxMembers Maximum number of members to retrieve (default: unlimited)\r\n   * @param progressCallback Optional callback to track progress\r\n   * @returns All group members with pagination info\r\n   *\r\n   * This function automatically handles pagination to retrieve all members,\r\n   * not limited by the 50-member API limit per request.\r\n   */\r\n  async getAllGroupMembers(\r\n    accessToken: string,\r\n    groupId: string,\r\n    maxMembers?: number,\r\n    progressCallback?: (progress: GetAllMembersProgress) => void\r\n  ): Promise<AllGroupMembersResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (maxMembers !== undefined && maxMembers <= 0) {\r\n        throw new ZaloSDKError(\"Max members must be greater than 0\", -1);\r\n      }\r\n\r\n      const allMembers: GroupMemberInfo[] = [];\r\n      let offset = 0;\r\n      const pageSize = 50; // Maximum allowed by Zalo API\r\n      let totalCount: number | undefined;\r\n      let pagesFetched = 0;\r\n      let hasMore = true;\r\n\r\n      // Progress tracking\r\n      const updateProgress = (currentCount: number, isComplete: boolean = false) => {\r\n        if (progressCallback) {\r\n          const percentage = totalCount ? Math.round((currentCount / totalCount) * 100) : undefined;\r\n          progressCallback({\r\n            currentCount,\r\n            totalCount,\r\n            percentage,\r\n            isComplete\r\n          });\r\n        }\r\n      };\r\n\r\n      // Initial progress\r\n      updateProgress(0);\r\n\r\n      while (hasMore) {\r\n        // Check if we've reached the maximum limit\r\n        if (maxMembers && allMembers.length >= maxMembers) {\r\n          break;\r\n        }\r\n\r\n        // Calculate how many members to request in this batch\r\n        const remainingLimit = maxMembers ? maxMembers - allMembers.length : pageSize;\r\n        const currentPageSize = Math.min(pageSize, remainingLimit);\r\n\r\n        // Fetch current page\r\n        const response = await this.getGroupMembers(\r\n          accessToken,\r\n          groupId,\r\n          offset,\r\n          currentPageSize\r\n        );\r\n\r\n        // Handle API errors\r\n        if (response.error !== 0) {\r\n          throw new ZaloSDKError(\r\n            `Failed to fetch members at offset ${offset}: ${response.message}`,\r\n            response.error\r\n          );\r\n        }\r\n\r\n        // Check if we have data\r\n        if (!response.data || !response.data.members) {\r\n          break;\r\n        }\r\n\r\n        // Update total count from first response\r\n        if (totalCount === undefined && response.data.total !== undefined) {\r\n          totalCount = response.data.total;\r\n\r\n          // If maxMembers is set and less than total, use maxMembers as effective total\r\n          if (maxMembers && maxMembers < totalCount) {\r\n            totalCount = maxMembers;\r\n          }\r\n        }\r\n\r\n        // Add members to our collection\r\n        allMembers.push(...response.data.members);\r\n        pagesFetched++;\r\n\r\n        // Update progress\r\n        updateProgress(allMembers.length);\r\n\r\n        // Check if we should continue\r\n        const receivedCount = response.data.members.length;\r\n        hasMore = receivedCount === currentPageSize &&\r\n          (!maxMembers || allMembers.length < maxMembers) &&\r\n          (response.data.total === undefined || allMembers.length < response.data.total);\r\n\r\n        // Move to next page\r\n        offset += receivedCount;\r\n\r\n        // Safety check to prevent infinite loops\r\n        if (pagesFetched > 1000) { // Max 50,000 members (50 * 1000)\r\n          console.warn(`getAllGroupMembers: Reached maximum page limit (${pagesFetched}) for group ${groupId}`);\r\n          break;\r\n        }\r\n      }\r\n\r\n      // Final progress update\r\n      updateProgress(allMembers.length, true);\r\n\r\n      // Apply maxMembers limit if specified\r\n      const finalMembers = maxMembers ? allMembers.slice(0, maxMembers) : allMembers;\r\n\r\n      // Return enhanced response\r\n      return {\r\n        error: 0,\r\n        message: \"Success\",\r\n        data: {\r\n          total_members: totalCount || allMembers.length,\r\n          members: finalMembers,\r\n          pages_fetched: pagesFetched,\r\n          is_complete: !maxMembers || allMembers.length <= maxMembers\r\n        }\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get all group members\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ALL members of a group with simple interface (no progress tracking)\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param maxMembers Maximum number of members to retrieve (optional)\r\n   * @returns Array of all group members\r\n   *\r\n   * Simplified version of getAllGroupMembers that returns just the members array\r\n   */\r\n  async getAllGroupMembersSimple(\r\n    accessToken: string,\r\n    groupId: string,\r\n    maxMembers?: number\r\n  ): Promise<GroupMemberInfo[]> {\r\n    const response = await this.getAllGroupMembers(accessToken, groupId, maxMembers);\r\n    return response.data.members;\r\n  }\r\n\r\n  /**\r\n   * Get ALL members of a group with DETAILED user information (advanced API)\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param maxMembers Maximum number of members to retrieve (default: unlimited)\r\n   * @param progressCallback Optional callback to track progress\r\n   * @param options Advanced options for fetching details\r\n   * @returns All group members with detailed user information\r\n   *\r\n   * This function:\r\n   * 1. First gets all basic member info using getAllGroupMembers()\r\n   * 2. Then fetches detailed user info for each member using UserService.getUserInfo()\r\n   * 3. Combines both basic and detailed info into EnhancedGroupMember objects\r\n   * 4. If detailed info fails to fetch, falls back to basic info only\r\n   * 5. Provides detailed progress tracking and statistics\r\n   */\r\n  async getAllGroupMembersWithDetails(\r\n    accessToken: string,\r\n    groupId: string,\r\n    maxMembers?: number,\r\n    progressCallback?: (progress: GetAllMembersWithDetailsProgress) => void,\r\n    options?: {\r\n      /** Batch size for fetching user details (default: 10) */\r\n      detailBatchSize?: number;\r\n      /** Timeout for each user detail request in ms (default: 5000) */\r\n      detailTimeout?: number;\r\n      /** Whether to continue if UserService is not available (default: true) */\r\n      continueWithoutUserService?: boolean;\r\n      /** Maximum concurrent detail requests (default: 5) */\r\n      maxConcurrentRequests?: number;\r\n    }\r\n  ): Promise<AllGroupMembersWithDetailsResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      if (!groupId || groupId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Group ID cannot be empty\", -1);\r\n      }\r\n\r\n      // Set default options\r\n      const opts = {\r\n        detailBatchSize: 10,\r\n        detailTimeout: 5000,\r\n        continueWithoutUserService: true,\r\n        maxConcurrentRequests: 5,\r\n        ...options\r\n      };\r\n\r\n      // Check if UserService is available\r\n      if (!this.userService && !opts.continueWithoutUserService) {\r\n        throw new ZaloSDKError(\r\n          \"UserService is required for fetching detailed member information. \" +\r\n          \"Please provide UserService in constructor or set continueWithoutUserService to true.\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Progress tracking helper\r\n      const updateProgress = (\r\n        currentCount: number,\r\n        totalCount: number | undefined,\r\n        phase: GetAllMembersWithDetailsProgress['phase'],\r\n        phaseDetails?: GetAllMembersWithDetailsProgress['phase_details'],\r\n        isComplete: boolean = false\r\n      ) => {\r\n        if (progressCallback) {\r\n          const percentage = totalCount ? Math.round((currentCount / totalCount) * 100) : undefined;\r\n          progressCallback({\r\n            currentCount,\r\n            totalCount,\r\n            percentage,\r\n            isComplete,\r\n            phase,\r\n            phase_details: phaseDetails\r\n          });\r\n        }\r\n      };\r\n\r\n      // Phase 1: Get all basic member information\r\n      updateProgress(0, undefined, 'fetching_members');\r\n\r\n      const basicMembersResponse = await this.getAllGroupMembers(\r\n        accessToken,\r\n        groupId,\r\n        maxMembers,\r\n        (basicProgress) => {\r\n          updateProgress(\r\n            basicProgress.currentCount,\r\n            basicProgress.totalCount,\r\n            'fetching_members'\r\n          );\r\n        }\r\n      );\r\n\r\n      const basicMembers = basicMembersResponse.data.members;\r\n      const totalMembers = basicMembers.length;\r\n\r\n      // Initialize enhanced members with basic info\r\n      const enhancedMembers: EnhancedGroupMember[] = basicMembers.map((member: GroupMemberInfo) => ({\r\n        basic_info: member,\r\n        detailed_info: null as UserInfo | null,\r\n        has_detailed_info: false\r\n      }));\r\n\r\n      // Statistics tracking\r\n      let successfulDetails = 0;\r\n      let failedDetails = 0;\r\n\r\n      // Phase 2: Fetch detailed user information (if UserService is available)\r\n      if (this.userService && totalMembers > 0) {\r\n        updateProgress(0, totalMembers, 'fetching_details', {\r\n          details_fetched: 0,\r\n          details_failed: 0,\r\n          current_batch: 0,\r\n          total_batches: Math.ceil(totalMembers / opts.detailBatchSize)\r\n        });\r\n\r\n        // Process members in batches to avoid overwhelming the API\r\n        for (let i = 0; i < totalMembers; i += opts.detailBatchSize) {\r\n          const batch = enhancedMembers.slice(i, i + opts.detailBatchSize);\r\n          const currentBatch = Math.floor(i / opts.detailBatchSize) + 1;\r\n          const totalBatches = Math.ceil(totalMembers / opts.detailBatchSize);\r\n\r\n          // Create promises for current batch with concurrency limit\r\n          const batchPromises = batch.map(async (enhancedMember) => {\r\n            const userId = enhancedMember.basic_info.user_id || enhancedMember.basic_info.oa_id;\r\n\r\n            if (!userId) {\r\n              enhancedMember.detail_fetch_error = \"No user_id or oa_id available\";\r\n              failedDetails++;\r\n              return;\r\n            }\r\n\r\n            try {\r\n              // Create timeout promise\r\n              const timeoutPromise = new Promise<never>((_, reject) => {\r\n                setTimeout(() => reject(new Error('Timeout')), opts.detailTimeout);\r\n              });\r\n\r\n              // Fetch user details with timeout\r\n              const userDetailPromise = this.userService!.getUserInfo(accessToken, userId);\r\n              const userDetail = await Promise.race([userDetailPromise, timeoutPromise]);\r\n\r\n              enhancedMember.detailed_info = userDetail;\r\n              enhancedMember.has_detailed_info = true;\r\n              successfulDetails++;\r\n\r\n            } catch (error) {\r\n              const errorMessage = error instanceof Error ? error.message : 'Unknown error';\r\n              enhancedMember.detail_fetch_error = `Failed to fetch user details: ${errorMessage}`;\r\n              failedDetails++;\r\n            }\r\n\r\n            // Update progress for each member processed\r\n            const processedCount = successfulDetails + failedDetails;\r\n            updateProgress(processedCount, totalMembers, 'fetching_details', {\r\n              details_fetched: successfulDetails,\r\n              details_failed: failedDetails,\r\n              current_batch: currentBatch,\r\n              total_batches: totalBatches\r\n            });\r\n          });\r\n\r\n          // Process batch with concurrency limit\r\n          const chunks = [];\r\n          for (let j = 0; j < batchPromises.length; j += opts.maxConcurrentRequests) {\r\n            chunks.push(batchPromises.slice(j, j + opts.maxConcurrentRequests));\r\n          }\r\n\r\n          for (const chunk of chunks) {\r\n            await Promise.all(chunk);\r\n          }\r\n        }\r\n      } else if (!this.userService) {\r\n        // UserService not available, mark all as failed but continue\r\n        failedDetails = totalMembers;\r\n        enhancedMembers.forEach(member => {\r\n          member.detail_fetch_error = \"UserService not available\";\r\n        });\r\n      }\r\n\r\n      // Final progress update\r\n      updateProgress(totalMembers, totalMembers, 'complete', {\r\n        details_fetched: successfulDetails,\r\n        details_failed: failedDetails\r\n      }, true);\r\n\r\n      // Calculate success rate\r\n      const successRate = totalMembers > 0 ? Math.round((successfulDetails / totalMembers) * 100) : 0;\r\n\r\n      // Apply maxMembers limit if specified\r\n      const finalMembers = maxMembers ? enhancedMembers.slice(0, maxMembers) : enhancedMembers;\r\n\r\n      // Return enhanced response\r\n      return {\r\n        error: 0,\r\n        message: \"Success\",\r\n        data: {\r\n          total_members: basicMembersResponse.data.total_members,\r\n          members: finalMembers,\r\n          pages_fetched: basicMembersResponse.data.pages_fetched,\r\n          is_complete: basicMembersResponse.data.is_complete,\r\n          detail_fetch_stats: {\r\n            total_processed: totalMembers,\r\n            successful_details: successfulDetails,\r\n            failed_details: failedDetails,\r\n            success_rate: successRate\r\n          }\r\n        }\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleGroupManagementError(\r\n        error,\r\n        \"Failed to get all group members with details\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get ALL members of a group with detailed info - Simple interface\r\n   * @param accessToken OA access token\r\n   * @param groupId Group ID\r\n   * @param maxMembers Maximum number of members to retrieve (optional)\r\n   * @returns Array of enhanced group members with detailed info\r\n   *\r\n   * Simplified version that returns just the enhanced members array\r\n   */\r\n  async getAllGroupMembersWithDetailsSimple(\r\n    accessToken: string,\r\n    groupId: string,\r\n    maxMembers?: number\r\n  ): Promise<EnhancedGroupMember[]> {\r\n    const response = await this.getAllGroupMembersWithDetails(accessToken, groupId, maxMembers);\r\n    return response.data.members;\r\n  }\r\n\r\n  private handleGroupManagementError(\r\n    error: any,\r\n    defaultMessage: string\r\n  ): Error {\r\n    if (error instanceof ZaloSDKError) {\r\n      return error;\r\n    }\r\n    if (error.response?.data) {\r\n      const errorData = error.response.data;\r\n      return new ZaloSDKError(\r\n        `${defaultMessage}: ${errorData.message || errorData.error || \"Unknown error\"\r\n        }`,\r\n        errorData.error || -1,\r\n        errorData\r\n      );\r\n    }\r\n    return new ZaloSDKError(\r\n      `${defaultMessage}: ${error.message || \"Unknown error\"}`,\r\n      -1,\r\n      error\r\n    );\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  ArticleRequest,\r\n  ArticleNormalRequest,\r\n  ArticleVideoRequest,\r\n  ArticleResponse,\r\n  ArticleProcessResponse,\r\n  ArticleVerifyResponse,\r\n  ArticleDetail,\r\n  ArticleDetailNormal,\r\n  ArticleDetailVideo,\r\n  ArticleListRequest,\r\n  ArticleListResponse,\r\n  ArticleListItem,\r\n  ArticleRemoveRequest,\r\n  ArticleRemoveResponse,\r\n  ArticleUpdateRequest,\r\n  ArticleUpdateNormalRequest,\r\n  ArticleUpdateVideoRequest,\r\n  ArticleUpdateResponse,\r\n  ArticleCover,\r\n  ArticleBodyItem,\r\n  ARTICLE_CONSTRAINTS,\r\n  ArticleStatus,\r\n  CommentStatus,\r\n  ArticleType,\r\n  CoverType,\r\n  BodyItemType,\r\n  BulkRemoveItemResult,\r\n  BulkRemoveProgress,\r\n  BulkRemoveOptions,\r\n  BulkRemoveResult,\r\n  BulkDetailItemResult,\r\n  BulkDetailProgress,\r\n  BulkDetailOptions,\r\n  BulkDetailResult,\r\n} from \"../types/article\";\r\nimport { ZaloSDKError, ZaloResponse } from \"../types/common\";\r\n\r\n/**\r\n * Service for handling Zalo Official Account Article Management APIs\r\n *\r\n * CONDITIONS FOR USING ZALO ARTICLE MANAGEMENT:\r\n *\r\n * 1. GENERAL CONDITIONS:\r\n *    - OA must have permission to create articles\r\n *    - Access token must have \"manage_article\" scope\r\n *    - OA must have active status and be verified\r\n *\r\n * 2. ARTICLE CREATION:\r\n *    - Title: required, max 150 characters\r\n *    - Author: required for normal articles, max 50 characters\r\n *    - Description: required, max 300 characters\r\n *    - Image size: max 1MB per image\r\n *    - Status: 'show' (publish immediately) or 'hide' (draft)\r\n *    - Comment: 'show' (allow comments) or 'hide' (disable comments)\r\n *\r\n * 3. CONTENT VALIDATION:\r\n *    - Normal articles: must have cover, body content, and author\r\n *    - Video articles: must have video_id and avatar\r\n *    - Body items: support text, image, video, and product types\r\n *    - Tracking links: must be valid URLs\r\n *\r\n * 4. LIMITS AND CONSTRAINTS:\r\n *    - Article list: max 10 items per request\r\n *    - Processing time: use token to check progress\r\n *    - Update frequency: reasonable intervals recommended\r\n */\r\nexport class ArticleService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    article: {\r\n      create: \"https://openapi.zalo.me/v2.0/article/create\",\r\n      update: \"https://openapi.zalo.me/v2.0/article/update\",\r\n      remove: \"https://openapi.zalo.me/v2.0/article/remove\",\r\n      verify: \"https://openapi.zalo.me/v2.0/article/verify\",\r\n      detail: \"https://openapi.zalo.me/v2.0/article/getdetail\",\r\n      list: \"https://openapi.zalo.me/v2.0/article/getslice\",\r\n    },\r\n  } as const;\r\n\r\n  constructor(private readonly client: ZaloClient) { }\r\n\r\n  /**\r\n   * Create normal article with rich content\r\n   * @param accessToken OA access token\r\n   * @param request Normal article information\r\n   * @returns Token for tracking creation progress\r\n   */\r\n  async createNormalArticle(\r\n    accessToken: string,\r\n    request: ArticleNormalRequest\r\n  ): Promise<ArticleResponse> {\r\n    try {\r\n      // Validate input\r\n      this.validateNormalArticleRequest(request);\r\n\r\n      // Process tracking link - use default if not provided or invalid\r\n      let processedTrackingLink = \"https://v2.redai.vn/auth\";\r\n      if (\r\n        request.tracking_link &&\r\n        this.isValidTrackingLink(request.tracking_link)\r\n      ) {\r\n        processedTrackingLink = request.tracking_link;\r\n      }\r\n\r\n      const data = {\r\n        type: request.type,\r\n        title: request.title,\r\n        author: request.author,\r\n        cover: request.cover,\r\n        description: request.description,\r\n        body: request.body,\r\n        related_medias: request.related_medias || [],\r\n        tracking_link: processedTrackingLink,\r\n        status: request.status || ArticleStatus.HIDE,\r\n        comment: request.comment || CommentStatus.SHOW,\r\n      };\r\n\r\n      // Gọi API và nhận đúng cấu trúc bao bọc của Zalo\r\n      const response = await this.client.apiPost<ZaloResponse<{ token: string }>>(\r\n        this.endpoints.article.create,\r\n        accessToken,\r\n        data\r\n      );\r\n      // Trả về đầy đủ cấu trúc response theo type chuẩn\r\n      if (!response.data || !response.data.token) {\r\n        throw new ZaloSDKError(\"No token received from article creation\", -1, response);\r\n      }\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to create normal article\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Create video article\r\n   * @param accessToken OA access token\r\n   * @param request Video article information\r\n   * @returns Token for tracking creation progress\r\n   */\r\n  async createVideoArticle(\r\n    accessToken: string,\r\n    request: ArticleVideoRequest\r\n  ): Promise<ArticleResponse> {\r\n    try {\r\n      // Validate input\r\n      this.validateVideoArticleRequest(request);\r\n\r\n      const data = {\r\n        type: request.type,\r\n        title: request.title,\r\n        description: request.description,\r\n        video_id: request.video_id,\r\n        avatar: request.avatar,\r\n        status: request.status || ArticleStatus.HIDE,\r\n        comment: request.comment || CommentStatus.SHOW,\r\n      };\r\n\r\n      // Gọi API và nhận đúng cấu trúc bao bọc của Zalo\r\n      const response = await this.client.apiPost<ZaloResponse<{ token: string }>>(\r\n        this.endpoints.article.create,\r\n        accessToken,\r\n        data\r\n      );\r\n      // Trả về đầy đủ cấu trúc response theo type chuẩn\r\n      if (!response.data || !response.data.token) {\r\n        throw new ZaloSDKError(\"No token received from article creation\", -1, response);\r\n      }\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to create video article\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Create article (automatically detect type)\r\n   * @param accessToken OA access token\r\n   * @param request Article information\r\n   * @returns Token for tracking creation progress\r\n   */\r\n  async createArticle(\r\n    accessToken: string,\r\n    request: ArticleRequest\r\n  ): Promise<ArticleResponse> {\r\n    if (request.type === ArticleType.NORMAL) {\r\n      return this.createNormalArticle(accessToken, request);\r\n    } else if (request.type === ArticleType.VIDEO) {\r\n      return this.createVideoArticle(accessToken, request);\r\n    } else {\r\n      throw new ZaloSDKError(\r\n        'Invalid article type. Only \"normal\" and \"video\" are supported',\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Check article creation progress\r\n   * @param accessToken OA access token\r\n   * @param token Token from article creation response\r\n   * @returns Progress information\r\n   */\r\n  async checkArticleProcess(\r\n    accessToken: string,\r\n    token: string\r\n  ): Promise<ArticleProcessResponse> {\r\n    try {\r\n      if (!token || token.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Token cannot be empty\", -1);\r\n      }\r\n\r\n      // Use verifyArticle method to check progress\r\n      const response = await this.verifyArticle(accessToken, token);\r\n\r\n      // Convert response to standard format\r\n      const result: ArticleProcessResponse = {\r\n        article_id: response?.id,\r\n        status: response?.id ? \"success\" : \"processing\",\r\n        created_time: Date.now(),\r\n        updated_time: Date.now(),\r\n      };\r\n\r\n      return result;\r\n    } catch (error) {\r\n      // Handle token expiration - means article processing is completed\r\n      if (\r\n        error instanceof ZaloSDKError &&\r\n        error.message.includes(\"token was expired\")\r\n      ) {\r\n        return {\r\n          status: \"success\",\r\n          error_message: \"Token expired - article processing completed\",\r\n          created_time: Date.now(),\r\n          updated_time: Date.now(),\r\n        };\r\n      }\r\n\r\n      // Handle API errors - article may still be processing\r\n      if (error instanceof ZaloSDKError && error.message.includes(\"API\")) {\r\n        return {\r\n          status: \"processing\",\r\n          created_time: Date.now(),\r\n          updated_time: Date.now(),\r\n        };\r\n      }\r\n\r\n      // Handle response structure errors\r\n      if (error instanceof TypeError) {\r\n        return {\r\n          status: \"processing\",\r\n          error_message: \"Article is being processed\",\r\n          created_time: Date.now(),\r\n          updated_time: Date.now(),\r\n        };\r\n      }\r\n\r\n      throw this.handleArticleError(error, \"Failed to check article process\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Verify article and get article ID\r\n   * @param accessToken OA access token\r\n   * @param token Token from article creation or update response\r\n   * @returns Article ID\r\n   */\r\n  async verifyArticle(\r\n    accessToken: string,\r\n    token: string\r\n  ): Promise<ArticleVerifyResponse> {\r\n    try {\r\n      if (!token || token.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Token cannot be empty\", -1);\r\n      }\r\n\r\n      const data = {\r\n        token: token,\r\n      };\r\n\r\n      const response = await this.client.apiPost<{ data: ArticleVerifyResponse }>(\r\n        this.endpoints.article.verify,\r\n        accessToken,\r\n        data\r\n      );\r\n\r\n      return response.data;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to verify article\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get article details\r\n   * @param accessToken OA access token\r\n   * @param articleId Article ID\r\n   * @returns Article details\r\n   */\r\n  async getArticleDetail(\r\n    accessToken: string,\r\n    articleId: string\r\n  ): Promise<ArticleDetail> {\r\n    try {\r\n      if (!articleId || articleId.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Article ID cannot be empty\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<{ data: ArticleDetail }>(\r\n        this.endpoints.article.detail,\r\n        accessToken,\r\n        { id: articleId }\r\n      );\r\n\r\n      return response.data;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to get article detail\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get article list\r\n   * @param accessToken OA access token\r\n   * @param request List parameters\r\n   * @returns Article list\r\n   */\r\n  async getArticleList(\r\n    accessToken: string,\r\n    request: ArticleListRequest\r\n  ): Promise<ArticleListResponse> {\r\n    try {\r\n      // Validate input\r\n      this.validateArticleListRequest(request);\r\n\r\n      const response = await this.client.apiGet<ArticleListResponse>(\r\n        this.endpoints.article.list,\r\n        accessToken,\r\n        {\r\n          offset: request.offset,\r\n          limit: request.limit,\r\n          type: request.type,\r\n        }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to get article list\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all articles by automatically fetching all pages\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * // Get all normal articles with progress tracking\r\n   * const result = await articleService.getAllArticles(accessToken, \"normal\", {\r\n   *   batchSize: 50,\r\n   *   maxArticles: 1000,\r\n   *   onProgress: (progress) => {\r\n   *     console.log(`Batch ${progress.currentBatch}: ${progress.totalFetched} articles fetched`);\r\n   *   }\r\n   * });\r\n   *\r\n   * console.log(`Total: ${result.totalFetched} articles in ${result.totalBatches} batches`);\r\n   * console.log(`Has more: ${result.hasMore}`);\r\n   * ```\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param type Article type (\"normal\" or \"video\")\r\n   * @param options Configuration options for fetching\r\n   * @returns All articles with pagination info\r\n   */\r\n  async getAllArticles(\r\n    accessToken: string,\r\n    type: \"normal\" | \"video\" = \"normal\",\r\n    options: {\r\n      /** Number of articles to fetch per request (default: 10, max: 10) */\r\n      batchSize?: number;\r\n      /** Maximum number of articles to fetch (default: 1000, 0 = no limit) */\r\n      maxArticles?: number;\r\n      /** Optional callback to track progress */\r\n      onProgress?: (progress: {\r\n        currentBatch: number;\r\n        totalFetched: number;\r\n        hasMore: boolean;\r\n      }) => void;\r\n    } = {}\r\n  ): Promise<{\r\n    /** Array of all fetched articles */\r\n    articles: ArticleListItem[];\r\n    /** Total number of articles fetched */\r\n    totalFetched: number;\r\n    /** Number of API calls made */\r\n    totalBatches: number;\r\n    /** Whether there are more articles available */\r\n    hasMore: boolean;\r\n  }> {\r\n    try {\r\n      const {\r\n        batchSize = 10, // Zalo API maximum limit is 10\r\n        maxArticles = 1000,\r\n        onProgress\r\n      } = options;\r\n\r\n      // Validate parameters\r\n      if (batchSize <= 0 || batchSize > ARTICLE_CONSTRAINTS.LIST_MAX_LIMIT) {\r\n        throw new ZaloSDKError(\r\n          `Batch size must be between 1 and ${ARTICLE_CONSTRAINTS.LIST_MAX_LIMIT}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (maxArticles < 0) {\r\n        throw new ZaloSDKError(\"Max articles must be >= 0 (0 = no limit)\", -1);\r\n      }\r\n\r\n      if (![\"normal\", \"video\"].includes(type)) {\r\n        throw new ZaloSDKError('Type must be \"normal\" or \"video\"', -1);\r\n      }\r\n\r\n      const allArticles: ArticleListItem[] = [];\r\n      let offset = 0;\r\n      let currentBatch = 0;\r\n      let hasMore = true;\r\n\r\n      while (hasMore) {\r\n        currentBatch++;\r\n\r\n        // Calculate limit for this batch\r\n        let currentLimit = batchSize;\r\n        if (maxArticles > 0) {\r\n          const remaining = maxArticles - allArticles.length;\r\n          if (remaining <= 0) break;\r\n          currentLimit = Math.min(batchSize, remaining);\r\n        }\r\n\r\n        // Fetch current batch\r\n        const response = await this.getArticleList(accessToken, {\r\n          offset,\r\n          limit: currentLimit,\r\n          type\r\n        });\r\n\r\n        // Add articles to collection\r\n        // Check if response is successful and has data\r\n        if ('data' in response && response.data?.medias && response.data.medias.length > 0) {\r\n          allArticles.push(...response.data.medias);\r\n          offset += response.data.medias.length;\r\n\r\n          // Check if we have more data\r\n          hasMore = response.data.medias.length === currentLimit;\r\n\r\n          // Stop if we've reached max articles\r\n          if (maxArticles > 0 && allArticles.length >= maxArticles) {\r\n            hasMore = false;\r\n          }\r\n        } else if ('medias' in response && response.medias && response.medias.length > 0) {\r\n          // Handle direct response format (fallback)\r\n          allArticles.push(...response.medias);\r\n          offset += response.medias.length;\r\n\r\n          // Check if we have more data\r\n          hasMore = response.medias.length === currentLimit;\r\n\r\n          // Stop if we've reached max articles\r\n          if (maxArticles > 0 && allArticles.length >= maxArticles) {\r\n            hasMore = false;\r\n          }\r\n        } else {\r\n          hasMore = false;\r\n        }\r\n\r\n        // Call progress callback if provided\r\n        if (onProgress) {\r\n          onProgress({\r\n            currentBatch,\r\n            totalFetched: allArticles.length,\r\n            hasMore\r\n          });\r\n        }\r\n\r\n        // Safety check to prevent infinite loops\r\n        if (currentBatch > 100) {\r\n          console.warn(\"Reached maximum batch limit (100), stopping fetch\");\r\n          break;\r\n        }\r\n      }\r\n\r\n      return {\r\n        articles: allArticles,\r\n        totalFetched: allArticles.length,\r\n        totalBatches: currentBatch,\r\n        hasMore: hasMore && (maxArticles === 0 || allArticles.length < maxArticles)\r\n      };\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to get all articles\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all articles of both types (normal and video) combined\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * // Get all articles (both normal and video)\r\n   * const result = await articleService.getAllArticlesCombined(accessToken, {\r\n   *   batchSize: 50,\r\n   *   maxArticlesPerType: 500,\r\n   *   onProgress: (progress) => {\r\n   *     console.log(`${progress.type}: Batch ${progress.currentBatch}, Total: ${progress.totalFetched}`);\r\n   *   }\r\n   * });\r\n   *\r\n   * console.log(`Total articles: ${result.totalFetched}`);\r\n   * console.log(`Normal articles: ${result.breakdown.normal.totalFetched}`);\r\n   * console.log(`Video articles: ${result.breakdown.video.totalFetched}`);\r\n   * ```\r\n   *\r\n   * @param accessToken OA access token\r\n   * @param options Configuration options for fetching\r\n   * @returns All articles (normal + video) with combined pagination info\r\n   */\r\n  async getAllArticlesCombined(\r\n    accessToken: string,\r\n    options: {\r\n      /** Number of articles to fetch per request for each type (default: 10, max: 10) */\r\n      batchSize?: number;\r\n      /** Maximum number of articles to fetch per type (default: 500, 0 = no limit) */\r\n      maxArticlesPerType?: number;\r\n      /** Optional callback to track progress */\r\n      onProgress?: (progress: {\r\n        type: \"normal\" | \"video\";\r\n        currentBatch: number;\r\n        totalFetched: number;\r\n        hasMore: boolean;\r\n      }) => void;\r\n    } = {}\r\n  ): Promise<{\r\n    /** Array of all fetched articles (normal + video) */\r\n    articles: ArticleListItem[];\r\n    /** Breakdown by type */\r\n    breakdown: {\r\n      normal: {\r\n        articles: ArticleListItem[];\r\n        totalFetched: number;\r\n        totalBatches: number;\r\n        hasMore: boolean;\r\n      };\r\n      video: {\r\n        articles: ArticleListItem[];\r\n        totalFetched: number;\r\n        totalBatches: number;\r\n        hasMore: boolean;\r\n      };\r\n    };\r\n    /** Total number of articles fetched */\r\n    totalFetched: number;\r\n    /** Total number of API calls made */\r\n    totalBatches: number;\r\n  }> {\r\n    try {\r\n      const {\r\n        batchSize = 10, // Zalo API maximum limit is 10\r\n        maxArticlesPerType = 500,\r\n        onProgress\r\n      } = options;\r\n\r\n      // Fetch normal articles\r\n      const normalResult = await this.getAllArticles(accessToken, \"normal\", {\r\n        batchSize,\r\n        maxArticles: maxArticlesPerType,\r\n        onProgress: onProgress ? (progress) => onProgress({\r\n          type: \"normal\",\r\n          ...progress\r\n        }) : undefined\r\n      });\r\n\r\n      // Fetch video articles\r\n      const videoResult = await this.getAllArticles(accessToken, \"video\", {\r\n        batchSize,\r\n        maxArticles: maxArticlesPerType,\r\n        onProgress: onProgress ? (progress) => onProgress({\r\n          type: \"video\",\r\n          ...progress\r\n        }) : undefined\r\n      });\r\n\r\n      // Combine results\r\n      const allArticles = [...normalResult.articles, ...videoResult.articles];\r\n\r\n      return {\r\n        articles: allArticles,\r\n        breakdown: {\r\n          normal: normalResult,\r\n          video: videoResult\r\n        },\r\n        totalFetched: allArticles.length,\r\n        totalBatches: normalResult.totalBatches + videoResult.totalBatches\r\n      };\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to get all articles combined\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Remove article\r\n   * @param accessToken OA access token\r\n   * @param articleId Article ID to remove\r\n   * @returns Removal result\r\n   */\r\n  async removeArticle(\r\n    accessToken: string,\r\n    articleId: string\r\n  ): Promise<ArticleRemoveResponse> {\r\n    try {\r\n      if (!articleId || articleId.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Article ID cannot be empty\", -1);\r\n      }\r\n\r\n      const data: ArticleRemoveRequest = {\r\n        id: articleId,\r\n      };\r\n\r\n      const response = await this.client.apiPost<ArticleRemoveResponse>(\r\n        this.endpoints.article.remove,\r\n        accessToken,\r\n        data\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      // Handle case where article may already be deleted\r\n      if (\r\n        error instanceof ZaloSDKError &&\r\n        error.message.includes(\"No data received\")\r\n      ) {\r\n        return {\r\n          message: \"Article has been deleted or does not exist\",\r\n        };\r\n      }\r\n\r\n      throw this.handleArticleError(error, \"Failed to remove article\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Remove multiple articles in bulk (advanced function with progress tracking)\r\n   * @param accessToken OA access token\r\n   * @param articleIds Array of article IDs to remove\r\n   * @param options Options for bulk removal operation\r\n   * @param progressCallback Optional callback to track progress\r\n   * @returns Complete bulk removal result with detailed statistics\r\n   *\r\n   * This function automatically handles:\r\n   * - Batch processing to avoid overwhelming the API\r\n   * - Concurrent requests with configurable limits\r\n   * - Error handling for individual articles\r\n   * - Progress tracking and statistics\r\n   * - Retry mechanism for failed requests\r\n   */\r\n  async removeBulkArticles(\r\n    accessToken: string,\r\n    articleIds: string[],\r\n    options?: BulkRemoveOptions,\r\n    progressCallback?: (progress: BulkRemoveProgress) => void\r\n  ): Promise<BulkRemoveResult> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      if (!articleIds || articleIds.length === 0) {\r\n        throw new ZaloSDKError(\"Article IDs array cannot be empty\", -1);\r\n      }\r\n\r\n      // Filter out empty/invalid IDs\r\n      const validArticleIds = articleIds.filter(id => id && id.trim().length > 0);\r\n      if (validArticleIds.length === 0) {\r\n        throw new ZaloSDKError(\"No valid article IDs found\", -1);\r\n      }\r\n\r\n      // Set default options\r\n      const opts: Required<BulkRemoveOptions> = {\r\n        batch_size: 10,\r\n        max_concurrency: 3,\r\n        continue_on_error: true,\r\n        batch_delay: 100,\r\n        request_timeout: 10000,\r\n        ...options\r\n      };\r\n\r\n      // Initialize tracking variables\r\n      const results: BulkRemoveItemResult[] = [];\r\n      const errorSummary: { [errorType: string]: number } = {};\r\n      const startTime = Date.now();\r\n      let successfulCount = 0;\r\n      let failedCount = 0;\r\n\r\n      // Progress tracking helper\r\n      const updateProgress = (currentCount: number, isComplete: boolean = false) => {\r\n        if (progressCallback) {\r\n          const percentage = Math.round((currentCount / validArticleIds.length) * 100);\r\n          const currentBatch = Math.ceil(currentCount / opts.batch_size);\r\n          const totalBatches = Math.ceil(validArticleIds.length / opts.batch_size);\r\n\r\n          progressCallback({\r\n            current_count: currentCount,\r\n            total_count: validArticleIds.length,\r\n            percentage,\r\n            successful_count: successfulCount,\r\n            failed_count: failedCount,\r\n            is_complete: isComplete,\r\n            current_batch: currentBatch,\r\n            total_batches: totalBatches\r\n          });\r\n        }\r\n      };\r\n\r\n      // Initial progress\r\n      updateProgress(0);\r\n\r\n      // Process articles in batches\r\n      for (let i = 0; i < validArticleIds.length; i += opts.batch_size) {\r\n        const batch = validArticleIds.slice(i, i + opts.batch_size);\r\n\r\n        // Create promises for current batch with concurrency limit\r\n        const batchPromises = batch.map(async (articleId) => {\r\n          const itemStartTime = Date.now();\r\n\r\n          try {\r\n            // Create timeout promise\r\n            const timeoutPromise = new Promise<never>((_, reject) => {\r\n              setTimeout(() => reject(new Error('Request timeout')), opts.request_timeout);\r\n            });\r\n\r\n            // Remove article with timeout\r\n            const removePromise = this.removeArticle(accessToken, articleId);\r\n            const response = await Promise.race([removePromise, timeoutPromise]);\r\n\r\n            const processingTime = Date.now() - itemStartTime;\r\n            successfulCount++;\r\n\r\n            const result: BulkRemoveItemResult = {\r\n              article_id: articleId,\r\n              success: true,\r\n              message: response.message,\r\n              processing_time: processingTime\r\n            };\r\n\r\n            results.push(result);\r\n            return result;\r\n\r\n          } catch (error) {\r\n            const processingTime = Date.now() - itemStartTime;\r\n            failedCount++;\r\n\r\n            const errorMessage = error instanceof Error ? error.message : 'Unknown error';\r\n            const errorType = error instanceof ZaloSDKError ? 'ZaloSDKError' : 'UnknownError';\r\n\r\n            // Track error types\r\n            errorSummary[errorType] = (errorSummary[errorType] || 0) + 1;\r\n\r\n            const result: BulkRemoveItemResult = {\r\n              article_id: articleId,\r\n              success: false,\r\n              error: errorMessage,\r\n              processing_time: processingTime\r\n            };\r\n\r\n            results.push(result);\r\n\r\n            // Stop processing if continue_on_error is false\r\n            if (!opts.continue_on_error) {\r\n              throw new ZaloSDKError(\r\n                `Bulk removal stopped due to error on article ${articleId}: ${errorMessage}`,\r\n                -1\r\n              );\r\n            }\r\n\r\n            return result;\r\n          }\r\n        });\r\n\r\n        // Process batch with concurrency limit\r\n        const chunks = [];\r\n        for (let j = 0; j < batchPromises.length; j += opts.max_concurrency) {\r\n          chunks.push(batchPromises.slice(j, j + opts.max_concurrency));\r\n        }\r\n\r\n        for (const chunk of chunks) {\r\n          await Promise.all(chunk);\r\n        }\r\n\r\n        // Update progress after each batch\r\n        updateProgress(Math.min(i + opts.batch_size, validArticleIds.length));\r\n\r\n        // Add delay between batches (except for the last batch)\r\n        if (i + opts.batch_size < validArticleIds.length && opts.batch_delay > 0) {\r\n          await new Promise(resolve => setTimeout(resolve, opts.batch_delay));\r\n        }\r\n      }\r\n\r\n      // Final progress update\r\n      updateProgress(validArticleIds.length, true);\r\n\r\n      // Calculate final statistics\r\n      const totalTime = Date.now() - startTime;\r\n      const successRate = validArticleIds.length > 0\r\n        ? Math.round((successfulCount / validArticleIds.length) * 100)\r\n        : 0;\r\n\r\n      // Return complete result\r\n      return {\r\n        total_processed: validArticleIds.length,\r\n        successful_count: successfulCount,\r\n        failed_count: failedCount,\r\n        success_rate: successRate,\r\n        total_time: totalTime,\r\n        results: results.sort((a, b) =>\r\n          validArticleIds.indexOf(a.article_id) - validArticleIds.indexOf(b.article_id)\r\n        ),\r\n        error_summary: Object.keys(errorSummary).length > 0 ? errorSummary : undefined\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to remove articles in bulk\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Remove multiple articles in bulk with simple interface (no progress tracking)\r\n   * @param accessToken OA access token\r\n   * @param articleIds Array of article IDs to remove\r\n   * @param options Optional settings for bulk removal\r\n   * @returns Simple summary of bulk removal operation\r\n   *\r\n   * Simplified version of removeBulkArticles that returns just the summary statistics\r\n   */\r\n  async removeBulkArticlesSimple(\r\n    accessToken: string,\r\n    articleIds: string[],\r\n    options?: BulkRemoveOptions\r\n  ): Promise<{\r\n    total_processed: number;\r\n    successful_count: number;\r\n    failed_count: number;\r\n    success_rate: number;\r\n    failed_articles?: string[];\r\n  }> {\r\n    const result = await this.removeBulkArticles(accessToken, articleIds, options);\r\n\r\n    const failedArticles = result.results\r\n      .filter(r => !r.success)\r\n      .map(r => r.article_id);\r\n\r\n    return {\r\n      total_processed: result.total_processed,\r\n      successful_count: result.successful_count,\r\n      failed_count: result.failed_count,\r\n      success_rate: result.success_rate,\r\n      failed_articles: failedArticles.length > 0 ? failedArticles : undefined\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Get all articles with full details (advanced function with progress tracking)\r\n   * @param accessToken OA access token\r\n   * @param type Article type (\"normal\", \"video\", or \"both\")\r\n   * @param options Options for bulk details fetch operation\r\n   * @param progressCallback Optional callback to track progress\r\n   * @returns Complete result with all articles and their full details\r\n   *\r\n   * This function automatically:\r\n   * 1. Fetches all articles list using getAllArticles() or getAllArticlesCombined()\r\n   * 2. Fetches detailed information for each article using getArticleDetail()\r\n   * 3. Handles batch processing and concurrency control\r\n   * 4. Provides detailed progress tracking and error handling\r\n   * 5. Returns both successful and failed results with statistics\r\n   */\r\n  async getAllArticlesWithDetails(\r\n    accessToken: string,\r\n    type: \"normal\" | \"video\" | \"both\" = \"both\",\r\n    options?: BulkDetailOptions,\r\n    progressCallback?: (progress: BulkDetailProgress) => void\r\n  ): Promise<BulkDetailResult> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      // Set default options\r\n      const opts: Required<BulkDetailOptions> = {\r\n        detail_batch_size: 5,\r\n        max_concurrency: 3,\r\n        continue_on_error: true,\r\n        batch_delay: 200,\r\n        request_timeout: 15000,\r\n        max_articles: 0, // 0 = unlimited\r\n        list_options: {\r\n          batch_size: 10, // Zalo API maximum limit is 10\r\n          max_articles: 0 // 0 = unlimited\r\n        },\r\n        ...options\r\n      };\r\n\r\n      // Initialize tracking variables\r\n      const results: BulkDetailItemResult[] = [];\r\n      const articlesWithDetails: ArticleDetail[] = [];\r\n      const errorSummary: { [errorType: string]: number } = {};\r\n      const startTime = Date.now();\r\n      let successfulCount = 0;\r\n      let failedCount = 0;\r\n\r\n      // Progress tracking helper\r\n      const updateProgress = (\r\n        currentCount: number,\r\n        totalCount: number,\r\n        phase: BulkDetailProgress['phase'],\r\n        isComplete: boolean = false\r\n      ) => {\r\n        if (progressCallback) {\r\n          const percentage = totalCount > 0 ? Math.round((currentCount / totalCount) * 100) : 0;\r\n          const currentBatch = phase === 'fetching_details'\r\n            ? Math.ceil(currentCount / opts.detail_batch_size)\r\n            : undefined;\r\n          const totalBatches = phase === 'fetching_details'\r\n            ? Math.ceil(totalCount / opts.detail_batch_size)\r\n            : undefined;\r\n\r\n          progressCallback({\r\n            current_count: currentCount,\r\n            total_count: totalCount,\r\n            percentage,\r\n            successful_count: successfulCount,\r\n            failed_count: failedCount,\r\n            is_complete: isComplete,\r\n            phase,\r\n            current_batch: currentBatch,\r\n            total_batches: totalBatches\r\n          });\r\n        }\r\n      };\r\n\r\n      // Phase 1: Fetch articles list\r\n      updateProgress(0, 0, 'fetching_list');\r\n\r\n      const listStartTime = Date.now();\r\n      let articlesList: ArticleListItem[] = [];\r\n      let listFetchStats = { total_pages_fetched: 0, list_fetch_time: 0 };\r\n      const listMaxArticles = opts.list_options.max_articles ?? 500;\r\n\r\n      if (type === \"both\") {\r\n        const combinedResult = await this.getAllArticlesCombined(accessToken, {\r\n          batchSize: opts.list_options.batch_size,\r\n          maxArticlesPerType: listMaxArticles,\r\n          onProgress: (progress) => {\r\n            // Convert combined progress to simple progress for our callback\r\n            updateProgress(progress.totalFetched, progress.totalFetched, 'fetching_list');\r\n          }\r\n        });\r\n\r\n        articlesList = combinedResult.articles;\r\n        listFetchStats.total_pages_fetched = combinedResult.totalBatches;\r\n\r\n      } else {\r\n        const singleResult = await this.getAllArticles(accessToken, type, {\r\n          batchSize: opts.list_options.batch_size,\r\n          maxArticles: listMaxArticles,\r\n          onProgress: (progress) => {\r\n            updateProgress(progress.totalFetched, progress.totalFetched, 'fetching_list');\r\n          }\r\n        });\r\n\r\n        articlesList = singleResult.articles;\r\n        listFetchStats.total_pages_fetched = singleResult.totalBatches;\r\n      }\r\n\r\n      listFetchStats.list_fetch_time = Date.now() - listStartTime;\r\n\r\n      // Apply max_articles limit if specified\r\n      if (opts.max_articles > 0 && articlesList.length > opts.max_articles) {\r\n        articlesList = articlesList.slice(0, opts.max_articles);\r\n      }\r\n\r\n      const totalArticles = articlesList.length;\r\n\r\n      if (totalArticles === 0) {\r\n        updateProgress(0, 0, 'complete', true);\r\n        return {\r\n          total_processed: 0,\r\n          successful_count: 0,\r\n          failed_count: 0,\r\n          success_rate: 0,\r\n          total_time: Date.now() - startTime,\r\n          results: [],\r\n          articles_with_details: [],\r\n          list_fetch_stats: listFetchStats\r\n        };\r\n      }\r\n\r\n      // Phase 2: Fetch details for each article\r\n      updateProgress(0, totalArticles, 'fetching_details');\r\n\r\n      // Process articles in batches\r\n      for (let i = 0; i < totalArticles; i += opts.detail_batch_size) {\r\n        const batch = articlesList.slice(i, i + opts.detail_batch_size);\r\n\r\n        // Create promises for current batch with concurrency limit\r\n        const batchPromises = batch.map(async (article) => {\r\n          const itemStartTime = Date.now();\r\n\r\n          try {\r\n            // Create timeout promise\r\n            const timeoutPromise = new Promise<never>((_, reject) => {\r\n              setTimeout(() => reject(new Error('Request timeout')), opts.request_timeout);\r\n            });\r\n\r\n            // Fetch article detail with timeout\r\n            const detailPromise = this.getArticleDetail(accessToken, article.id);\r\n            const detail = await Promise.race([detailPromise, timeoutPromise]);\r\n\r\n            const processingTime = Date.now() - itemStartTime;\r\n            successfulCount++;\r\n\r\n            const result: BulkDetailItemResult = {\r\n              article_id: article.id,\r\n              success: true,\r\n              detail: detail,\r\n              processing_time: processingTime\r\n            };\r\n\r\n            results.push(result);\r\n            articlesWithDetails.push(detail);\r\n            return result;\r\n\r\n          } catch (error) {\r\n            const processingTime = Date.now() - itemStartTime;\r\n            failedCount++;\r\n\r\n            const errorMessage = error instanceof Error ? error.message : 'Unknown error';\r\n            const errorType = error instanceof ZaloSDKError ? 'ZaloSDKError' : 'UnknownError';\r\n\r\n            // Track error types\r\n            errorSummary[errorType] = (errorSummary[errorType] || 0) + 1;\r\n\r\n            const result: BulkDetailItemResult = {\r\n              article_id: article.id,\r\n              success: false,\r\n              error: errorMessage,\r\n              processing_time: processingTime\r\n            };\r\n\r\n            results.push(result);\r\n\r\n            // Stop processing if continue_on_error is false\r\n            if (!opts.continue_on_error) {\r\n              throw new ZaloSDKError(\r\n                `Bulk details fetch stopped due to error on article ${article.id}: ${errorMessage}`,\r\n                -1\r\n              );\r\n            }\r\n\r\n            return result;\r\n          }\r\n        });\r\n\r\n        // Process batch with concurrency limit\r\n        const chunks = [];\r\n        for (let j = 0; j < batchPromises.length; j += opts.max_concurrency) {\r\n          chunks.push(batchPromises.slice(j, j + opts.max_concurrency));\r\n        }\r\n\r\n        for (const chunk of chunks) {\r\n          await Promise.all(chunk);\r\n        }\r\n\r\n        // Update progress after each batch\r\n        updateProgress(Math.min(i + opts.detail_batch_size, totalArticles), totalArticles, 'fetching_details');\r\n\r\n        // Add delay between batches (except for the last batch)\r\n        if (i + opts.detail_batch_size < totalArticles && opts.batch_delay > 0) {\r\n          await new Promise(resolve => setTimeout(resolve, opts.batch_delay));\r\n        }\r\n      }\r\n\r\n      // Final progress update\r\n      updateProgress(totalArticles, totalArticles, 'complete', true);\r\n\r\n      // Calculate final statistics\r\n      const totalTime = Date.now() - startTime;\r\n      const successRate = totalArticles > 0\r\n        ? Math.round((successfulCount / totalArticles) * 100)\r\n        : 0;\r\n\r\n      // Return complete result\r\n      return {\r\n        total_processed: totalArticles,\r\n        successful_count: successfulCount,\r\n        failed_count: failedCount,\r\n        success_rate: successRate,\r\n        total_time: totalTime,\r\n        results: results.sort((a, b) =>\r\n          articlesList.findIndex(art => art.id === a.article_id) -\r\n          articlesList.findIndex(art => art.id === b.article_id)\r\n        ),\r\n        articles_with_details: articlesWithDetails,\r\n        error_summary: Object.keys(errorSummary).length > 0 ? errorSummary : undefined,\r\n        list_fetch_stats: listFetchStats\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to get all articles with details\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all articles with full details - Simple interface (no progress tracking)\r\n   * @param accessToken OA access token\r\n   * @param type Article type (\"normal\", \"video\", or \"both\")\r\n   * @param options Optional settings for details fetch\r\n   * @returns Array of articles with full details\r\n   *\r\n   * Simplified version that returns just the articles with details array\r\n   */\r\n  async getAllArticlesWithDetailsSimple(\r\n    accessToken: string,\r\n    type: \"normal\" | \"video\" | \"both\" = \"both\",\r\n    options?: BulkDetailOptions\r\n  ): Promise<ArticleDetail[]> {\r\n    const result = await this.getAllArticlesWithDetails(accessToken, type, options);\r\n    return result.articles_with_details;\r\n  }\r\n\r\n  /**\r\n   * Create article and wait for completion - All-in-one method\r\n   * @param accessToken OA access token\r\n   * @param request Article information\r\n   * @param options Options for tracking and timeout\r\n   * @param progressCallback Optional callback to track progress\r\n   * @returns Article ID when creation is complete\r\n   *\r\n   * This method combines create + tracking + verification into one call:\r\n   * 1. Creates the article and gets token\r\n   * 2. Automatically tracks progress until completion\r\n   * 3. Returns the final article ID\r\n   * 4. Handles all errors and timeouts gracefully\r\n   */\r\n  async createArticleAndWaitForCompletion(\r\n    accessToken: string,\r\n    request: ArticleRequest,\r\n    options?: {\r\n      /** Maximum time to wait for completion in milliseconds (default: 60000 = 1 minute) */\r\n      timeout?: number;\r\n      /** Interval between progress checks in milliseconds (default: 2000 = 2 seconds) */\r\n      checkInterval?: number;\r\n      /** Whether to return article details instead of just ID (default: false) */\r\n      returnDetails?: boolean;\r\n    },\r\n    progressCallback?: (progress: {\r\n      phase: 'creating' | 'processing' | 'verifying' | 'complete';\r\n      message: string;\r\n      token?: string;\r\n      article_id?: string;\r\n      elapsed_time: number;\r\n    }) => void\r\n  ): Promise<{\r\n    article_id: string;\r\n    token: string;\r\n    total_time: number;\r\n    article_details?: ArticleDetail;\r\n  }> {\r\n    try {\r\n      // Validate input\r\n      if (!accessToken || accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token cannot be empty\", -1);\r\n      }\r\n\r\n      if (!request) {\r\n        throw new ZaloSDKError(\"Article request cannot be empty\", -1);\r\n      }\r\n\r\n      // Set default options\r\n      const opts = {\r\n        timeout: 60000, // 1 minute\r\n        checkInterval: 2000, // 2 seconds\r\n        returnDetails: false,\r\n        ...options\r\n      };\r\n\r\n      const startTime = Date.now();\r\n      let token: string;\r\n      let articleId: string | undefined;\r\n\r\n      // Progress helper\r\n      const updateProgress = (\r\n        phase: 'creating' | 'processing' | 'verifying' | 'complete',\r\n        message: string,\r\n        additionalData?: { token?: string; article_id?: string }\r\n      ) => {\r\n        if (progressCallback) {\r\n          progressCallback({\r\n            phase,\r\n            message,\r\n            token: additionalData?.token || token,\r\n            article_id: additionalData?.article_id || articleId,\r\n            elapsed_time: Date.now() - startTime\r\n          });\r\n        }\r\n      };\r\n\r\n      // Phase 1: Create article\r\n      updateProgress('creating', 'Creating article...');\r\n\r\n      const createResponse = await this.createArticle(accessToken, request);\r\n      token = createResponse.data?.token as string;\r\n\r\n      if (!token) {\r\n        throw new ZaloSDKError(\"No token received from article creation\", -1);\r\n      }\r\n\r\n      updateProgress('processing', 'Article created, waiting for processing...', { token });\r\n\r\n      // Phase 2: Track progress until completion\r\n      const maxAttempts = Math.ceil(opts.timeout / opts.checkInterval);\r\n      let attempts = 0;\r\n      let isComplete = false;\r\n\r\n      while (attempts < maxAttempts && !isComplete) {\r\n        attempts++;\r\n\r\n        try {\r\n          // Check if timeout exceeded\r\n          const elapsedTime = Date.now() - startTime;\r\n          if (elapsedTime >= opts.timeout) {\r\n            throw new ZaloSDKError(\r\n              `Article creation timeout after ${Math.round(elapsedTime / 1000)} seconds. Token: ${token}`,\r\n              -1\r\n            );\r\n          }\r\n\r\n          updateProgress('processing', `Checking progress... (attempt ${attempts}/${maxAttempts})`);\r\n\r\n          // Try to verify article (this will throw if still processing)\r\n          const verifyResult = await this.verifyArticle(accessToken, token);\r\n\r\n          if (verifyResult && verifyResult.id) {\r\n            articleId = verifyResult.id;\r\n            isComplete = true;\r\n            updateProgress('verifying', 'Article processing completed!', { article_id: articleId });\r\n            break;\r\n          }\r\n\r\n        } catch (error) {\r\n          const elapsedTime = Date.now() - startTime;\r\n\r\n          // Handle expected errors during processing\r\n          if (error instanceof ZaloSDKError) {\r\n            if (error.message.includes(\"token was expired\")) {\r\n              // Token expired usually means processing is done, but we need to find the article\r\n              updateProgress('verifying', 'Token expired, attempting to find article...');\r\n\r\n              // Try to find the article by checking recent articles\r\n              try {\r\n                const recentArticles: ArticleListResponse = await this.getArticleList(accessToken, {\r\n                  offset: 0,\r\n                  limit: 10,\r\n                  type: request.type\r\n                });\r\n\r\n                // Look for article created around the same time (within last 5 minutes)\r\n                // ArticleListResponse có thể là ZaloResponse<ArticleListData> hoặc error response\r\n                if ('data' in recentArticles && recentArticles.data?.medias) {\r\n                  const recentArticle = recentArticles.data.medias.find((article: ArticleListItem) => {\r\n                    const articleTime = new Date(article.create_date).getTime();\r\n                    const timeDiff = Math.abs(articleTime - startTime);\r\n                    return timeDiff < 5 * 60 * 1000 && article.title === request.title;\r\n                  });\r\n\r\n                  if (recentArticle) {\r\n                    articleId = recentArticle.id;\r\n                    isComplete = true;\r\n                    updateProgress('complete', 'Article found after token expiration!', { article_id: articleId });\r\n                    break;\r\n                  }\r\n                }\r\n              } catch (searchError) {\r\n                // Continue with normal timeout handling\r\n              }\r\n            }\r\n\r\n            // For other API errors, continue waiting if we haven't timed out\r\n            if (elapsedTime < opts.timeout) {\r\n              updateProgress('processing', `Still processing... (${Math.round(elapsedTime / 1000)}s elapsed)`);\r\n              await new Promise(resolve => setTimeout(resolve, opts.checkInterval));\r\n              continue;\r\n            }\r\n          }\r\n\r\n          // If we've reached here and timed out, throw the error\r\n          if (elapsedTime >= opts.timeout) {\r\n            throw new ZaloSDKError(\r\n              `Article creation timeout after ${Math.round(elapsedTime / 1000)} seconds. Last error: ${error instanceof Error ? error.message : 'Unknown error'}. Token: ${token}`,\r\n              -1\r\n            );\r\n          }\r\n\r\n          // For unexpected errors, wait and retry\r\n          updateProgress('processing', `Retrying after error... (${error instanceof Error ? error.message : 'Unknown error'})`);\r\n          await new Promise(resolve => setTimeout(resolve, opts.checkInterval));\r\n        }\r\n      }\r\n\r\n      // Final check if we didn't get article ID\r\n      if (!articleId) {\r\n        throw new ZaloSDKError(\r\n          `Failed to get article ID after ${attempts} attempts. Token: ${token}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      const totalTime = Date.now() - startTime;\r\n      updateProgress('complete', `Article created successfully! ID: ${articleId}`, { article_id: articleId });\r\n\r\n      // Phase 3: Get article details if requested\r\n      let articleDetails: ArticleDetail | undefined;\r\n      if (opts.returnDetails) {\r\n        try {\r\n          updateProgress('complete', 'Fetching article details...');\r\n          articleDetails = await this.getArticleDetail(accessToken, articleId);\r\n        } catch (error) {\r\n          // Don't fail the whole operation if we can't get details\r\n          console.warn('Failed to fetch article details:', error);\r\n        }\r\n      }\r\n\r\n      return {\r\n        article_id: articleId,\r\n        token: token,\r\n        total_time: totalTime,\r\n        article_details: articleDetails\r\n      };\r\n\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to create article and wait for completion\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Create article and wait for completion - Simple interface\r\n   * @param accessToken OA access token\r\n   * @param request Article information\r\n   * @param timeoutMs Optional timeout in milliseconds (default: 60000)\r\n   * @returns Article ID when creation is complete\r\n   *\r\n   * Simplified version that returns just the article ID\r\n   */\r\n  async createArticleAndGetId(\r\n    accessToken: string,\r\n    request: ArticleRequest,\r\n    timeoutMs: number = 60000\r\n  ): Promise<string> {\r\n    const result = await this.createArticleAndWaitForCompletion(\r\n      accessToken,\r\n      request,\r\n      { timeout: timeoutMs }\r\n    );\r\n    return result.article_id;\r\n  }\r\n\r\n  private handleArticleError(error: any, defaultMessage: string): Error {\r\n    if (error instanceof ZaloSDKError) {\r\n      return error;\r\n    }\r\n    if (error.response?.data) {\r\n      const errorData = error.response.data;\r\n      return new ZaloSDKError(\r\n        `${defaultMessage}: ${errorData.message || errorData.error || \"Unknown error\"\r\n        }`,\r\n        errorData.error || -1,\r\n        errorData\r\n      );\r\n    }\r\n    return new ZaloSDKError(\r\n      `${defaultMessage}: ${error.message || \"Unknown error\"}`,\r\n      -1,\r\n      error\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Update normal article\r\n   * @param accessToken OA access token\r\n   * @param request Update information\r\n   * @returns Token for tracking update progress\r\n   */\r\n  async updateNormalArticle(\r\n    accessToken: string,\r\n    request: ArticleUpdateNormalRequest\r\n  ): Promise<ArticleUpdateResponse> {\r\n    try {\r\n      // Validate input\r\n      this.validateUpdateNormalArticleRequest(request);\r\n\r\n      // Process tracking link\r\n      let processedTrackingLink = \"https://v2.redai.vn/auth\";\r\n      if (\r\n        request.tracking_link &&\r\n        this.isValidTrackingLink(request.tracking_link)\r\n      ) {\r\n        processedTrackingLink = request.tracking_link;\r\n      }\r\n\r\n      const data = {\r\n        id: request.id,\r\n        type: request.type,\r\n        title: request.title,\r\n        author: request.author,\r\n        cover: request.cover,\r\n        description: request.description,\r\n        body: request.body,\r\n        related_medias: request.related_medias || [],\r\n        tracking_link: processedTrackingLink,\r\n        status: request.status || ArticleStatus.HIDE,\r\n        comment: request.comment || CommentStatus.SHOW,\r\n      };\r\n\r\n      const response = await this.client.apiPost<ArticleUpdateResponse>(\r\n        this.endpoints.article.update,\r\n        accessToken,\r\n        data\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to update normal article\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Update video article\r\n   * @param accessToken OA access token\r\n   * @param request Update information\r\n   * @returns Token for tracking update progress\r\n   */\r\n  async updateVideoArticle(\r\n    accessToken: string,\r\n    request: ArticleUpdateVideoRequest\r\n  ): Promise<ArticleUpdateResponse> {\r\n    try {\r\n      // Validate input\r\n      this.validateUpdateVideoArticleRequest(request);\r\n\r\n      const data = {\r\n        id: request.id,\r\n        type: request.type,\r\n        title: request.title,\r\n        description: request.description,\r\n        video_id: request.video_id,\r\n        avatar: request.avatar,\r\n        status: request.status || ArticleStatus.HIDE,\r\n        comment: request.comment || CommentStatus.SHOW,\r\n      };\r\n\r\n      const response = await this.client.apiPost<ArticleUpdateResponse>(\r\n        this.endpoints.article.update,\r\n        accessToken,\r\n        data\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to update video article\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Update article (automatically detect type)\r\n   * @param accessToken OA access token\r\n   * @param request Update information\r\n   * @returns Token for tracking update progress\r\n   */\r\n  async updateArticle(\r\n    accessToken: string,\r\n    request: ArticleUpdateRequest\r\n  ): Promise<ArticleUpdateResponse> {\r\n    if (request.type === ArticleType.NORMAL) {\r\n      return this.updateNormalArticle(accessToken, request);\r\n    } else if (request.type === ArticleType.VIDEO) {\r\n      return this.updateVideoArticle(accessToken, request);\r\n    } else {\r\n      throw new ZaloSDKError(\r\n        'Invalid article type. Only \"normal\" and \"video\" are supported',\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Update article status only\r\n   * @param accessToken OA access token\r\n   * @param articleId Article ID to update\r\n   * @param status New status ('show' or 'hide')\r\n   * @param comment Optional comment status ('show' or 'hide')\r\n   * @returns Token for tracking update progress\r\n   */\r\n  async updateArticleStatus(\r\n    accessToken: string,\r\n    articleId: string,\r\n    status: ArticleStatus,\r\n    comment?: CommentStatus\r\n  ): Promise<ArticleUpdateResponse> {\r\n    try {\r\n      // Validate input\r\n      if (!articleId || articleId.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Article ID cannot be empty\", -1);\r\n      }\r\n\r\n      if (!Object.values(ArticleStatus).includes(status)) {\r\n        throw new ZaloSDKError(\r\n          `Invalid status. Must be one of: ${Object.values(ArticleStatus).join(', ')}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (comment && !Object.values(CommentStatus).includes(comment)) {\r\n        throw new ZaloSDKError(\r\n          `Invalid comment status. Must be one of: ${Object.values(CommentStatus).join(', ')}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      // 1. Get article details first\r\n      const articleDetail = await this.getArticleDetail(accessToken, articleId);\r\n\r\n      // 2. Prepare update request based on article type\r\n      let updateRequest: ArticleUpdateRequest;\r\n\r\n      if (articleDetail.type === ArticleType.NORMAL) {\r\n        // For normal articles\r\n        const normalDetail = articleDetail as ArticleDetailNormal;\r\n        updateRequest = {\r\n          id: articleId,\r\n          type: ArticleType.NORMAL,\r\n          title: normalDetail.title,\r\n          author: normalDetail.author,\r\n          cover: normalDetail.cover,\r\n          description: normalDetail.description,\r\n          body: normalDetail.body,\r\n          related_medias: normalDetail.related_medias,\r\n          tracking_link: normalDetail.tracking_link,\r\n          status: status,\r\n          comment: comment || normalDetail.comment,\r\n        } as ArticleUpdateNormalRequest;\r\n      } else if (articleDetail.type === ArticleType.VIDEO) {\r\n        // For video articles\r\n        const videoDetail = articleDetail as ArticleDetailVideo;\r\n        updateRequest = {\r\n          id: articleId,\r\n          type: ArticleType.VIDEO,\r\n          title: videoDetail.title,\r\n          description: videoDetail.description,\r\n          video_id: videoDetail.video_id,\r\n          avatar: videoDetail.avatar,\r\n          status: status,\r\n          comment: comment || videoDetail.comment,\r\n        } as ArticleUpdateVideoRequest;\r\n      } else {\r\n        throw new ZaloSDKError(\r\n          `Unsupported article type: ${(articleDetail as any).type}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      // 3. Update the article with new status\r\n      return await this.updateArticle(accessToken, updateRequest);\r\n    } catch (error) {\r\n      throw this.handleArticleError(error, \"Failed to update article status\");\r\n    }\r\n  }\r\n\r\n  // ==================== VALIDATION METHODS ====================\r\n\r\n  /**\r\n   * Validate article request (universal method)\r\n   * @param request Article creation request\r\n   */\r\n  public validateArticleRequest(request: ArticleRequest): void {\r\n    if (!request) {\r\n      throw new ZaloSDKError(\"Request cannot be empty\", -1);\r\n    }\r\n\r\n    if (request.type === ArticleType.NORMAL) {\r\n      this.validateNormalArticleRequest(request);\r\n    } else if (request.type === ArticleType.VIDEO) {\r\n      this.validateVideoArticleRequest(request);\r\n    } else {\r\n      throw new ZaloSDKError(\r\n        'Invalid article type. Only \"normal\" and \"video\" are supported',\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n\r\n  /**\r\n   * Validate normal article creation request\r\n   */\r\n  private validateNormalArticleRequest(request: ArticleNormalRequest): void {\r\n    if (!request.title || request.title.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Title cannot be empty\", -1);\r\n    }\r\n\r\n    if (request.title.length > ARTICLE_CONSTRAINTS.TITLE_MAX_LENGTH) {\r\n      throw new ZaloSDKError(\r\n        `Title cannot exceed ${ARTICLE_CONSTRAINTS.TITLE_MAX_LENGTH} characters`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (!request.author || request.author.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Author cannot be empty\", -1);\r\n    }\r\n\r\n    if (request.author.length > ARTICLE_CONSTRAINTS.AUTHOR_MAX_LENGTH) {\r\n      throw new ZaloSDKError(\r\n        `Author name cannot exceed ${ARTICLE_CONSTRAINTS.AUTHOR_MAX_LENGTH} characters`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (!request.description || request.description.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Description cannot be empty\", -1);\r\n    }\r\n\r\n    if (\r\n      request.description.length > ARTICLE_CONSTRAINTS.DESCRIPTION_MAX_LENGTH\r\n    ) {\r\n      throw new ZaloSDKError(\r\n        `Description cannot exceed ${ARTICLE_CONSTRAINTS.DESCRIPTION_MAX_LENGTH} characters`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (!request.cover) {\r\n      throw new ZaloSDKError(\"Cover information cannot be empty\", -1);\r\n    }\r\n\r\n    if (!request.body || request.body.length === 0) {\r\n      throw new ZaloSDKError(\"Article body cannot be empty\", -1);\r\n    }\r\n\r\n    // Validate cover\r\n    this.validateCover(request.cover);\r\n\r\n    // Validate body items\r\n    request.body.forEach((item, index) => {\r\n      this.validateBodyItem(item, index);\r\n    });\r\n\r\n    // Validate optional fields\r\n    if (request.status && ![\"show\", \"hide\"].includes(request.status)) {\r\n      throw new ZaloSDKError('Status must be \"show\" or \"hide\"', -1);\r\n    }\r\n\r\n    if (request.comment && ![\"show\", \"hide\"].includes(request.comment)) {\r\n      throw new ZaloSDKError('Comment status must be \"show\" or \"hide\"', -1);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate video article creation request\r\n   */\r\n  private validateVideoArticleRequest(request: ArticleVideoRequest): void {\r\n    if (!request.title || request.title.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Title cannot be empty\", -1);\r\n    }\r\n\r\n    if (request.title.length > ARTICLE_CONSTRAINTS.TITLE_MAX_LENGTH) {\r\n      throw new ZaloSDKError(\r\n        `Title cannot exceed ${ARTICLE_CONSTRAINTS.TITLE_MAX_LENGTH} characters`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (!request.description || request.description.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Description cannot be empty\", -1);\r\n    }\r\n\r\n    if (\r\n      request.description.length > ARTICLE_CONSTRAINTS.DESCRIPTION_MAX_LENGTH\r\n    ) {\r\n      throw new ZaloSDKError(\r\n        `Description cannot exceed ${ARTICLE_CONSTRAINTS.DESCRIPTION_MAX_LENGTH} characters`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (!request.video_id || request.video_id.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Video ID cannot be empty\", -1);\r\n    }\r\n\r\n    if (!request.avatar || request.avatar.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Avatar URL cannot be empty\", -1);\r\n    }\r\n\r\n    // Validate optional fields\r\n    if (request.status && ![\"show\", \"hide\"].includes(request.status)) {\r\n      throw new ZaloSDKError('Status must be \"show\" or \"hide\"', -1);\r\n    }\r\n\r\n    if (request.comment && ![\"show\", \"hide\"].includes(request.comment)) {\r\n      throw new ZaloSDKError('Comment status must be \"show\" or \"hide\"', -1);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate article list request\r\n   */\r\n  private validateArticleListRequest(request: ArticleListRequest): void {\r\n    if (request.offset < 0) {\r\n      throw new ZaloSDKError(\"Offset must be >= 0\", -1);\r\n    }\r\n\r\n    if (request.limit <= 0) {\r\n      throw new ZaloSDKError(\"Limit must be > 0\", -1);\r\n    }\r\n\r\n    if (request.limit > ARTICLE_CONSTRAINTS.LIST_MAX_LIMIT) {\r\n      throw new ZaloSDKError(\r\n        `Limit cannot exceed ${ARTICLE_CONSTRAINTS.LIST_MAX_LIMIT}`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (![\"normal\", \"video\"].includes(request.type)) {\r\n      throw new ZaloSDKError('Type must be \"normal\" or \"video\"', -1);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate normal article update request\r\n   */\r\n  private validateUpdateNormalArticleRequest(\r\n    request: ArticleUpdateNormalRequest\r\n  ): void {\r\n    if (!request.id || request.id.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Article ID cannot be empty\", -1);\r\n    }\r\n\r\n    // Use the same validation as creation\r\n    this.validateNormalArticleRequest(request);\r\n  }\r\n\r\n  /**\r\n   * Validate video article update request\r\n   */\r\n  private validateUpdateVideoArticleRequest(\r\n    request: ArticleUpdateVideoRequest\r\n  ): void {\r\n    if (!request.id || request.id.trim() === \"\") {\r\n      throw new ZaloSDKError(\"Article ID cannot be empty\", -1);\r\n    }\r\n\r\n    // Use the same validation as creation\r\n    this.validateVideoArticleRequest(request);\r\n  }\r\n\r\n  /**\r\n   * Validate cover information\r\n   */\r\n  private validateCover(cover: ArticleCover): void {\r\n    if (!cover.cover_type) {\r\n      throw new ZaloSDKError(\"Cover type cannot be empty\", -1);\r\n    }\r\n\r\n    if (![\"photo\", \"video\"].includes(cover.cover_type)) {\r\n      throw new ZaloSDKError('Cover type must be \"photo\" or \"video\"', -1);\r\n    }\r\n\r\n    if (\r\n      cover.cover_type === CoverType.PHOTO &&\r\n      (!cover.photo_url || cover.photo_url.trim() === \"\")\r\n    ) {\r\n      throw new ZaloSDKError(\r\n        'Photo URL cannot be empty when cover_type is \"photo\"',\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (cover.cover_type === CoverType.VIDEO) {\r\n      if (!cover.video_id || cover.video_id.trim() === \"\") {\r\n        throw new ZaloSDKError(\r\n          'Video ID cannot be empty when cover_type is \"video\"',\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (\r\n        cover.cover_view &&\r\n        ![\"horizontal\", \"vertical\", \"square\"].includes(cover.cover_view)\r\n      ) {\r\n        throw new ZaloSDKError(\r\n          'cover_view must be \"horizontal\", \"vertical\" or \"square\"',\r\n          -1\r\n        );\r\n      }\r\n    }\r\n\r\n    if (![\"show\", \"hide\"].includes(cover.status)) {\r\n      throw new ZaloSDKError('Cover status must be \"show\" or \"hide\"', -1);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate body item\r\n   */\r\n  private validateBodyItem(item: ArticleBodyItem, index: number): void {\r\n    if (!item.type) {\r\n      throw new ZaloSDKError(\r\n        `Content type at position ${index + 1} cannot be empty`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (![\"text\", \"image\", \"video\", \"product\"].includes(item.type)) {\r\n      throw new ZaloSDKError(\r\n        `Content type at position ${index + 1\r\n        } must be \"text\", \"image\", \"video\" or \"product\"`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    switch (item.type) {\r\n      case BodyItemType.TEXT:\r\n        if (!item.content || item.content.trim() === \"\") {\r\n          throw new ZaloSDKError(\r\n            `Text content at position ${index + 1} cannot be empty`,\r\n            -1\r\n          );\r\n        }\r\n        break;\r\n\r\n      case BodyItemType.IMAGE:\r\n        if (!item.url || item.url.trim() === \"\") {\r\n          throw new ZaloSDKError(\r\n            `Image URL at position ${index + 1} cannot be empty`,\r\n            -1\r\n          );\r\n        }\r\n        break;\r\n\r\n      case BodyItemType.VIDEO:\r\n        if (!item.video_id && !item.url) {\r\n          throw new ZaloSDKError(\r\n            `Video at position ${index + 1} must have video_id or url`,\r\n            -1\r\n          );\r\n        }\r\n        break;\r\n\r\n      case BodyItemType.PRODUCT:\r\n        if (!item.id || item.id.trim() === \"\") {\r\n          throw new ZaloSDKError(\r\n            `Product ID at position ${index + 1} cannot be empty`,\r\n            -1\r\n          );\r\n        }\r\n        break;\r\n    }\r\n  }\r\n\r\n  private isValidTrackingLink(url: string): boolean {\r\n    try {\r\n      new URL(url);\r\n      return true;\r\n    } catch {\r\n      return false;\r\n    }\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  VideoUploadResponse,\r\n  VideoStatusResponse,\r\n  VIDEO_CONSTRAINTS,\r\n  VideoUploadStatus,\r\n} from \"../types/article\";\r\nimport { ZaloSDKError } from \"../types/common\";\r\nimport axios from \"axios\";\r\nimport FormData from \"form-data\";\r\n\r\n/**\r\n * Service for handling Zalo Official Account Video Upload APIs\r\n *\r\n * CONDITIONS FOR USING ZALO VIDEO UPLOAD:\r\n *\r\n * 1. GENERAL CONDITIONS:\r\n *    - OA must have permission to upload videos\r\n *    - Access token must have \"manage_article\" scope\r\n *    - OA must have active status and be verified\r\n *\r\n * 2. VIDEO FILE REQUIREMENTS:\r\n *    - File formats: MP4, AVI only\r\n *    - Maximum file size: 50MB\r\n *    - Video processing: asynchronous, use token to check status\r\n *    - Processing time: varies based on video size and quality\r\n *\r\n * 3. UPLOAD PROCESS:\r\n *    - Step 1: Upload video file and get token\r\n *    - Step 2: Use token to check processing status\r\n *    - Step 3: Get video_id when processing is complete\r\n *    - Step 4: Use video_id in article creation\r\n *\r\n * 4. STATUS CODES:\r\n *    - 0: Trạng thái không xác định\r\n *    - 1: Video đã được xử lý thành công và có thể sử dụng\r\n *    - 2: Video đã bị khóa\r\n *    - 3: Video đang được xử lý\r\n *    - 4: Video xử lý thất bại\r\n *    - 5: Video đã bị xóa\r\n */\r\nexport class VideoUploadService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    video: {\r\n      upload: \"https://openapi.zalo.me/v2.0/article/upload_video/preparevideo\",\r\n      verify: \"https://openapi.zalo.me/v2.0/article/upload_video/verify\",\r\n      info: \"https://openapi.zalo.me/v2.0/article/video/info\",\r\n    },\r\n  } as const;\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Upload video file for article\r\n   * @param accessToken OA access token\r\n   * @param file Video file (Buffer or File)\r\n   * @param filename Original filename (optional, will be generated if not provided)\r\n   * @returns Token for tracking upload progress\r\n   */\r\n  async uploadVideo(\r\n    accessToken: string,\r\n    file: Buffer | File,\r\n    filename?: string\r\n  ): Promise<VideoUploadResponse> {\r\n    try {\r\n      // Validate file\r\n      this.validateVideoFile(file, filename);\r\n\r\n      // Prepare filename\r\n      const actualFilename = Buffer.isBuffer(file)\r\n        ? filename || \"video.mp4\"\r\n        : (file as File).name || filename || \"video.mp4\";\r\n\r\n      // Convert file to Buffer if needed\r\n      let bufferToUpload: Buffer;\r\n      if (Buffer.isBuffer(file)) {\r\n        bufferToUpload = file as Buffer;\r\n      } else {\r\n        // Convert File (browser-like) to Buffer for Node upload\r\n        const arrayBuffer = await (file as unknown as { arrayBuffer: () => Promise<ArrayBuffer> }).arrayBuffer();\r\n        bufferToUpload = Buffer.from(arrayBuffer);\r\n      }\r\n\r\n      // Use direct axios call with FormData (same as successful direct upload)\r\n      const formData = new FormData();\r\n      formData.append('file', bufferToUpload, {\r\n        filename: actualFilename,\r\n        contentType: this.getMimeTypeFromFilename(actualFilename),\r\n      });\r\n\r\n      const response = await axios.post(this.endpoints.video.upload, formData, {\r\n        headers: {\r\n          ...formData.getHeaders(),\r\n          'access_token': accessToken,\r\n        },\r\n        timeout: 300000, // 5 minutes timeout\r\n        maxContentLength: Infinity,\r\n        maxBodyLength: Infinity,\r\n      });\r\n\r\n      // Parse response according to Zalo API format\r\n      const responseData = response.data;\r\n\r\n      if (responseData.error && responseData.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          `Zalo API error: ${responseData.message || 'Unknown error'}`,\r\n          responseData.error\r\n        );\r\n      }\r\n\r\n      return responseData.data;\r\n    } catch (error) {\r\n      throw this.handleVideoUploadError(error, \"Failed to upload video\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Check video upload status\r\n   * @param accessToken OA access token\r\n   * @param token Token from video upload response\r\n   * @returns Video status information\r\n   */\r\n  async checkVideoStatus(\r\n    accessToken: string,\r\n    token: string\r\n  ): Promise<VideoStatusResponse> {\r\n    try {\r\n      if (!token || token.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Token cannot be empty\", -1);\r\n      }\r\n\r\n      // According to docs, token should be in header, not query params\r\n      const response = await this.client.apiGetWithHeaders<{ data: VideoStatusResponse }>(\r\n        this.endpoints.video.verify,\r\n        {\r\n          access_token: accessToken,\r\n          token: token,\r\n        }\r\n      );\r\n\r\n      return response.data;\r\n    } catch (error) {\r\n      throw this.handleVideoUploadError(error, \"Failed to check video status\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Wait for video upload completion with polling\r\n   * @param accessToken OA access token\r\n   * @param token Token from video upload response\r\n   * @param maxWaitTime Maximum wait time in milliseconds (default: 5 minutes)\r\n   * @param pollInterval Polling interval in milliseconds (default: 5 seconds)\r\n   * @returns Final video status when completed\r\n   */\r\n  async waitForUploadCompletion(\r\n    accessToken: string,\r\n    token: string,\r\n    maxWaitTime: number = 5 * 60 * 1000, // 5 minutes\r\n    pollInterval: number = 5 * 1000 // 5 seconds\r\n  ): Promise<VideoStatusResponse> {\r\n    const startTime = Date.now();\r\n\r\n    while (Date.now() - startTime < maxWaitTime) {\r\n      try {\r\n        const status = await this.checkVideoStatus(accessToken, token);\r\n\r\n        // Status 1 = Video đã được xử lý thành công và có thể sử dụng\r\n        if (status.status === VideoUploadStatus.SUCCESS && status.video_id) {\r\n          return status;\r\n        }\r\n\r\n        // Status 4 = Video xử lý thất bại\r\n        if (status.status === VideoUploadStatus.FAILED) {\r\n          throw new ZaloSDKError(\r\n            `Video processing failed: ${status.status_message}`,\r\n            status.convert_error_code\r\n          );\r\n        }\r\n\r\n        // Status 2 = Video đã bị khóa\r\n        if (status.status === VideoUploadStatus.LOCKED) {\r\n          throw new ZaloSDKError(\r\n            `Video has been locked: ${status.status_message}`,\r\n            -1\r\n          );\r\n        }\r\n\r\n        // Status 5 = Video đã bị xóa\r\n        if (status.status === VideoUploadStatus.DELETED) {\r\n          throw new ZaloSDKError(\r\n            `Video has been deleted: ${status.status_message}`,\r\n            -1\r\n          );\r\n        }\r\n\r\n        // Continue polling for status 3 (đang được xử lý) or 0 (không xác định)\r\n        if (status.status === VideoUploadStatus.PROCESSING || status.status === VideoUploadStatus.UNKNOWN) {\r\n          await this.sleep(pollInterval);\r\n          continue;\r\n        }\r\n\r\n        // Unknown status\r\n        throw new ZaloSDKError(\r\n          `Unknown video processing status: ${status.status}`,\r\n          -1\r\n        );\r\n      } catch (error) {\r\n        if (error instanceof ZaloSDKError) {\r\n          throw error;\r\n        }\r\n        // Continue polling on network errors\r\n        await this.sleep(pollInterval);\r\n      }\r\n    }\r\n\r\n    throw new ZaloSDKError(\r\n      `Video processing timeout after ${maxWaitTime / 1000} seconds`,\r\n      -1\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Upload video from URL\r\n   * @param accessToken OA access token\r\n   * @param videoUrl URL of the video to upload\r\n   * @returns Token for tracking upload progress\r\n   */\r\n  async uploadVideoFromUrl(\r\n    accessToken: string,\r\n    videoUrl: string\r\n  ): Promise<VideoUploadResponse> {\r\n    try {\r\n      if (!videoUrl || videoUrl.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Video URL cannot be empty\", -1);\r\n      }\r\n\r\n      // Validate URL format\r\n      try {\r\n        new URL(videoUrl);\r\n      } catch {\r\n        throw new ZaloSDKError(\"Invalid video URL format\", -1);\r\n      }\r\n\r\n      // Download video from URL\r\n      const response = await fetch(videoUrl);\r\n      if (!response.ok) {\r\n        throw new ZaloSDKError(\r\n          `Failed to download video from URL: ${response.statusText}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      const videoBuffer = Buffer.from(await response.arrayBuffer());\r\n      const filename = this.extractFilenameFromUrl(videoUrl) || \"video.mp4\";\r\n\r\n      // Upload the downloaded video\r\n      return await this.uploadVideo(accessToken, videoBuffer, filename);\r\n    } catch (error) {\r\n      throw this.handleVideoUploadError(\r\n        error,\r\n        \"Failed to upload video from URL\"\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get video information by video ID\r\n   * @param accessToken OA access token\r\n   * @param videoId Video ID\r\n   * @returns Video information\r\n   */\r\n  async getVideoInfo(accessToken: string, videoId: string): Promise<any> {\r\n    try {\r\n      if (!videoId || videoId.trim() === \"\") {\r\n        throw new ZaloSDKError(\"Video ID cannot be empty\", -1);\r\n      }\r\n\r\n      const response = await this.client.apiGet<any>(\r\n        this.endpoints.video.info,\r\n        accessToken,\r\n        { video_id: videoId }\r\n      );\r\n\r\n      return response;\r\n    } catch (error) {\r\n      throw this.handleVideoUploadError(error, \"Failed to get video info\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Upload video and wait for completion - Combined method\r\n   * @param accessToken OA access token\r\n   * @param file Video file (Buffer or File)\r\n   * @param filename Original filename (optional, will be generated if not provided)\r\n   * @param maxWaitTime Maximum wait time in milliseconds (default: 5 minutes)\r\n   * @param pollInterval Polling interval in milliseconds (default: 5 seconds)\r\n   * @returns Final video status when completed\r\n   */\r\n  async uploadVideoAndWaitForCompletion(\r\n    accessToken: string,\r\n    file: Buffer | File,\r\n    filename?: string,\r\n    maxWaitTime: number = 5 * 60 * 1000, // 5 minutes\r\n    pollInterval: number = 5 * 1000 // 5 seconds\r\n  ): Promise<VideoStatusResponse> {\r\n    try {\r\n      // Step 1: Upload video and get token\r\n      const uploadResult: VideoUploadResponse = await this.uploadVideo(accessToken, file, filename);\r\n\r\n      // Step 2: Wait for upload completion and return final status\r\n      return await this.waitForUploadCompletion(\r\n        accessToken,\r\n        uploadResult.token,\r\n        maxWaitTime,\r\n        pollInterval\r\n      );\r\n    } catch (error) {\r\n      throw this.handleVideoUploadError(\r\n        error,\r\n        \"Failed to upload video and wait for completion\"\r\n      );\r\n    }\r\n  }\r\n\r\n  // ==================== VALIDATION METHODS ====================\r\n\r\n  /**\r\n   * Validate video file\r\n   */\r\n  private validateVideoFile(file: Buffer | File, filename?: string): void {\r\n    if (!file) {\r\n      throw new ZaloSDKError(\"Video file cannot be empty\", -1);\r\n    }\r\n\r\n    let fileSize: number;\r\n    let fileName: string;\r\n    let mimeType: string;\r\n\r\n    if (Buffer.isBuffer(file)) {\r\n      fileSize = file.length;\r\n      fileName = filename || \"video.mp4\";\r\n      mimeType = this.getMimeTypeFromFilename(fileName);\r\n    } else {\r\n      // Handle File input with proper type casting\r\n      const fileObj = file as File;\r\n      fileSize = fileObj.size;\r\n      fileName = fileObj.name || filename || \"video.mp4\";\r\n      mimeType = fileObj.type || this.getMimeTypeFromFilename(fileName);\r\n    }\r\n\r\n    // Check file size (50MB)\r\n    if (fileSize > VIDEO_CONSTRAINTS.maxSize) {\r\n      throw new ZaloSDKError(\r\n        `Video file size exceeds ${\r\n          VIDEO_CONSTRAINTS.maxSize / (1024 * 1024)\r\n        }MB limit`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    // Check file extension\r\n    const fileExtension = fileName\r\n      .toLowerCase()\r\n      .substring(fileName.lastIndexOf(\".\"));\r\n    if (!VIDEO_CONSTRAINTS.allowedExtensions.includes(fileExtension)) {\r\n      throw new ZaloSDKError(\r\n        `Unsupported video format. Only ${VIDEO_CONSTRAINTS.allowedExtensions.join(\r\n          \", \"\r\n        )} are supported`,\r\n        -1\r\n      );\r\n    }\r\n\r\n    // Check MIME type\r\n    if (!VIDEO_CONSTRAINTS.allowedMimeTypes.includes(mimeType)) {\r\n      throw new ZaloSDKError(\r\n        `Unsupported video MIME type. Only ${VIDEO_CONSTRAINTS.allowedMimeTypes.join(\r\n          \", \"\r\n        )} are supported`,\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n  // ==================== HELPER METHODS ====================\r\n\r\n  private getMimeTypeFromFilename(filename: string): string {\r\n    const extension = filename\r\n      .toLowerCase()\r\n      .substring(filename.lastIndexOf(\".\"));\r\n    switch (extension) {\r\n      case \".mp4\":\r\n        return \"video/mp4\";\r\n      case \".avi\":\r\n        return \"video/x-msvideo\";\r\n      default:\r\n        return \"video/mp4\"; // Default fallback\r\n    }\r\n  }\r\n\r\n  private extractFilenameFromUrl(url: string): string | null {\r\n    try {\r\n      const urlObj = new URL(url);\r\n      const pathname = urlObj.pathname;\r\n      const filename = pathname.substring(pathname.lastIndexOf(\"/\") + 1);\r\n      return filename || null;\r\n    } catch {\r\n      return null;\r\n    }\r\n  }\r\n\r\n  private sleep(ms: number): Promise<void> {\r\n    return new Promise((resolve) => setTimeout(resolve, ms));\r\n  }\r\n\r\n\r\n\r\n  private handleVideoUploadError(error: any, defaultMessage: string): Error {\r\n    if (error instanceof ZaloSDKError) {\r\n      return error;\r\n    }\r\n    if (error.response?.data) {\r\n      const errorData = error.response.data;\r\n      return new ZaloSDKError(\r\n        `${defaultMessage}: ${\r\n          errorData.message || errorData.error || \"Unknown error\"\r\n        }`,\r\n        errorData.error || -1,\r\n        errorData\r\n      );\r\n    }\r\n    return new ZaloSDKError(\r\n      `${defaultMessage}: ${error.message || \"Unknown error\"}`,\r\n      -1,\r\n      error\r\n    );\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  MessageRecipient,\r\n  SendMessageResponse,\r\n  TextMessage,\r\n  ConsultationQuoteMessage,\r\n} from \"../types/message\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\nimport {\r\n  TemplateAction,\r\n  TemplateElement,\r\n  TemplateButton,\r\n  MessageItem,\r\n  SendMessageSequenceRequest,\r\n  SendMessageSequenceResponse,\r\n  SendMessageSequenceToMultipleUsersRequest,\r\n  SendMessageSequenceToMultipleUsersResponse,\r\n  UserProgressInfo,\r\n  UserCustomMessage,\r\n  SendCustomMessageSequenceToMultipleUsersRequest,\r\n  SendCustomMessageSequenceToMultipleUsersResponse,\r\n} from \"../types/consultation\";\r\n\r\n\r\n\r\n/**\r\n * Service xử lý các API tin nhắn tư vấn của Zalo Official Account\r\n *\r\n * Tin nhắn tư vấn (CS - Customer Service) là loại tin nhắn đặc biệt\r\n * cho phép OA gửi tin nhắn chủ động đến người dùng trong khung thời gian nhất định\r\n *\r\n * ĐIỀU KIỆN GỬI TIN TƯ VẤN:\r\n *\r\n * 1. THỜI GIAN GỬI:\r\n *    - Chỉ được gửi trong vòng 48 giờ kể từ khi người dùng tương tác cuối cùng với OA\r\n *    - Tương tác bao gồm: gửi tin nhắn, nhấn button, gọi điện, truy cập website từ OA\r\n *\r\n * 2. NỘI DUNG TIN NHẮN:\r\n *    - Phải liên quan đến tư vấn, hỗ trợ khách hàng\r\n *    - Bao gồm: trả lời câu hỏi, hướng dẫn sử dụng, hỗ trợ kỹ thuật\r\n *    - Không được chứa nội dung quảng cáo trực tiếp\r\n *\r\n * 3. TẦN SUẤT GỬI:\r\n *    - Không giới hạn số lượng tin nhắn tư vấn trong ngày\r\n *    - Tuy nhiên cần tuân thủ nguyên tắc không spam\r\n *\r\n * 4. NGƯỜI DÙNG:\r\n *    - Người dùng phải đã follow OA\r\n *    - Người dùng không được block OA\r\n *    - Người dùng phải có tương tác gần đây với OA\r\n */\r\nexport class ConsultationService {\r\n  // Zalo API endpoint for consultation messages\r\n  private readonly endpoint = \"https://openapi.zalo.me/v3.0/oa/message/cs\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn văn bản\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param message Nội dung tin nhắn văn bản\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  public async sendTextMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    message: TextMessage\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validate text length theo quy định của Zalo\r\n      if (!message.text || message.text.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Nội dung tin nhắn không được để trống\", -1);\r\n      }\r\n\r\n      if (message.text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Nội dung tin nhắn không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Request structure theo API spec\r\n      const request = {\r\n        recipient: {\r\n          user_id: recipient.user_id,\r\n        },\r\n        message: {\r\n          text: message.text,\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation text message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation text message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn trích dẫn (quote message)\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param text Nội dung tin nhắn trả lời (tối đa 2000 ký tự)\r\n   * @param quoteMessageId ID của tin nhắn muốn trích dẫn\r\n   * @returns Thông tin tin nhắn đã gửi với quota information\r\n   */\r\n  public async sendQuoteMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    text: string,\r\n    quoteMessageId: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validate input parameters\r\n      if (!text || text.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Nội dung tin nhắn không được để trống\", -1);\r\n      }\r\n\r\n      if (text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Nội dung tin nhắn không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (!quoteMessageId || quoteMessageId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Quote message ID không được để trống\", -1);\r\n      }\r\n\r\n      if (!recipient.user_id || recipient.user_id.trim().length === 0) {\r\n        throw new ZaloSDKError(\"User ID không được để trống\", -1);\r\n      }\r\n\r\n      // Request structure theo API spec\r\n      const request = {\r\n        recipient: {\r\n          user_id: recipient.user_id,\r\n        },\r\n        message: {\r\n          text: text.trim(),\r\n          quote_message_id: quoteMessageId.trim(),\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation quote message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation quote message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Tạo ConsultationQuoteMessage object\r\n   * @param text Nội dung tin nhắn trả lời\r\n   * @param quoteMessageId ID của tin nhắn muốn trích dẫn\r\n   * @param quoteContent Nội dung của tin nhắn được trích dẫn (optional)\r\n   * @returns ConsultationQuoteMessage object\r\n   */\r\n  public createQuoteMessage(\r\n    text: string,\r\n    quoteMessageId: string,\r\n    quoteContent?: string\r\n  ): ConsultationQuoteMessage {\r\n    return {\r\n      type: \"consultation_quote\",\r\n      text: text.trim(),\r\n      quote: {\r\n        message_id: quoteMessageId.trim(),\r\n        content: quoteContent || \"\",\r\n      },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn hình ảnh bằng URL\r\n   * \r\n   * API Specification:\r\n   * - URL: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * - Method: POST\r\n   * - Content-Type: application/json\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param imageUrl URL trực tiếp đến hình ảnh (jpg, png, tối đa 1MB, tỷ lệ tối ưu 16:9)\r\n   * @param text Tiêu đề ảnh (optional, tối đa 2000 ký tự)\r\n   * @returns Thông tin tin nhắn đã gửi với quota information\r\n   */\r\n  public async sendImageMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    imageUrl: string,\r\n    text?: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!imageUrl || imageUrl.trim().length === 0) {\r\n        throw new ZaloSDKError(\"URL hình ảnh không được để trống\", -1);\r\n      }\r\n\r\n      if (text && text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Tiêu đề ảnh không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Request structure theo API specification v3.0\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          ...(text && text.trim().length > 0 && { text: text.trim() }),\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: \"media\",\r\n              elements: [\r\n                {\r\n                  media_type: \"image\",\r\n                  url: imageUrl.trim(),\r\n                },\r\n              ],\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation image message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation image message: ${\r\n          (error as Error).message\r\n        }`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn hình ảnh bằng attachment_id (ảnh đã upload trước)\r\n   * \r\n   * API Specification:\r\n   * - URL: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * - Method: POST\r\n   * - Content-Type: application/json\r\n   * - Lưu ý: Chỉ sử dụng attachment_id HOẶC url, không được dùng cả hai\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param attachmentId ID của ảnh đã upload trước đó (từ API upload ảnh)\r\n   * @param text Tiêu đề ảnh (optional, tối đa 2000 ký tự)\r\n   * @returns Thông tin tin nhắn đã gửi với quota information\r\n   */\r\n  public async sendImageByAttachmentId(\r\n    accessToken: string,\r\n    userId: string,\r\n    attachmentId: string,\r\n    text?: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!attachmentId || attachmentId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Attachment ID không được để trống\", -1);\r\n      }\r\n\r\n      if (text && text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Tiêu đề ảnh không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Request structure theo API specification v3.0\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          ...(text && text.trim().length > 0 && { text: text.trim() }),\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: \"media\",\r\n              elements: [\r\n                {\r\n                  media_type: \"image\",\r\n                  attachment_id: attachmentId.trim(),\r\n                },\r\n              ],\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation image message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation image message: ${\r\n          (error as Error).message\r\n        }`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn hình ảnh (hỗ trợ cả URL và attachment_id)\r\n   * \r\n   * API Specification:\r\n   * - URL: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * - Method: POST\r\n   * - Content-Type: application/json\r\n   * - Lưu ý: Chỉ sử dụng MỘT trong hai: imageUrl HOẶC attachmentId\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param options Tùy chọn gửi ảnh\r\n   * @param options.imageUrl URL trực tiếp đến hình ảnh (jpg, png, tối đa 1MB)\r\n   * @param options.attachmentId ID của ảnh đã upload trước đó\r\n   * @param options.text Tiêu đề ảnh (optional, tối đa 2000 ký tự)\r\n   * @returns Thông tin tin nhắn đã gửi với quota information\r\n   */\r\n  public async sendImage(\r\n    accessToken: string,\r\n    userId: string,\r\n    options: {\r\n      imageUrl?: string;\r\n      attachmentId?: string;\r\n      text?: string;\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validation: phải có ít nhất một trong hai\r\n      if (!options.imageUrl && !options.attachmentId) {\r\n        throw new ZaloSDKError(\r\n          \"Phải cung cấp imageUrl hoặc attachmentId\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Validation: không được có cả hai\r\n      if (options.imageUrl && options.attachmentId) {\r\n        throw new ZaloSDKError(\r\n          \"Chỉ được sử dụng imageUrl HOẶC attachmentId, không được cả hai\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Validation imageUrl\r\n      if (options.imageUrl && options.imageUrl.trim().length === 0) {\r\n        throw new ZaloSDKError(\"URL hình ảnh không được để trống\", -1);\r\n      }\r\n\r\n      // Validation attachmentId\r\n      if (options.attachmentId && options.attachmentId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Attachment ID không được để trống\", -1);\r\n      }\r\n\r\n      // Validation text\r\n      if (options.text && options.text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Tiêu đề ảnh không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Tạo element dựa trên loại input\r\n      const element: any = {\r\n        media_type: \"image\",\r\n      };\r\n\r\n      if (options.imageUrl) {\r\n        element.url = options.imageUrl.trim();\r\n      } else if (options.attachmentId) {\r\n        element.attachment_id = options.attachmentId.trim();\r\n      }\r\n\r\n      // Request structure theo API specification v3.0\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          ...(options.text && options.text.trim().length > 0 && { \r\n            text: options.text.trim() \r\n          }),\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: \"media\",\r\n              elements: [element],\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation image message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation image message: ${\r\n          (error as Error).message\r\n        }`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn ảnh GIF động\r\n   * \r\n   * API Specification:\r\n   * - URL: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * - Method: POST\r\n   * - Content-Type: application/json\r\n   * - Lưu ý: width và height là BẮT BUỘC cho media_type = \"gif\"\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param gifUrl URL trực tiếp đến ảnh GIF (tối đa 1MB)\r\n   * @param width Chiều rộng của ảnh GIF (bắt buộc, > 0)\r\n   * @param height Chiều cao của ảnh GIF (bắt buộc, > 0)\r\n   * @param text Tiêu đề ảnh (optional, tối đa 2000 ký tự)\r\n   * @returns Thông tin tin nhắn đã gửi với quota information\r\n   */\r\n  public async sendGifMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    gifUrl: string,\r\n    width: number,\r\n    height: number,\r\n    text?: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!gifUrl || gifUrl.trim().length === 0) {\r\n        throw new ZaloSDKError(\"URL GIF không được để trống\", -1);\r\n      }\r\n\r\n      if (width <= 0 || height <= 0) {\r\n        throw new ZaloSDKError(\"Chiều rộng và chiều cao phải lớn hơn 0\", -1);\r\n      }\r\n\r\n      if (text && text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Tiêu đề ảnh không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Request structure theo API specification v3.0\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          ...(text && text.trim().length > 0 && { text: text.trim() }),\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: \"media\",\r\n              elements: [\r\n                {\r\n                  media_type: \"gif\",\r\n                  url: gifUrl.trim(),\r\n                  width: width,\r\n                  height: height,\r\n                },\r\n              ],\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation gif message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation gif message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn đính kèm file\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * Method: POST\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param fileToken Token của file đã upload (từ API upload file)\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   *\r\n   * Note: Cần sử dụng API upload file trước để lấy token\r\n   */\r\n  public async sendFileMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    fileToken: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!fileToken || fileToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"File token không được để trống\", -1);\r\n      }\r\n\r\n      // Request structure theo API spec\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          attachment: {\r\n            type: \"file\",\r\n            payload: {\r\n              token: fileToken,\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation file message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation file message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn theo mẫu yêu cầu thông tin người dùng\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * Method: POST\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param title Tiêu đề hiển thị của template (tối đa 100 ký tự)\r\n   * @param subtitle Tiêu đề phụ của template (tối đa 500 ký tự)\r\n   * @param imageUrl Đường dẫn đến ảnh\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  public async sendRequestUserInfoMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    title: string,\r\n    subtitle: string,\r\n    imageUrl: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validation theo API spec\r\n      if (!title || title.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Tiêu đề không được để trống\", -1);\r\n      }\r\n\r\n      if (title.length > 100) {\r\n        throw new ZaloSDKError(\"Tiêu đề không được vượt quá 100 ký tự\", -1);\r\n      }\r\n\r\n      if (!subtitle || subtitle.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Tiêu đề phụ không được để trống\", -1);\r\n      }\r\n\r\n      if (subtitle.length > 500) {\r\n        throw new ZaloSDKError(\"Tiêu đề phụ không được vượt quá 500 ký tự\", -1);\r\n      }\r\n\r\n      if (!imageUrl || imageUrl.trim().length === 0) {\r\n        throw new ZaloSDKError(\"URL hình ảnh không được để trống\", -1);\r\n      }\r\n\r\n      // Request structure theo API spec\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: \"request_user_info\",\r\n              elements: [\r\n                {\r\n                  title: title,\r\n                  subtitle: subtitle,\r\n                  image_url: imageUrl,\r\n                },\r\n              ],\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send request user info message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send request user info message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn kèm Sticker\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * Method: POST\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param stickerAttachmentId ID của sticker (lấy từ https://stickers.zaloapp.com/)\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   *\r\n   * Note: Sticker ID lấy từ nguồn https://stickers.zaloapp.com/\r\n   * Xem video hướng dẫn: https://vimeo.com/649330161\r\n   */\r\n  public async sendStickerMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    stickerAttachmentId: string\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!stickerAttachmentId || stickerAttachmentId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Sticker attachment ID không được để trống\", -1);\r\n      }\r\n\r\n      // Request structure theo API spec\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: \"media\",\r\n              elements: [\r\n                {\r\n                  media_type: \"sticker\",\r\n                  attachment_id: stickerAttachmentId,\r\n                },\r\n              ],\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send consultation sticker message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send consultation sticker message: ${\r\n          (error as Error).message\r\n        }`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi chuỗi tin nhắn tư vấn với delay tùy chỉnh\r\n   * Hỗ trợ tất cả các loại tin nhắn: text, image, gif, file, sticker, request_user_info\r\n   *\r\n   * @param request Thông tin request gửi chuỗi tin nhắn\r\n   * @returns Kết quả gửi từng tin nhắn\r\n   */\r\n  public async sendMessageSequence(\r\n    request: SendMessageSequenceRequest\r\n  ): Promise<SendMessageSequenceResponse> {\r\n    const startTime = Date.now();\r\n    const results: SendMessageSequenceResponse[\"results\"] = [];\r\n    let successCount = 0;\r\n    let failureCount = 0;\r\n\r\n    try {\r\n      // Validate input\r\n      if (!request.accessToken || request.accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.userId || request.userId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"User ID không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.messages || request.messages.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách tin nhắn không được để trống\", -1);\r\n      }\r\n\r\n      // Gửi từng tin nhắn theo thứ tự\r\n      for (let i = 0; i < request.messages.length; i++) {\r\n        const message = request.messages[i];\r\n        const messageResult = {\r\n          index: i,\r\n          type: message.type,\r\n          success: false,\r\n          timestamp: Date.now(),\r\n        } as SendMessageSequenceResponse[\"results\"][0];\r\n\r\n        try {\r\n          let response: SendMessageResponse;\r\n\r\n          // Gửi tin nhắn theo loại\r\n          switch (message.type) {\r\n            case \"text\":\r\n              if (!message.text) {\r\n                throw new Error(\"Text không được để trống cho text message\");\r\n              }\r\n              response = await this.sendTextMessage(\r\n                request.accessToken,\r\n                { user_id: request.userId },\r\n                { type: \"text\", text: message.text }\r\n              );\r\n              break;\r\n\r\n            case \"image\":\r\n              if (message.imageUrl) {\r\n                response = await this.sendImageMessage(\r\n                  request.accessToken,\r\n                  request.userId,\r\n                  message.imageUrl,\r\n                  message.text\r\n                );\r\n              } else if (message.attachmentId) {\r\n                response = await this.sendImageByAttachmentId(\r\n                  request.accessToken,\r\n                  request.userId,\r\n                  message.attachmentId,\r\n                  message.text\r\n                );\r\n              } else {\r\n                throw new Error(\"imageUrl hoặc attachmentId là bắt buộc cho image message\");\r\n              }\r\n              break;\r\n\r\n            case \"gif\":\r\n              if (!message.gifUrl || !message.width || !message.height) {\r\n                throw new Error(\"gifUrl, width và height là bắt buộc cho gif message\");\r\n              }\r\n              response = await this.sendGifMessage(\r\n                request.accessToken,\r\n                request.userId,\r\n                message.gifUrl,\r\n                message.width,\r\n                message.height,\r\n                message.text\r\n              );\r\n              break;\r\n\r\n            case \"file\":\r\n              if (!message.fileToken) {\r\n                throw new Error(\"fileToken là bắt buộc cho file message\");\r\n              }\r\n              response = await this.sendFileMessage(\r\n                request.accessToken,\r\n                request.userId,\r\n                message.fileToken\r\n              );\r\n              break;\r\n\r\n            case \"sticker\":\r\n              if (!message.stickerAttachmentId) {\r\n                throw new Error(\"stickerAttachmentId là bắt buộc cho sticker message\");\r\n              }\r\n              response = await this.sendStickerMessage(\r\n                request.accessToken,\r\n                request.userId,\r\n                message.stickerAttachmentId\r\n              );\r\n              break;\r\n\r\n            case \"request_user_info\":\r\n              if (!message.title || !message.subtitle || !message.imageUrl) {\r\n                throw new Error(\"title, subtitle và imageUrl là bắt buộc cho request_user_info message\");\r\n              }\r\n              response = await this.sendRequestUserInfoMessage(\r\n                request.accessToken,\r\n                request.userId,\r\n                message.title,\r\n                message.subtitle,\r\n                message.imageUrl\r\n              );\r\n              break;\r\n\r\n            default:\r\n              throw new Error(`Loại tin nhắn không được hỗ trợ: ${message.type}`);\r\n          }\r\n\r\n          // Ghi nhận thành công\r\n          messageResult.success = true;\r\n          messageResult.response = response;\r\n          successCount++;\r\n\r\n        } catch (error) {\r\n          // Ghi nhận thất bại\r\n          messageResult.success = false;\r\n          messageResult.error = error instanceof Error ? error.message : String(error);\r\n          failureCount++;\r\n        }\r\n\r\n        results.push(messageResult);\r\n\r\n        // Delay trước khi gửi tin nhắn tiếp theo (trừ tin nhắn cuối cùng)\r\n        if (i < request.messages.length - 1) {\r\n          const delayTime = message.delay ?? request.defaultDelay ?? 0;\r\n          if (delayTime > 0) {\r\n            await this.sleep(delayTime);\r\n          }\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      return {\r\n        successCount,\r\n        failureCount,\r\n        results,\r\n        totalDuration,\r\n      };\r\n\r\n    } catch (error) {\r\n      // Lỗi tổng quát\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n\r\n      throw new ZaloSDKError(\r\n        `Failed to send message sequence: ${(error as Error).message}`,\r\n        -1,\r\n        {\r\n          successCount,\r\n          failureCount,\r\n          results,\r\n          totalDuration,\r\n        }\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi chuỗi tin nhắn tư vấn tới nhiều users với callback tracking\r\n   *\r\n   * @param request Thông tin request gửi chuỗi tin nhắn tới nhiều users\r\n   * @returns Kết quả gửi tin nhắn cho tất cả users\r\n   */\r\n  public async sendMessageSequenceToMultipleUsers(\r\n    request: SendMessageSequenceToMultipleUsersRequest\r\n  ): Promise<SendMessageSequenceToMultipleUsersResponse> {\r\n    const startTime = Date.now();\r\n    const userResults: SendMessageSequenceToMultipleUsersResponse[\"userResults\"] = [];\r\n    let successfulUsers = 0;\r\n    let failedUsers = 0;\r\n    let totalSuccessfulMessages = 0;\r\n    let totalFailedMessages = 0;\r\n\r\n    try {\r\n      // Validate input\r\n      if (!request.accessToken || request.accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.userIds || request.userIds.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách user IDs không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.messages || request.messages.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách tin nhắn không được để trống\", -1);\r\n      }\r\n\r\n      // Loại bỏ user IDs trùng lặp và rỗng\r\n      const uniqueUserIds = [...new Set(request.userIds.filter(id => id && id.trim().length > 0))];\r\n\r\n      if (uniqueUserIds.length === 0) {\r\n        throw new ZaloSDKError(\"Không có user ID hợp lệ nào\", -1);\r\n      }\r\n\r\n      // Gửi tin nhắn cho từng user tuần tự\r\n      for (let i = 0; i < uniqueUserIds.length; i++) {\r\n        const userId = uniqueUserIds[i];\r\n        const userStartTime = Date.now();\r\n\r\n        // Callback: Bắt đầu gửi cho user\r\n        if (request.onProgress) {\r\n          request.onProgress({\r\n            userId,\r\n            userIndex: i,\r\n            totalUsers: uniqueUserIds.length,\r\n            status: 'started',\r\n            startTime: userStartTime,\r\n          });\r\n        }\r\n\r\n        const userResult = {\r\n          userId,\r\n          userIndex: i,\r\n          success: false,\r\n          startTime: userStartTime,\r\n          endTime: 0,\r\n          duration: 0,\r\n        } as SendMessageSequenceToMultipleUsersResponse[\"userResults\"][0];\r\n\r\n        try {\r\n          // Gửi chuỗi tin nhắn cho user này\r\n          const messageSequenceResult = await this.sendMessageSequence({\r\n            accessToken: request.accessToken,\r\n            userId,\r\n            messages: request.messages,\r\n            defaultDelay: request.defaultDelay,\r\n          });\r\n\r\n          // Ghi nhận thành công\r\n          const userEndTime = Date.now();\r\n          userResult.success = true;\r\n          userResult.messageSequenceResult = messageSequenceResult;\r\n          userResult.endTime = userEndTime;\r\n          userResult.duration = userEndTime - userStartTime;\r\n\r\n          successfulUsers++;\r\n          totalSuccessfulMessages += messageSequenceResult.successCount;\r\n          totalFailedMessages += messageSequenceResult.failureCount;\r\n\r\n          // Callback: Hoàn thành thành công\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              userId,\r\n              userIndex: i,\r\n              totalUsers: uniqueUserIds.length,\r\n              status: 'completed',\r\n              result: messageSequenceResult,\r\n              startTime: userStartTime,\r\n              endTime: userEndTime,\r\n            });\r\n          }\r\n\r\n        } catch (error) {\r\n          // Ghi nhận thất bại\r\n          const userEndTime = Date.now();\r\n          const errorMessage = error instanceof Error ? error.message : String(error);\r\n\r\n          userResult.success = false;\r\n          userResult.error = errorMessage;\r\n          userResult.endTime = userEndTime;\r\n          userResult.duration = userEndTime - userStartTime;\r\n\r\n          failedUsers++;\r\n          // Với user thất bại, coi như tất cả tin nhắn đều thất bại\r\n          totalFailedMessages += request.messages.length;\r\n\r\n          // Callback: Thất bại\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              userId,\r\n              userIndex: i,\r\n              totalUsers: uniqueUserIds.length,\r\n              status: 'failed',\r\n              error: errorMessage,\r\n              startTime: userStartTime,\r\n              endTime: userEndTime,\r\n            });\r\n          }\r\n        }\r\n\r\n        userResults.push(userResult);\r\n\r\n        // Delay giữa các user (trừ user cuối cùng)\r\n        if (i < uniqueUserIds.length - 1 && request.delayBetweenUsers && request.delayBetweenUsers > 0) {\r\n          await this.sleep(request.delayBetweenUsers);\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n      const totalMessages = uniqueUserIds.length * request.messages.length;\r\n\r\n      return {\r\n        totalUsers: uniqueUserIds.length,\r\n        successfulUsers,\r\n        failedUsers,\r\n        userResults,\r\n        totalDuration,\r\n        messageStats: {\r\n          totalSuccessfulMessages,\r\n          totalFailedMessages,\r\n          totalMessages,\r\n        },\r\n      };\r\n\r\n    } catch (error) {\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n\r\n      throw new ZaloSDKError(\r\n        `Failed to send message sequence to multiple users: ${(error as Error).message}`,\r\n        -1,\r\n        {\r\n          totalUsers: request.userIds?.length || 0,\r\n          successfulUsers,\r\n          failedUsers,\r\n          userResults,\r\n          totalDuration,\r\n          messageStats: {\r\n            totalSuccessfulMessages,\r\n            totalFailedMessages,\r\n            totalMessages: (request.userIds?.length || 0) * (request.messages?.length || 0),\r\n          },\r\n        }\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn tư vấn tùy chỉnh tới nhiều users với callback tracking\r\n   * Mỗi user có thể có bộ tin nhắn riêng biệt\r\n   *\r\n   * @param request Thông tin request gửi tin nhắn tùy chỉnh tới nhiều users\r\n   * @returns Kết quả gửi tin nhắn cho tất cả users\r\n   */\r\n  public async sendCustomMessageSequenceToMultipleUsers(\r\n    request: SendCustomMessageSequenceToMultipleUsersRequest\r\n  ): Promise<SendCustomMessageSequenceToMultipleUsersResponse> {\r\n    const startTime = Date.now();\r\n    const userResults: SendCustomMessageSequenceToMultipleUsersResponse[\"userResults\"] = [];\r\n    let successfulUsers = 0;\r\n    let failedUsers = 0;\r\n    let totalSuccessfulMessages = 0;\r\n    let totalFailedMessages = 0;\r\n\r\n    try {\r\n      // Validate input\r\n      if (!request.accessToken || request.accessToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Access token không được để trống\", -1);\r\n      }\r\n\r\n      if (!request.userCustomMessages || request.userCustomMessages.length === 0) {\r\n        throw new ZaloSDKError(\"Danh sách user custom messages không được để trống\", -1);\r\n      }\r\n\r\n      // Validate từng user custom message\r\n      const validUserCustomMessages: UserCustomMessage[] = [];\r\n      const seenUserIds = new Set<string>();\r\n\r\n      for (let i = 0; i < request.userCustomMessages.length; i++) {\r\n        const userCustomMessage = request.userCustomMessages[i];\r\n\r\n        // Validate userId\r\n        if (!userCustomMessage.userId || userCustomMessage.userId.trim().length === 0) {\r\n          throw new ZaloSDKError(`User custom message ${i + 1}: userId không được để trống`, -1);\r\n        }\r\n\r\n        // Check duplicate userId\r\n        if (seenUserIds.has(userCustomMessage.userId)) {\r\n          throw new ZaloSDKError(`User ID \"${userCustomMessage.userId}\" bị trùng lặp`, -1);\r\n        }\r\n        seenUserIds.add(userCustomMessage.userId);\r\n\r\n        // Validate messages\r\n        if (!userCustomMessage.messages || userCustomMessage.messages.length === 0) {\r\n          throw new ZaloSDKError(`User custom message ${i + 1}: danh sách tin nhắn không được để trống`, -1);\r\n        }\r\n\r\n        validUserCustomMessages.push(userCustomMessage);\r\n      }\r\n\r\n      // Gửi tin nhắn cho từng user tuần tự\r\n      for (let i = 0; i < validUserCustomMessages.length; i++) {\r\n        const userCustomMessage = validUserCustomMessages[i];\r\n        const userId = userCustomMessage.userId;\r\n        const userStartTime = Date.now();\r\n\r\n        // Callback: Bắt đầu gửi cho user\r\n        if (request.onProgress) {\r\n          request.onProgress({\r\n            userId,\r\n            userIndex: i,\r\n            totalUsers: validUserCustomMessages.length,\r\n            status: 'started',\r\n            startTime: userStartTime,\r\n          });\r\n        }\r\n\r\n        const userResult = {\r\n          userId,\r\n          userIndex: i,\r\n          success: false,\r\n          startTime: userStartTime,\r\n          endTime: 0,\r\n          duration: 0,\r\n        } as SendCustomMessageSequenceToMultipleUsersResponse[\"userResults\"][0];\r\n\r\n        try {\r\n          // Gửi chuỗi tin nhắn tùy chỉnh cho user này\r\n          const messageSequenceResult = await this.sendMessageSequence({\r\n            accessToken: request.accessToken,\r\n            userId,\r\n            messages: userCustomMessage.messages,\r\n            defaultDelay: request.defaultDelay,\r\n          });\r\n\r\n          // Ghi nhận thành công\r\n          const userEndTime = Date.now();\r\n          userResult.success = true;\r\n          userResult.messageSequenceResult = messageSequenceResult;\r\n          userResult.endTime = userEndTime;\r\n          userResult.duration = userEndTime - userStartTime;\r\n\r\n          successfulUsers++;\r\n          totalSuccessfulMessages += messageSequenceResult.successCount;\r\n          totalFailedMessages += messageSequenceResult.failureCount;\r\n\r\n          // Callback: Hoàn thành thành công\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              userId,\r\n              userIndex: i,\r\n              totalUsers: validUserCustomMessages.length,\r\n              status: 'completed',\r\n              result: messageSequenceResult,\r\n              startTime: userStartTime,\r\n              endTime: userEndTime,\r\n            });\r\n          }\r\n\r\n        } catch (error) {\r\n          // Ghi nhận thất bại\r\n          const userEndTime = Date.now();\r\n          const errorMessage = error instanceof Error ? error.message : String(error);\r\n\r\n          userResult.success = false;\r\n          userResult.error = errorMessage;\r\n          userResult.endTime = userEndTime;\r\n          userResult.duration = userEndTime - userStartTime;\r\n\r\n          failedUsers++;\r\n          // Với user thất bại, coi như tất cả tin nhắn đều thất bại\r\n          totalFailedMessages += userCustomMessage.messages.length;\r\n\r\n          // Callback: Thất bại\r\n          if (request.onProgress) {\r\n            request.onProgress({\r\n              userId,\r\n              userIndex: i,\r\n              totalUsers: validUserCustomMessages.length,\r\n              status: 'failed',\r\n              error: errorMessage,\r\n              startTime: userStartTime,\r\n              endTime: userEndTime,\r\n            });\r\n          }\r\n        }\r\n\r\n        userResults.push(userResult);\r\n\r\n        // Delay giữa các user (trừ user cuối cùng)\r\n        if (i < validUserCustomMessages.length - 1 && request.delayBetweenUsers && request.delayBetweenUsers > 0) {\r\n          await this.sleep(request.delayBetweenUsers);\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      // Tính tổng số tin nhắn\r\n      const totalMessages = validUserCustomMessages.reduce((total, userCustomMessage) => {\r\n        return total + userCustomMessage.messages.length;\r\n      }, 0);\r\n\r\n      return {\r\n        totalUsers: validUserCustomMessages.length,\r\n        successfulUsers,\r\n        failedUsers,\r\n        userResults,\r\n        totalDuration,\r\n        messageStats: {\r\n          totalSuccessfulMessages,\r\n          totalFailedMessages,\r\n          totalMessages,\r\n        },\r\n      };\r\n\r\n    } catch (error) {\r\n      const totalDuration = Date.now() - startTime;\r\n\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n\r\n      // Tính tổng số tin nhắn cho error response\r\n      const totalMessages = request.userCustomMessages?.reduce((total, userCustomMessage) => {\r\n        return total + (userCustomMessage.messages?.length || 0);\r\n      }, 0) || 0;\r\n\r\n      throw new ZaloSDKError(\r\n        `Failed to send custom message sequence to multiple users: ${(error as Error).message}`,\r\n        -1,\r\n        {\r\n          totalUsers: request.userCustomMessages?.length || 0,\r\n          successfulUsers,\r\n          failedUsers,\r\n          userResults,\r\n          totalDuration,\r\n          messageStats: {\r\n            totalSuccessfulMessages,\r\n            totalFailedMessages,\r\n            totalMessages,\r\n          },\r\n        }\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn template tổng quát với elements và buttons\r\n   * Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs\r\n   * Method: POST\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID người nhận (user_id)\r\n   * @param templateType Loại template (ví dụ: \"request_user_info\")\r\n   * @param elements Mảng elements (tối đa 5 phần tử)\r\n   * @param buttons Mảng buttons (tối đa 5 phần tử, optional)\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  public async sendTemplateMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    templateType: string,\r\n    elements: TemplateElement[],\r\n    buttons?: TemplateButton[]\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validation cơ bản\r\n      if (!templateType || templateType.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Template type không được để trống\", -1);\r\n      }\r\n\r\n      if (!elements || elements.length === 0) {\r\n        throw new ZaloSDKError(\"Elements không được để trống\", -1);\r\n      }\r\n\r\n      if (elements.length > 5) {\r\n        throw new ZaloSDKError(\"Elements không được vượt quá 5 phần tử\", -1);\r\n      }\r\n\r\n      if (buttons && buttons.length > 5) {\r\n        throw new ZaloSDKError(\"Buttons không được vượt quá 5 phần tử\", -1);\r\n      }\r\n\r\n      // Validate từng element\r\n      elements.forEach((element, index) => {\r\n        this.validateTemplateElement(element, index);\r\n      });\r\n\r\n      // Validate từng button (nếu có)\r\n      if (buttons) {\r\n        buttons.forEach((button, index) => {\r\n          this.validateTemplateButton(button, index);\r\n        });\r\n      }\r\n\r\n      // Request structure theo API specification\r\n      const request = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          attachment: {\r\n            type: \"template\",\r\n            payload: {\r\n              template_type: templateType,\r\n              elements: elements,\r\n              ...(buttons && buttons.length > 0 && { buttons: buttons }),\r\n            },\r\n          },\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send template message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send template message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate template element theo quy tắc API\r\n   * @param element Template element cần validate\r\n   * @param index Index của element trong mảng (để báo lỗi)\r\n   */\r\n  private validateTemplateElement(element: TemplateElement, index: number): void {\r\n    // title là bắt buộc, ≤ 100 ký tự\r\n    if (!element.title || element.title.trim().length === 0) {\r\n      throw new ZaloSDKError(`Element ${index + 1}: title không được để trống`, -1);\r\n    }\r\n\r\n    if (element.title.length > 100) {\r\n      throw new ZaloSDKError(`Element ${index + 1}: title không được vượt quá 100 ký tự`, -1);\r\n    }\r\n\r\n    // subtitle: bắt buộc cho element đầu tiên, tùy chọn cho các element sau, ≤ 500 ký tự\r\n    if (index === 0) {\r\n      if (!element.subtitle || element.subtitle.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Element đầu tiên phải có subtitle\", -1);\r\n      }\r\n    }\r\n\r\n    if (element.subtitle && element.subtitle.length > 500) {\r\n      throw new ZaloSDKError(`Element ${index + 1}: subtitle không được vượt quá 500 ký tự`, -1);\r\n    }\r\n\r\n    // Validate default_action nếu có\r\n    if (element.default_action) {\r\n      this.validateTemplateAction(element.default_action, `Element ${index + 1} default_action`);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate template button theo quy tắc API\r\n   * @param button Template button cần validate\r\n   * @param index Index của button trong mảng (để báo lỗi)\r\n   */\r\n  private validateTemplateButton(button: TemplateButton, index: number): void {\r\n    // title là bắt buộc, ≤ 100 ký tự\r\n    if (!button.title || button.title.trim().length === 0) {\r\n      throw new ZaloSDKError(`Button ${index + 1}: title không được để trống`, -1);\r\n    }\r\n\r\n    if (button.title.length > 100) {\r\n      throw new ZaloSDKError(`Button ${index + 1}: title không được vượt quá 100 ký tự`, -1);\r\n    }\r\n\r\n    // type là bắt buộc\r\n    if (!button.type || button.type.trim().length === 0) {\r\n      throw new ZaloSDKError(`Button ${index + 1}: type không được để trống`, -1);\r\n    }\r\n\r\n    // Validate payload theo type\r\n    this.validateButtonPayload(button, index);\r\n  }\r\n\r\n  /**\r\n   * Validate template action (cho default_action)\r\n   * @param action Template action cần validate\r\n   * @param context Context để báo lỗi\r\n   */\r\n  private validateTemplateAction(action: TemplateAction, context: string): void {\r\n    if (!action.type || action.type.trim().length === 0) {\r\n      throw new ZaloSDKError(`${context}: type không được để trống`, -1);\r\n    }\r\n\r\n    const validActionTypes = [\"oa.open.url\", \"oa.query.show\", \"oa.query.hide\", \"oa.open.sms\", \"oa.open.phone\"];\r\n    if (!validActionTypes.includes(action.type)) {\r\n      throw new ZaloSDKError(`${context}: type phải là một trong ${validActionTypes.join(\", \")}`, -1);\r\n    }\r\n\r\n    // Validate payload theo type\r\n    switch (action.type) {\r\n      case \"oa.open.url\":\r\n        if (!action.url || action.url.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: url không được để trống cho type oa.open.url`, -1);\r\n        }\r\n        break;\r\n\r\n      case \"oa.query.show\":\r\n      case \"oa.query.hide\":\r\n        if (typeof action.payload !== 'string') {\r\n          throw new ZaloSDKError(`${context}: payload phải là string cho type ${action.type}`, -1);\r\n        }\r\n        if (!action.payload || action.payload.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload không được để trống cho type ${action.type}`, -1);\r\n        }\r\n        if (action.payload.length > 1000) {\r\n          throw new ZaloSDKError(`${context}: payload không được vượt quá 1000 ký tự`, -1);\r\n        }\r\n        break;\r\n\r\n      case \"oa.open.sms\":\r\n        if (!action.payload || typeof action.payload !== 'object') {\r\n          throw new ZaloSDKError(`${context}: payload phải là object cho type oa.open.sms`, -1);\r\n        }\r\n        const smsPayload = action.payload as { content?: string; phone_code?: string };\r\n        if (!smsPayload.phone_code || smsPayload.phone_code.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload.phone_code không được để trống`, -1);\r\n        }\r\n        if (smsPayload.content && smsPayload.content.length > 160) {\r\n          throw new ZaloSDKError(`${context}: payload.content không được vượt quá 160 ký tự`, -1);\r\n        }\r\n        break;\r\n\r\n      case \"oa.open.phone\":\r\n        if (!action.payload || typeof action.payload !== 'object') {\r\n          throw new ZaloSDKError(`${context}: payload phải là object cho type oa.open.phone`, -1);\r\n        }\r\n        const phonePayload = action.payload as { phone_code?: string };\r\n        if (!phonePayload.phone_code || phonePayload.phone_code.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload.phone_code không được để trống`, -1);\r\n        }\r\n        break;\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate button payload theo type\r\n   * @param button Template button cần validate\r\n   * @param index Index của button trong mảng (để báo lỗi)\r\n   */\r\n  private validateButtonPayload(button: TemplateButton, index: number): void {\r\n    const context = `Button ${index + 1}`;\r\n\r\n    switch (button.type) {\r\n      case \"oa.open.url\":\r\n        if (!button.payload || typeof button.payload !== 'object') {\r\n          throw new ZaloSDKError(`${context}: payload phải là object cho type oa.open.url`, -1);\r\n        }\r\n        const urlPayload = button.payload as { url?: string };\r\n        if (!urlPayload.url || urlPayload.url.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload.url không được để trống`, -1);\r\n        }\r\n        break;\r\n\r\n      case \"oa.query.show\":\r\n      case \"oa.query.hide\":\r\n        if (typeof button.payload !== 'string') {\r\n          throw new ZaloSDKError(`${context}: payload phải là string cho type ${button.type}`, -1);\r\n        }\r\n        if (!button.payload || button.payload.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload không được để trống`, -1);\r\n        }\r\n        if (button.payload.length > 1000) {\r\n          throw new ZaloSDKError(`${context}: payload không được vượt quá 1000 ký tự`, -1);\r\n        }\r\n        break;\r\n\r\n      case \"oa.open.sms\":\r\n        if (!button.payload || typeof button.payload !== 'object') {\r\n          throw new ZaloSDKError(`${context}: payload phải là object cho type oa.open.sms`, -1);\r\n        }\r\n        const smsPayload = button.payload as { content?: string; phone_code?: string };\r\n        if (!smsPayload.phone_code || smsPayload.phone_code.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload.phone_code không được để trống`, -1);\r\n        }\r\n        if (smsPayload.content && smsPayload.content.length > 160) {\r\n          throw new ZaloSDKError(`${context}: payload.content không được vượt quá 160 ký tự`, -1);\r\n        }\r\n        break;\r\n\r\n      case \"oa.open.phone\":\r\n        if (!button.payload || typeof button.payload !== 'object') {\r\n          throw new ZaloSDKError(`${context}: payload phải là object cho type oa.open.phone`, -1);\r\n        }\r\n        const phonePayload = button.payload as { phone_code?: string };\r\n        if (!phonePayload.phone_code || phonePayload.phone_code.trim().length === 0) {\r\n          throw new ZaloSDKError(`${context}: payload.phone_code không được để trống`, -1);\r\n        }\r\n        break;\r\n\r\n      default:\r\n        const validButtonTypes = [\"oa.open.url\", \"oa.query.show\", \"oa.query.hide\", \"oa.open.sms\", \"oa.open.phone\"];\r\n        throw new ZaloSDKError(`${context}: type phải là một trong ${validButtonTypes.join(\", \")}`, -1);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template element\r\n   * @param title Tiêu đề (bắt buộc, ≤ 100 ký tự)\r\n   * @param subtitle Tiêu đề phụ (≤ 500 ký tự)\r\n   * @param imageUrl URL ảnh (optional)\r\n   * @param defaultAction Hành động khi click (optional)\r\n   * @returns TemplateElement object\r\n   */\r\n  public createTemplateElement(\r\n    title: string,\r\n    subtitle?: string,\r\n    imageUrl?: string,\r\n    defaultAction?: TemplateAction\r\n  ): TemplateElement {\r\n    return {\r\n      title,\r\n      ...(subtitle && { subtitle }),\r\n      ...(imageUrl && { image_url: imageUrl }),\r\n      ...(defaultAction && { default_action: defaultAction }),\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template button với URL\r\n   * @param title Tiêu đề button (≤ 100 ký tự)\r\n   * @param url URL để mở\r\n   * @returns TemplateButton object\r\n   */\r\n  public createUrlButton(title: string, url: string): TemplateButton {\r\n    return {\r\n      title,\r\n      type: \"oa.open.url\",\r\n      payload: { url },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template button với query (hiển thị)\r\n   * @param title Tiêu đề button (≤ 100 ký tự)\r\n   * @param payload Dữ liệu callback (≤ 1000 ký tự)\r\n   * @returns TemplateButton object\r\n   */\r\n  public createQueryShowButton(title: string, payload: string): TemplateButton {\r\n    return {\r\n      title,\r\n      type: \"oa.query.show\",\r\n      payload,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template button với query (ẩn)\r\n   * @param title Tiêu đề button (≤ 100 ký tự)\r\n   * @param payload Dữ liệu callback (≤ 1000 ký tự)\r\n   * @returns TemplateButton object\r\n   */\r\n  public createQueryHideButton(title: string, payload: string): TemplateButton {\r\n    return {\r\n      title,\r\n      type: \"oa.query.hide\",\r\n      payload,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template button với SMS\r\n   * @param title Tiêu đề button (≤ 100 ký tự)\r\n   * @param phoneCode Số điện thoại\r\n   * @param content Nội dung SMS (≤ 160 ký tự)\r\n   * @returns TemplateButton object\r\n   */\r\n  public createSmsButton(title: string, phoneCode: string, content?: string): TemplateButton {\r\n    return {\r\n      title,\r\n      type: \"oa.open.sms\",\r\n      payload: {\r\n        phone_code: phoneCode,\r\n        ...(content && { content }),\r\n      },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template button với Phone\r\n   * @param title Tiêu đề button (≤ 100 ký tự)\r\n   * @param phoneCode Số điện thoại\r\n   * @returns TemplateButton object\r\n   */\r\n  public createPhoneButton(title: string, phoneCode: string): TemplateButton {\r\n    return {\r\n      title,\r\n      type: \"oa.open.phone\",\r\n      payload: { phone_code: phoneCode },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template action với URL\r\n   * @param url URL để mở\r\n   * @returns TemplateAction object\r\n   */\r\n  public createUrlAction(url: string): TemplateAction {\r\n    return {\r\n      type: \"oa.open.url\",\r\n      url,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template action với query (hiển thị)\r\n   * @param payload Dữ liệu callback (≤ 1000 ký tự)\r\n   * @returns TemplateAction object\r\n   */\r\n  public createQueryShowAction(payload: string): TemplateAction {\r\n    return {\r\n      type: \"oa.query.show\",\r\n      payload,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template action với query (ẩn)\r\n   * @param payload Dữ liệu callback (≤ 1000 ký tự)\r\n   * @returns TemplateAction object\r\n   */\r\n  public createQueryHideAction(payload: string): TemplateAction {\r\n    return {\r\n      type: \"oa.query.hide\",\r\n      payload,\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template action với SMS\r\n   * @param phoneCode Số điện thoại\r\n   * @param content Nội dung SMS (≤ 160 ký tự)\r\n   * @returns TemplateAction object\r\n   */\r\n  public createSmsAction(phoneCode: string, content?: string): TemplateAction {\r\n    return {\r\n      type: \"oa.open.sms\",\r\n      payload: {\r\n        phone_code: phoneCode,\r\n        ...(content && { content }),\r\n      },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Helper function để tạo template action với Phone\r\n   * @param phoneCode Số điện thoại\r\n   * @returns TemplateAction object\r\n   */\r\n  public createPhoneAction(phoneCode: string): TemplateAction {\r\n    return {\r\n      type: \"oa.open.phone\",\r\n      payload: { phone_code: phoneCode },\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Utility method để sleep/delay\r\n   * @param ms Thời gian delay tính bằng milliseconds\r\n   */\r\n  private sleep(ms: number): Promise<void> {\r\n    return new Promise(resolve => setTimeout(resolve, ms));\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  SendMessageResponse,\r\n  TransactionMessage,\r\n  MessageRecipient,\r\n} from \"../types/message\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\n\r\n/**\r\n * Service xử lý các API tin nhắn giao dịch của Zalo Official Account\r\n *\r\n * ⚠️ **DEPRECATED**: Transaction Message API (v2.0) đã bị deprecated bởi Zalo\r\n *\r\n * **Khuyến nghị**: Sử dụng **ZNS Service** thay thế:\r\n * - `ZNSService.sendMessageByUid()` - Gửi tin nhắn template theo UID\r\n * - `ZNSService.sendMessage()` - Gửi tin nhắn theo số điện thoại\r\n *\r\n * Tham khảo: https://developers.zalo.me/docs/zalo-notification-service/\r\n *\r\n * ===\r\n *\r\n * ĐIỀU KIỆN GỬI TIN GIAO DỊCH:\r\n *\r\n * 1. THỜI GIAN GỬI:\r\n *    - Chỉ được gửi trong vòng 24 giờ kể từ khi người dùng tương tác cuối cùng với OA\r\n *    - Tương tác bao gồm: gửi tin nhắn, nhấn button, gọi điện, truy cập website từ OA\r\n *\r\n * 2. NỘI DUNG TIN NHẮN:\r\n *    - Phải liên quan trực tiếp đến giao dịch thực tế\r\n *    - Bao gồm: xác nhận đơn hàng, thông báo thanh toán, cập nhật trạng thái giao hàng\r\n *    - Không được chứa nội dung quảng cáo, khuyến mãi\r\n *\r\n * 3. TẦN SUẤT GỬI:\r\n *    - Tối đa 3 tin nhắn giao dịch/ngày cho mỗi người dùng\r\n *    - Phải có khoảng cách ít nhất 1 giờ giữa các tin nhắn\r\n *\r\n * 4. ĐỊNH DẠNG:\r\n *    - Phải sử dụng template được Zalo phê duyệt trước\r\n *    - Template phải tuân thủ format chuẩn của tin giao dịch\r\n *\r\n * 5. NGƯỜI DÙNG:\r\n *    - Người dùng phải đã follow OA\r\n *    - Người dùng không được block OA\r\n *    - Người dùng phải có tương tác gần đây với OA\r\n *\r\n * 6. OFFICIAL ACCOUNT:\r\n *    - OA phải được xác minh (verified)\r\n *    - OA phải có quyền gửi tin giao dịch được Zalo cấp phép\r\n *    - OA không được vi phạm chính sách của Zalo\r\n *\r\n * LỖI THƯỜNG GẶP:\r\n * - 1004: Người dùng chưa follow OA hoặc đã unfollow\r\n * - 1005: Vượt quá thời gian 24 giờ từ lần tương tác cuối\r\n * - 1006: Vượt quá giới hạn 3 tin/ngày\r\n * - 1007: Template chưa được phê duyệt hoặc không hợp lệ\r\n * - 1008: Nội dung tin nhắn vi phạm chính sách\r\n *\r\n * @deprecated Sử dụng ZNSService thay vì TransactionService\r\n */\r\nexport class TransactionService {\r\n  private readonly transactionApiUrl =\r\n    \"https://openapi.zalo.me/v2.0/oa/message/transaction\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Gửi tin nhắn giao dịch\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param message Nội dung tin nhắn giao dịch\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendTransactionMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    message: TransactionMessage\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validate transaction message\r\n      this.validateTransactionMessage(message);\r\n\r\n      const request = {\r\n        recipient,\r\n        message,\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.transactionApiUrl, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send transaction message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send transaction message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn xác nhận đơn hàng\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param orderInfo Thông tin đơn hàng\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendOrderConfirmation(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    orderInfo: {\r\n      orderId: string;\r\n      orderDate: string;\r\n      totalAmount: number;\r\n      items: Array<{\r\n        name: string;\r\n        quantity: number;\r\n        price: number;\r\n      }>;\r\n      customerInfo: {\r\n        name: string;\r\n        phone: string;\r\n        address: string;\r\n      };\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const message: TransactionMessage = {\r\n        type: \"transaction\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"transaction\",\r\n            elements: [\r\n              {\r\n                title: `Xác nhận đơn hàng #${orderInfo.orderId}`,\r\n                subtitle: `Ngày đặt: ${orderInfo.orderDate}`,\r\n                default_action: {\r\n                  type: \"web_url\",\r\n                  url: `https://example.com/orders/${orderInfo.orderId}`,\r\n                },\r\n                buttons: [\r\n                  {\r\n                    type: \"web_url\",\r\n                    title: \"Xem chi tiết\",\r\n                    url: `https://example.com/orders/${orderInfo.orderId}`,\r\n                  },\r\n                ],\r\n              },\r\n            ],\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendTransactionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send order confirmation: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn thông báo thanh toán\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param paymentInfo Thông tin thanh toán\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendPaymentNotification(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    paymentInfo: {\r\n      orderId: string;\r\n      amount: number;\r\n      paymentMethod: string;\r\n      paymentDate: string;\r\n      status: \"success\" | \"failed\" | \"pending\";\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const statusText = {\r\n        success: \"Thành công\",\r\n        failed: \"Thất bại\",\r\n        pending: \"Đang xử lý\",\r\n      };\r\n\r\n      const message: TransactionMessage = {\r\n        type: \"transaction\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"transaction\",\r\n            elements: [\r\n              {\r\n                title: `Thông báo thanh toán - ${\r\n                  statusText[paymentInfo.status]\r\n                }`,\r\n                subtitle: `Đơn hàng #${\r\n                  paymentInfo.orderId\r\n                } - ${paymentInfo.amount.toLocaleString(\"vi-VN\")} VNĐ`,\r\n                default_action: {\r\n                  type: \"web_url\",\r\n                  url: `https://example.com/payments/${paymentInfo.orderId}`,\r\n                },\r\n                buttons: [\r\n                  {\r\n                    type: \"web_url\",\r\n                    title: \"Xem chi tiết\",\r\n                    url: `https://example.com/payments/${paymentInfo.orderId}`,\r\n                  },\r\n                ],\r\n              },\r\n            ],\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendTransactionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send payment notification: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn cập nhật trạng thái giao hàng\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param shippingInfo Thông tin giao hàng\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendShippingUpdate(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    shippingInfo: {\r\n      orderId: string;\r\n      trackingNumber: string;\r\n      status: \"preparing\" | \"shipped\" | \"in_transit\" | \"delivered\";\r\n      estimatedDelivery?: string;\r\n      carrier?: string;\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const statusText = {\r\n        preparing: \"Đang chuẩn bị hàng\",\r\n        shipped: \"Đã giao cho đơn vị vận chuyển\",\r\n        in_transit: \"Đang vận chuyển\",\r\n        delivered: \"Đã giao hàng thành công\",\r\n      };\r\n\r\n      const message: TransactionMessage = {\r\n        type: \"transaction\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"transaction\",\r\n            elements: [\r\n              {\r\n                title: `Cập nhật giao hàng - ${\r\n                  statusText[shippingInfo.status]\r\n                }`,\r\n                subtitle: `Đơn hàng #${shippingInfo.orderId} - Mã vận đơn: ${shippingInfo.trackingNumber}`,\r\n                default_action: {\r\n                  type: \"web_url\",\r\n                  url: `https://example.com/tracking/${shippingInfo.trackingNumber}`,\r\n                },\r\n                buttons: [\r\n                  {\r\n                    type: \"web_url\",\r\n                    title: \"Theo dõi đơn hàng\",\r\n                    url: `https://example.com/tracking/${shippingInfo.trackingNumber}`,\r\n                  },\r\n                ],\r\n              },\r\n            ],\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendTransactionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send shipping update: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate transaction message format\r\n   * @param message Transaction message to validate\r\n   */\r\n  private validateTransactionMessage(message: TransactionMessage): void {\r\n    if (!message.attachment) {\r\n      throw new ZaloSDKError(\"Transaction message must have attachment\", -1);\r\n    }\r\n\r\n    if (!message.attachment.payload) {\r\n      throw new ZaloSDKError(\r\n        \"Transaction message attachment must have payload\",\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (message.attachment.payload.template_type !== \"transaction\") {\r\n      throw new ZaloSDKError(\r\n        \"Transaction message must use transaction template type\",\r\n        -1\r\n      );\r\n    }\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  SendMessageResponse,\r\n  MessageRecipient,\r\n} from \"../types/message\";\r\nimport {\r\n  PromotionMessage,\r\n  PromotionUserResult,\r\n  MultiplePromotionProgress,\r\n  MultiplePromotionResult,\r\n  PromotionButtonInput,\r\n  BannerConfig,\r\n} from \"../types/promotion\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\n\r\n/**\r\n * Service xử lý các API tin nhắn truyền thông/quảng cáo của Zalo Official Account\r\n *\r\n * ⚠️ **DEPRECATED**: Promotion Message API (v3.0) đã bị deprecated bởi Zalo\r\n *\r\n * **Khuyến nghị**: Sử dụng **ZNS Service** thay thế:\r\n * - `ZNSService.sendMessageByUid()` - Gửi tin nhắn template theo UID\r\n * - `ZNSService.sendMessage()` - Gửi tin nhắn theo số điện thoại\r\n *\r\n * Tham khảo: https://developers.zalo.me/docs/zalo-notification-service/\r\n *\r\n * ===\r\n *\r\n * ĐIỀU KIỆN GỬI TIN TRUYỀN THÔNG:\r\n *\r\n * 1. THỜI GIAN GỬI:\r\n *    - Chỉ được gửi trong khung giờ từ 8:00 - 22:00 hàng ngày\r\n *    - Không được gửi vào các ngày lễ, tết theo quy định\r\n *\r\n * 2. NỘI DUNG TIN NHẮN:\r\n *    - Phải là nội dung quảng cáo, khuyến mãi, tin tức\r\n *    - Không được chứa nội dung vi phạm pháp luật\r\n *    - Phải tuân thủ quy định về quảng cáo của Việt Nam\r\n *\r\n * 3. TẦN SUẤT GỬI:\r\n *    - Tối đa 1 tin nhắn truyền thông/ngày cho mỗi người dùng\r\n *    - Người dùng có thể từ chối nhận tin truyền thông\r\n *\r\n * 4. ĐỊNH DẠNG:\r\n *    - Phải sử dụng template được Zalo phê duyệt trước\r\n *    - Template phải tuân thủ format chuẩn của tin truyền thông\r\n *\r\n * 5. NGƯỜI DÙNG:\r\n *    - Người dùng phải đã follow OA\r\n *    - Người dùng không được block OA\r\n *    - Người dùng không được từ chối nhận tin truyền thông\r\n *\r\n * 6. OFFICIAL ACCOUNT:\r\n *    - OA phải được xác minh (verified)\r\n *    - OA phải có quyền gửi tin truyền thông được Zalo cấp phép\r\n *    - OA không được vi phạm chính sách về quảng cáo\r\n *\r\n * LỖI THƯỜNG GẶP:\r\n * - 2001: Ngoài khung giờ cho phép (8:00-22:00)\r\n * - 2002: Người dùng đã từ chối nhận tin truyền thông\r\n * - 2003: Vượt quá giới hạn 1 tin/ngày\r\n * - 2004: Template chưa được phê duyệt\r\n * - 2005: Nội dung vi phạm chính sách quảng cáo\r\n * - 2006: OA chưa có quyền gửi tin truyền thông\r\n *\r\n * @deprecated Sử dụng ZNSService thay vì PromotionService\r\n */\r\nexport class PromotionService {\r\n  private readonly promotionApiUrl =\r\n    \"https://openapi.zalo.me/v3.0/oa/message/promotion\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Convert input button to proper typed button for API\r\n   * @param button Input button with flexible typing\r\n   * @returns Properly typed button for API\r\n   */\r\n  private convertToTypedButton(button: PromotionButtonInput): any {\r\n    const baseButton = {\r\n      title: button.title,\r\n      image_icon: button.imageIcon || \"\",\r\n      type: button.type,\r\n    };\r\n\r\n    // Convert payload based on button type using discriminated union\r\n    switch (button.type) {\r\n      case \"oa.open.url\":\r\n        return {\r\n          ...baseButton,\r\n          payload: { url: button.payload.url }\r\n        };\r\n\r\n      case \"oa.query.show\":\r\n      case \"oa.query.hide\":\r\n        return {\r\n          ...baseButton,\r\n          payload: button.payload // Already string type\r\n        };\r\n\r\n      case \"oa.open.sms\":\r\n        return {\r\n          ...baseButton,\r\n          payload: {\r\n            content: button.payload.content,\r\n            phone_code: button.payload.phone_code || button.payload.phoneCode || \"\"\r\n          }\r\n        };\r\n\r\n      case \"oa.open.phone\":\r\n        return {\r\n          ...baseButton,\r\n          payload: typeof button.payload === 'string'\r\n            ? { phone_code: button.payload }\r\n            : { phone_code: button.payload.phone_code || button.payload.phoneCode || \"\" }\r\n        };\r\n\r\n      default:\r\n        // This should never happen with proper typing\r\n        return {\r\n          ...baseButton,\r\n          payload: {}\r\n        };\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn truyền thông/quảng cáo\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param message Nội dung tin nhắn truyền thông\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendPromotionMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    message: PromotionMessage\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validate promotion message\r\n      this.validatePromotionMessage(message);\r\n\r\n      // Check sending time (8:00 - 22:00)\r\n      this.validateSendingTime();\r\n\r\n      const request = {\r\n        recipient,\r\n        message,\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(this.promotionApiUrl, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send promotion message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send promotion message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn khuyến mãi sản phẩm\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param promotionInfo Thông tin khuyến mãi\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendProductPromotion(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    promotionInfo: {\r\n      title: string;\r\n      description: string;\r\n      imageUrl?: string;\r\n      attachmentId?: string;\r\n      originalPrice: number;\r\n      discountPrice: number;\r\n      discountPercent: number;\r\n      validUntil: string;\r\n      productUrl: string;\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const message: PromotionMessage = {\r\n        type: \"promotion\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"promotion\",\r\n            language: \"VI\",\r\n            elements: [\r\n              // Banner element\r\n              {\r\n                type: \"banner\",\r\n                ...(promotionInfo.attachmentId\r\n                  ? { attachment_id: promotionInfo.attachmentId }\r\n                  : { image_url: promotionInfo.imageUrl }\r\n                ),\r\n              },\r\n              // Header element\r\n              {\r\n                type: \"header\",\r\n                content: `💥💥 ${promotionInfo.title} 💥💥`,\r\n                align: \"center\",\r\n              },\r\n              // Description text\r\n              {\r\n                type: \"text\",\r\n                content: promotionInfo.description,\r\n                align: \"left\",\r\n              },\r\n              // Price table\r\n              {\r\n                type: \"table\",\r\n                content: [\r\n                  {\r\n                    key: \"Giá gốc\",\r\n                    value: `${promotionInfo.originalPrice.toLocaleString('vi-VN')}đ`,\r\n                  },\r\n                  {\r\n                    key: \"Giá khuyến mãi\",\r\n                    value: `${promotionInfo.discountPrice.toLocaleString('vi-VN')}đ`,\r\n                  },\r\n                  {\r\n                    key: \"Giảm giá\",\r\n                    value: `${promotionInfo.discountPercent}%`,\r\n                  },\r\n                  {\r\n                    key: \"Hạn sử dụng\",\r\n                    value: promotionInfo.validUntil,\r\n                  },\r\n                ],\r\n              },\r\n              // Footer text\r\n              {\r\n                type: \"text\",\r\n                content: \"🛒 Nhanh tay đặt hàng để không bỏ lỡ ưu đãi!\",\r\n                align: \"center\",\r\n              },\r\n            ],\r\n            buttons: [\r\n              {\r\n                title: \"Mua ngay\",\r\n                image_icon: \"\",\r\n                type: \"oa.open.url\",\r\n                payload: {\r\n                  url: promotionInfo.productUrl,\r\n                },\r\n              },\r\n              {\r\n                title: \"Xem thêm sản phẩm\",\r\n                image_icon: \"\",\r\n                type: \"oa.query.hide\",\r\n                payload: \"#xemthem\",\r\n              },\r\n            ] as any,\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendPromotionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send product promotion: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn thông báo sự kiện\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param eventInfo Thông tin sự kiện\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendEventNotification(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    eventInfo: {\r\n      title: string;\r\n      description: string;\r\n      imageUrl?: string;\r\n      attachmentId?: string;\r\n      eventDate: string;\r\n      location: string;\r\n      registrationUrl: string;\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const message: PromotionMessage = {\r\n        type: \"promotion\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"promotion\",\r\n            language: \"VI\",\r\n            elements: [\r\n              // Banner element\r\n              {\r\n                type: \"banner\",\r\n                ...(eventInfo.attachmentId\r\n                  ? { attachment_id: eventInfo.attachmentId }\r\n                  : { image_url: eventInfo.imageUrl }\r\n                ),\r\n              },\r\n              // Header element\r\n              {\r\n                type: \"header\",\r\n                content: `🎉 ${eventInfo.title} 🎉`,\r\n                align: \"center\",\r\n              },\r\n              // Description text\r\n              {\r\n                type: \"text\",\r\n                content: eventInfo.description,\r\n                align: \"left\",\r\n              },\r\n              // Event details table\r\n              {\r\n                type: \"table\",\r\n                content: [\r\n                  {\r\n                    key: \"Thời gian\",\r\n                    value: eventInfo.eventDate,\r\n                  },\r\n                  {\r\n                    key: \"Địa điểm\",\r\n                    value: eventInfo.location,\r\n                  },\r\n                ],\r\n              },\r\n              // Call to action text\r\n              {\r\n                type: \"text\",\r\n                content: \"🎫 Đăng ký ngay để không bỏ lỡ sự kiện!\",\r\n                align: \"center\",\r\n              },\r\n            ],\r\n            buttons: [\r\n              {\r\n                title: \"Đăng ký tham gia\",\r\n                image_icon: \"\",\r\n                type: \"oa.open.url\",\r\n                payload: {\r\n                  url: eventInfo.registrationUrl,\r\n                },\r\n              },\r\n              {\r\n                title: \"Chia sẻ sự kiện\",\r\n                image_icon: \"\",\r\n                type: \"oa.query.hide\",\r\n                payload: \"#chiase\",\r\n              },\r\n            ] as any,\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendPromotionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send event notification: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn newsletter\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param newsletterInfo Thông tin newsletter\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendNewsletter(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    newsletterInfo: {\r\n      title: string;\r\n      summary: string;\r\n      imageUrl?: string;\r\n      attachmentId?: string;\r\n      articles: Array<{\r\n        title: string;\r\n        url: string;\r\n      }>;\r\n      unsubscribeUrl: string;\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const message: PromotionMessage = {\r\n        type: \"promotion\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"promotion\",\r\n            language: \"VI\",\r\n            elements: [\r\n              // Banner element\r\n              {\r\n                type: \"banner\",\r\n                ...(newsletterInfo.attachmentId\r\n                  ? { attachment_id: newsletterInfo.attachmentId }\r\n                  : { image_url: newsletterInfo.imageUrl }\r\n                ),\r\n              },\r\n              // Header element\r\n              {\r\n                type: \"header\",\r\n                content: `📰 ${newsletterInfo.title}`,\r\n                align: \"center\",\r\n              },\r\n              // Summary text\r\n              {\r\n                type: \"text\",\r\n                content: newsletterInfo.summary,\r\n                align: \"left\",\r\n              },\r\n              // Articles table\r\n              {\r\n                type: \"table\",\r\n                content: newsletterInfo.articles.slice(0, 5).map((article, index) => ({\r\n                  key: `Bài viết ${index + 1}`,\r\n                  value: article.title,\r\n                })),\r\n              },\r\n              // Footer text\r\n              {\r\n                type: \"text\",\r\n                content: \"📖 Đọc ngay để cập nhật thông tin mới nhất!\",\r\n                align: \"center\",\r\n              },\r\n            ],\r\n            buttons: [\r\n              {\r\n                title: \"Đọc toàn bộ\",\r\n                image_icon: \"\",\r\n                type: \"oa.open.url\",\r\n                payload: {\r\n                  url: newsletterInfo.articles[0]?.url || \"#\",\r\n                },\r\n              },\r\n              {\r\n                title: \"Hủy đăng ký\",\r\n                image_icon: \"\",\r\n                type: \"oa.open.url\",\r\n                payload: {\r\n                  url: newsletterInfo.unsubscribeUrl,\r\n                },\r\n              },\r\n            ] as any,\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendPromotionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send newsletter: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Tạo promotion message theo format chuẩn v3.0 (như trong docs)\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param promotionData Dữ liệu promotion\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendCustomPromotion(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    promotionData: {\r\n      banner: BannerConfig;\r\n      headerContent: string;\r\n      textContent: string;\r\n      tableData: Array<{ key: string; value: string }>;\r\n      footerText?: string;\r\n      buttons: PromotionButtonInput[];\r\n      language?: \"VI\" | \"EN\";\r\n    }\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      // Validate banner configuration\r\n      this.validateBannerConfig(promotionData.banner);\r\n\r\n      const elements: any[] = [\r\n        // Banner element\r\n        {\r\n          type: \"banner\",\r\n          ...promotionData.banner,\r\n        },\r\n        // Header element\r\n        {\r\n          type: \"header\",\r\n          content: promotionData.headerContent,\r\n        },\r\n        // Text element\r\n        {\r\n          type: \"text\",\r\n          align: \"left\",\r\n          content: promotionData.textContent,\r\n        },\r\n      ];\r\n\r\n      // Add table if provided\r\n      if (promotionData.tableData && promotionData.tableData.length > 0) {\r\n        elements.push({\r\n          type: \"table\",\r\n          content: promotionData.tableData,\r\n        });\r\n      }\r\n\r\n      // Add footer text if provided\r\n      if (promotionData.footerText) {\r\n        elements.push({\r\n          type: \"text\",\r\n          align: \"center\",\r\n          content: promotionData.footerText,\r\n        });\r\n      }\r\n\r\n      const message: PromotionMessage = {\r\n        type: \"promotion\",\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"promotion\",\r\n            language: promotionData.language || \"VI\",\r\n            elements,\r\n            buttons: promotionData.buttons.map(btn => this.convertToTypedButton(btn)),\r\n          },\r\n        },\r\n      };\r\n\r\n      return this.sendPromotionMessage(accessToken, recipient, message);\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send custom promotion: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate banner configuration\r\n   * @param banner Banner configuration to validate\r\n   */\r\n  private validateBannerConfig(banner: BannerConfig): void {\r\n    if (!banner.image_url && !banner.attachment_id) {\r\n      throw new ZaloSDKError(\r\n        \"Banner must have either image_url or attachment_id\",\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (banner.image_url && banner.attachment_id) {\r\n      throw new ZaloSDKError(\r\n        \"Banner cannot have both image_url and attachment_id. Use only one.\",\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate promotion message format\r\n   * @param message Promotion message to validate\r\n   */\r\n  private validatePromotionMessage(message: PromotionMessage): void {\r\n    if (!message.attachment) {\r\n      throw new ZaloSDKError(\"Promotion message must have attachment\", -1);\r\n    }\r\n\r\n    if (!message.attachment.payload) {\r\n      throw new ZaloSDKError(\r\n        \"Promotion message attachment must have payload\",\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (message.attachment.payload.template_type !== \"promotion\") {\r\n      throw new ZaloSDKError(\r\n        \"Promotion message must use promotion template type\",\r\n        -1\r\n      );\r\n    }\r\n\r\n    if (!message.attachment.payload.elements || message.attachment.payload.elements.length === 0) {\r\n      throw new ZaloSDKError(\r\n        \"Promotion message must have at least one element\",\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi promotion message đến nhiều user_ids với callback tracking\r\n   * @param accessToken Access token của Official Account\r\n   * @param userIds Danh sách user IDs cần gửi\r\n   * @param promotionData Dữ liệu promotion\r\n   * @param options Tùy chọn gửi và tracking\r\n   * @returns Kết quả gửi đến tất cả users\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendCustomPromotionToMultipleUsers(\r\n    accessToken: string,\r\n    userIds: string[],\r\n    promotionData: {\r\n      banner: BannerConfig;\r\n      headerContent: string;\r\n      textContent: string;\r\n      tableData: Array<{ key: string; value: string }>;\r\n      footerText?: string;\r\n      buttons: PromotionButtonInput[];\r\n      language?: \"VI\" | \"EN\";\r\n    },\r\n    options?: {\r\n      mode?: \"sequential\" | \"parallel\"; // Chế độ gửi: tuần tự hoặc song song\r\n      delayMs?: number; // Delay giữa các tin nhắn (chỉ cho sequential mode)\r\n      onProgress?: (progress: MultiplePromotionProgress) => void; // Callback tracking\r\n      onUserComplete?: (result: PromotionUserResult) => void; // Callback cho từng user\r\n      continueOnError?: boolean; // Tiếp tục khi có lỗi (default: true)\r\n    }\r\n  ): Promise<MultiplePromotionResult> {\r\n    try {\r\n      // Validate inputs\r\n      if (!userIds || userIds.length === 0) {\r\n        throw new ZaloSDKError(\"User IDs array cannot be empty\", -1);\r\n      }\r\n\r\n      // Remove duplicates\r\n      const uniqueUserIds = [...new Set(userIds)];\r\n\r\n      // Validate sending time\r\n      this.validateSendingTime();\r\n\r\n      const startTime = Date.now();\r\n      const results: PromotionUserResult[] = [];\r\n      const mode = options?.mode || \"sequential\";\r\n      const delayMs = options?.delayMs || 1000;\r\n      const continueOnError = options?.continueOnError !== false;\r\n\r\n      // Initial progress callback\r\n      if (options?.onProgress) {\r\n        options.onProgress({\r\n          total: uniqueUserIds.length,\r\n          completed: 0,\r\n          successful: 0,\r\n          failed: 0,\r\n          currentUserId: null,\r\n          startTime,\r\n          estimatedTimeRemaining: null,\r\n        });\r\n      }\r\n\r\n      if (mode === \"parallel\") {\r\n        // Gửi song song\r\n        const promises = uniqueUserIds.map(async (userId) => {\r\n          try {\r\n            const result = await this.sendCustomPromotion(\r\n              accessToken,\r\n              { user_id: userId },\r\n              promotionData\r\n            );\r\n\r\n            const userResult: PromotionUserResult = {\r\n              userId,\r\n              success: true,\r\n              result,\r\n              error: null,\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            return userResult;\r\n          } catch (error) {\r\n            const userResult: PromotionUserResult = {\r\n              userId,\r\n              success: false,\r\n              result: null,\r\n              error: error instanceof ZaloSDKError ? error : new ZaloSDKError(\r\n                `Failed to send to user ${userId}: ${(error as Error).message}`,\r\n                -1,\r\n                error\r\n              ),\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            if (!continueOnError) {\r\n              throw userResult.error;\r\n            }\r\n\r\n            return userResult;\r\n          }\r\n        });\r\n\r\n        const parallelResults = await Promise.all(promises);\r\n        results.push(...parallelResults);\r\n      } else {\r\n        // Gửi tuần tự\r\n        for (let i = 0; i < uniqueUserIds.length; i++) {\r\n          const userId = uniqueUserIds[i];\r\n\r\n          try {\r\n            // Progress callback\r\n            if (options?.onProgress) {\r\n              const completed = i;\r\n              const successful = results.filter(r => r.success).length;\r\n              const failed = results.filter(r => !r.success).length;\r\n              const elapsed = Date.now() - startTime;\r\n              const avgTimePerUser = elapsed / Math.max(completed, 1);\r\n              const remaining = uniqueUserIds.length - completed;\r\n              const estimatedTimeRemaining = remaining * avgTimePerUser;\r\n\r\n              options.onProgress({\r\n                total: uniqueUserIds.length,\r\n                completed,\r\n                successful,\r\n                failed,\r\n                currentUserId: userId,\r\n                startTime,\r\n                estimatedTimeRemaining,\r\n              });\r\n            }\r\n\r\n            const result = await this.sendCustomPromotion(\r\n              accessToken,\r\n              { user_id: userId },\r\n              promotionData\r\n            );\r\n\r\n            const userResult: PromotionUserResult = {\r\n              userId,\r\n              success: true,\r\n              result,\r\n              error: null,\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            results.push(userResult);\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            // Delay giữa các tin nhắn (trừ tin cuối cùng)\r\n            if (i < uniqueUserIds.length - 1 && delayMs > 0) {\r\n              await new Promise(resolve => setTimeout(resolve, delayMs));\r\n            }\r\n\r\n          } catch (error) {\r\n            const userResult: PromotionUserResult = {\r\n              userId,\r\n              success: false,\r\n              result: null,\r\n              error: error instanceof ZaloSDKError ? error : new ZaloSDKError(\r\n                `Failed to send to user ${userId}: ${(error as Error).message}`,\r\n                -1,\r\n                error\r\n              ),\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            results.push(userResult);\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            if (!continueOnError) {\r\n              throw userResult.error;\r\n            }\r\n          }\r\n        }\r\n      }\r\n\r\n      const endTime = Date.now();\r\n      const successful = results.filter(r => r.success);\r\n      const failed = results.filter(r => !r.success);\r\n\r\n      // Final progress callback\r\n      if (options?.onProgress) {\r\n        options.onProgress({\r\n          total: uniqueUserIds.length,\r\n          completed: uniqueUserIds.length,\r\n          successful: successful.length,\r\n          failed: failed.length,\r\n          currentUserId: null,\r\n          startTime,\r\n          estimatedTimeRemaining: 0,\r\n        });\r\n      }\r\n\r\n      return {\r\n        total: uniqueUserIds.length,\r\n        successful: successful.length,\r\n        failed: failed.length,\r\n        results,\r\n        executionTime: endTime - startTime,\r\n        mode,\r\n        startTime: new Date(startTime),\r\n        endTime: new Date(endTime),\r\n        successRate: (successful.length / uniqueUserIds.length) * 100,\r\n      };\r\n\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send promotion to multiple users: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi promotion message đến nhiều user_ids với promotionData riêng cho từng user\r\n   * @param accessToken Access token của Official Account\r\n   * @param userPromotions Danh sách user và promotionData tương ứng\r\n   * @param options Tùy chọn gửi và tracking\r\n   * @returns Kết quả gửi đến tất cả users\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendPersonalizedPromotionToMultipleUsers(\r\n    accessToken: string,\r\n    userPromotions: Array<{\r\n      userId: string;\r\n      promotionData: {\r\n        banner: BannerConfig;\r\n        headerContent: string;\r\n        textContent: string;\r\n        tableData: Array<{ key: string; value: string }>;\r\n        footerText?: string;\r\n        buttons: PromotionButtonInput[];\r\n        language?: \"VI\" | \"EN\";\r\n      };\r\n    }>,\r\n    options?: {\r\n      mode?: \"sequential\" | \"parallel\"; // Chế độ gửi: tuần tự hoặc song song\r\n      delayMs?: number; // Delay giữa các tin nhắn (chỉ cho sequential mode)\r\n      onProgress?: (progress: MultiplePromotionProgress) => void; // Callback tracking\r\n      onUserComplete?: (result: PromotionUserResult) => void; // Callback cho từng user\r\n      continueOnError?: boolean; // Tiếp tục khi có lỗi (default: true)\r\n    }\r\n  ): Promise<MultiplePromotionResult> {\r\n    try {\r\n      // Validate inputs\r\n      if (!userPromotions || userPromotions.length === 0) {\r\n        throw new ZaloSDKError(\"User promotions array cannot be empty\", -1);\r\n      }\r\n\r\n      // Extract unique user IDs\r\n      const userIds = userPromotions.map(up => up.userId);\r\n      const uniqueUserIds = [...new Set(userIds)];\r\n\r\n      if (uniqueUserIds.length !== userPromotions.length) {\r\n        throw new ZaloSDKError(\"Duplicate user IDs found in user promotions\", -1);\r\n      }\r\n\r\n      // Validate sending time\r\n      this.validateSendingTime();\r\n\r\n      const startTime = Date.now();\r\n      const results: PromotionUserResult[] = [];\r\n      const mode = options?.mode || \"sequential\";\r\n      const delayMs = options?.delayMs || 1000;\r\n      const continueOnError = options?.continueOnError !== false;\r\n\r\n      // Initial progress callback\r\n      if (options?.onProgress) {\r\n        options.onProgress({\r\n          total: uniqueUserIds.length,\r\n          completed: 0,\r\n          successful: 0,\r\n          failed: 0,\r\n          currentUserId: null,\r\n          startTime,\r\n          estimatedTimeRemaining: null,\r\n        });\r\n      }\r\n\r\n      if (mode === \"parallel\") {\r\n        // Gửi song song\r\n        const promises = userPromotions.map(async (userPromotion) => {\r\n          try {\r\n            const result = await this.sendCustomPromotion(\r\n              accessToken,\r\n              { user_id: userPromotion.userId },\r\n              userPromotion.promotionData\r\n            );\r\n\r\n            const userResult: PromotionUserResult = {\r\n              userId: userPromotion.userId,\r\n              success: true,\r\n              result,\r\n              error: null,\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            return userResult;\r\n          } catch (error) {\r\n            const userResult: PromotionUserResult = {\r\n              userId: userPromotion.userId,\r\n              success: false,\r\n              result: null,\r\n              error: error instanceof ZaloSDKError ? error : new ZaloSDKError(\r\n                `Failed to send to user ${userPromotion.userId}: ${(error as Error).message}`,\r\n                -1,\r\n                error\r\n              ),\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            if (!continueOnError) {\r\n              throw userResult.error;\r\n            }\r\n\r\n            return userResult;\r\n          }\r\n        });\r\n\r\n        const parallelResults = await Promise.all(promises);\r\n        results.push(...parallelResults);\r\n      } else {\r\n        // Gửi tuần tự\r\n        for (let i = 0; i < userPromotions.length; i++) {\r\n          const userPromotion = userPromotions[i];\r\n\r\n          try {\r\n            // Progress callback\r\n            if (options?.onProgress) {\r\n              const completed = i;\r\n              const successful = results.filter(r => r.success).length;\r\n              const failed = results.filter(r => !r.success).length;\r\n              const elapsed = Date.now() - startTime;\r\n              const avgTimePerUser = elapsed / Math.max(completed, 1);\r\n              const remaining = userPromotions.length - completed;\r\n              const estimatedTimeRemaining = remaining * avgTimePerUser;\r\n\r\n              options.onProgress({\r\n                total: userPromotions.length,\r\n                completed,\r\n                successful,\r\n                failed,\r\n                currentUserId: userPromotion.userId,\r\n                startTime,\r\n                estimatedTimeRemaining,\r\n              });\r\n            }\r\n\r\n            const result = await this.sendCustomPromotion(\r\n              accessToken,\r\n              { user_id: userPromotion.userId },\r\n              userPromotion.promotionData\r\n            );\r\n\r\n            const userResult: PromotionUserResult = {\r\n              userId: userPromotion.userId,\r\n              success: true,\r\n              result,\r\n              error: null,\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            results.push(userResult);\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            // Delay giữa các tin nhắn (trừ tin cuối cùng)\r\n            if (i < userPromotions.length - 1 && delayMs > 0) {\r\n              await new Promise(resolve => setTimeout(resolve, delayMs));\r\n            }\r\n\r\n          } catch (error) {\r\n            const userResult: PromotionUserResult = {\r\n              userId: userPromotion.userId,\r\n              success: false,\r\n              result: null,\r\n              error: error instanceof ZaloSDKError ? error : new ZaloSDKError(\r\n                `Failed to send to user ${userPromotion.userId}: ${(error as Error).message}`,\r\n                -1,\r\n                error\r\n              ),\r\n              timestamp: new Date(),\r\n            };\r\n\r\n            results.push(userResult);\r\n\r\n            // Callback cho từng user\r\n            if (options?.onUserComplete) {\r\n              options.onUserComplete(userResult);\r\n            }\r\n\r\n            if (!continueOnError) {\r\n              throw userResult.error;\r\n            }\r\n          }\r\n        }\r\n      }\r\n\r\n      const endTime = Date.now();\r\n      const successful = results.filter(r => r.success);\r\n      const failed = results.filter(r => !r.success);\r\n\r\n      // Final progress callback\r\n      if (options?.onProgress) {\r\n        options.onProgress({\r\n          total: userPromotions.length,\r\n          completed: userPromotions.length,\r\n          successful: successful.length,\r\n          failed: failed.length,\r\n          currentUserId: null,\r\n          startTime,\r\n          estimatedTimeRemaining: 0,\r\n        });\r\n      }\r\n\r\n      return {\r\n        total: userPromotions.length,\r\n        successful: successful.length,\r\n        failed: failed.length,\r\n        results,\r\n        executionTime: endTime - startTime,\r\n        mode,\r\n        startTime: new Date(startTime),\r\n        endTime: new Date(endTime),\r\n        successRate: (successful.length / userPromotions.length) * 100,\r\n      };\r\n\r\n    } catch (error) {\r\n      throw new ZaloSDKError(\r\n        `Failed to send personalized promotion to multiple users: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Validate sending time (8:00 - 22:00)\r\n   */\r\n  private validateSendingTime(): void {\r\n    const now = new Date();\r\n    const hour = now.getHours();\r\n\r\n    if (hour < 8 || hour >= 22) {\r\n      throw new ZaloSDKError(\r\n        \"Tin nhắn truyền thông chỉ được gửi trong khung giờ từ 8:00 - 22:00\",\r\n        2001\r\n      );\r\n    }\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport {\r\n  Message,\r\n  SendMessageRequest,\r\n  SendMessageResponse,\r\n  ImageMessage,\r\n  MessageRecipient,\r\n  AnonymousTextMessage,\r\n} from \"../types/message\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\n\r\n/**\r\n * Service xử lý các API tin nhắn tổng quát của Zalo Official Account\r\n *\r\n * Bao gồm các loại tin nhắn:\r\n * - Tin nhắn phản hồi (Response Message)\r\n * - Tin nhắn ẩn danh (Anonymous Message)\r\n * - Tin nhắn tự động (Auto Message)\r\n * - Tin nhắn hệ thống (System Message)\r\n *\r\n * ĐIỀU KIỆN GỬI TIN NHẮN TỔNG QUÁT:\r\n *\r\n * 1. TIN NHẮN PHẢN HỒI:\r\n *    - Chỉ được gửi trong vòng 24 giờ kể từ khi người dùng gửi tin nhắn đến OA\r\n *    - Không giới hạn số lượng tin nhắn phản hồi\r\n *    - Có thể chứa bất kỳ nội dung nào (tư vấn, quảng cáo, thông tin)\r\n *\r\n * 2. TIN NHẮN ẨN DANH:\r\n *    - Gửi tin nhắn mà không hiển thị thông tin OA\r\n *    - Chỉ dành cho các trường hợp đặc biệt\r\n *    - Cần được Zalo phê duyệt trước khi sử dụng\r\n *\r\n * 3. TIN NHẮN TỰ ĐỘNG:\r\n *    - Tin nhắn được gửi tự động dựa trên trigger\r\n *    - Bao gồm: tin chào mừng, tin cảm ơn, tin nhắc nhở\r\n *\r\n * 4. TIN NHẮN HỆ THỐNG:\r\n *    - Tin nhắn thông báo từ hệ thống\r\n *    - Bao gồm: thông báo bảo trì, cập nhật chính sách\r\n */\r\nexport class GeneralMessageService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    // Message endpoints\r\n    message: {\r\n      send: \"https://openapi.zalo.me/v3.0/oa/message/cs\",\r\n      sendAnonymous: \"https://openapi.zalo.me/v3.0/oa/message/anonymous\",\r\n    },\r\n\r\n  };\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n\r\n\r\n  /**\r\n   * Gửi tin nhắn phản hồi hình ảnh\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param message Nội dung tin nhắn hình ảnh\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  public async sendResponseImageMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    message: ImageMessage\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!message.attachment?.payload?.url) {\r\n        throw new ZaloSDKError(\"URL hình ảnh không được để trống\", -1);\r\n      }\r\n\r\n      const endpoint = this.endpoints.message.send;\r\n      const request: SendMessageRequest = {\r\n        recipient,\r\n        message,\r\n        messaging_type: \"response\",\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send response image message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send response image message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn ẩn danh\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param message Nội dung tin nhắn ẩn danh\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  public async sendAnonymousMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    message: AnonymousTextMessage\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      if (!message.text || message.text.trim().length === 0) {\r\n        throw new ZaloSDKError(\r\n          \"Nội dung tin nhắn ẩn danh không được để trống\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      const endpoint = this.endpoints.message.sendAnonymous;\r\n      const request = {\r\n        recipient,\r\n        message,\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send anonymous message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send anonymous message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n\r\n\r\n\r\n\r\n  /**\r\n   * Gửi tin nhắn tổng quát\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận\r\n   * @param message Nội dung tin nhắn\r\n   * @param messagingType Loại tin nhắn (response, update, message_tag)\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  public async sendMessage(\r\n    accessToken: string,\r\n    recipient: MessageRecipient,\r\n    message: Message,\r\n    messagingType:\r\n      | \"consultation\"\r\n      | \"transaction\"\r\n      | \"promotion\"\r\n      | \"response\"\r\n      | \"update\"\r\n      | \"message_tag\" = \"response\"\r\n  ): Promise<SendMessageResponse> {\r\n    try {\r\n      const endpoint = this.endpoints.message.send;\r\n      const request: SendMessageRequest = {\r\n        recipient,\r\n        message,\r\n        messaging_type: messagingType,\r\n      };\r\n\r\n      const result: ZaloResponse<SendMessageResponse> =\r\n        await this.client.apiPost(endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\n\r\n/**\r\n * Interface cho thông tin quota tin nhắn\r\n */\r\nexport interface MessageQuotaInfo {\r\n  quota: number;\r\n  used: number;\r\n  remaining: number;\r\n  reset_time: number;\r\n}\r\n\r\n/**\r\n * Interface cho tin nhắn trong cuộc hội thoại - matches Zalo API docs exactly\r\n */\r\nexport interface ConversationMessage {\r\n  /** ID của tin nhắn */\r\n  message_id: string;\r\n  /** Thuộc tính cho biết tin nhắn được gửi từ OA hoặc người dùng. 0: tin nhắn từ OA đến user, 1: tin nhắn từ user đến OA */\r\n  src: number;\r\n  /** Thời gian tin nhắn được gửi (định dạng timestamp) */\r\n  time: number;\r\n  /** Loại tin nhắn: text, voice, photo, GIF, link, links, sticker, location */\r\n  type: string;\r\n  /** Nội dung tin nhắn */\r\n  message: string;\r\n  /** Thuộc tính trả về 1 danh sách các đường dẫn được đính kèm trong tin nhắn (chỉ có khi type là link hoặc links) */\r\n  links?: string[];\r\n  /** URL của ảnh hoặc GIF (chỉ có khi type là GIF hoặc photo) */\r\n  thumb?: string;\r\n  /** Đường dẫn đến audio/ảnh/GIF/nhãn dán */\r\n  url?: string;\r\n  /** Mô tả của ảnh (chỉ có khi type là photo) */\r\n  description?: string;\r\n  /** ID của đối tượng gửi tin nhắn (có thể là user_id hoặc oa_id) */\r\n  from_id: string;\r\n  /** ID của đối tượng nhận tin nhắn (có thể là user_id hoặc oa_id) */\r\n  to_id: string;\r\n  /** Tên hiển thị của đối tượng gửi tin nhắn */\r\n  from_display_name: string;\r\n  /** Đường dẫn đến ảnh đại diện của đối tượng gửi tin nhắn */\r\n  from_avatar: string;\r\n  /** Tên hiển thị của đối tượng nhận tin nhắn */\r\n  to_display_name: string;\r\n  /** Đường dẫn đến ảnh đại diện của đối tượng nhận tin nhắn */\r\n  to_avatar: string;\r\n  /** Thuộc tính trả về giá trị longitude và latitude của người dùng (chỉ có khi type là location) */\r\n  location?: string;\r\n}\r\n\r\n/**\r\n * Interface cho cuộc hội thoại\r\n */\r\nexport interface Conversation {\r\n  conversation_id: string;\r\n  user_id: string;\r\n  last_message: ConversationMessage;\r\n  unread_count: number;\r\n  updated_time: number;\r\n}\r\n\r\n/**\r\n * Interface cho kết quả upload file - matches Zalo API docs exactly\r\n */\r\nexport interface UploadFileResult {\r\n  /** Token của file, sử dụng cho API gửi thông báo đính kèm file */\r\n  token: string;\r\n}\r\n\r\n/**\r\n * Interface cho kết quả upload image - matches Zalo API docs exactly\r\n */\r\nexport interface UploadImageResult {\r\n  /** ID của ảnh được upload */\r\n  attachment_id: string;\r\n}\r\n\r\n\r\n/**\r\n * Service xử lý các API quản lý tin nhắn của Zalo Official Account\r\n *\r\n * Bao gồm các chức năng:\r\n * - Kiểm tra hạn mức gửi tin nhắn\r\n * - Lấy danh sách tin nhắn trong cuộc hội thoại với user cụ thể\r\n * - Lấy thông tin tin nhắn gần nhất\r\n * - Upload file và hình ảnh\r\n *\r\n * ĐIỀU KIỆN SỮCDNG:\r\n *\r\n * 1. KIỂM TRA HẠN MỨC:\r\n *    - Cần quyền truy cập thông tin quota từ Zalo\r\n *    - Quota được reset hàng ngày vào 00:00 GMT+7\r\n *\r\n * 2. LẤY TIN NHẮN:\r\n *    - API hỗ trợ lấy thông tin tin nhắn giữa OA và một người dùng cụ thể\r\n *    - Mỗi request lấy tối đa được 10 tin nhắn\r\n *    - Tin nhắn gần nhất có thứ tự là 0\r\n *\r\n * 3. UPLOAD FILE:\r\n *    - Kích thước tối đa 5MB\r\n *    - Hỗ trợ các định dạng: PDF, DOC, DOCX\r\n *    - Quota: 5000 request/tháng\r\n *\r\n * 4. UPLOAD HÌNH ẢNH:\r\n *    - Dung lượng tối đa 1MB\r\n *    - Hỗ trợ các định dạng: JPG và PNG\r\n *    - Ảnh sẽ được lưu trên server tối đa 7 ngày\r\n *    - Quota: 5000 request/tháng\r\n *\r\n * 5. UPLOAD ẢNH GIF:\r\n *    - Kích thước tối đa 5MB\r\n *    - Chỉ hỗ trợ định dạng GIF\r\n *    - Ảnh GIF được lưu trên server tối đa 7 ngày\r\n *    - Quota: 5000 request/tháng\r\n */\r\nexport class MessageManagementService {\r\n  // Zalo API endpoints - organized by functionality\r\n  private readonly endpoints = {\r\n    // Message management endpoints\r\n    message: {\r\n      quota: \"https://openapi.zalo.me/v2.0/oa/quota\",\r\n      conversation: \"https://openapi.zalo.me/v2.0/oa/conversation\",\r\n      recentConversations: \"https://openapi.zalo.me/v2.0/oa/listrecentchat\",\r\n    },\r\n    // Upload endpoints\r\n    upload: {\r\n      file: \"https://openapi.zalo.me/v2.0/oa/upload/file\",\r\n      image: \"https://openapi.zalo.me/v2.0/oa/upload/image\",\r\n      gif: \"https://openapi.zalo.me/v2.0/oa/upload/gif\",\r\n    },\r\n  };\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Kiểm tra hạn mức gửi tin nhắn đến user cụ thể\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người dùng Zalo\r\n   * @returns Thông tin hạn mức gửi tin nhắn\r\n   */\r\n  async checkMessageQuota(\r\n    accessToken: string,\r\n    userId: string\r\n  ): Promise<MessageQuotaInfo> {\r\n    try {\r\n      const result: ZaloResponse<MessageQuotaInfo> = await this.client.apiGet(\r\n        this.endpoints.message.quota,\r\n        accessToken,\r\n        { user_id: userId }\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to check message quota\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No quota data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to check message quota: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Lấy danh sách tin nhắn trong cuộc hội thoại với user\r\n   *\r\n   * API hỗ trợ lấy thông tin tin nhắn giữa OA và một người dùng cụ thể.\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người dùng Zalo cần lấy danh sách hội thoại\r\n   * @param offset Thứ tự của tin nhắn đầu tiên trong danh sách trả về (tin nhắn gần nhất có thứ tự là 0)\r\n   * @param count Số lượng tin nhắn cần lấy (mỗi request lấy tối đa 10 tin nhắn)\r\n   * @returns Danh sách tin nhắn\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * const messages = await messageService.getConversationMessages(\r\n   *   accessToken,\r\n   *   '2512523625412515',\r\n   *   0,\r\n   *   5\r\n   * );\r\n   * ```\r\n   */\r\n  async getConversationMessages(\r\n    accessToken: string,\r\n    userId: string,\r\n    offset: number = 0,\r\n    count: number = 10\r\n  ): Promise<ConversationMessage[]> {\r\n    try {\r\n      // Validate count theo docs: mỗi request lấy tối đa 10 tin nhắn\r\n      if (count > 10) {\r\n        throw new ZaloSDKError(\"Mỗi request lấy tối đa được 10 tin nhắn\", -1);\r\n      }\r\n\r\n      // Theo docs Zalo: params phải được wrap trong object data dạng JSON string\r\n      const params = {\r\n        data: JSON.stringify({\r\n          user_id: userId,\r\n          offset,\r\n          count,\r\n        }),\r\n      };\r\n\r\n      const result: ZaloResponse<ConversationMessage[]> = await this.client.apiGet(\r\n        this.endpoints.message.conversation,\r\n        accessToken,\r\n        params\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get conversation messages\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No conversation data received\", -1);\r\n      }\r\n\r\n      // Response trả về mảng trực tiếp trong data\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get conversation messages: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Lấy danh sách tin nhắn trong cuộc hội thoại với user (sử dụng POST method)\r\n   *\r\n   * API hỗ trợ lấy thông tin tin nhắn giữa OA và một người dùng cụ thể sử dụng phương thức POST.\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người dùng Zalo cần lấy danh sách hội thoại\r\n   * @param offset Thứ tự của tin nhắn đầu tiên trong danh sách trả về (tin nhắn gần nhất có thứ tự là 0)\r\n   * @param count Số lượng tin nhắn cần lấy (mỗi request lấy tối đa 10 tin nhắn)\r\n   * @returns Danh sách tin nhắn\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * const messages = await messageService.postConversationMessages(\r\n   *   accessToken,\r\n   *   '2512523625412515',\r\n   *   0,\r\n   *   5\r\n   * );\r\n   * ```\r\n   */\r\n  async postConversationMessages(\r\n    accessToken: string,\r\n    userId: string,\r\n    offset: number = 0,\r\n    count: number = 10\r\n  ): Promise<ConversationMessage[]> {\r\n    try {\r\n      // Validate count theo docs: mỗi request lấy tối đa 10 tin nhắn\r\n      if (count > 10) {\r\n        throw new ZaloSDKError(\"Mỗi request lấy tối đa được 10 tin nhắn\", -1);\r\n      }\r\n\r\n      // Body format for POST request - gửi trực tiếp các tham số\r\n      const body = {\r\n        user_id: userId,\r\n        offset,\r\n        count,\r\n      };\r\n\r\n      const result: ZaloResponse<ConversationMessage[]> = await this.client.apiPost(\r\n        this.endpoints.message.conversation,\r\n        accessToken,\r\n        body\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get conversation messages\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No conversation data received\", -1);\r\n      }\r\n\r\n      // Response trả về mảng trực tiếp trong data\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get conversation messages: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Lấy thông tin tin nhắn gần nhất\r\n   * \r\n   * API hỗ trợ lấy thông tin tối đa 10 tin nhắn gần nhất giữa OA và người dùng.\r\n   * \r\n   * @param accessToken Access token của Official Account\r\n   * @param offset Thứ tự của tin nhắn đầu tiên trong danh sách trả về (tin nhắn gần nhất có thứ tự là 0)\r\n   * @param count Số lượng tin nhắn muốn lấy (mỗi request lấy tối đa 10 tin nhắn)\r\n   * @returns Danh sách tin nhắn gần nhất\r\n   * \r\n   * @example\r\n   * ```typescript\r\n   * const recentMessages = await messageService.getRecentConversations(\r\n   *   accessToken,\r\n   *   0,\r\n   *   5\r\n   * );\r\n   * ```\r\n   */\r\n  async getRecentConversations(\r\n    accessToken: string,\r\n    offset: number = 0,\r\n    count: number = 10\r\n  ): Promise<ConversationMessage[]> {\r\n    try {\r\n      // Validate count theo docs: mỗi request lấy tối đa 10 tin nhắn\r\n      if (count > 10) {\r\n        throw new ZaloSDKError(\"Mỗi request lấy tối đa 10 tin nhắn\", -1);\r\n      }\r\n\r\n      // Theo docs Zalo: params phải được wrap trong object data dạng JSON string\r\n      const params = {\r\n        data: JSON.stringify({\r\n          offset,\r\n          count,\r\n        }),\r\n      };\r\n\r\n      const result: ZaloResponse<ConversationMessage[]> = await this.client.apiGet(\r\n        this.endpoints.message.recentConversations,\r\n        accessToken,\r\n        params\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to get recent messages\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No recent messages data received\", -1);\r\n      }\r\n\r\n      // Response trả về mảng trực tiếp trong data\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to get recent messages: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Upload file để sử dụng trong tin nhắn\r\n   * @param accessToken Access token của Official Account\r\n   * @param fileData Dữ liệu file (base64 hoặc buffer)\r\n   * @param fileName Tên file\r\n   * @returns Token của file đã upload\r\n   *\r\n   * Lưu ý:\r\n   * - Chỉ hỗ trợ file PDF/DOC/DOCX\r\n   * - Dung lượng file không vượt quá 5MB\r\n   * - File sẽ được lưu trên server tối đa 7 ngày\r\n   * - Quota: 5000 request/tháng\r\n   */\r\n  async uploadFile(\r\n    accessToken: string,\r\n    fileData: string | Buffer,\r\n    fileName: string\r\n  ): Promise<UploadFileResult> {\r\n    try {\r\n      // Validate file format (only PDF/DOC/DOCX)\r\n      const allowedExtensions = ['.pdf', '.doc', '.docx'];\r\n      const fileExt = fileName.toLowerCase().substring(fileName.lastIndexOf('.'));\r\n      if (!allowedExtensions.includes(fileExt)) {\r\n        throw new ZaloSDKError(\"Chỉ hỗ trợ file PDF, DOC, DOCX\", -1);\r\n      }\r\n\r\n      // Validate file size (max 5MB)\r\n      const maxSize = 5 * 1024 * 1024; // 5MB\r\n      let fileSize: number;\r\n\r\n      if (typeof fileData === \"string\") {\r\n        // Base64 string\r\n        fileSize = Buffer.from(fileData, \"base64\").length;\r\n      } else {\r\n        // Buffer\r\n        fileSize = fileData.length;\r\n      }\r\n\r\n      if (fileSize > maxSize) {\r\n        throw new ZaloSDKError(\"Kích thước file không được vượt quá 5MB\", -1);\r\n      }\r\n\r\n      let buffer: Buffer;\r\n      if (typeof fileData === \"string\") {\r\n        buffer = Buffer.from(fileData, \"base64\");\r\n      } else {\r\n        buffer = fileData;\r\n      }\r\n\r\n      const result: ZaloResponse<UploadFileResult> = await this.client.apiUploadFile(\r\n        this.endpoints.upload.file,\r\n        accessToken,\r\n        buffer,\r\n        fileName\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to upload file\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No upload result received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to upload file: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Upload hình ảnh để sử dụng trong tin nhắn\r\n   * @param accessToken Access token của Official Account\r\n   * @param imageData Dữ liệu hình ảnh (base64 hoặc buffer)\r\n   * @param fileName Tên file hình ảnh\r\n   * @returns ID của ảnh đã upload\r\n   *\r\n   * Lưu ý:\r\n   * - Hỗ trợ các định dạng: JPG và PNG\r\n   * - Dung lượng tối đa: 1MB\r\n   * - Ảnh sẽ được lưu trên server tối đa 7 ngày\r\n   * - Quota: 5000 request/tháng\r\n   */\r\n  async uploadImage(\r\n    accessToken: string,\r\n    imageData: string | Buffer,\r\n    fileName: string\r\n  ): Promise<UploadImageResult> {\r\n    try {\r\n      // Validate image size (max 1MB)\r\n      const maxSize = 1 * 1024 * 1024; // 1MB\r\n      let imageSize: number;\r\n\r\n      if (typeof imageData === \"string\") {\r\n        // Base64 string\r\n        imageSize = Buffer.from(imageData, \"base64\").length;\r\n      } else {\r\n        // Buffer\r\n        imageSize = imageData.length;\r\n      }\r\n\r\n      if (imageSize > maxSize) {\r\n        throw new ZaloSDKError(\r\n          \"Kích thước hình ảnh không được vượt quá 1MB\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      let buffer: Buffer;\r\n      if (typeof imageData === \"string\") {\r\n        buffer = Buffer.from(imageData, \"base64\");\r\n      } else {\r\n        buffer = imageData;\r\n      }\r\n\r\n      const result: ZaloResponse<UploadImageResult> = await this.client.apiUploadFile(\r\n        this.endpoints.upload.image,\r\n        accessToken,\r\n        buffer,\r\n        fileName\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to upload image\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No upload result received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to upload image: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Upload ảnh GIF để sử dụng trong tin nhắn\r\n   * @param accessToken Access token của Official Account\r\n   * @param gifData Dữ liệu ảnh GIF (base64 hoặc buffer)\r\n   * @param fileName Tên file ảnh GIF\r\n   * @returns Thông tin ảnh GIF đã upload\r\n   *\r\n   * Lưu ý:\r\n   * - Dung lượng tối đa: 5MB\r\n   * - Ảnh GIF sẽ được lưu trên server tối đa 7 ngày\r\n   * - Quota: 5000 request/tháng\r\n   * - Định dạng hỗ trợ: GIF\r\n   */\r\n  async uploadGif(\r\n    accessToken: string,\r\n    gifData: string | Buffer,\r\n    fileName: string\r\n  ): Promise<UploadImageResult> {\r\n    try {\r\n      // Validate GIF size (max 5MB)\r\n      const maxSize = 5 * 1024 * 1024; // 5MB\r\n      let gifSize: number;\r\n\r\n      if (typeof gifData === \"string\") {\r\n        // Base64 string\r\n        gifSize = Buffer.from(gifData, \"base64\").length;\r\n      } else {\r\n        // Buffer\r\n        gifSize = gifData.length;\r\n      }\r\n\r\n      if (gifSize > maxSize) {\r\n        throw new ZaloSDKError(\r\n          \"Kích thước ảnh GIF không được vượt quá 5MB\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      // Validate file extension\r\n      if (!fileName.toLowerCase().endsWith(\".gif\")) {\r\n        throw new ZaloSDKError(\"File phải có định dạng GIF\", -1);\r\n      }\r\n\r\n      let buffer: Buffer;\r\n      if (typeof gifData === \"string\") {\r\n        buffer = Buffer.from(gifData, \"base64\");\r\n      } else {\r\n        buffer = gifData;\r\n      }\r\n\r\n      const result: ZaloResponse<UploadImageResult> = await this.client.apiUploadFile(\r\n        this.endpoints.upload.gif,\r\n        accessToken,\r\n        buffer,\r\n        fileName\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to upload GIF\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No upload result received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to upload GIF: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n}\r\n","/**\r\n * Broadcast Service for Zalo Official Account\r\n *\r\n * ⚠️ **DEPRECATED**: Broadcast Message API (v2.0) đã bị deprecated bởi Zalo\r\n *\r\n * **Khuyến nghị**: Sử dụng **ZNS Service** thay thế:\r\n * - `ZNSService.sendMessageByUid()` - Gửi tin nhắn template theo UID\r\n * - `ZNSService.sendMessage()` - Gửi tin nhắn theo số điện thoại\r\n * - `ZNSService.sendHashPhoneMessage()` - Gửi tin nhắn theo hash phone\r\n *\r\n * Tham khảo: https://developers.zalo.me/docs/zalo-notification-service/\r\n *\r\n * ===\r\n *\r\n * Gửi tin Truyền thông Broadcast đến nhiều người dùng dựa trên tiêu chí targeting\r\n * API: https://openapi.zalo.me/v2.0/oa/message\r\n *\r\n * TÍNH NĂNG:\r\n * 1. GỬI TIN BROADCAST:\r\n *    - Gửi tin nhắn đến nhiều người dùng cùng lúc\r\n *    - Targeting theo tuổi, giới tính, địa điểm, platform\r\n *    - Chỉ hỗ trợ template type \"media\" với article attachment\r\n *\r\n * 2. TARGETING CRITERIA:\r\n *    - Ages: Nhóm tuổi (0-12, 13-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+)\r\n *    - Gender: Giới tính (All, Male, Female)\r\n *    - Cities: Tỉnh thành cụ thể (63 tỉnh thành Việt Nam)\r\n *    - Locations: Miền (Bắc, Trung, Nam)\r\n *    - Platform: Hệ điều hành (iOS, Android, Windows Phone)\r\n *\r\n * 3. GIỚI HẠN:\r\n *    - Chỉ gửi được article attachment\r\n *    - Cần có quyền broadcast từ Zalo\r\n *    - Tuân thủ chính sách spam và nội dung\r\n *\r\n * LỖI THƯỜNG GẶP:\r\n * - 3001: Không có quyền gửi broadcast\r\n * - 3002: Article attachment không tồn tại\r\n * - 3003: Targeting criteria không hợp lệ\r\n * - 3004: Vượt quá giới hạn broadcast\r\n * - 3005: Nội dung vi phạm chính sách\r\n *\r\n * @deprecated Sử dụng ZNSService thay vì BroadcastService\r\n */\r\n\r\nimport { ZaloClient } from '../clients/zalo-client';\r\nimport { ZaloResponse, ZaloSDKError } from '../types/common';\r\nimport {\r\n  BroadcastRequest,\r\n  BroadcastResponse,\r\n  BroadcastTarget,\r\n  BroadcastRecipient,\r\n  BroadcastMessage,\r\n  BROADCAST_CITY_CODES,\r\n  BROADCAST_AGE_CODES,\r\n  BROADCAST_GENDER_CODES,\r\n  BROADCAST_LOCATION_CODES,\r\n  BROADCAST_PLATFORM_CODES,\r\n  MultipleBroadcastResult,\r\n  MultipleBroadcastProgress,\r\n  BroadcastMessageResult\r\n} from '../types/broadcast';\r\n\r\nexport class BroadcastService {\r\n  // Zalo API endpoint for broadcast messages\r\n  private readonly broadcastApiUrl = \"https://openapi.zalo.me/v2.0/oa/message\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Gửi tin nhắn broadcast đến nhiều người dùng\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin targeting người nhận\r\n   * @param attachmentId ID của article attachment\r\n   * @returns Thông tin tin nhắn broadcast đã gửi\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendBroadcastMessage(\r\n    accessToken: string,\r\n    recipient: BroadcastRecipient,\r\n    attachmentId: string\r\n  ): Promise<BroadcastResponse> {\r\n    try {\r\n      // Validate input parameters\r\n      this.validateBroadcastRequest(recipient, attachmentId);\r\n\r\n      // Build broadcast message\r\n      const message: BroadcastMessage = {\r\n        attachment: {\r\n          type: \"template\",\r\n          payload: {\r\n            template_type: \"media\",\r\n            elements: [\r\n              {\r\n                media_type: \"article\",\r\n                attachment_id: attachmentId\r\n              }\r\n            ]\r\n          }\r\n        }\r\n      };\r\n\r\n      // Build request\r\n      const request: BroadcastRequest = {\r\n        recipient,\r\n        message\r\n      };\r\n\r\n      // Send broadcast request\r\n      const result: ZaloResponse<BroadcastResponse> = await this.client.apiPost(\r\n        this.broadcastApiUrl,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send broadcast message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No broadcast response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send broadcast message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Tạo broadcast target với các tiêu chí targeting\r\n   * @param criteria Các tiêu chí targeting\r\n   * @returns BroadcastTarget object\r\n   */\r\n  public createBroadcastTarget(criteria: {\r\n    ages?: string[];\r\n    gender?: \"ALL\" | \"MALE\" | \"FEMALE\";\r\n    cities?: string[];\r\n    locations?: (\"NORTH\" | \"CENTRAL\" | \"SOUTH\")[];\r\n    platforms?: (\"IOS\" | \"ANDROID\" | \"WINDOWS_PHONE\")[];\r\n  }): BroadcastTarget {\r\n    const target: BroadcastTarget = {};\r\n\r\n    // Convert ages to comma-separated string\r\n    if (criteria.ages && criteria.ages.length > 0) {\r\n      const ageCodes = criteria.ages.map(age => {\r\n        const code = BROADCAST_AGE_CODES[age as keyof typeof BROADCAST_AGE_CODES];\r\n        if (!code) {\r\n          throw new ZaloSDKError(`Invalid age group: ${age}`, -1);\r\n        }\r\n        return code;\r\n      });\r\n      target.ages = ageCodes.join(\",\");\r\n    }\r\n\r\n    // Convert gender\r\n    if (criteria.gender) {\r\n      const genderCode = BROADCAST_GENDER_CODES[criteria.gender];\r\n      if (!genderCode) {\r\n        throw new ZaloSDKError(`Invalid gender: ${criteria.gender}`, -1);\r\n      }\r\n      target.gender = genderCode;\r\n    }\r\n\r\n    // Convert cities to comma-separated string\r\n    if (criteria.cities && criteria.cities.length > 0) {\r\n      const cityCodes = criteria.cities.map(city => {\r\n        const code = BROADCAST_CITY_CODES[city as keyof typeof BROADCAST_CITY_CODES];\r\n        if (!code) {\r\n          throw new ZaloSDKError(`Invalid city: ${city}`, -1);\r\n        }\r\n        return code;\r\n      });\r\n      target.cities = cityCodes.join(\",\");\r\n    }\r\n\r\n    // Convert locations to comma-separated string\r\n    if (criteria.locations && criteria.locations.length > 0) {\r\n      const locationCodes = criteria.locations.map(location => {\r\n        const code = BROADCAST_LOCATION_CODES[location];\r\n        if (!code) {\r\n          throw new ZaloSDKError(`Invalid location: ${location}`, -1);\r\n        }\r\n        return code;\r\n      });\r\n      target.locations = locationCodes.join(\",\");\r\n    }\r\n\r\n    // Convert platforms to comma-separated string\r\n    if (criteria.platforms && criteria.platforms.length > 0) {\r\n      const platformCodes = criteria.platforms.map(platform => {\r\n        const code = BROADCAST_PLATFORM_CODES[platform];\r\n        if (!code) {\r\n          throw new ZaloSDKError(`Invalid platform: ${platform}`, -1);\r\n        }\r\n        return code;\r\n      });\r\n      target.platform = platformCodes.join(\",\");\r\n    }\r\n\r\n    return target;\r\n  }\r\n\r\n  /**\r\n   * Validate broadcast request parameters\r\n   * @param recipient Broadcast recipient\r\n   * @param attachmentId Article attachment ID\r\n   */\r\n  private validateBroadcastRequest(\r\n    recipient: BroadcastRecipient,\r\n    attachmentId: string\r\n  ): void {\r\n    // Validate recipient\r\n    if (!recipient || !recipient.target) {\r\n      throw new ZaloSDKError(\"Recipient target is required for broadcast\", -1);\r\n    }\r\n\r\n    // Validate attachment ID\r\n    if (!attachmentId || attachmentId.trim().length === 0) {\r\n      throw new ZaloSDKError(\"Article attachment ID is required\", -1);\r\n    }\r\n\r\n    // Validate target has at least one criteria\r\n    const target = recipient.target;\r\n    const hasTargeting = target.ages || target.gender || target.cities || \r\n                        target.locations || target.platform;\r\n    \r\n    if (!hasTargeting) {\r\n      throw new ZaloSDKError(\r\n        \"At least one targeting criteria is required (ages, gender, cities, locations, or platform)\",\r\n        -1\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get available city codes for targeting\r\n   * @returns Object mapping city names to codes\r\n   */\r\n  public getCityCodes(): typeof BROADCAST_CITY_CODES {\r\n    return BROADCAST_CITY_CODES;\r\n  }\r\n\r\n  /**\r\n   * Get available age group codes for targeting\r\n   * @returns Object mapping age groups to codes\r\n   */\r\n  public getAgeGroupCodes(): typeof BROADCAST_AGE_CODES {\r\n    return BROADCAST_AGE_CODES;\r\n  }\r\n\r\n  /**\r\n   * Get available gender codes for targeting\r\n   * @returns Object mapping genders to codes\r\n   */\r\n  public getGenderCodes(): typeof BROADCAST_GENDER_CODES {\r\n    return BROADCAST_GENDER_CODES;\r\n  }\r\n\r\n  /**\r\n   * Get available location codes for targeting\r\n   * @returns Object mapping locations to codes\r\n   */\r\n  public getLocationCodes(): typeof BROADCAST_LOCATION_CODES {\r\n    return BROADCAST_LOCATION_CODES;\r\n  }\r\n\r\n  /**\r\n   * Get available platform codes for targeting\r\n   * @returns Object mapping platforms to codes\r\n   */\r\n  public getPlatformCodes(): typeof BROADCAST_PLATFORM_CODES {\r\n    return BROADCAST_PLATFORM_CODES;\r\n  }\r\n\r\n  /**\r\n   * Gửi nhiều tin nhắn broadcast với nhiều attachment IDs\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin targeting người nhận\r\n   * @param attachmentIds Danh sách các article attachment IDs\r\n   * @param options Tùy chọn gửi (delay giữa các tin, parallel/sequential)\r\n   * @returns Danh sách kết quả gửi broadcast\r\n   * @deprecated Sử dụng ZNSService.sendMessageByUid() thay vì method này\r\n   */\r\n  public async sendMultipleBroadcastMessages(\r\n    accessToken: string,\r\n    recipient: BroadcastRecipient,\r\n    attachmentIds: string[],\r\n    options?: {\r\n      delay?: number; // Delay giữa các tin nhắn (ms)\r\n      mode?: 'parallel' | 'sequential'; // Gửi song song hay tuần tự\r\n      onProgress?: (progress: MultipleBroadcastProgress) => void; // Callback theo dõi tiến độ\r\n    }\r\n  ): Promise<MultipleBroadcastResult> {\r\n    try {\r\n      // Validate input\r\n      if (!attachmentIds || attachmentIds.length === 0) {\r\n        throw new ZaloSDKError(\"At least one attachment ID is required\", -1);\r\n      }\r\n\r\n      // Remove duplicates\r\n      const uniqueAttachmentIds = [...new Set(attachmentIds)];\r\n\r\n      const startTime = Date.now();\r\n      const results: BroadcastMessageResult[] = [];\r\n      const mode = options?.mode || 'sequential';\r\n      const delay = options?.delay || 0;\r\n\r\n      if (mode === 'parallel') {\r\n        // Gửi song song tất cả\r\n        const promises = uniqueAttachmentIds.map(async (attachmentId, index) => {\r\n          try {\r\n            const result = await this.sendBroadcastMessage(\r\n              accessToken,\r\n              recipient,\r\n              attachmentId\r\n            );\r\n\r\n            const messageResult: BroadcastMessageResult = {\r\n              attachmentId,\r\n              success: true,\r\n              messageId: result.data.message_id,\r\n              error: null,\r\n              sentAt: new Date()\r\n            };\r\n\r\n            // Report progress\r\n            if (options?.onProgress) {\r\n              options.onProgress({\r\n                total: uniqueAttachmentIds.length,\r\n                completed: index + 1,\r\n                successful: results.filter(r => r.success).length + 1,\r\n                failed: results.filter(r => !r.success).length,\r\n                currentAttachmentId: attachmentId,\r\n                isCompleted: index === uniqueAttachmentIds.length - 1\r\n              });\r\n            }\r\n\r\n            return messageResult;\r\n          } catch (error) {\r\n            const messageResult: BroadcastMessageResult = {\r\n              attachmentId,\r\n              success: false,\r\n              messageId: null,\r\n              error: error instanceof ZaloSDKError ? error : new ZaloSDKError(error.message, -1),\r\n              sentAt: new Date()\r\n            };\r\n\r\n            // Report progress\r\n            if (options?.onProgress) {\r\n              options.onProgress({\r\n                total: uniqueAttachmentIds.length,\r\n                completed: index + 1,\r\n                successful: results.filter(r => r.success).length,\r\n                failed: results.filter(r => !r.success).length + 1,\r\n                currentAttachmentId: attachmentId,\r\n                isCompleted: index === uniqueAttachmentIds.length - 1\r\n              });\r\n            }\r\n\r\n            return messageResult;\r\n          }\r\n        });\r\n\r\n        const parallelResults = await Promise.all(promises);\r\n        results.push(...parallelResults);\r\n\r\n      } else {\r\n        // Gửi tuần tự\r\n        for (let i = 0; i < uniqueAttachmentIds.length; i++) {\r\n          const attachmentId = uniqueAttachmentIds[i];\r\n\r\n          try {\r\n            const result = await this.sendBroadcastMessage(\r\n              accessToken,\r\n              recipient,\r\n              attachmentId\r\n            );\r\n\r\n            const messageResult: BroadcastMessageResult = {\r\n              attachmentId,\r\n              success: true,\r\n              messageId: result.data.message_id,\r\n              error: null,\r\n              sentAt: new Date()\r\n            };\r\n\r\n            results.push(messageResult);\r\n\r\n            // Report progress\r\n            if (options?.onProgress) {\r\n              options.onProgress({\r\n                total: uniqueAttachmentIds.length,\r\n                completed: i + 1,\r\n                successful: results.filter(r => r.success).length,\r\n                failed: results.filter(r => !r.success).length,\r\n                currentAttachmentId: attachmentId,\r\n                isCompleted: i === uniqueAttachmentIds.length - 1\r\n              });\r\n            }\r\n\r\n          } catch (error) {\r\n            const messageResult: BroadcastMessageResult = {\r\n              attachmentId,\r\n              success: false,\r\n              messageId: null,\r\n              error: error instanceof ZaloSDKError ? error : new ZaloSDKError(error.message, -1),\r\n              sentAt: new Date()\r\n            };\r\n\r\n            results.push(messageResult);\r\n\r\n            // Report progress\r\n            if (options?.onProgress) {\r\n              options.onProgress({\r\n                total: uniqueAttachmentIds.length,\r\n                completed: i + 1,\r\n                successful: results.filter(r => r.success).length,\r\n                failed: results.filter(r => !r.success).length,\r\n                currentAttachmentId: attachmentId,\r\n                isCompleted: i === uniqueAttachmentIds.length - 1\r\n              });\r\n            }\r\n          }\r\n\r\n          // Delay before next message (except for last one)\r\n          if (delay > 0 && i < uniqueAttachmentIds.length - 1) {\r\n            await this.sleep(delay);\r\n          }\r\n        }\r\n      }\r\n\r\n      const totalDuration = Date.now() - startTime;\r\n      const successful = results.filter(r => r.success).length;\r\n      const failed = results.filter(r => !r.success).length;\r\n\r\n      return {\r\n        totalMessages: uniqueAttachmentIds.length,\r\n        successfulMessages: successful,\r\n        failedMessages: failed,\r\n        results,\r\n        totalDuration,\r\n        mode,\r\n        targetCriteria: recipient.target\r\n      };\r\n\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send multiple broadcast messages: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Sleep utility for delays\r\n   * @param ms Milliseconds to sleep\r\n   */\r\n  private sleep(ms: number): Promise<void> {\r\n    return new Promise(resolve => setTimeout(resolve, ms));\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\nimport {\r\n  CreateOrderRequest,\r\n  CreateOrderResponse,\r\n  OrderData,\r\n  ConfirmOrderRequest,\r\n  ConfirmOrderResponse,\r\n  ConfirmedOrderData,\r\n  isProductOrderRequest,\r\n  isRedeemOrderRequest,\r\n  PurchaseErrorCode,\r\n  OA_PRODUCTS,\r\n  PRODUCT_INFO,\r\n  ProductInfo,\r\n} from \"../types/purchase\";\r\n\r\n/**\r\n * Service xử lý các API mua sản phẩm/dịch vụ OA của Zalo\r\n *\r\n * ĐIỀU KIỆN SỬ DỤNG PURCHASE API:\r\n *\r\n * 1. QUYỀN TRUY CẬP:\r\n *    - Ứng dụng cần được cấp quyền quản lý \"Mua sản phẩm dịch vụ OA\"\r\n *    - OA phải được xác minh và có quyền bán sản phẩm/dịch vụ\r\n *\r\n * 2. THỜI GIAN TẠO ĐỚN:\r\n *    - Order chỉ được tạo từ 00:01 đến 23:54 hàng ngày\r\n *    - Không thể tạo đơn hàng trong khung giờ bảo trì (23:55 - 00:00)\r\n *\r\n * 3. SẢN PHẨM/DỊCH VỤ:\r\n *    - Sản phẩm phải có sẵn và được phép bán\r\n *    - Đối tượng thụ hưởng (beneficiary) phải phù hợp với sản phẩm\r\n *    - Có thể sử dụng product_id HOẶC redeem_code, không được dùng cả hai\r\n *\r\n * 4. THANH TOÁN:\r\n *    - Tài khoản ZCA phải có đủ số dư để thanh toán\r\n *    - Voucher code (nếu có) phải hợp lệ và chưa hết hạn\r\n *\r\n * 5. XÁC THỰC:\r\n *    - Mỗi đơn hàng sẽ có verified_token (OTT) để xác nhận thanh toán\r\n *    - OTT có hiệu lực trong vòng 5 phút kể từ khi tạo đơn\r\n *\r\n * LỖI THƯỜNG GẶP:\r\n * - 1001: Beneficiary không hợp lệ\r\n * - 1002: Product ID không tồn tại hoặc không khả dụng\r\n * - 1003: Redeem code không hợp lệ hoặc đã được sử dụng\r\n * - 1004: Voucher code không hợp lệ hoặc đã hết hạn\r\n * - 1005: Sản phẩm không khả dụng cho đối tượng thụ hưởng\r\n * - 1006: Số dư tài khoản không đủ\r\n * - 1007: Ngoài thời gian cho phép tạo đơn hàng\r\n * - 1008: Đơn hàng trùng lặp\r\n * - 1009: Không có quyền truy cập\r\n */\r\nexport class PurchaseService {\r\n  private readonly purchaseApiUrl = \"https://openapi.zalo.me/v3.0/oa/purchase\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Tạo đơn hàng mua sản phẩm/dịch vụ OA\r\n   * @param accessToken Access token của Official Account\r\n   * @param request Thông tin đơn hàng cần tạo\r\n   * @returns Thông tin đơn hàng đã tạo\r\n   */\r\n  public async createOrder(\r\n    accessToken: string,\r\n    request: CreateOrderRequest\r\n  ): Promise<OrderData> {\r\n    try {\r\n      // Validate request\r\n      this.validateCreateOrderRequest(request);\r\n\r\n      const result: ZaloResponse<OrderData> = await this.client.apiPost(\r\n        `${this.purchaseApiUrl}/create_order`,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to create order\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to create order: ${(error as Error).message}`,\r\n        PurchaseErrorCode.SYSTEM_ERROR,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Tạo đơn hàng với product_id\r\n   * @param accessToken Access token của Official Account\r\n   * @param beneficiary Đối tượng thụ hưởng (OA hoặc APP)\r\n   * @param productId ID sản phẩm\r\n   * @param voucherCode Mã giảm giá (tùy chọn)\r\n   * @returns Thông tin đơn hàng đã tạo\r\n   */\r\n  public async createOrderWithProduct(\r\n    accessToken: string,\r\n    beneficiary: \"OA\" | \"APP\",\r\n    productId: number,\r\n    voucherCode?: string\r\n  ): Promise<OrderData> {\r\n    const request = {\r\n      beneficiary,\r\n      product_id: productId,\r\n      ...(voucherCode && { voucher_code: voucherCode }),\r\n    };\r\n\r\n    return this.createOrder(accessToken, request);\r\n  }\r\n\r\n  /**\r\n   * Tạo đơn hàng với redeem_code (mã quà tặng)\r\n   * @param accessToken Access token của Official Account\r\n   * @param beneficiary Đối tượng thụ hưởng (OA hoặc APP)\r\n   * @param redeemCode Mã quà tặng\r\n   * @param voucherCode Mã giảm giá (tùy chọn)\r\n   * @returns Thông tin đơn hàng đã tạo\r\n   */\r\n  public async createOrderWithRedeemCode(\r\n    accessToken: string,\r\n    beneficiary: \"OA\" | \"APP\",\r\n    redeemCode: string,\r\n    voucherCode?: string\r\n  ): Promise<OrderData> {\r\n    const request = {\r\n      beneficiary,\r\n      redeem_code: redeemCode,\r\n      ...(voucherCode && { voucher_code: voucherCode }),\r\n    };\r\n\r\n    return this.createOrder(accessToken, request);\r\n  }\r\n\r\n  /**\r\n   * Validate create order request\r\n   * @param request Request to validate\r\n   */\r\n  private validateCreateOrderRequest(request: CreateOrderRequest): void {\r\n    // Validate beneficiary\r\n    if (!request.beneficiary || ![\"OA\", \"APP\"].includes(request.beneficiary)) {\r\n      throw new ZaloSDKError(\r\n        \"Beneficiary must be either 'OA' or 'APP'\",\r\n        PurchaseErrorCode.INVALID_BENEFICIARY\r\n      );\r\n    }\r\n\r\n    // Validate that either product_id or redeem_code is provided, but not both\r\n    const hasProductId = isProductOrderRequest(request);\r\n    const hasRedeemCode = isRedeemOrderRequest(request);\r\n\r\n    if (!hasProductId && !hasRedeemCode) {\r\n      throw new ZaloSDKError(\r\n        \"Either product_id or redeem_code must be provided\",\r\n        PurchaseErrorCode.INVALID_PRODUCT_ID\r\n      );\r\n    }\r\n\r\n    if (hasProductId && hasRedeemCode) {\r\n      throw new ZaloSDKError(\r\n        \"Cannot use both product_id and redeem_code in the same request\",\r\n        PurchaseErrorCode.INVALID_PRODUCT_ID\r\n      );\r\n    }\r\n\r\n    // Validate product_id\r\n    if (hasProductId && (!request.product_id || request.product_id <= 0)) {\r\n      throw new ZaloSDKError(\r\n        \"Product ID must be a positive number\",\r\n        PurchaseErrorCode.INVALID_PRODUCT_ID\r\n      );\r\n    }\r\n\r\n    // Validate redeem_code\r\n    if (hasRedeemCode && (!request.redeem_code || request.redeem_code.trim() === \"\")) {\r\n      throw new ZaloSDKError(\r\n        \"Redeem code cannot be empty\",\r\n        PurchaseErrorCode.INVALID_REDEEM_CODE\r\n      );\r\n    }\r\n\r\n    // Validate voucher_code if provided\r\n    if (request.voucher_code && request.voucher_code.trim() === \"\") {\r\n      throw new ZaloSDKError(\r\n        \"Voucher code cannot be empty if provided\",\r\n        PurchaseErrorCode.INVALID_VOUCHER_CODE\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Kiểm tra thời gian có thể tạo đơn hàng\r\n   * @returns true nếu trong thời gian cho phép tạo đơn hàng\r\n   */\r\n  public isOrderCreationTimeValid(): boolean {\r\n    const now = new Date();\r\n    const hours = now.getHours();\r\n    const minutes = now.getMinutes();\r\n\r\n    // Order chỉ được tạo từ 00:01 đến 23:54\r\n    if (hours === 0 && minutes === 0) {\r\n      return false; // 00:00\r\n    }\r\n    if (hours === 23 && minutes >= 55) {\r\n      return false; // 23:55 - 23:59\r\n    }\r\n\r\n    return true;\r\n  }\r\n\r\n  /**\r\n   * Tính toán thời gian hết hạn của OTT (One Time Token)\r\n   * @param createdTime Thời gian tạo đơn hàng (milliseconds)\r\n   * @returns Thời gian hết hạn OTT (milliseconds)\r\n   */\r\n  public calculateOTTExpiration(createdTime: number): number {\r\n    // OTT có hiệu lực trong vòng 5 phút\r\n    return createdTime + 5 * 60 * 1000;\r\n  }\r\n\r\n  /**\r\n   * Kiểm tra OTT còn hiệu lực hay không\r\n   * @param createdTime Thời gian tạo đơn hàng (milliseconds)\r\n   * @returns true nếu OTT còn hiệu lực\r\n   */\r\n  public isOTTValid(createdTime: number): boolean {\r\n    const now = Date.now();\r\n    const expirationTime = this.calculateOTTExpiration(createdTime);\r\n    return now < expirationTime;\r\n  }\r\n\r\n  /**\r\n   * Xác nhận thanh toán đơn hàng\r\n   * @param accessToken Access token của Official Account\r\n   * @param request Thông tin xác nhận đơn hàng\r\n   * @returns Thông tin đơn hàng đã được xác nhận thanh toán\r\n   */\r\n  public async confirmOrder(\r\n    accessToken: string,\r\n    request: ConfirmOrderRequest\r\n  ): Promise<ConfirmedOrderData> {\r\n    try {\r\n      // Validate request\r\n      this.validateConfirmOrderRequest(request);\r\n\r\n      const result: ZaloResponse<ConfirmedOrderData> = await this.client.apiPost(\r\n        `${this.purchaseApiUrl}/confirm_order`,\r\n        accessToken,\r\n        request\r\n      );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to confirm order\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to confirm order: ${(error as Error).message}`,\r\n        PurchaseErrorCode.SYSTEM_ERROR,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Xác nhận thanh toán đơn hàng với order ID và verified token\r\n   * @param accessToken Access token của Official Account\r\n   * @param orderId ID đơn hàng\r\n   * @param verifiedToken OTT token để xác nhận\r\n   * @returns Thông tin đơn hàng đã được xác nhận thanh toán\r\n   */\r\n  public async confirmOrderById(\r\n    accessToken: string,\r\n    orderId: string,\r\n    verifiedToken: string\r\n  ): Promise<ConfirmedOrderData> {\r\n    const request: ConfirmOrderRequest = {\r\n      order_id: orderId,\r\n      verified_token: verifiedToken,\r\n    };\r\n\r\n    return this.confirmOrder(accessToken, request);\r\n  }\r\n\r\n  /**\r\n   * Lấy thông tin sản phẩm theo ID\r\n   * @param productId ID sản phẩm\r\n   * @returns Thông tin sản phẩm hoặc undefined nếu không tìm thấy\r\n   */\r\n  public getProductInfo(productId: number): ProductInfo | undefined {\r\n    return PRODUCT_INFO[productId];\r\n  }\r\n\r\n  /**\r\n   * Lấy danh sách tất cả sản phẩm có sẵn\r\n   * @returns Danh sách thông tin sản phẩm\r\n   */\r\n  public getAllProducts(): ProductInfo[] {\r\n    return Object.values(PRODUCT_INFO);\r\n  }\r\n\r\n  /**\r\n   * Lấy danh sách sản phẩm theo loại\r\n   * @param category Loại sản phẩm\r\n   * @returns Danh sách sản phẩm thuộc loại đó\r\n   */\r\n  public getProductsByCategory(category: 'subscription' | 'gmf' | 'quota'): ProductInfo[] {\r\n    return Object.values(PRODUCT_INFO).filter(product => product.category === category);\r\n  }\r\n\r\n  /**\r\n   * Lấy danh sách sản phẩm theo đối tượng thụ hưởng\r\n   * @param beneficiary Đối tượng thụ hưởng\r\n   * @returns Danh sách sản phẩm phù hợp với đối tượng thụ hưởng\r\n   */\r\n  public getProductsByBeneficiary(beneficiary: \"OA\" | \"APP\"): ProductInfo[] {\r\n    return Object.values(PRODUCT_INFO).filter(product =>\r\n      product.beneficiary.includes(beneficiary)\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Kiểm tra sản phẩm có phù hợp với đối tượng thụ hưởng không\r\n   * @param productId ID sản phẩm\r\n   * @param beneficiary Đối tượng thụ hưởng\r\n   * @returns true nếu sản phẩm phù hợp với đối tượng thụ hưởng\r\n   */\r\n  public isProductCompatibleWithBeneficiary(\r\n    productId: number,\r\n    beneficiary: \"OA\" | \"APP\"\r\n  ): boolean {\r\n    const product = this.getProductInfo(productId);\r\n    if (!product) {\r\n      return false;\r\n    }\r\n    return product.beneficiary.includes(beneficiary);\r\n  }\r\n\r\n  // ==================== SPECIALIZED PRODUCT APIS ====================\r\n\r\n  /**\r\n   * Tạo đơn hàng gói OA Subscription\r\n   * @param accessToken Access token của Official Account\r\n   * @param subscriptionType Loại gói subscription\r\n   * @param voucherCode Mã giảm giá (tùy chọn)\r\n   * @returns Thông tin đơn hàng đã tạo\r\n   */\r\n  public async createOASubscriptionOrder(\r\n    accessToken: string,\r\n    subscriptionType: 'advanced_6m' | 'advanced_12m' | 'premium_6m' | 'premium_12m',\r\n    voucherCode?: string\r\n  ): Promise<OrderData> {\r\n    const productIdMap = {\r\n      'advanced_6m': OA_PRODUCTS.OA_ADVANCED_6M,\r\n      'advanced_12m': OA_PRODUCTS.OA_ADVANCED_12M,\r\n      'premium_6m': OA_PRODUCTS.OA_PREMIUM_6M,\r\n      'premium_12m': OA_PRODUCTS.OA_PREMIUM_12M,\r\n    };\r\n\r\n    const productId = productIdMap[subscriptionType];\r\n    return this.createOrderWithProduct(accessToken, \"OA\", productId, voucherCode);\r\n  }\r\n\r\n  /**\r\n   * Tạo đơn hàng gói GMF (Group Message Framework)\r\n   * @param accessToken Access token của Official Account\r\n   * @param memberLimit Giới hạn số thành viên\r\n   * @param voucherCode Mã giảm giá (tùy chọn)\r\n   * @returns Thông tin đơn hàng đã tạo\r\n   */\r\n  public async createGMFOrder(\r\n    accessToken: string,\r\n    memberLimit: 10 | 50 | 100 | 1000,\r\n    voucherCode?: string\r\n  ): Promise<OrderData> {\r\n    const productIdMap = {\r\n      10: OA_PRODUCTS.GMF_10_MEMBERS,\r\n      50: OA_PRODUCTS.GMF_50_MEMBERS,\r\n      100: OA_PRODUCTS.GMF_100_MEMBERS,\r\n      1000: OA_PRODUCTS.GMF_1000_MEMBERS,\r\n    };\r\n\r\n    const productId = productIdMap[memberLimit];\r\n    return this.createOrderWithProduct(accessToken, \"OA\", productId, voucherCode);\r\n  }\r\n\r\n  /**\r\n   * Tạo đơn hàng gói quota tin nhắn giao dịch\r\n   * @param accessToken Access token của Official Account\r\n   * @param beneficiary Đối tượng thụ hưởng (OA hoặc APP)\r\n   * @param quotaSize Kích thước gói quota\r\n   * @param voucherCode Mã giảm giá (tùy chọn)\r\n   * @returns Thông tin đơn hàng đã tạo\r\n   */\r\n  public async createTransactionQuotaOrder(\r\n    accessToken: string,\r\n    beneficiary: \"OA\" | \"APP\",\r\n    quotaSize: '5k' | '50k' | '500k',\r\n    voucherCode?: string\r\n  ): Promise<OrderData> {\r\n    const productIdMap = {\r\n      '5k': OA_PRODUCTS.TRANSACTION_5K,\r\n      '50k': OA_PRODUCTS.TRANSACTION_50K,\r\n      '500k': OA_PRODUCTS.TRANSACTION_500K,\r\n    };\r\n\r\n    const productId = productIdMap[quotaSize];\r\n\r\n    // Validate beneficiary compatibility\r\n    if (!this.isProductCompatibleWithBeneficiary(productId, beneficiary)) {\r\n      throw new ZaloSDKError(\r\n        `Product ${quotaSize} transaction quota is not compatible with beneficiary ${beneficiary}`,\r\n        PurchaseErrorCode.PRODUCT_NOT_AVAILABLE\r\n      );\r\n    }\r\n\r\n    return this.createOrderWithProduct(accessToken, beneficiary, productId, voucherCode);\r\n  }\r\n\r\n  // ==================== BULK ORDER OPERATIONS ====================\r\n\r\n  /**\r\n   * Tạo nhiều đơn hàng cùng lúc (batch operation)\r\n   * @param accessToken Access token của Official Account\r\n   * @param orders Danh sách đơn hàng cần tạo\r\n   * @returns Danh sách kết quả tạo đơn hàng\r\n   */\r\n  public async createMultipleOrders(\r\n    accessToken: string,\r\n    orders: CreateOrderRequest[]\r\n  ): Promise<Array<{ success: boolean; data?: OrderData; error?: string }>> {\r\n    const results: Array<{ success: boolean; data?: OrderData; error?: string }> = [];\r\n\r\n    for (const order of orders) {\r\n      try {\r\n        const orderData = await this.createOrder(accessToken, order);\r\n        results.push({ success: true, data: orderData });\r\n      } catch (error) {\r\n        results.push({\r\n          success: false,\r\n          error: error instanceof ZaloSDKError ? error.message : (error as Error).message\r\n        });\r\n      }\r\n    }\r\n\r\n    return results;\r\n  }\r\n\r\n  /**\r\n   * Tạo combo đơn hàng OA Premium + GMF\r\n   * @param accessToken Access token của Official Account\r\n   * @param premiumDuration Thời hạn gói Premium\r\n   * @param gmfMemberLimit Giới hạn thành viên GMF\r\n   * @param voucherCode Mã giảm giá (tùy chọn)\r\n   * @returns Danh sách kết quả tạo đơn hàng\r\n   */\r\n  public async createOAPremiumGMFCombo(\r\n    accessToken: string,\r\n    premiumDuration: '6m' | '12m',\r\n    gmfMemberLimit: 10 | 50 | 100 | 1000,\r\n    voucherCode?: string\r\n  ): Promise<{\r\n    premium: { success: boolean; data?: OrderData; error?: string };\r\n    gmf: { success: boolean; data?: OrderData; error?: string };\r\n  }> {\r\n    const premiumResult = await this.createOASubscriptionOrder(\r\n      accessToken,\r\n      premiumDuration === '6m' ? 'premium_6m' : 'premium_12m',\r\n      voucherCode\r\n    ).then(data => ({ success: true, data }))\r\n      .catch(error => ({\r\n        success: false,\r\n        error: error instanceof ZaloSDKError ? error.message : (error as Error).message\r\n      }));\r\n\r\n    const gmfResult = await this.createGMFOrder(\r\n      accessToken,\r\n      gmfMemberLimit,\r\n      voucherCode\r\n    ).then(data => ({ success: true, data }))\r\n      .catch(error => ({\r\n        success: false,\r\n        error: error instanceof ZaloSDKError ? error.message : (error as Error).message\r\n      }));\r\n\r\n    return {\r\n      premium: premiumResult,\r\n      gmf: gmfResult\r\n    };\r\n  }\r\n\r\n  // ==================== PRODUCT RECOMMENDATION ====================\r\n\r\n  /**\r\n   * Gợi ý sản phẩm dựa trên nhu cầu\r\n   * @param requirements Yêu cầu của khách hàng\r\n   * @returns Danh sách sản phẩm được gợi ý\r\n   */\r\n  public recommendProducts(requirements: {\r\n    beneficiary: \"OA\" | \"APP\";\r\n    budget?: 'low' | 'medium' | 'high';\r\n    features?: ('subscription' | 'group_messaging' | 'transaction_quota')[];\r\n    duration?: 'short' | 'long';\r\n  }): ProductInfo[] {\r\n    let recommendations: ProductInfo[] = [];\r\n\r\n    // Filter by beneficiary\r\n    const compatibleProducts = this.getProductsByBeneficiary(requirements.beneficiary);\r\n\r\n    // Add recommendations based on features\r\n    if (requirements.features?.includes('subscription')) {\r\n      const subscriptionProducts = compatibleProducts.filter(p => p.category === 'subscription');\r\n      if (requirements.duration === 'long') {\r\n        recommendations.push(...subscriptionProducts.filter(p => p.name.includes('12 tháng')));\r\n      } else {\r\n        recommendations.push(...subscriptionProducts.filter(p => p.name.includes('6 tháng')));\r\n      }\r\n    }\r\n\r\n    if (requirements.features?.includes('group_messaging')) {\r\n      const gmfProducts = compatibleProducts.filter(p => p.category === 'gmf');\r\n      if (requirements.budget === 'low') {\r\n        recommendations.push(...gmfProducts.filter(p => p.name.includes('10 thành viên')));\r\n      } else if (requirements.budget === 'high') {\r\n        recommendations.push(...gmfProducts.filter(p => p.name.includes('1000 thành viên')));\r\n      } else {\r\n        recommendations.push(...gmfProducts.filter(p =>\r\n          p.name.includes('50 thành viên') || p.name.includes('100 thành viên')\r\n        ));\r\n      }\r\n    }\r\n\r\n    if (requirements.features?.includes('transaction_quota')) {\r\n      const quotaProducts = compatibleProducts.filter(p => p.category === 'quota');\r\n      if (requirements.budget === 'low') {\r\n        recommendations.push(...quotaProducts.filter(p => p.name.includes('5k tin')));\r\n      } else if (requirements.budget === 'high') {\r\n        recommendations.push(...quotaProducts.filter(p => p.name.includes('500k tin')));\r\n      } else {\r\n        recommendations.push(...quotaProducts.filter(p => p.name.includes('50k tin')));\r\n      }\r\n    }\r\n\r\n    // Remove duplicates\r\n    return recommendations.filter((product, index, self) =>\r\n      index === self.findIndex(p => p.id === product.id)\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Validate confirm order request\r\n   * @param request Request to validate\r\n   */\r\n  private validateConfirmOrderRequest(request: ConfirmOrderRequest): void {\r\n    // Validate order_id\r\n    if (!request.order_id || request.order_id.trim() === \"\") {\r\n      throw new ZaloSDKError(\r\n        \"Order ID cannot be empty\",\r\n        PurchaseErrorCode.INVALID_ORDER_ID\r\n      );\r\n    }\r\n\r\n    // Validate verified_token\r\n    if (!request.verified_token || request.verified_token.trim() === \"\") {\r\n      throw new ZaloSDKError(\r\n        \"Verified token cannot be empty\",\r\n        PurchaseErrorCode.INVALID_VERIFIED_TOKEN\r\n      );\r\n    }\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\nimport {\r\n  AnonymousMessageRecipient,\r\n  AnonymousMessageResponse,\r\n} from \"../types/message\";\r\n\r\n/**\r\n * Attachment cho gửi ảnh ẩn danh\r\n */\r\nexport interface AnonymousImageAttachment {\r\n  type: \"template\";\r\n  payload: {\r\n    template_type: \"media\";\r\n    elements: Array<{\r\n      media_type: \"image\";\r\n      url?: string;\r\n      attachment_id?: string;\r\n    }>;\r\n  };\r\n}\r\n\r\n/**\r\n * Attachment cho gửi file ẩn danh\r\n */\r\nexport interface AnonymousFileAttachment {\r\n  type: \"file\";\r\n  payload: {\r\n    token: string;\r\n  };\r\n}\r\n\r\n/**\r\n * Attachment cho gửi sticker ẩn danh\r\n */\r\nexport interface AnonymousStickerAttachment {\r\n  type: \"template\";\r\n  payload: {\r\n    template_type: \"media\";\r\n    elements: Array<{\r\n      media_type: \"sticker\";\r\n      attachment_id: string;\r\n    }>;\r\n  };\r\n}\r\n\r\n/**\r\n * Request gửi tin nhắn văn bản đến người dùng ẩn danh\r\n */\r\nexport interface AnonymousTextMessageRequest {\r\n  recipient: AnonymousMessageRecipient;\r\n  message: {\r\n    text: string;\r\n  };\r\n}\r\n\r\n/**\r\n * Request gửi tin nhắn ảnh đến người dùng ẩn danh\r\n */\r\nexport interface AnonymousImageMessageRequest {\r\n  recipient: AnonymousMessageRecipient;\r\n  message: {\r\n    attachment: AnonymousImageAttachment;\r\n  };\r\n}\r\n\r\n/**\r\n * Request gửi tin nhắn file đến người dùng ẩn danh\r\n */\r\nexport interface AnonymousFileMessageRequest {\r\n  recipient: AnonymousMessageRecipient;\r\n  message: {\r\n    attachment: AnonymousFileAttachment;\r\n  };\r\n}\r\n\r\n/**\r\n * Request gửi tin nhắn sticker đến người dùng ẩn danh\r\n */\r\nexport interface AnonymousStickerMessageRequest {\r\n  recipient: AnonymousMessageRecipient;\r\n  message: {\r\n    attachment: AnonymousStickerAttachment;\r\n  };\r\n}\r\n\r\n/**\r\n * Service xử lý các API gửi tin nhắn đến người dùng ẩn danh của Zalo Official Account\r\n *\r\n * Bao gồm các chức năng:\r\n * - Gửi tin nhắn văn bản đến người dùng ẩn danh\r\n * - Gửi tin nhắn ảnh đến người dùng ẩn danh\r\n * - Gửi tin nhắn file đến người dùng ẩn danh\r\n * - Gửi tin nhắn sticker đến người dùng ẩn danh\r\n *\r\n * ĐIỀU KIỆN SỬ DỤNG:\r\n *\r\n * 1. QUYỀN TRUY CẬP:\r\n *    - Cần quyền: Gửi tin và thông báo qua OA\r\n *\r\n * 2. THÔNG TIN NGƯỜI DÙNG ẨN DANH:\r\n *    - anonymous_id: ID đại diện cho người dùng ẩn danh\r\n *    - conversation_id: ID của cuộc hội thoại\r\n *    - Các thông tin này nhận được từ webhook khi người dùng ẩn danh gửi tin nhắn\r\n *\r\n * 3. GIỚI HẠN:\r\n *    - Tin nhắn văn bản: Tối đa 2000 ký tự\r\n *    - Tin nhắn ảnh: Hỗ trợ định dạng JPG, PNG, dung lượng tối đa 1MB\r\n *    - Tin nhắn sticker: Sử dụng sticker_id từ nguồn https://stickers.zaloapp.com/\r\n *    - Tin nhắn file: Sử dụng token từ API upload file\r\n */\r\nexport class AnonymousMessageService {\r\n  private readonly endpoint = \"https://openapi.zalo.me/v2.0/oa/message\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Gửi tin nhắn văn bản đến người dùng ẩn danh\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận ẩn danh\r\n   * @param text Nội dung văn bản (tối đa 2000 ký tự)\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * const result = await anonymousService.sendTextMessage(\r\n   *   accessToken,\r\n   *   { anonymous_id: 'xxx', conversation_id: 'yyy' },\r\n   *   'hello, world!'\r\n   * );\r\n   * ```\r\n   */\r\n  async sendTextMessage(\r\n    accessToken: string,\r\n    recipient: AnonymousMessageRecipient,\r\n    text: string\r\n  ): Promise<AnonymousMessageResponse> {\r\n    try {\r\n      // Validate text length theo docs: tối đa 2000 ký tự\r\n      if (text.length > 2000) {\r\n        throw new ZaloSDKError(\r\n          \"Nội dung văn bản không được vượt quá 2000 ký tự\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (text.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Nội dung văn bản không được để trống\", -1);\r\n      }\r\n\r\n      const request: AnonymousTextMessageRequest = {\r\n        recipient,\r\n        message: { text },\r\n      };\r\n\r\n      const result: ZaloResponse<AnonymousMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send anonymous text message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send anonymous text message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn ảnh đến người dùng ẩn danh\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận ẩn danh\r\n   * @param imageUrl URL của ảnh (định dạng JPG/PNG, tối đa 1MB) HOẶC attachment_id từ upload\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * // Gửi bằng URL\r\n   * const result = await anonymousService.sendImageMessage(\r\n   *   accessToken,\r\n   *   { anonymous_id: 'xxx', conversation_id: 'yyy' },\r\n   *   'https://example.com/image.jpg'\r\n   * );\r\n   *\r\n   * // Gửi bằng attachment_id\r\n   * const result2 = await anonymousService.sendImageMessage(\r\n   *   accessToken,\r\n   *   { anonymous_id: 'xxx', conversation_id: 'yyy' },\r\n   *   undefined,\r\n   *   'uploaded_attachment_id'\r\n   * );\r\n   * ```\r\n   */\r\n  async sendImageMessage(\r\n    accessToken: string,\r\n    recipient: AnonymousMessageRecipient,\r\n    imageUrl?: string,\r\n    attachmentId?: string\r\n  ): Promise<AnonymousMessageResponse> {\r\n    try {\r\n      if (!imageUrl && !attachmentId) {\r\n        throw new ZaloSDKError(\r\n          \"Phải cung cấp imageUrl hoặc attachmentId\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      const attachment: AnonymousImageAttachment = {\r\n        type: \"template\",\r\n        payload: {\r\n          template_type: \"media\",\r\n          elements: [\r\n            {\r\n              media_type: \"image\",\r\n              ...(imageUrl && { url: imageUrl }),\r\n              ...(attachmentId && { attachment_id: attachmentId }),\r\n            },\r\n          ],\r\n        },\r\n      };\r\n\r\n      const request: AnonymousImageMessageRequest = {\r\n        recipient,\r\n        message: { attachment },\r\n      };\r\n\r\n      const result: ZaloResponse<AnonymousMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send anonymous image message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send anonymous image message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn file đến người dùng ẩn danh\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận ẩn danh\r\n   * @param fileToken Token từ API upload file\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * // Đầu tiên upload file\r\n   * const uploadResult = await messageService.uploadFile(accessToken, fileData, fileName);\r\n   *\r\n   * // Sau đó gửi tin nhắn file\r\n   * const result = await anonymousService.sendFileMessage(\r\n   *   accessToken,\r\n   *   { anonymous_id: 'xxx', conversation_id: 'yyy' },\r\n   *   uploadResult.token\r\n   * );\r\n   * ```\r\n   */\r\n  async sendFileMessage(\r\n    accessToken: string,\r\n    recipient: AnonymousMessageRecipient,\r\n    fileToken: string\r\n  ): Promise<AnonymousMessageResponse> {\r\n    try {\r\n      if (!fileToken || fileToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\"File token không được để trống\", -1);\r\n      }\r\n\r\n      const attachment: AnonymousFileAttachment = {\r\n        type: \"file\",\r\n        payload: {\r\n          token: fileToken,\r\n        },\r\n      };\r\n\r\n      const request: AnonymousFileMessageRequest = {\r\n        recipient,\r\n        message: { attachment },\r\n      };\r\n\r\n      const result: ZaloResponse<AnonymousMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send anonymous file message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send anonymous file message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn sticker đến người dùng ẩn danh\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param recipient Thông tin người nhận ẩn danh\r\n   * @param stickerId ID của sticker (lấy từ https://stickers.zaloapp.com/)\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * const result = await anonymousService.sendStickerMessage(\r\n   *   accessToken,\r\n   *   { anonymous_id: 'xxx', conversation_id: 'yyy' },\r\n   *   'sticker_id_from_zalo'\r\n   * );\r\n   * ```\r\n   */\r\n  async sendStickerMessage(\r\n    accessToken: string,\r\n    recipient: AnonymousMessageRecipient,\r\n    stickerId: string\r\n  ): Promise<AnonymousMessageResponse> {\r\n    try {\r\n      if (!stickerId || stickerId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Sticker ID không được để trống\", -1);\r\n      }\r\n\r\n      const attachment: AnonymousStickerAttachment = {\r\n        type: \"template\",\r\n        payload: {\r\n          template_type: \"media\",\r\n          elements: [\r\n            {\r\n              media_type: \"sticker\",\r\n              attachment_id: stickerId,\r\n            },\r\n          ],\r\n        },\r\n      };\r\n\r\n      const request: AnonymousStickerMessageRequest = {\r\n        recipient,\r\n        message: { attachment },\r\n      };\r\n\r\n      const result: ZaloResponse<AnonymousMessageResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send anonymous sticker message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send anonymous sticker message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\nimport {\r\n  MessageRecipient,\r\n  SenderAction,\r\n  MessageReactionResponse,\r\n  ReactIcon,\r\n} from \"../types/message\";\r\n\r\n/**\r\n * Request thả biểu tượng cảm xúc vào tin nhắn\r\n */\r\nexport interface MessageReactionRequest {\r\n  recipient: MessageRecipient;\r\n  sender_action: SenderAction;\r\n}\r\n\r\n/**\r\n * Service xử lý API thả biểu tượng cảm xúc vào tin nhắn của Zalo Official Account\r\n *\r\n * Bao gồm chức năng:\r\n * - Thả biểu tượng cảm xúc vào tin nhắn\r\n * - Thu hồi biểu tượng cảm xúc\r\n *\r\n * ĐIỀU KIỆN SỬ DỤNG:\r\n *\r\n * 1. QUYỀN TRUY CẬP:\r\n *    - Cần quyền: Gửi tin nhắn / Thả biểu tượng cảm xúc vào tin nhắn\r\n *\r\n * 2. GIỚI HẠN:\r\n *    - Một message_id chỉ được thả tối đa 50 biểu tượng cảm xúc\r\n *    - API này KHÔNG bị tính vào quota chủ động hàng tháng\r\n *\r\n * 3. CÁC BIỂU TƯỢNG CẢM XÚC HỖ TRỢ:\r\n *    - :> (Thích - Like)\r\n *    - --b (Haha)\r\n *    - :-(( (Buồn - Sad)\r\n *    - /-strong (Mạnh mẽ - Strong)\r\n *    - /-heart (Yêu thích - Love)\r\n *    - :-h (Ngạc nhiên - Wow)\r\n *    - :o (Wow)\r\n *    - /-remove (Thu hồi biểu tượng cảm xúc)\r\n */\r\nexport class MessageReactionService {\r\n  private readonly endpoint = \"https://openapi.zalo.me/v2.0/oa/message\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Thả biểu tượng cảm xúc vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @param reactIcon Biểu tượng cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * // Thả icon tim\r\n   * const result = await reactionService.reactToMessage(\r\n   *   accessToken,\r\n   *   '186729651760683225',\r\n   *   '789f573aecf8a9a4f0eb',\r\n   *   '/-heart'\r\n   * );\r\n   *\r\n   * // Thu hồi biểu tượng cảm xúc\r\n   * const removeResult = await reactionService.reactToMessage(\r\n   *   accessToken,\r\n   *   '186729651760683225',\r\n   *   '789f573aecf8a9a4f0eb',\r\n   *   '/-remove'\r\n   * );\r\n   * ```\r\n   */\r\n  async reactToMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string,\r\n    reactIcon: ReactIcon\r\n  ): Promise<MessageReactionResponse> {\r\n    try {\r\n      // Validate inputs\r\n      if (!userId || userId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"User ID không được để trống\", -1);\r\n      }\r\n\r\n      if (!messageId || messageId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Message ID không được để trống\", -1);\r\n      }\r\n\r\n      // Danh sách các react icon hợp lệ\r\n      const validIcons: ReactIcon[] = [\r\n        \":>\",\r\n        \"--b\",\r\n        \":-((\",\r\n        \"/-strong\",\r\n        \"/-heart\",\r\n        \":-h\",\r\n        \":o\",\r\n        \"/-remove\",\r\n      ];\r\n\r\n      if (!validIcons.includes(reactIcon)) {\r\n        throw new ZaloSDKError(\r\n          `Biểu tượng cảm xúc không hợp lệ. Các giá trị hợp lệ: ${validIcons.join(\r\n            \", \"\r\n          )}`,\r\n          -1\r\n        );\r\n      }\r\n\r\n      const request: MessageReactionRequest = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        sender_action: {\r\n          react_icon: reactIcon,\r\n          react_message_id: messageId,\r\n        },\r\n      };\r\n\r\n      const result: ZaloResponse<MessageReactionResponse> =\r\n        await this.client.apiPost(this.endpoint, accessToken, request);\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to react to message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to react to message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Thả icon \"Thích\" (Like) vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async likeMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \":>\");\r\n  }\r\n\r\n  /**\r\n   * Thả icon \"Haha\" vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async hahaMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \"--b\");\r\n  }\r\n\r\n  /**\r\n   * Thả icon \"Buồn\" (Sad) vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async sadMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \":-((\");\r\n  }\r\n\r\n  /**\r\n   * Thả icon \"Mạnh mẽ\" (Strong) vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async strongMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \"/-strong\");\r\n  }\r\n\r\n  /**\r\n   * Thả icon \"Yêu thích\" (Love) vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async loveMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \"/-heart\");\r\n  }\r\n\r\n  /**\r\n   * Thả icon \"Ngạc nhiên\" (Wow) vào tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thả cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async wowMessage(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \":-h\");\r\n  }\r\n\r\n  /**\r\n   * Thu hồi biểu tượng cảm xúc khỏi tin nhắn\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param userId ID của người nhận\r\n   * @param messageId message_id của tin nhắn cần thu hồi cảm xúc\r\n   * @returns Thông tin tin nhắn phản hồi\r\n   */\r\n  async removeReaction(\r\n    accessToken: string,\r\n    userId: string,\r\n    messageId: string\r\n  ): Promise<MessageReactionResponse> {\r\n    return this.reactToMessage(accessToken, userId, messageId, \"/-remove\");\r\n  }\r\n}\r\n\r\n/**\r\n * Enum các loại biểu tượng cảm xúc hỗ trợ\r\n */\r\nexport enum ReactionIcon {\r\n  /** Thích (Like) */\r\n  LIKE = \":>\",\r\n  /** Haha */\r\n  HAHA = \"--b\",\r\n  /** Buồn (Sad) */\r\n  SAD = \":-((\",\r\n  /** Mạnh mẽ (Strong) */\r\n  STRONG = \"/-strong\",\r\n  /** Yêu thích (Love) */\r\n  HEART = \"/-heart\",\r\n  /** Ngạc nhiên (Wow) */\r\n  WOW_H = \":-h\",\r\n  /** Wow */\r\n  WOW_O = \":o\",\r\n  /** Thu hồi biểu tượng cảm xúc */\r\n  REMOVE = \"/-remove\",\r\n}\r\n","import { ZaloClient } from \"../clients/zalo-client\";\r\nimport { ZaloResponse, ZaloSDKError } from \"../types/common\";\r\nimport {\r\n  MessageRecipient,\r\n  MiniAppMessageRequest,\r\n  MiniAppMessageResponse,\r\n} from \"../types/message\";\r\n\r\n/**\r\n * Request gửi tin nhắn miniapp\r\n */\r\nexport interface MiniAppSendMessageRequest {\r\n  recipient: MessageRecipient;\r\n  message: MiniAppMessageRequest;\r\n}\r\n\r\n/**\r\n * Service xử lý API gửi tin nhắn miniapp của Zalo Official Account\r\n *\r\n * Bao gồm chức năng:\r\n * - Gửi tin nhắn miniapp với template tùy chỉnh\r\n *\r\n * ĐIỀU KIỆN SỬ DỤNG:\r\n *\r\n * 1. QUYỀN TRUY CẬP:\r\n *    - Cần quyền: Gửi tin và thông báo qua OA\r\n *\r\n * 2. THÔNG TIN BẮT BUỘC:\r\n *    - access_token: Token của OA\r\n *    - miniapp_message_token: Token được sinh ra từ miniapp, đại diện cho miniapp\r\n *    - user_id: Phải khớp với user đã tương tác với Miniapp\r\n *    - template_type: Token của template miniapp\r\n *    - template_data: Các thuộc tính của mẫu tin (được quy định theo từng template_type)\r\n *\r\n * 3. QUOTA (HẠN MỨC):\r\n *    API này có thể trả về thông tin quota trong response:\r\n *    - reply: Tin Tư vấn miễn phí trong hạn mức 8 tin 48 giờ\r\n *    - sub_quota: Tin Tư vấn miễn phí theo gói dịch vụ OA\r\n *    - purchase_quota: Tin trong hạn mức gói tính năng lẻ\r\n *    - reward_quota: Tin trong hạn mức Redeem code\r\n *\r\n *    Các loại quota không có thông tin chi tiết:\r\n *    - Không có quota info: Tin không tính quota (ví dụ: tin trong 48h không còn hạn mức)\r\n */\r\nexport class MiniAppMessageService {\r\n  private readonly endpoint = \"https://openapi.zalo.me/v3.0/oa/message/cs/miniapp\";\r\n\r\n  constructor(private readonly client: ZaloClient) {}\r\n\r\n  /**\r\n   * Gửi tin nhắn miniapp đến người dùng\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param miniappMessageToken Token được sinh ra từ miniapp\r\n   * @param userId ID của người nhận (phải khớp với user đã tương tác với Miniapp)\r\n   * @param templateType Token của template miniapp\r\n   * @param templateData Các thuộc tính của mẫu tin (tuỳ theo từng template_type)\r\n   * @returns Thông tin tin nhắn đã gửi (bao gồm quota info nếu có)\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * const result = await miniappService.sendMessage(\r\n   *   accessToken,\r\n   *   'miniapp_message_token',\r\n   *   '4153814862349165539',\r\n   *   'FB0001',\r\n   *   {\r\n   *     customer_name: 'Tùng Nguyễn',\r\n   *     queue_number: '12',\r\n   *     note: 'Khách hàng thanh toán bằng thẻ',\r\n   *     buttons: [\r\n   *       {\r\n   *         title: 'Chi tiết đơn hàng',\r\n   *         image_icon: 'https://stc-zmp.zadn.vn/oa/basket.png',\r\n   *         url: 'https://zalo.me/s/194839900003483517/'\r\n   *       },\r\n   *       {\r\n   *         title: 'Đánh giá',\r\n   *         image_icon: 'https://stc-zmp.zadn.vn/oa/star.png',\r\n   *         url: 'https://zalo.me/s/194839900003483517/'\r\n   *       }\r\n   *     ]\r\n   *   }\r\n   * );\r\n   *\r\n   * // Kiểm tra quota\r\n   * if (result.quota) {\r\n   *   console.log('Quota type:', result.quota.quota_type);\r\n   *   console.log('Remaining:', result.quota.remain);\r\n   * }\r\n   * ```\r\n   */\r\n  async sendMessage(\r\n    accessToken: string,\r\n    miniappMessageToken: string,\r\n    userId: string,\r\n    templateType: string,\r\n    templateData: Record<string, any>\r\n  ): Promise<MiniAppMessageResponse> {\r\n    try {\r\n      // Validate inputs\r\n      if (!miniappMessageToken || miniappMessageToken.trim().length === 0) {\r\n        throw new ZaloSDKError(\r\n          \"Miniapp message token không được để trống\",\r\n          -1\r\n        );\r\n      }\r\n\r\n      if (!userId || userId.trim().length === 0) {\r\n        throw new ZaloSDKError(\"User ID không được để trống\", -1);\r\n      }\r\n\r\n      if (!templateType || templateType.trim().length === 0) {\r\n        throw new ZaloSDKError(\"Template type không được để trống\", -1);\r\n      }\r\n\r\n      if (!templateData || Object.keys(templateData).length === 0) {\r\n        throw new ZaloSDKError(\"Template data không được để trống\", -1);\r\n      }\r\n\r\n      const request: MiniAppSendMessageRequest = {\r\n        recipient: {\r\n          user_id: userId,\r\n        },\r\n        message: {\r\n          template_type: templateType,\r\n          template_data: templateData,\r\n        },\r\n      };\r\n\r\n      // Gọi API với custom header miniapp_message_token\r\n      const result: ZaloResponse<MiniAppMessageResponse> =\r\n        await this.client.apiPostWithHeaders(\r\n          this.endpoint,\r\n          accessToken,\r\n          request,\r\n          {\r\n            miniapp_messsage_token: miniappMessageToken,\r\n          }\r\n        );\r\n\r\n      if (result.error !== 0) {\r\n        throw new ZaloSDKError(\r\n          result.message || \"Failed to send miniapp message\",\r\n          result.error,\r\n          result\r\n        );\r\n      }\r\n\r\n      if (!result.data) {\r\n        throw new ZaloSDKError(\"No response data received\", -1);\r\n      }\r\n\r\n      return result.data;\r\n    } catch (error) {\r\n      if (error instanceof ZaloSDKError) {\r\n        throw error;\r\n      }\r\n      throw new ZaloSDKError(\r\n        `Failed to send miniapp message: ${(error as Error).message}`,\r\n        -1,\r\n        error\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Gửi tin nhắn miniapp với template queue notification\r\n   * (Template thông báo số thứ tự trong hàng đợi)\r\n   *\r\n   * @param accessToken Access token của Official Account\r\n   * @param miniappMessageToken Token từ miniapp\r\n   * @param userId ID của người nhận\r\n   * @param customerName Tên khách hàng\r\n   * @param queueNumber Số thứ tự trong hàng đợi\r\n   * @param note Ghi chú thêm\r\n   * @param buttons Danh sách các button kèm theo\r\n   * @returns Thông tin tin nhắn đã gửi\r\n   */\r\n  async sendQueueNotification(\r\n    accessToken: string,\r\n    miniappMessageToken: string,\r\n    userId: string,\r\n    customerName: string,\r\n    queueNumber: string,\r\n    note: string,\r\n    buttons?: Array<{\r\n      title: string;\r\n      image_icon: string;\r\n      url: string;\r\n    }>\r\n  ): Promise<MiniAppMessageResponse> {\r\n    const templateData: Record<string, any> = {\r\n      customer_name: customerName,\r\n      queue_number: queueNumber,\r\n      note: note,\r\n    };\r\n\r\n    if (buttons && buttons.length > 0) {\r\n      templateData.buttons = buttons;\r\n    }\r\n\r\n    return this.sendMessage(\r\n      accessToken,\r\n      miniappMessageToken,\r\n      userId,\r\n      \"FB0001\",\r\n      templateData\r\n    );\r\n  }\r\n}\r\n\r\n/**\r\n * Enum các loại quota (hạn mức) khi gửi tin nhắn miniapp\r\n */\r\nexport enum MiniAppQuotaType {\r\n  /** Tin Tư vấn miễn phí trong hạn mức 8 tin 48 giờ */\r\n  REPLY = \"reply\",\r\n  /** Tin Tư vấn miễn phí theo gói dịch vụ OA */\r\n  SUB_QUOTA = \"sub_quota\",\r\n  /** Tin trong hạn mức gói tính năng lẻ */\r\n  PURCHASE_QUOTA = \"purchase_quota\",\r\n  /** Tin trong hạn mức Redeem code */\r\n  REWARD_QUOTA = \"reward_quota\",\r\n}\r\n\r\n/**\r\n * Enum các loại owner sở hữu Quota Package\r\n */\r\nexport enum QuotaOwnerType {\r\n  /** Thực thể sở hữu là OA */\r\n  OA = \"OA\",\r\n  /** Thực thể sở hữu là App */\r\n  APP = \"App\",\r\n}\r\n","/**\r\n * Main Zalo SDK class\r\n */\r\n\r\nimport { ZaloClient } from \"./clients/zalo-client\";\r\nimport { AuthService } from \"./services/auth.service\";\r\nimport { OAService } from \"./services/oa.service\";\r\n// import { MessageService } from \"./services/message.service\"; // Replaced by specialized services\r\nimport { UserService } from \"./services/user.service\";\r\n// import { WebhookService } from \"./services/webhook.service\";\r\nimport { ZNSService } from \"./services/zns.service\";\r\nimport { GroupMessageService } from \"./services/group-message.service\";\r\nimport { GroupManagementService } from \"./services/group-management.service\";\r\nimport { ArticleService } from \"./services/article.service\";\r\nimport { VideoUploadService } from \"./services/video-upload.service\";\r\n\r\n// Message Services\r\nimport { ConsultationService } from \"./services/consultation.service\";\r\nimport { TransactionService } from \"./services/transaction.service\";\r\nimport { PromotionService } from \"./services/promotion.service\";\r\nimport { GeneralMessageService } from \"./services/general-message.service\";\r\nimport { MessageManagementService } from \"./services/message-management.service\";\r\nimport { BroadcastService } from \"./services/broadcast.service\";\r\nimport { PurchaseService } from \"./services/purchase.service\";\r\n// New services from api.md\r\nimport { AnonymousMessageService } from \"./services/anonymous-message.service\";\r\nimport { MessageReactionService } from \"./services/message-reaction.service\";\r\nimport { MiniAppMessageService } from \"./services/miniapp-message.service\";\r\nimport {\r\n  ZaloSDKConfig,\r\n  ZaloSDKError,\r\n  Logger,\r\n  ConsoleLogger,\r\n} from \"./types/common\";\r\nimport {\r\n  AccessToken,\r\n  AuthCodeParams,\r\n  RefreshTokenParams,\r\n  SocialUserInfo,\r\n} from \"./types/auth\";\r\nimport { OAInfo, MessageQuota } from \"./types/oa\";\r\nimport { SendMessageResponse } from \"./types/message\";\r\nimport { UserInfo, UserListRequest, UserListResponse } from \"./types/user\";\r\n// import { WebhookHandlers } from \"./types/webhook\";\r\n\r\n/**\r\n * Main Zalo SDK class providing access to all Zalo APIs\r\n */\r\nexport class ZaloSDK {\r\n  private readonly client: ZaloClient;\r\n  private readonly logger: Logger;\r\n\r\n  // Services\r\n  public readonly auth: AuthService;\r\n  public readonly oa: OAService;\r\n  // public readonly message: MessageService; // Replaced by specialized message services\r\n  public readonly user: UserService;\r\n  // public readonly webhook: WebhookService;\r\n  public readonly zns: ZNSService;\r\n  public readonly groupMessage: GroupMessageService;\r\n  public readonly groupManagement: GroupManagementService;\r\n  public readonly article: ArticleService;\r\n  public readonly videoUpload: VideoUploadService;\r\n\r\n  // Message Services - Specialized by message type\r\n  public readonly consultation: ConsultationService;\r\n  public readonly transaction: TransactionService;\r\n  public readonly promotion: PromotionService;\r\n  public readonly generalMessage: GeneralMessageService;\r\n  public readonly messageManagement: MessageManagementService;\r\n  public readonly broadcast: BroadcastService;\r\n  public readonly purchase: PurchaseService;\r\n\r\n  // New services from api.md\r\n  public readonly anonymousMessage: AnonymousMessageService;\r\n  public readonly messageReaction: MessageReactionService;\r\n  public readonly miniappMessage: MiniAppMessageService;\r\n\r\n  // Configuration\r\n  public readonly config: Required<ZaloSDKConfig>;\r\n\r\n  constructor(config: ZaloSDKConfig) {\r\n    // Validate required configuration\r\n    if (!config.appId) {\r\n      throw new ZaloSDKError(\"appId is required in SDK configuration\");\r\n    }\r\n    if (!config.appSecret) {\r\n      throw new ZaloSDKError(\"appSecret is required in SDK configuration\");\r\n    }\r\n\r\n    // Set default configuration\r\n    this.config = {\r\n      appId: config.appId,\r\n      appSecret: config.appSecret,\r\n      timeout: config.timeout || 30000,\r\n      debug: config.debug || false,\r\n      apiBaseUrl: config.apiBaseUrl || \"https://openapi.zalo.me\",\r\n      retry: {\r\n        attempts: config.retry?.attempts || 3,\r\n        delay: config.retry?.delay || 1000,\r\n        ...config.retry,\r\n      },\r\n    };\r\n\r\n    this.logger = new ConsoleLogger(this.config.debug);\r\n    this.client = new ZaloClient(this.config);\r\n\r\n    // Initialize services\r\n    this.auth = new AuthService(\r\n      this.client,\r\n      this.config.appId,\r\n      this.config.appSecret\r\n    );\r\n    this.oa = new OAService(this.client);\r\n    // this.message = new MessageService(this.client); // Replaced by specialized message services\r\n    this.user = new UserService(this.client);\r\n    // this.webhook = new WebhookService(this.config.appSecret, this.config.debug);\r\n    this.zns = new ZNSService(this.client);\r\n    this.groupMessage = new GroupMessageService(this.client);\r\n    this.groupManagement = new GroupManagementService(this.client);\r\n    this.article = new ArticleService(this.client);\r\n    this.videoUpload = new VideoUploadService(this.client);\r\n\r\n    // Initialize message services\r\n    this.consultation = new ConsultationService(this.client);\r\n    this.transaction = new TransactionService(this.client);\r\n    this.promotion = new PromotionService(this.client);\r\n    this.generalMessage = new GeneralMessageService(this.client);\r\n    this.messageManagement = new MessageManagementService(this.client);\r\n    this.broadcast = new BroadcastService(this.client);\r\n    this.purchase = new PurchaseService(this.client);\r\n\r\n    // Initialize new services from api.md\r\n    this.anonymousMessage = new AnonymousMessageService(this.client);\r\n    this.messageReaction = new MessageReactionService(this.client);\r\n    this.miniappMessage = new MiniAppMessageService(this.client);\r\n\r\n    this.logger.info(\"Zalo SDK initialized\", {\r\n      appId: this.config.appId,\r\n      debug: this.config.debug,\r\n      timeout: this.config.timeout,\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Quick method to get OA access token from authorization code\r\n   */\r\n  public async getOAAccessToken(\r\n    code: string,\r\n    redirectUri: string\r\n  ): Promise<AccessToken> {\r\n    const params: AuthCodeParams = {\r\n      app_id: this.config.appId,\r\n      app_secret: this.config.appSecret,\r\n      code,\r\n      redirect_uri: redirectUri,\r\n    };\r\n\r\n    return this.auth.getOAAccessToken(params);\r\n  }\r\n\r\n  /**\r\n   * Quick method to get Social access token from authorization code\r\n   */\r\n  public async getSocialAccessToken(\r\n    code: string,\r\n    redirectUri: string,\r\n    codeVerifier?: string\r\n  ): Promise<AccessToken> {\r\n    const params: AuthCodeParams = {\r\n      app_id: this.config.appId,\r\n      app_secret: this.config.appSecret,\r\n      code,\r\n      redirect_uri: redirectUri,\r\n      code_verifier: codeVerifier || \"\",\r\n    };\r\n\r\n    return this.auth.getSocialAccessToken(params);\r\n  }\r\n\r\n  /**\r\n   * Quick method to refresh OA access token\r\n   */\r\n  public async refreshOAAccessToken(\r\n    refreshToken: string\r\n  ): Promise<AccessToken> {\r\n    const params: RefreshTokenParams = {\r\n      app_id: this.config.appId,\r\n      app_secret: this.config.appSecret,\r\n      refresh_token: refreshToken,\r\n    };\r\n\r\n    return this.auth.refreshOAAccessToken(params);\r\n  }\r\n\r\n  /**\r\n   * Quick method to refresh Social access token\r\n   */\r\n  public async refreshSocialAccessToken(\r\n    refreshToken: string\r\n  ): Promise<AccessToken> {\r\n    const params: RefreshTokenParams = {\r\n      app_id: this.config.appId,\r\n      app_secret: this.config.appSecret,\r\n      refresh_token: refreshToken,\r\n    };\r\n\r\n    return this.auth.refreshSocialAccessToken(params);\r\n  }\r\n\r\n  /**\r\n   * Quick method to get OA information\r\n   */\r\n  public async getOAInfo(accessToken: string): Promise<OAInfo> {\r\n    return this.oa.getOAInfo(accessToken);\r\n  }\r\n\r\n  /**\r\n   * Quick method to get message quota\r\n   */\r\n  public async getMessageQuota(accessToken: string): Promise<MessageQuota> {\r\n    return this.oa.getMessageQuota(accessToken);\r\n  }\r\n\r\n  /**\r\n   * Quick method to get Social user information\r\n   */\r\n  public async getSocialUserInfo(\r\n    accessToken: string,\r\n    fields?: string\r\n  ): Promise<SocialUserInfo> {\r\n    return this.auth.getSocialUserInfo(accessToken, fields);\r\n  }\r\n\r\n  /**\r\n   * Quick method to send consultation text message\r\n   */\r\n  public async sendConsultationText(\r\n    accessToken: string,\r\n    userId: string,\r\n    text: string\r\n  ): Promise<SendMessageResponse> {\r\n    return this.consultation.sendTextMessage(\r\n      accessToken,\r\n      { user_id: userId },\r\n      { type: \"text\", text }\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Quick method to get user information\r\n   */\r\n  public async getUserInfo(\r\n    accessToken: string,\r\n    userId: string\r\n  ): Promise<UserInfo> {\r\n    return this.user.getUserInfo(accessToken, userId);\r\n  }\r\n\r\n  /**\r\n   * Quick method to get user list\r\n   */\r\n  public async getUserList(\r\n    accessToken: string,\r\n    request: UserListRequest\r\n  ): Promise<UserListResponse> {\r\n    return this.user.getUserList(accessToken, request);\r\n  }\r\n\r\n  /**\r\n   * Quick method to get all followers\r\n   */\r\n  public async getFollowers(accessToken: string): Promise<UserListResponse> {\r\n    return this.user.getFollowers(accessToken);\r\n  }\r\n\r\n  /**\r\n   * Quick method to register webhook handlers\r\n   */\r\n  // public registerWebhookHandlers(handlers: WebhookHandlers): void {\r\n  //   this.webhook.registerHandlers(handlers);\r\n  // }\r\n\r\n  /**\r\n   * Quick method to process webhook\r\n   */\r\n  // public async processWebhook(\r\n  //   payload: string,\r\n  //   signature?: string,\r\n  //   timestamp?: string\r\n  // ): Promise<void> {\r\n  //   return this.webhook.processWebhook(payload, signature, timestamp);\r\n  // }\r\n\r\n  /**\r\n   * Create OA authorization URL with PKCE support\r\n   * @deprecated Use auth.createOAAuthUrl() directly for full control over PKCE and state\r\n   */\r\n  public createOAAuthUrl(redirectUri: string, state?: string): string {\r\n    const result = this.auth.createOAAuthUrl(redirectUri, state, undefined, false);\r\n    return result.url;\r\n  }\r\n\r\n  /**\r\n   * Create OA authorization URL with full PKCE support\r\n   * Returns both URL and state for enhanced security\r\n   */\r\n  public createSecureOAAuthUrl(redirectUri: string, state?: string, enablePKCE: boolean = true) {\r\n    const result = this.auth.createOAAuthUrl(redirectUri, state, undefined, enablePKCE);\r\n    return result;\r\n  }\r\n\r\n  /**\r\n   * Create Social authorization URL\r\n   */\r\n  public createSocialAuthUrl(redirectUri: string, state?: string): string {\r\n    return this.auth.createSocialAuthUrl(redirectUri, state);\r\n  }\r\n\r\n  /**\r\n   * Generate PKCE for Social API\r\n   */\r\n  public generatePKCE() {\r\n    return this.auth.generatePKCE();\r\n  }\r\n\r\n  /**\r\n   * Validate access token\r\n   */\r\n  public async validateAccessToken(\r\n    accessToken: string,\r\n    scope: \"oa\" | \"social\" = \"social\"\r\n  ): Promise<boolean> {\r\n    try {\r\n      if (scope === \"oa\") {\r\n        return this.oa.validateOAToken(accessToken);\r\n      } else {\r\n        const validation = await this.auth.validateAccessToken(accessToken);\r\n        return validation.valid;\r\n      }\r\n    } catch (error) {\r\n      this.logger.warn(`Token validation failed: ${(error as Error).message}`);\r\n      return false;\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get SDK version\r\n   */\r\n  public getVersion(): string {\r\n    return \"1.0.0\";\r\n  }\r\n\r\n  /**\r\n   * Get SDK configuration (without sensitive data)\r\n   */\r\n  public getConfig(): Omit<Required<ZaloSDKConfig>, \"appSecret\"> {\r\n    const { appSecret, ...safeConfig } = this.config;\r\n    return safeConfig;\r\n  }\r\n\r\n  /**\r\n   * Enable or disable debug logging\r\n   */\r\n  public setDebug(enabled: boolean): void {\r\n    (this.config as any).debug = enabled;\r\n    // Note: ConsoleLogger debug setting is read-only, create new logger if needed\r\n    if (enabled !== this.config.debug) {\r\n      this.logger.info(`Debug mode ${enabled ? \"enabled\" : \"disabled\"}`);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Test SDK connectivity\r\n   */\r\n  public async testConnection(): Promise<boolean> {\r\n    try {\r\n      // Try to make a simple request to test connectivity\r\n      const testUrl = `${this.config.apiBaseUrl}/v2.0/oa/getoa`;\r\n      await this.client.apiGet(testUrl, \"test-token\");\r\n      return true;\r\n    } catch (error) {\r\n      // We expect this to fail with authentication error, not network error\r\n      if (\r\n        (error as Error).message.includes(\"access_token\") ||\r\n        (error as Error).message.includes(\"401\")\r\n      ) {\r\n        return true; // Network is working, just auth failed as expected\r\n      }\r\n      this.logger.error(`Connection test failed: ${(error as Error).message}`);\r\n      return false;\r\n    }\r\n  }\r\n\r\n\r\n\r\n  /**\r\n   * Make a custom API request (advanced usage)\r\n   */\r\n  public async customRequest<T = any>(\r\n    method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\",\r\n    endpoint: string,\r\n    accessToken: string,\r\n    data?: any,\r\n    params?: Record<string, any>\r\n  ): Promise<T> {\r\n    switch (method) {\r\n      case \"GET\":\r\n        return this.client.apiGet<T>(endpoint, accessToken, params);\r\n      case \"POST\":\r\n        return this.client.apiPost<T>(endpoint, accessToken, data, params);\r\n      case \"PUT\":\r\n        return this.client.apiPut<T>(endpoint, accessToken, data, params);\r\n      case \"DELETE\":\r\n        return this.client.apiDelete<T>(endpoint, accessToken, params);\r\n      default:\r\n        throw new ZaloSDKError(`Unsupported HTTP method: ${method}`);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Upload file to Zalo API\r\n   */\r\n  public async uploadFile<T = any>(\r\n    endpoint: string,\r\n    accessToken: string,\r\n    file: Buffer | NodeJS.ReadableStream,\r\n    filename: string,\r\n    additionalFields?: Record<string, any>\r\n  ): Promise<T> {\r\n    return this.client.apiUploadFile<T>(\r\n      endpoint,\r\n      accessToken,\r\n      file,\r\n      filename,\r\n      additionalFields\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Dispose SDK resources\r\n   */\r\n  public dispose(): void {\r\n    this.logger.info(\"Zalo SDK disposed\");\r\n  }\r\n}\r\n"],"mappings":"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","names":["AuthScope","AuthMethod","GMFProductType","QuotaType","UserInfoFieldType","ZNSTemplateType","ZNSTemplateTag","ZNSButtonType","ZNSParamType","ArticleStatus","CommentStatus","ArticleType","CoverType","BodyItemType","VideoUploadStatus","PurchaseErrorCode","WebhookEventName","AttachmentType","axios","FormData","successful","failed","ReactionIcon","MiniAppQuotaType","QuotaOwnerType"]}