titanium_model/
builder.rs

1//! Builders for creating Discord entities.
2
3use crate::{
4    Component, CreateMessage, Embed, EmbedAuthor, EmbedField, EmbedFooter, EmbedMedia, TitanString,
5};
6
7/// Builder for creating an Embed.
8#[derive(Debug, Clone, Default)]
9pub struct EmbedBuilder<'a> {
10    embed: Embed<'a>,
11}
12
13impl<'a> EmbedBuilder<'a> {
14    /// Create a new EmbedBuilder.
15    #[inline]
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    /// Set the title of the embed.
21    #[inline]
22    pub fn title(mut self, title: impl Into<TitanString<'a>>) -> Self {
23        self.embed.title = Some(title.into());
24        self
25    }
26
27    /// Set the description of the embed.
28    pub fn description(mut self, description: impl Into<TitanString<'a>>) -> Self {
29        self.embed.description = Some(description.into());
30        self
31    }
32
33    /// Set the URL of the embed.
34    pub fn url(mut self, url: impl Into<TitanString<'a>>) -> Self {
35        self.embed.url = Some(url.into());
36        self
37    }
38
39    /// Set the timestamp of the embed.
40    pub fn timestamp(mut self, timestamp: impl Into<TitanString<'a>>) -> Self {
41        self.embed.timestamp = Some(timestamp.into());
42        self
43    }
44
45    /// Set the color of the embed.
46    pub fn color(mut self, color: u32) -> Self {
47        self.embed.color = Some(color);
48        self
49    }
50
51    /// Set the color of the embed from RGB values.
52    pub fn color_rgb(mut self, r: u8, g: u8, b: u8) -> Self {
53        self.embed.color = Some(((r as u32) << 16) | ((g as u32) << 8) | (b as u32));
54        self
55    }
56
57    /// Set the footer of the embed.
58    pub fn footer(
59        mut self,
60        text: impl Into<TitanString<'a>>,
61        icon_url: Option<impl Into<TitanString<'a>>>,
62    ) -> Self {
63        self.embed.footer = Some(EmbedFooter {
64            text: text.into(),
65            icon_url: icon_url.map(Into::into),
66            proxy_icon_url: None,
67        });
68        self
69    }
70
71    /// Set the image of the embed.
72    pub fn image(mut self, url: impl Into<TitanString<'a>>) -> Self {
73        self.embed.image = Some(EmbedMedia {
74            url: Some(url.into()),
75            proxy_url: None,
76            height: None,
77            width: None,
78        });
79        self
80    }
81
82    /// Set the thumbnail of the embed.
83    pub fn thumbnail(mut self, url: impl Into<TitanString<'a>>) -> Self {
84        self.embed.thumbnail = Some(EmbedMedia {
85            url: Some(url.into()),
86            proxy_url: None,
87            height: None,
88            width: None,
89        });
90        self
91    }
92
93    /// Set the author of the embed.
94    pub fn author(
95        mut self,
96        name: impl Into<TitanString<'a>>,
97        url: Option<impl Into<TitanString<'a>>>,
98        icon_url: Option<impl Into<TitanString<'a>>>,
99    ) -> Self {
100        self.embed.author = Some(EmbedAuthor {
101            name: name.into(),
102            url: url.map(Into::into),
103            icon_url: icon_url.map(Into::into),
104            proxy_icon_url: None,
105        });
106        self
107    }
108
109    /// Add a field to the embed.
110    pub fn field(
111        mut self,
112        name: impl Into<TitanString<'a>>,
113        value: impl Into<TitanString<'a>>,
114        inline: bool,
115    ) -> Self {
116        self.embed.fields.push(EmbedField {
117            name: name.into(),
118            value: value.into(),
119            inline,
120        });
121        self
122    }
123
124    /// Add an inline field.
125    pub fn field_inline(
126        self,
127        name: impl Into<TitanString<'a>>,
128        value: impl Into<TitanString<'a>>,
129    ) -> Self {
130        self.field(name, value, true)
131    }
132
133    /// Add a block field (not inline).
134    pub fn field_block(
135        self,
136        name: impl Into<TitanString<'a>>,
137        value: impl Into<TitanString<'a>>,
138    ) -> Self {
139        self.field(name, value, false)
140    }
141
142    /// Build the Embed.
143    pub fn build(self) -> Embed<'a> {
144        self.embed
145    }
146}
147
148/// Builder for creating a Message.
149#[derive(Debug, Clone, Default)]
150pub struct MessageBuilder<'a> {
151    message: CreateMessage<'a>,
152}
153
154impl<'a> MessageBuilder<'a> {
155    /// Create a new MessageBuilder.
156    #[inline]
157    pub fn new() -> Self {
158        Self::default()
159    }
160
161    /// Set the content of the message.
162    #[inline]
163    pub fn content(mut self, content: impl Into<TitanString<'a>>) -> Self {
164        self.message.content = Some(content.into());
165        self
166    }
167
168    /// Enable/disable TTS.
169    pub fn tts(mut self, tts: bool) -> Self {
170        self.message.tts = Some(tts);
171        self
172    }
173
174    /// Reply to a message (sets message_reference).
175    pub fn reply(mut self, message_id: impl Into<crate::Snowflake>) -> Self {
176        self.message.message_reference = Some(crate::MessageReference {
177            message_id: Some(message_id.into()),
178            channel_id: None,
179            guild_id: None,
180            fail_if_not_exists: Some(true),
181        });
182        self
183    }
184
185    /// Add an embed to the message.
186    pub fn embed(mut self, embed: impl Into<Embed<'a>>) -> Self {
187        if let Some(embeds) = &mut self.message.embeds {
188            embeds.push(embed.into());
189        } else {
190            self.message.embeds = Some(vec![embed.into()]);
191        }
192        self
193    }
194
195    /// Add multiple embeds.
196    pub fn embeds(mut self, embeds: Vec<Embed<'a>>) -> Self {
197        if let Some(existing) = &mut self.message.embeds {
198            existing.extend(embeds);
199        } else {
200            self.message.embeds = Some(embeds);
201        }
202        self
203    }
204
205    /// Add a component (ActionRow, etc.) to the message.
206    pub fn component(mut self, component: impl Into<Component<'a>>) -> Self {
207        if let Some(components) = &mut self.message.components {
208            components.push(component.into());
209        } else {
210            self.message.components = Some(vec![component.into()]);
211        }
212        self
213    }
214
215    /// Add a file to upload.
216    pub fn add_file(
217        mut self,
218        filename: impl Into<TitanString<'a>>,
219        data: impl Into<Vec<u8>>,
220    ) -> Self {
221        self.message.files.push(crate::file::FileUpload::new(
222            filename.into().into_owned(),
223            data,
224        ));
225        self
226    }
227
228    /// Build the CreateMessage payload.
229    pub fn build(self) -> CreateMessage<'a> {
230        self.message
231    }
232}
233
234// ============================================================================
235// Modify Guild Builder
236// ============================================================================
237
238/// Payload for modifying a guild.
239#[derive(Debug, Clone, serde::Serialize, Default)]
240pub struct ModifyGuild<'a> {
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub name: Option<TitanString<'a>>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub region: Option<TitanString<'a>>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub verification_level: Option<u8>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub default_message_notifications: Option<u8>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub explicit_content_filter: Option<u8>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub afk_channel_id: Option<crate::Snowflake>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub afk_timeout: Option<u32>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub icon: Option<TitanString<'a>>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub owner_id: Option<crate::Snowflake>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub splash: Option<TitanString<'a>>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub discovery_splash: Option<TitanString<'a>>,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub banner: Option<TitanString<'a>>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub system_channel_id: Option<crate::Snowflake>,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub system_channel_flags: Option<u64>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub rules_channel_id: Option<crate::Snowflake>,
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub public_updates_channel_id: Option<crate::Snowflake>,
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub preferred_locale: Option<TitanString<'a>>,
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub features: Option<Vec<TitanString<'a>>>,
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub description: Option<TitanString<'a>>,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub premium_progress_bar_enabled: Option<bool>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub safety_alerts_channel_id: Option<crate::Snowflake>,
283}
284
285/// Builder for modifying a Guild.
286#[derive(Debug, Clone, Default)]
287pub struct ModifyGuildBuilder<'a> {
288    params: ModifyGuild<'a>,
289}
290
291impl<'a> ModifyGuildBuilder<'a> {
292    /// Create a new ModifyGuildBuilder.
293    pub fn new() -> Self {
294        Self::default()
295    }
296
297    /// Set name.
298    pub fn name(mut self, name: impl Into<TitanString<'a>>) -> Self {
299        self.params.name = Some(name.into());
300        self
301    }
302
303    /// Set region (deprecated).
304    pub fn region(mut self, region: impl Into<TitanString<'a>>) -> Self {
305        self.params.region = Some(region.into());
306        self
307    }
308
309    /// Set verification level.
310    pub fn verification_level(mut self, level: u8) -> Self {
311        self.params.verification_level = Some(level);
312        self
313    }
314
315    /// Set default message notifications.
316    pub fn default_message_notifications(mut self, level: u8) -> Self {
317        self.params.default_message_notifications = Some(level);
318        self
319    }
320
321    /// Set explicit content filter.
322    pub fn explicit_content_filter(mut self, level: u8) -> Self {
323        self.params.explicit_content_filter = Some(level);
324        self
325    }
326
327    /// Set AFK channel ID.
328    pub fn afk_channel_id(mut self, id: impl Into<crate::Snowflake>) -> Self {
329        self.params.afk_channel_id = Some(id.into());
330        self
331    }
332
333    /// Set AFK timeout.
334    pub fn afk_timeout(mut self, timeout: u32) -> Self {
335        self.params.afk_timeout = Some(timeout);
336        self
337    }
338
339    /// Set icon (base64).
340    pub fn icon(mut self, icon: impl Into<TitanString<'a>>) -> Self {
341        self.params.icon = Some(icon.into());
342        self
343    }
344
345    /// Set system channel ID.
346    pub fn system_channel_id(mut self, id: impl Into<crate::Snowflake>) -> Self {
347        self.params.system_channel_id = Some(id.into());
348        self
349    }
350
351    /// Build the ModifyGuild payload.
352    pub fn build(self) -> ModifyGuild<'a> {
353        self.params
354    }
355}
356
357// ============================================================================
358// Modify Member Builder
359// ============================================================================
360
361/// Payload for modifying a guild member.
362#[derive(Debug, Clone, serde::Serialize, Default)]
363pub struct ModifyMember<'a> {
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub nick: Option<TitanString<'a>>,
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub roles: Option<Vec<crate::Snowflake>>,
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub mute: Option<bool>,
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub deaf: Option<bool>,
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub channel_id: Option<crate::Snowflake>, // Move to voice channel
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub communication_disabled_until: Option<TitanString<'a>>, // Timeout
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub flags: Option<u64>,
378}
379
380/// Builder for modifying a GuildMember.
381#[derive(Debug, Clone, Default)]
382pub struct ModifyMemberBuilder<'a> {
383    params: ModifyMember<'a>,
384}
385
386impl<'a> ModifyMemberBuilder<'a> {
387    /// Create a new ModifyMemberBuilder.
388    pub fn new() -> Self {
389        Self::default()
390    }
391
392    /// Set nickname.
393    pub fn nick(mut self, nick: impl Into<TitanString<'a>>) -> Self {
394        self.params.nick = Some(nick.into());
395        self
396    }
397
398    /// Set roles (replaces all roles).
399    pub fn roles(mut self, roles: Vec<crate::Snowflake>) -> Self {
400        self.params.roles = Some(roles);
401        self
402    }
403
404    /// Mute or unmute.
405    pub fn mute(mut self, mute: bool) -> Self {
406        self.params.mute = Some(mute);
407        self
408    }
409
410    /// Deafen or undeafen.
411    pub fn deaf(mut self, deaf: bool) -> Self {
412        self.params.deaf = Some(deaf);
413        self
414    }
415
416    /// Move to voice channel (or disconnect if null, but we use strict type here).
417    pub fn move_to_channel(mut self, channel_id: impl Into<crate::Snowflake>) -> Self {
418        self.params.channel_id = Some(channel_id.into());
419        self
420    }
421
422    /// Timeout user until timestamp (ISO8601).
423    pub fn timeout_until(mut self, timestamp: impl Into<TitanString<'a>>) -> Self {
424        self.params.communication_disabled_until = Some(timestamp.into());
425        self
426    }
427
428    /// Build the ModifyMember payload.
429    pub fn build(self) -> ModifyMember<'a> {
430        self.params
431    }
432}
433
434// ============================================================================
435// Start Thread Builder
436// ============================================================================
437
438/// Payload for starting a thread.
439#[derive(Debug, Clone, serde::Serialize, Default)]
440pub struct StartThread<'a> {
441    pub name: TitanString<'a>,
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub auto_archive_duration: Option<u32>,
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub type_: Option<u8>, // For Start Thread without Message
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub invitable: Option<bool>,
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub rate_limit_per_user: Option<u32>,
450}
451
452/// Builder for starting a Thread.
453#[derive(Debug, Clone)]
454pub struct StartThreadBuilder<'a> {
455    params: StartThread<'a>,
456}
457
458impl<'a> StartThreadBuilder<'a> {
459    /// Create a new StartThreadBuilder.
460    pub fn new(name: impl Into<TitanString<'a>>) -> Self {
461        Self {
462            params: StartThread {
463                name: name.into(),
464                ..Default::default()
465            },
466        }
467    }
468
469    /// Set auto archive duration (60, 1440, 4320, 10080).
470    pub fn auto_archive_duration(mut self, duration: u32) -> Self {
471        self.params.auto_archive_duration = Some(duration);
472        self
473    }
474
475    /// Set thread type (for standalone threads).
476    pub fn kind(mut self, kind: u8) -> Self {
477        self.params.type_ = Some(kind);
478        self
479    }
480
481    /// Set invitable (private threads).
482    pub fn invitable(mut self, invitable: bool) -> Self {
483        self.params.invitable = Some(invitable);
484        self
485    }
486
487    /// Set rate limit per user.
488    pub fn rate_limit_per_user(mut self, limit: u32) -> Self {
489        self.params.rate_limit_per_user = Some(limit);
490        self
491    }
492
493    /// Build the StartThread payload.
494    pub fn build(self) -> StartThread<'a> {
495        self.params
496    }
497}
498
499// ============================================================================
500// Component Builders
501// ============================================================================
502
503/// Builder for creating a Button.
504#[derive(Debug, Clone)]
505pub struct ButtonBuilder<'a> {
506    component: crate::Component<'a>,
507}
508
509impl<'a> ButtonBuilder<'a> {
510    /// Create a new ButtonBuilder.
511    #[inline]
512    pub fn new() -> Self {
513        Self {
514            component: crate::Component::Button(crate::component::Button {
515                style: crate::component::ButtonStyle::Primary, // Default
516                label: None,
517                emoji: None,
518                custom_id: None,
519                url: None,
520                disabled: false,
521                component_type: crate::component::ComponentType::Button,
522            }),
523        }
524    }
525
526    /// Set style.
527    pub fn style(mut self, style: crate::component::ButtonStyle) -> Self {
528        if let crate::Component::Button(b) = &mut self.component {
529            b.style = style;
530        }
531        self
532    }
533
534    /// Set label.
535    pub fn label(mut self, label: impl Into<TitanString<'a>>) -> Self {
536        if let crate::Component::Button(b) = &mut self.component {
537            b.label = Some(label.into());
538        }
539        self
540    }
541
542    /// Set emoji.
543    pub fn emoji(mut self, emoji: impl Into<crate::reaction::ReactionEmoji<'a>>) -> Self {
544        if let crate::Component::Button(b) = &mut self.component {
545            b.emoji = Some(emoji.into());
546        }
547        self
548    }
549
550    /// Set custom ID.
551    pub fn custom_id(mut self, id: impl Into<TitanString<'a>>) -> Self {
552        if let crate::Component::Button(b) = &mut self.component {
553            b.custom_id = Some(id.into());
554        }
555        self
556    }
557
558    /// Set URL.
559    pub fn url(mut self, url: impl Into<TitanString<'a>>) -> Self {
560        if let crate::Component::Button(b) = &mut self.component {
561            b.url = Some(url.into());
562        }
563        self
564    }
565
566    /// Set disabled.
567    pub fn disabled(mut self, disabled: bool) -> Self {
568        if let crate::Component::Button(b) = &mut self.component {
569            b.disabled = disabled;
570        }
571        self
572    }
573
574    /// Build the Component.
575    pub fn build(self) -> crate::Component<'a> {
576        self.component
577    }
578}
579
580/// Builder for creating a Select Menu.
581#[derive(Debug, Clone)]
582pub struct SelectMenuBuilder<'a> {
583    component: crate::Component<'a>,
584}
585
586impl<'a> SelectMenuBuilder<'a> {
587    /// Create a new SelectMenuBuilder.
588    #[inline]
589    pub fn new(custom_id: impl Into<TitanString<'a>>) -> Self {
590        Self {
591            component: crate::Component::SelectMenu(crate::component::SelectMenu {
592                custom_id: custom_id.into(),
593                options: Vec::with_capacity(25), // Discord max options
594                placeholder: None,
595                min_values: None,
596                max_values: None,
597                disabled: false,
598                component_type: crate::component::ComponentType::StringSelect, // Default
599            }),
600        }
601    }
602
603    /// Add an option.
604    pub fn option(
605        mut self,
606        label: impl Into<TitanString<'a>>,
607        value: impl Into<TitanString<'a>>,
608    ) -> Self {
609        if let crate::Component::SelectMenu(s) = &mut self.component {
610            s.options.push(crate::component::SelectOption {
611                label: label.into(),
612                value: value.into(),
613                description: None,
614                emoji: None,
615                default: false,
616            });
617        }
618        self
619    }
620
621    /// Set placeholder.
622    pub fn placeholder(mut self, placeholder: impl Into<TitanString<'a>>) -> Self {
623        if let crate::Component::SelectMenu(s) = &mut self.component {
624            s.placeholder = Some(placeholder.into());
625        }
626        self
627    }
628
629    /// Set min values.
630    pub fn min_values(mut self, min: u8) -> Self {
631        if let crate::Component::SelectMenu(s) = &mut self.component {
632            s.min_values = Some(min);
633        }
634        self
635    }
636
637    /// Set max values.
638    pub fn max_values(mut self, max: u8) -> Self {
639        if let crate::Component::SelectMenu(s) = &mut self.component {
640            s.max_values = Some(max);
641        }
642        self
643    }
644
645    /// Set disabled.
646    pub fn disabled(mut self, disabled: bool) -> Self {
647        if let crate::Component::SelectMenu(s) = &mut self.component {
648            s.disabled = disabled;
649        }
650        self
651    }
652
653    /// Build the Component.
654    pub fn build(self) -> crate::Component<'a> {
655        self.component
656    }
657}
658
659/// Builder for creating an Action Row.
660#[derive(Debug, Clone)]
661pub struct ActionRowBuilder<'a> {
662    component: crate::Component<'a>,
663}
664
665impl<'a> ActionRowBuilder<'a> {
666    pub fn new() -> Self {
667        Self {
668            component: crate::Component::ActionRow(crate::component::ActionRow {
669                components: Vec::with_capacity(5), // Discord max components per row
670                component_type: crate::component::ComponentType::ActionRow,
671            }),
672        }
673    }
674
675    pub fn add_button(mut self, button: ButtonBuilder<'a>) -> Self {
676        if let crate::Component::ActionRow(r) = &mut self.component {
677            r.components.push(button.build());
678        }
679        self
680    }
681
682    pub fn add_select_menu(mut self, menu: SelectMenuBuilder<'a>) -> Self {
683        if let crate::Component::ActionRow(r) = &mut self.component {
684            r.components.push(menu.build());
685        }
686        self
687    }
688
689    pub fn build(self) -> crate::Component<'a> {
690        self.component
691    }
692
693    pub fn input(mut self, input: TextInputBuilder<'a>) -> Self {
694        if let crate::Component::ActionRow(r) = &mut self.component {
695            r.components.push(input.build());
696        }
697        self
698    }
699}
700
701/// Builder for creating a Modal.
702#[derive(Debug, Clone)]
703pub struct ModalBuilder<'a> {
704    custom_id: TitanString<'a>,
705    title: TitanString<'a>,
706    components: Vec<crate::Component<'a>>,
707}
708
709impl<'a> ModalBuilder<'a> {
710    pub fn new(custom_id: impl Into<TitanString<'a>>, title: impl Into<TitanString<'a>>) -> Self {
711        Self {
712            custom_id: custom_id.into(),
713            title: title.into(),
714            components: Vec::new(),
715        }
716    }
717
718    pub fn row(mut self, row: ActionRowBuilder<'a>) -> Self {
719        self.components.push(row.build());
720        self
721    }
722
723    pub fn build(self) -> crate::interaction::Modal<'a> {
724        crate::interaction::Modal {
725            custom_id: self.custom_id,
726            title: self.title,
727            components: self.components,
728        }
729    }
730}
731
732/// Builder for creating a Text Input.
733#[derive(Debug, Clone)]
734pub struct TextInputBuilder<'a> {
735    component: crate::Component<'a>,
736}
737
738impl<'a> TextInputBuilder<'a> {
739    pub fn new(
740        custom_id: impl Into<TitanString<'a>>,
741        style: crate::component::TextInputStyle,
742        label: impl Into<TitanString<'a>>,
743    ) -> Self {
744        Self {
745            component: crate::Component::TextInput(crate::component::TextInput {
746                component_type: crate::component::ComponentType::TextInput,
747                custom_id: custom_id.into(),
748                style,
749                label: label.into(),
750                min_length: None,
751                max_length: None,
752                required: None,
753                value: None,
754                placeholder: None,
755            }),
756        }
757    }
758
759    pub fn min_length(mut self, min: u16) -> Self {
760        if let crate::Component::TextInput(t) = &mut self.component {
761            t.min_length = Some(min);
762        }
763        self
764    }
765
766    pub fn max_length(mut self, max: u16) -> Self {
767        if let crate::Component::TextInput(t) = &mut self.component {
768            t.max_length = Some(max);
769        }
770        self
771    }
772
773    pub fn required(mut self, required: bool) -> Self {
774        if let crate::Component::TextInput(t) = &mut self.component {
775            t.required = Some(required);
776        }
777        self
778    }
779
780    pub fn value(mut self, value: impl Into<TitanString<'a>>) -> Self {
781        if let crate::Component::TextInput(t) = &mut self.component {
782            t.value = Some(value.into());
783        }
784        self
785    }
786
787    pub fn placeholder(mut self, placeholder: impl Into<TitanString<'a>>) -> Self {
788        if let crate::Component::TextInput(t) = &mut self.component {
789            t.placeholder = Some(placeholder.into());
790        }
791        self
792    }
793
794    pub fn build(self) -> crate::Component<'a> {
795        self.component
796    }
797}
798
799/// Builder for Interaction Response.
800#[derive(Debug, Clone)]
801pub struct InteractionResponseBuilder<'a> {
802    response: crate::interaction::InteractionResponse<'a>,
803}
804
805impl<'a> Default for InteractionResponseBuilder<'a> {
806    fn default() -> Self {
807        Self::new()
808    }
809}
810
811impl<'a> InteractionResponseBuilder<'a> {
812    /// Create a new builder.
813    pub fn new() -> Self {
814        Self {
815            response: crate::interaction::InteractionResponse {
816                response_type:
817                    crate::interaction::InteractionCallbackType::ChannelMessageWithSource,
818                data: Some(Default::default()),
819            },
820        }
821    }
822
823    pub fn kind(mut self, kind: crate::interaction::InteractionCallbackType) -> Self {
824        self.response.response_type = kind;
825        self
826    }
827
828    pub fn content(mut self, content: impl Into<TitanString<'a>>) -> Self {
829        if let Some(data) = &mut self.response.data {
830            data.content = Some(content.into());
831        }
832        self
833    }
834
835    pub fn embed(mut self, embed: impl Into<crate::Embed<'a>>) -> Self {
836        if self.response.data.is_none() {
837            self.response.data = Some(Default::default());
838        }
839        if let Some(data) = &mut self.response.data {
840            data.embeds.push(embed.into());
841        }
842        self
843    }
844
845    pub fn build(self) -> crate::interaction::InteractionResponse<'a> {
846        self.response
847    }
848
849    pub fn modal(mut self, modal: crate::interaction::Modal<'a>) -> Self {
850        self.response.data = Some(crate::interaction::InteractionCallbackData {
851            custom_id: Some(modal.custom_id),
852            title: Some(modal.title),
853            components: modal.components,
854            ..Default::default()
855        });
856        self
857    }
858}
859
860// ============================================================================
861// Create Channel Builder
862// ============================================================================
863
864/// Payload for creating a channel.
865/// Payload for creating a channel.
866#[derive(Debug, Clone, serde::Serialize, Default)]
867pub struct CreateChannel<'a> {
868    pub name: TitanString<'a>,
869    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
870    pub kind: Option<u8>,
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub topic: Option<TitanString<'a>>,
873    #[serde(skip_serializing_if = "Option::is_none")]
874    pub bitrate: Option<u32>,
875    #[serde(skip_serializing_if = "Option::is_none")]
876    pub user_limit: Option<u32>,
877    #[serde(skip_serializing_if = "Option::is_none")]
878    pub rate_limit_per_user: Option<u32>,
879    #[serde(skip_serializing_if = "Option::is_none")]
880    pub position: Option<u32>,
881    #[serde(skip_serializing_if = "Option::is_none")]
882    pub permission_overwrites: Option<Vec<crate::json::Value>>,
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub parent_id: Option<crate::Snowflake>,
885    #[serde(skip_serializing_if = "Option::is_none")]
886    pub nsfw: Option<bool>,
887}
888
889/// Builder for creating a Channel.
890#[derive(Debug, Clone, Default)]
891pub struct CreateChannelBuilder<'a> {
892    params: CreateChannel<'a>,
893}
894
895impl<'a> CreateChannelBuilder<'a> {
896    pub fn new(name: impl Into<TitanString<'a>>) -> Self {
897        let mut builder = Self::default();
898        builder.params.name = name.into();
899        builder
900    }
901
902    pub fn kind(mut self, kind: u8) -> Self {
903        self.params.kind = Some(kind);
904        self
905    }
906
907    pub fn topic(mut self, topic: impl Into<TitanString<'a>>) -> Self {
908        self.params.topic = Some(topic.into());
909        self
910    }
911
912    pub fn build(self) -> CreateChannel<'a> {
913        self.params
914    }
915}
916
917// ============================================================================
918// Create Role Builder
919// ============================================================================
920
921/// Payload for creating a role.
922/// Payload for creating a role.
923#[derive(Debug, Clone, serde::Serialize, Default)]
924pub struct CreateRole<'a> {
925    #[serde(skip_serializing_if = "Option::is_none")]
926    pub name: Option<TitanString<'a>>,
927    #[serde(skip_serializing_if = "Option::is_none")]
928    pub permissions: Option<String>,
929    #[serde(skip_serializing_if = "Option::is_none")]
930    pub color: Option<u32>,
931    #[serde(skip_serializing_if = "Option::is_none")]
932    pub hoist: Option<bool>,
933    #[serde(skip_serializing_if = "Option::is_none")]
934    pub icon: Option<TitanString<'a>>,
935    #[serde(skip_serializing_if = "Option::is_none")]
936    pub unicode_emoji: Option<TitanString<'a>>,
937    #[serde(skip_serializing_if = "Option::is_none")]
938    pub mentionable: Option<bool>,
939}
940
941/// Builder for creating a Role.
942#[derive(Debug, Clone, Default)]
943pub struct CreateRoleBuilder<'a> {
944    params: CreateRole<'a>,
945}
946
947impl<'a> CreateRoleBuilder<'a> {
948    pub fn new() -> Self {
949        Self::default()
950    }
951
952    pub fn name(mut self, name: impl Into<TitanString<'a>>) -> Self {
953        self.params.name = Some(name.into());
954        self
955    }
956
957    pub fn color(mut self, color: u32) -> Self {
958        self.params.color = Some(color);
959        self
960    }
961
962    pub fn hoist(mut self, hoist: bool) -> Self {
963        self.params.hoist = Some(hoist);
964        self
965    }
966
967    pub fn icon(mut self, icon: impl Into<TitanString<'a>>) -> Self {
968        self.params.icon = Some(icon.into());
969        self
970    }
971
972    pub fn unicode_emoji(mut self, emoji: impl Into<TitanString<'a>>) -> Self {
973        self.params.unicode_emoji = Some(emoji.into());
974        self
975    }
976
977    pub fn mentionable(mut self, mentionable: bool) -> Self {
978        self.params.mentionable = Some(mentionable);
979        self
980    }
981
982    pub fn build(self) -> CreateRole<'a> {
983        self.params
984    }
985}
986
987// ============================================================================
988// Command Builder (Slash Commands)
989// ============================================================================
990
991/// Builder for creating an Application Command (Slash Command).
992#[derive(Debug, Clone, serde::Serialize)]
993pub struct CommandBuilder<'a> {
994    pub name: TitanString<'a>,
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub name_localizations: Option<std::collections::HashMap<String, String>>,
997    #[serde(skip_serializing_if = "Option::is_none")]
998    pub description: Option<TitanString<'a>>,
999    #[serde(skip_serializing_if = "Option::is_none")]
1000    pub description_localizations: Option<std::collections::HashMap<String, String>>,
1001    #[serde(skip_serializing_if = "Option::is_none")]
1002    pub default_member_permissions: Option<TitanString<'a>>,
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    pub dm_permission: Option<bool>,
1005    #[serde(default)]
1006    #[serde(rename = "type")]
1007    pub kind: Option<crate::command::CommandType>,
1008    #[serde(skip_serializing_if = "Option::is_none")]
1009    pub nsfw: Option<bool>,
1010}
1011
1012impl<'a> CommandBuilder<'a> {
1013    pub fn new(name: impl Into<TitanString<'a>>, description: impl Into<TitanString<'a>>) -> Self {
1014        Self {
1015            name: name.into(),
1016            description: Some(description.into()),
1017            name_localizations: None,
1018            description_localizations: None,
1019            default_member_permissions: None,
1020            dm_permission: None,
1021            kind: Some(crate::command::CommandType::ChatInput),
1022            nsfw: None,
1023        }
1024    }
1025
1026    pub fn build(self) -> Self {
1027        self
1028    }
1029}
1030
1031// ============================================================================
1032// AutoMod Rule Builder
1033// ============================================================================
1034
1035#[derive(Debug, Clone, serde::Serialize, Default)]
1036pub struct AutoModRuleBuilder {
1037    pub name: String,
1038}
1039
1040// Duplicates removed
1041
1042// ============================================================================
1043// Missing Variants Placeholders
1044// ============================================================================
1045
1046/// Payload for creating a scheduled event.
1047#[derive(Debug, Clone, serde::Serialize, Default)]
1048pub struct CreateScheduledEvent<'a> {
1049    pub name: TitanString<'a>,
1050    pub privacy_level: crate::scheduled::ScheduledEventPrivacyLevel,
1051    pub scheduled_start_time: TitanString<'a>,
1052    #[serde(skip_serializing_if = "Option::is_none")]
1053    pub scheduled_end_time: Option<TitanString<'a>>,
1054    #[serde(skip_serializing_if = "Option::is_none")]
1055    pub description: Option<TitanString<'a>>,
1056    pub entity_type: crate::scheduled::ScheduledEventEntityType,
1057    #[serde(skip_serializing_if = "Option::is_none")]
1058    pub channel_id: Option<crate::Snowflake>,
1059    #[serde(skip_serializing_if = "Option::is_none")]
1060    pub entity_metadata: Option<crate::scheduled::ScheduledEventEntityMetadata<'a>>,
1061    #[serde(skip_serializing_if = "Option::is_none")]
1062    pub image: Option<TitanString<'a>>, // Base64
1063}
1064
1065/// Builder for creating a Scheduled Event.
1066#[derive(Debug, Clone)]
1067pub struct ScheduledEventBuilder<'a> {
1068    params: CreateScheduledEvent<'a>,
1069}
1070
1071impl<'a> ScheduledEventBuilder<'a> {
1072    /// Create a new ScheduledEventBuilder.
1073    pub fn new(
1074        name: impl Into<TitanString<'a>>,
1075        start_time: impl Into<TitanString<'a>>,
1076        entity_type: crate::scheduled::ScheduledEventEntityType,
1077    ) -> Self {
1078        Self {
1079            params: CreateScheduledEvent {
1080                name: name.into(),
1081                scheduled_start_time: start_time.into(),
1082                entity_type,
1083                privacy_level: crate::scheduled::ScheduledEventPrivacyLevel::GuildOnly,
1084                ..Default::default()
1085            },
1086        }
1087    }
1088
1089    /// Set description.
1090    #[inline]
1091    pub fn description(mut self, description: impl Into<TitanString<'a>>) -> Self {
1092        self.params.description = Some(description.into());
1093        self
1094    }
1095
1096    /// Set end time.
1097    #[inline]
1098    pub fn end_time(mut self, time: impl Into<TitanString<'a>>) -> Self {
1099        self.params.scheduled_end_time = Some(time.into());
1100        self
1101    }
1102
1103    /// Set channel ID (required for Stage/Voice events).
1104    #[inline]
1105    pub fn channel_id(mut self, id: impl Into<crate::Snowflake>) -> Self {
1106        self.params.channel_id = Some(id.into());
1107        self
1108    }
1109
1110    /// Set location (required for External events).
1111    #[inline]
1112    pub fn location(mut self, location: impl Into<TitanString<'a>>) -> Self {
1113        self.params.entity_metadata = Some(crate::scheduled::ScheduledEventEntityMetadata {
1114            location: Some(location.into()),
1115        });
1116        self
1117    }
1118
1119    /// Set cover image (base64).
1120    #[inline]
1121    pub fn image(mut self, image: impl Into<TitanString<'a>>) -> Self {
1122        self.params.image = Some(image.into());
1123        self
1124    }
1125
1126    /// Build the payload.
1127    #[inline]
1128    pub fn build(self) -> CreateScheduledEvent<'a> {
1129        self.params
1130    }
1131}
1132
1133/// Builder for creating a Poll.
1134#[derive(Debug, Clone)]
1135pub struct PollBuilder<'a> {
1136    poll: crate::poll::Poll<'a>,
1137}
1138
1139impl<'a> PollBuilder<'a> {
1140    /// Create a new PollBuilder.
1141    pub fn new(question: impl Into<TitanString<'a>>) -> Self {
1142        Self {
1143            poll: crate::poll::Poll {
1144                question: crate::poll::PollMedia {
1145                    text: Some(question.into()),
1146                    emoji: None,
1147                },
1148                answers: Vec::new(),
1149                expiry: None,
1150                allow_multiselect: false,
1151                layout_type: None,
1152                results: None,
1153            },
1154        }
1155    }
1156
1157    /// Add an answer.
1158    pub fn answer(mut self, answer: impl Into<crate::poll::PollAnswer<'a>>) -> Self {
1159        self.poll.answers.push(answer.into());
1160        self
1161    }
1162
1163    /// Set expiry.
1164    pub fn expiry(mut self, expiry: impl Into<TitanString<'a>>) -> Self {
1165        self.poll.expiry = Some(expiry.into());
1166        self
1167    }
1168
1169    /// Allow multiselect.
1170    pub fn allow_multiselect(mut self, allow: bool) -> Self {
1171        self.poll.allow_multiselect = allow;
1172        self
1173    }
1174
1175    /// Build the Poll.
1176    pub fn build(self) -> crate::poll::Poll<'a> {
1177        self.poll
1178    }
1179}
1180
1181/// Payload for executing a webhook.
1182#[derive(Debug, Clone, serde::Serialize, Default)]
1183pub struct ExecuteWebhook {
1184    #[serde(skip_serializing_if = "Option::is_none")]
1185    pub content: Option<String>,
1186    #[serde(skip_serializing_if = "Option::is_none")]
1187    pub username: Option<String>,
1188    #[serde(skip_serializing_if = "Option::is_none")]
1189    pub avatar_url: Option<String>,
1190    #[serde(skip_serializing_if = "Option::is_none")]
1191    pub tts: Option<bool>,
1192    #[serde(skip_serializing_if = "Vec::is_empty")]
1193    pub embeds: Vec<crate::Embed<'static>>,
1194    // Note: files and components omitted for brevity in this iteration, but can be added
1195}
1196
1197/// Builder for executing a Webhook.
1198#[derive(Debug, Clone, Default)]
1199pub struct WebhookExecuteBuilder<'a> {
1200    params: ExecuteWebhook,
1201    _phantom: std::marker::PhantomData<&'a ()>,
1202}
1203
1204impl<'a> WebhookExecuteBuilder<'a> {
1205    /// Create a new WebhookExecuteBuilder.
1206    pub fn new() -> Self {
1207        Self::default()
1208    }
1209
1210    /// Set content.
1211    #[inline]
1212    pub fn content(mut self, content: impl Into<String>) -> Self {
1213        self.params.content = Some(content.into());
1214        self
1215    }
1216
1217    /// Set username override.
1218    #[inline]
1219    pub fn username(mut self, username: impl Into<String>) -> Self {
1220        self.params.username = Some(username.into());
1221        self
1222    }
1223
1224    /// Set avatar URL override.
1225    #[inline]
1226    pub fn avatar_url(mut self, url: impl Into<String>) -> Self {
1227        self.params.avatar_url = Some(url.into());
1228        self
1229    }
1230
1231    /// Set TTS.
1232    #[inline]
1233    pub fn tts(mut self, tts: bool) -> Self {
1234        self.params.tts = Some(tts);
1235        self
1236    }
1237
1238    /// Add an embed.
1239    #[inline]
1240    pub fn embed(mut self, embed: impl Into<crate::Embed<'static>>) -> Self {
1241        self.params.embeds.push(embed.into());
1242        self
1243    }
1244
1245    /// Add multiple embeds.
1246    #[inline]
1247    pub fn embeds(mut self, embeds: Vec<crate::Embed<'static>>) -> Self {
1248        self.params.embeds.extend(embeds);
1249        self
1250    }
1251
1252    /// Build the payload.
1253    #[inline]
1254    pub fn build(self) -> ExecuteWebhook {
1255        self.params
1256    }
1257}
1258
1259// Add missing Default implementations and inlines to other builders where applicable
1260impl<'a> Default for ButtonBuilder<'a> {
1261    fn default() -> Self {
1262        Self::new()
1263    }
1264}
1265
1266impl<'a> Default for ActionRowBuilder<'a> {
1267    fn default() -> Self {
1268        Self::new()
1269    }
1270}
1271
1272impl<'a> Default for SelectMenuBuilder<'a> {
1273    fn default() -> Self {
1274        Self::new("default_select") // Fallback, though usually ID is required
1275    }
1276}
1277
1278#[cfg(test)]
1279mod modify_tests {
1280    use super::*;
1281    #[test]
1282    fn test_modify_guild_builder() {
1283        let payload = ModifyGuildBuilder::new()
1284            .name("New Guild Name")
1285            .region("us-west")
1286            .verification_level(1)
1287            .build();
1288
1289        assert_eq!(
1290            payload.name,
1291            Some(TitanString::from("New Guild Name".to_string()))
1292        );
1293        assert_eq!(
1294            payload.region,
1295            Some(TitanString::from("us-west".to_string()))
1296        );
1297        assert_eq!(payload.verification_level, Some(1));
1298    }
1299
1300    #[test]
1301    fn test_modify_member_builder() {
1302        let payload = ModifyMemberBuilder::new()
1303            .nick("New Nick")
1304            .mute(true)
1305            .deaf(false)
1306            .build();
1307
1308        assert_eq!(
1309            payload.nick,
1310            Some(TitanString::from("New Nick".to_string()))
1311        );
1312        assert_eq!(payload.mute, Some(true));
1313        assert_eq!(payload.deaf, Some(false));
1314    }
1315
1316    #[test]
1317    fn test_start_thread_builder() {
1318        let payload = StartThreadBuilder::new("Thread Name")
1319            .auto_archive_duration(60)
1320            .kind(11) // Public Thread
1321            .build();
1322
1323        assert_eq!(payload.name, "Thread Name".to_string());
1324        assert_eq!(payload.auto_archive_duration, Some(60));
1325        assert_eq!(payload.type_, Some(11));
1326    }
1327}
1328
1329// ============================================================================
1330// Create Invite Builder
1331// ============================================================================
1332
1333/// Payload for creating an invite.
1334#[derive(Debug, Clone, serde::Serialize, Default)]
1335pub struct CreateInvite {
1336    #[serde(skip_serializing_if = "Option::is_none")]
1337    pub max_age: Option<u32>,
1338    #[serde(skip_serializing_if = "Option::is_none")]
1339    pub max_uses: Option<u32>,
1340    #[serde(skip_serializing_if = "Option::is_none")]
1341    pub temporary: Option<bool>,
1342    #[serde(skip_serializing_if = "Option::is_none")]
1343    pub unique: Option<bool>,
1344    #[serde(skip_serializing_if = "Option::is_none")]
1345    pub target_type: Option<u8>,
1346    #[serde(skip_serializing_if = "Option::is_none")]
1347    pub target_user_id: Option<crate::Snowflake>,
1348    #[serde(skip_serializing_if = "Option::is_none")]
1349    pub target_application_id: Option<crate::Snowflake>,
1350}
1351
1352/// Builder for creating an Invite.
1353#[derive(Debug, Clone, Default)]
1354pub struct CreateInviteBuilder {
1355    params: CreateInvite,
1356}
1357
1358impl CreateInviteBuilder {
1359    /// Create a new CreateInviteBuilder.
1360    pub fn new() -> Self {
1361        Self::default()
1362    }
1363
1364    /// Set max age in seconds (0 = never expire).
1365    pub fn max_age(mut self, seconds: u32) -> Self {
1366        self.params.max_age = Some(seconds);
1367        self
1368    }
1369
1370    /// Set max uses (0 = unlimited).
1371    pub fn max_uses(mut self, uses: u32) -> Self {
1372        self.params.max_uses = Some(uses);
1373        self
1374    }
1375
1376    /// Set temporary (kick after disconnect).
1377    pub fn temporary(mut self, temp: bool) -> Self {
1378        self.params.temporary = Some(temp);
1379        self
1380    }
1381
1382    /// Set unique (don't reuse similar invite).
1383    pub fn unique(mut self, unique: bool) -> Self {
1384        self.params.unique = Some(unique);
1385        self
1386    }
1387
1388    /// Build the CreateInvite payload.
1389    pub fn build(self) -> CreateInvite {
1390        self.params
1391    }
1392}
1393
1394// ============================================================================
1395// Create Emoji Builder
1396// ============================================================================
1397
1398/// Payload for creating an emoji.
1399#[derive(Debug, Clone, serde::Serialize, Default)]
1400pub struct CreateEmoji {
1401    pub name: String,
1402    pub image: String, // Data URI
1403    #[serde(skip_serializing_if = "Vec::is_empty")]
1404    pub roles: Vec<crate::Snowflake>,
1405}
1406
1407/// Builder for creating an Emoji.
1408#[derive(Debug, Clone)]
1409pub struct CreateEmojiBuilder {
1410    params: CreateEmoji,
1411}
1412
1413impl CreateEmojiBuilder {
1414    /// Create a new CreateEmojiBuilder.
1415    /// `image_data` should be a Data URI Scheme string (e.g. "data:image/jpeg;base64,...").
1416    pub fn new(name: impl Into<String>, image_data: impl Into<String>) -> Self {
1417        Self {
1418            params: CreateEmoji {
1419                name: name.into(),
1420                image: image_data.into(),
1421                roles: Vec::new(),
1422            },
1423        }
1424    }
1425
1426    /// Add a role that can use this emoji.
1427    pub fn role(mut self, role_id: impl Into<crate::Snowflake>) -> Self {
1428        self.params.roles.push(role_id.into());
1429        self
1430    }
1431
1432    /// Build the CreateEmoji payload.
1433    pub fn build(self) -> CreateEmoji {
1434        self.params
1435    }
1436}
1437
1438#[cfg(test)]
1439mod final_tests {
1440    use super::*;
1441    use crate::Mention;
1442    use crate::Snowflake; // Ensure Mention trait is in scope
1443
1444    #[test]
1445    fn test_create_invite_builder() {
1446        let payload = CreateInviteBuilder::new()
1447            .max_age(86400)
1448            .max_uses(10)
1449            .unique(true)
1450            .build();
1451
1452        assert_eq!(payload.max_age, Some(86400));
1453        assert_eq!(payload.max_uses, Some(10));
1454        assert_eq!(payload.unique, Some(true));
1455    }
1456
1457    #[test]
1458    fn test_create_emoji_builder() {
1459        let payload = CreateEmojiBuilder::new("test_emoji", "data:image/png;base64,...")
1460            .role(Snowflake(12345))
1461            .build();
1462
1463        assert_eq!(payload.name, "test_emoji");
1464        assert_eq!(payload.roles.len(), 1);
1465    }
1466
1467    #[test]
1468    fn test_mention_trait() {
1469        let user = crate::User {
1470            id: Snowflake(123),
1471            username: "test".to_string().into(),
1472            discriminator: "0000".to_string().into(),
1473            global_name: None,
1474            avatar: None,
1475            bot: false,
1476            system: false,
1477            mfa_enabled: None,
1478            banner: None,
1479            accent_color: None,
1480            locale: None,
1481            verified: None,
1482            email: None,
1483            flags: None,
1484            premium_type: None,
1485            public_flags: None,
1486            avatar_decoration_data: None,
1487        };
1488
1489        // This test relies on Mention being implemented for User
1490        // Since Mention is in lib.rs, we might need to import it properly.
1491        // But here we are in builder.rs, so crate::Mention works if public.
1492        // Wait, builder.rs is a module, lib.rs creates the crate. crate::Mention is correct.
1493
1494        let mention = user.mention();
1495        assert_eq!(mention, "<@123>");
1496    }
1497
1498    #[test]
1499    fn test_add_file_builder() {
1500        let msg = MessageBuilder::new()
1501            .content("With file")
1502            .add_file("test.txt", vec![1, 2, 3])
1503            .build();
1504
1505        assert_eq!(msg.files.len(), 1);
1506        assert_eq!(msg.files[0].filename, "test.txt");
1507        assert_eq!(msg.files[0].data, vec![1, 2, 3]);
1508    }
1509}
1510#[cfg(test)]
1511mod optimization_tests {
1512    use super::*;
1513    use TitanString;
1514
1515    #[test]
1516    fn test_embed_builder_zero_allocation() {
1517        let title = "Static Title";
1518        // Should accept &str directly
1519        let embed = EmbedBuilder::new().title(title).build();
1520        match embed.title {
1521            Some(TitanString::Borrowed(t)) => assert_eq!(t, "Static Title"),
1522            _ => panic!("Expected Borrowed Cow for static string"),
1523        }
1524    }
1525
1526    #[test]
1527    fn test_embed_builder_owned() {
1528        let title = String::from("Owned Title");
1529        let embed = EmbedBuilder::new().title(title).build();
1530        match embed.title {
1531            Some(TitanString::Owned(t)) => assert_eq!(t, "Owned Title"),
1532            _ => panic!("Expected Owned Cow for String"),
1533        }
1534    }
1535
1536    #[test]
1537    fn test_component_builder_zero_allocation() {
1538        let id = "custom_id";
1539        let btn = ButtonBuilder::new().custom_id(id).build();
1540        if let crate::Component::Button(b) = btn {
1541            match b.custom_id {
1542                Some(TitanString::Borrowed(s)) => assert_eq!(s, "custom_id"),
1543                _ => panic!("Expected Borrowed Cow for button custom_id"),
1544            }
1545        }
1546    }
1547}
1548
1549// ============================================================================
1550// Create Sticker Builder
1551// ============================================================================
1552
1553/// Payload for creating a sticker.
1554#[derive(Debug, Clone, serde::Serialize, Default)]
1555pub struct CreateSticker {
1556    pub name: String,
1557    pub description: String,
1558    pub tags: String,
1559}
1560
1561/// Builder for creating a Sticker.
1562#[derive(Debug, Clone)]
1563pub struct CreateStickerBuilder {
1564    params: CreateSticker,
1565}
1566
1567impl CreateStickerBuilder {
1568    /// Create a new CreateStickerBuilder.
1569    pub fn new(
1570        name: impl Into<String>,
1571        description: impl Into<String>,
1572        tags: impl Into<String>,
1573    ) -> Self {
1574        Self {
1575            params: CreateSticker {
1576                name: name.into(),
1577                description: description.into(),
1578                tags: tags.into(),
1579            },
1580        }
1581    }
1582
1583    /// Build the CreateSticker payload.
1584    pub fn build(self) -> CreateSticker {
1585        self.params
1586    }
1587}
1588
1589// ============================================================================
1590// Modify Emoji Builder
1591// ============================================================================
1592
1593/// Payload for modifying an emoji.
1594#[derive(Debug, Clone, serde::Serialize, Default)]
1595pub struct ModifyEmoji {
1596    #[serde(skip_serializing_if = "Option::is_none")]
1597    pub name: Option<String>,
1598    #[serde(skip_serializing_if = "Option::is_none")]
1599    pub roles: Option<Vec<crate::Snowflake>>,
1600}
1601
1602/// Builder for modifying an Emoji.
1603#[derive(Debug, Clone, Default)]
1604pub struct ModifyEmojiBuilder {
1605    params: ModifyEmoji,
1606}
1607
1608impl ModifyEmojiBuilder {
1609    /// Create a new ModifyEmojiBuilder.
1610    pub fn new() -> Self {
1611        Self::default()
1612    }
1613
1614    /// Set name.
1615    pub fn name(mut self, name: impl Into<String>) -> Self {
1616        self.params.name = Some(name.into());
1617        self
1618    }
1619
1620    /// Set roles.
1621    pub fn roles(mut self, roles: Vec<crate::Snowflake>) -> Self {
1622        self.params.roles = Some(roles);
1623        self
1624    }
1625
1626    /// Build the ModifyEmoji payload.
1627    pub fn build(self) -> ModifyEmoji {
1628        self.params
1629    }
1630}
1631
1632// ============================================================================
1633// Stage Instance Builder
1634// ============================================================================
1635
1636/// Payload for creating a stage instance.
1637#[derive(Debug, Clone, serde::Serialize, Default)]
1638pub struct CreateStageInstance {
1639    pub channel_id: crate::Snowflake,
1640    pub topic: String,
1641    #[serde(skip_serializing_if = "Option::is_none")]
1642    pub privacy_level: Option<crate::stage::StagePrivacyLevel>,
1643    #[serde(skip_serializing_if = "Option::is_none")]
1644    pub send_start_notification: Option<bool>,
1645}
1646
1647/// Builder for creating a Stage Instance.
1648#[derive(Debug, Clone)]
1649pub struct StageInstanceBuilder {
1650    params: CreateStageInstance,
1651}
1652
1653impl StageInstanceBuilder {
1654    /// Create a new StageInstanceBuilder.
1655    pub fn new(channel_id: impl Into<crate::Snowflake>, topic: impl Into<String>) -> Self {
1656        Self {
1657            params: CreateStageInstance {
1658                channel_id: channel_id.into(),
1659                topic: topic.into(),
1660                privacy_level: None,
1661                send_start_notification: None,
1662            },
1663        }
1664    }
1665
1666    /// Set privacy level.
1667    #[inline]
1668    pub fn privacy_level(mut self, level: crate::stage::StagePrivacyLevel) -> Self {
1669        self.params.privacy_level = Some(level);
1670        self
1671    }
1672
1673    /// Set send start notification.
1674    #[inline]
1675    pub fn send_start_notification(mut self, send: bool) -> Self {
1676        self.params.send_start_notification = Some(send);
1677        self
1678    }
1679
1680    /// Build the CreateStageInstance payload.
1681    #[inline]
1682    pub fn build(self) -> CreateStageInstance {
1683        self.params
1684    }
1685}
1686
1687// ============================================================================
1688// Guild Builders (Basic)
1689// ============================================================================
1690
1691/// Payload for creating a Guild.
1692#[derive(Debug, Clone, serde::Serialize, Default)]
1693pub struct CreateGuild {
1694    pub name: String,
1695    #[serde(skip_serializing_if = "Option::is_none")]
1696    pub icon: Option<String>,
1697    #[serde(skip_serializing_if = "Option::is_none")]
1698    pub verification_level: Option<u8>,
1699    #[serde(skip_serializing_if = "Option::is_none")]
1700    pub default_message_notifications: Option<u8>,
1701    #[serde(skip_serializing_if = "Option::is_none")]
1702    pub explicit_content_filter: Option<u8>,
1703    #[serde(skip_serializing_if = "Vec::is_empty")]
1704    pub roles: Vec<crate::json::Value>,
1705    #[serde(skip_serializing_if = "Vec::is_empty")]
1706    pub channels: Vec<crate::json::Value>,
1707    #[serde(skip_serializing_if = "Option::is_none")]
1708    pub afk_channel_id: Option<crate::Snowflake>,
1709    #[serde(skip_serializing_if = "Option::is_none")]
1710    pub afk_timeout: Option<u32>,
1711    #[serde(skip_serializing_if = "Option::is_none")]
1712    pub system_channel_id: Option<crate::Snowflake>,
1713    #[serde(skip_serializing_if = "Option::is_none")]
1714    pub system_channel_flags: Option<u64>,
1715}
1716
1717/// Builder for creating a Guild.
1718#[derive(Debug, Clone)]
1719pub struct CreateGuildBuilder {
1720    params: CreateGuild,
1721}
1722
1723impl CreateGuildBuilder {
1724    pub fn new(name: impl Into<String>) -> Self {
1725        Self {
1726            params: CreateGuild {
1727                name: name.into(),
1728                ..Default::default()
1729            },
1730        }
1731    }
1732
1733    #[inline]
1734    pub fn icon(mut self, icon: impl Into<String>) -> Self {
1735        self.params.icon = Some(icon.into());
1736        self
1737    }
1738
1739    pub fn verification_level(mut self, level: u8) -> Self {
1740        self.params.verification_level = Some(level);
1741        self
1742    }
1743
1744    pub fn build(self) -> CreateGuild {
1745        self.params
1746    }
1747}