import type { ActivityAccessoryStatLayout, ActivityAccessorySurface, AddWidgetPreviewContainedLayout, AddWidgetPreviewHeroLayout, AddWidgetPreviewSurface, BottomStatFields, ItemFields, MiniProfileContainedStatLayout, MiniProfileHeroStatLayout, MiniProfileSurface, ObjectiveFields, ProgressFields, StatFields, SubtitleFields, WidgetBottomCollectionLayout, WidgetBottomProgressLayout, WidgetBottomStatsLayout, WidgetBottomSurface, WidgetField, WidgetPayload, WidgetPresentationType, WidgetTopContainedLayout, WidgetTopHeroLayout, WidgetTopSurface, WidgetValueType, } from "./types"; export const createField =

( valueType: WidgetValueType, presentationType: P, value: string, fallback?: WidgetField

, ): WidgetField

=> ({ value_type: valueType, presentation_type: presentationType, value, fallback, }); export const customString =

( presentation: P, value: string, fallback?: WidgetField

, ): WidgetField

=> createField("custom_string", presentation, value, fallback); export const appAsset = (key: string, fallback?: WidgetField<"image">): WidgetField<"image"> => createField("application_asset", "image", key, fallback); export const appLocalizedString =

( presentation: P, value: string, fallback?: WidgetField

, ): WidgetField

=> createField("application_localized_string", presentation, value, fallback); export const dataField =

( presentation: P, value: string, fallback?: WidgetField

, ): WidgetField

=> createField("data", presentation, value, fallback); // export const topHero = ( heroImage: WidgetField<"image">, titleText: WidgetField<"number" | "text">, subtitles?: { subtitle_1?: SubtitleFields; subtitle_2?: SubtitleFields; subtitle_3?: SubtitleFields; }, ): WidgetTopHeroLayout => ({ layout: "widget_top_hero", components: { hero_image: { fields: { image: heroImage } }, title: { fields: { text: titleText } }, ...(subtitles?.subtitle_1 ? { subtitle_1: { fields: subtitles.subtitle_1 } } : {}), ...(subtitles?.subtitle_2 ? { subtitle_2: { fields: subtitles.subtitle_2 } } : {}), ...(subtitles?.subtitle_3 ? { subtitle_3: { fields: subtitles.subtitle_3 } } : {}), }, }); export const topContained = ( containedImage: WidgetField<"image">, titleText: WidgetField<"number" | "text">, subtitles?: { subtitle_1?: SubtitleFields; subtitle_2?: SubtitleFields; subtitle_3?: SubtitleFields; }, ): WidgetTopContainedLayout => ({ layout: "widget_top_contained", components: { contained_image: { fields: { image: containedImage } }, title: { fields: { text: titleText } }, ...(subtitles?.subtitle_1 ? { subtitle_1: { fields: subtitles.subtitle_1 } } : {}), ...(subtitles?.subtitle_2 ? { subtitle_2: { fields: subtitles.subtitle_2 } } : {}), ...(subtitles?.subtitle_3 ? { subtitle_3: { fields: subtitles.subtitle_3 } } : {}), }, }); export const bottomStats = ( stats: [ BottomStatFields, BottomStatFields, BottomStatFields, BottomStatFields, BottomStatFields, BottomStatFields, ], ): WidgetBottomStatsLayout => ({ layout: "widget_bottom_stats", components: { stat_1: { fields: stats[0] }, stat_2: { fields: stats[1] }, stat_3: { fields: stats[2] }, stat_4: { fields: stats[3] }, stat_5: { fields: stats[4] }, stat_6: { fields: stats[5] }, }, }); export const bottomProgress = ( objective: ObjectiveFields, progress: ProgressFields, ): WidgetBottomProgressLayout => ({ layout: "widget_bottom_progress", components: { objective: { fields: objective }, progress: { fields: progress }, }, }); export const bottomCollection = ( items: [ItemFields, ItemFields, ItemFields, ItemFields], ): WidgetBottomCollectionLayout => ({ layout: "widget_bottom_collection", components: { item_1: { fields: items[0] }, item_2: { fields: items[1] }, item_3: { fields: items[2] }, item_4: { fields: items[3] }, }, }); export const miniProfileHeroStat = ( stat: StatFields, heroImage: WidgetField<"image">, ): MiniProfileHeroStatLayout => ({ layout: "mini_profile_hero_stat", components: { stat: { fields: stat }, hero_image: { fields: { image: heroImage } }, }, }); export const miniProfileContainedStat = ( stat: StatFields, containedImage: WidgetField<"image">, ): MiniProfileContainedStatLayout => ({ layout: "mini_profile_contained_stat", components: { stat: { fields: stat }, contained_image: { fields: { image: containedImage } }, }, }); export const activityAccessoryStat = (stat: StatFields): ActivityAccessoryStatLayout => ({ layout: "activity_accessory_stat", components: { stat: { fields: stat }, }, }); export const addWidgetPreviewHero = ( heroImage: WidgetField<"image">, ): AddWidgetPreviewHeroLayout => ({ layout: "add_widget_preview_hero", components: { hero_image: { fields: { image: heroImage } }, }, }); export const addWidgetPreviewContained = ( containedImage: WidgetField<"image">, ): AddWidgetPreviewContainedLayout => ({ layout: "add_widget_preview_contained", components: { contained_image: { fields: { image: containedImage } }, }, }); export class WidgetTopHeroBuilder { private heroImage?: WidgetField<"image">; private title?: WidgetField<"number" | "text">; private subtitles: SubtitleFields[] = []; setHeroImage(image: WidgetField<"image">): this { this.heroImage = image; return this; } setTitle(text: WidgetField<"number" | "text">): this { this.title = text; return this; } addSubtitle( text: WidgetField<"number" | "text">, label?: WidgetField<"number" | "text">, icon?: WidgetField<"image">, ): this { if (this.subtitles.length >= 3) { throw new Error("widget_top_hero allows a maximum of 3 subtitles."); } this.subtitles.push({ text, label, icon }); return this; } build(): WidgetTopHeroLayout { if (!this.heroImage) { throw new Error("Hero Image is required for widget_top_hero."); } if (!this.title) { throw new Error("Title is required for widget_top_hero."); } return { layout: "widget_top_hero", components: { hero_image: { fields: { image: this.heroImage } }, title: { fields: { text: this.title } }, ...(this.subtitles[0] ? { subtitle_1: { fields: this.subtitles[0] } } : {}), ...(this.subtitles[1] ? { subtitle_2: { fields: this.subtitles[1] } } : {}), ...(this.subtitles[2] ? { subtitle_3: { fields: this.subtitles[2] } } : {}), }, }; } } export class WidgetTopContainedBuilder { private containedImage?: WidgetField<"image">; private title?: WidgetField<"number" | "text">; private subtitles: SubtitleFields[] = []; setContainedImage(image: WidgetField<"image">): this { this.containedImage = image; return this; } setTitle(text: WidgetField<"number" | "text">): this { this.title = text; return this; } addSubtitle( text: WidgetField<"number" | "text">, label?: WidgetField<"number" | "text">, icon?: WidgetField<"image">, ): this { if (this.subtitles.length >= 3) { throw new Error("widget_top_contained allows a maximum of 3 subtitles."); } this.subtitles.push({ text, label, icon }); return this; } build(): WidgetTopContainedLayout { if (!this.containedImage) { throw new Error("Contained Image is required for widget_top_contained."); } if (!this.title) { throw new Error("Title is required for widget_top_contained."); } return { layout: "widget_top_contained", components: { contained_image: { fields: { image: this.containedImage } }, title: { fields: { text: this.title } }, ...(this.subtitles[0] ? { subtitle_1: { fields: this.subtitles[0] } } : {}), ...(this.subtitles[1] ? { subtitle_2: { fields: this.subtitles[1] } } : {}), ...(this.subtitles[2] ? { subtitle_3: { fields: this.subtitles[2] } } : {}), }, }; } } export class WidgetBottomStatsBuilder { private stats: BottomStatFields[] = []; addStat( value: WidgetField<"duration" | "number" | "text">, label: WidgetField<"text">, icon?: WidgetField<"image">, ): this { if (this.stats.length >= 6) { throw new Error("widget_bottom_stats allows a maximum of 6 stats."); } this.stats.push({ value, label, icon }); return this; } build(): WidgetBottomStatsLayout { if (this.stats.length !== 6) { throw new Error( `widget_bottom_stats requires exactly 6 stats. Currently has ${this.stats.length}.`, ); } return { layout: "widget_bottom_stats", components: { stat_1: { fields: this.stats[0]! }, stat_2: { fields: this.stats[1]! }, stat_3: { fields: this.stats[2]! }, stat_4: { fields: this.stats[3]! }, stat_5: { fields: this.stats[4]! }, stat_6: { fields: this.stats[5]! }, }, }; } } export class WidgetBottomProgressBuilder { private objective?: ObjectiveFields; private progress?: ProgressFields; setObjective( image: WidgetField<"image">, name: WidgetField<"text">, description?: WidgetField<"text">, ): this { this.objective = { image, name, description }; return this; } setProgress(current: WidgetField<"number">, max?: WidgetField<"number">): this { this.progress = { current, max }; return this; } build(): WidgetBottomProgressLayout { if (!this.objective) { throw new Error("Objective component is required for widget_bottom_progress."); } if (!this.progress) { throw new Error("Progress component is required for widget_bottom_progress."); } return { layout: "widget_bottom_progress", components: { objective: { fields: this.objective }, progress: { fields: this.progress }, }, }; } } export class WidgetBottomCollectionBuilder { private items: ItemFields[] = []; addItem( image: WidgetField<"image">, name: WidgetField<"text">, description?: WidgetField<"text">, ): this { if (this.items.length >= 4) { throw new Error("widget_bottom_collection allows a maximum of 4 items."); } this.items.push({ image, name, description }); return this; } build(): WidgetBottomCollectionLayout { if (this.items.length !== 4) { throw new Error( `widget_bottom_collection requires exactly 4 items. Currently has ${this.items.length}.`, ); } return { layout: "widget_bottom_collection", components: { item_1: { fields: this.items[0]! }, item_2: { fields: this.items[1]! }, item_3: { fields: this.items[2]! }, item_4: { fields: this.items[3]! }, }, }; } } export class MiniProfileHeroStatBuilder { private stat?: StatFields; private heroImage?: WidgetField<"image">; setStat( text: WidgetField<"number" | "text">, label?: WidgetField<"number" | "text">, icon?: WidgetField<"image">, ): this { this.stat = { text, label, icon }; return this; } setHeroImage(image: WidgetField<"image">): this { this.heroImage = image; return this; } build(): MiniProfileHeroStatLayout { if (!this.stat) { throw new Error("Stat is required for mini_profile_hero_stat."); } if (!this.heroImage) { throw new Error("Hero Image is required for mini_profile_hero_stat."); } return { layout: "mini_profile_hero_stat", components: { stat: { fields: this.stat }, hero_image: { fields: { image: this.heroImage } }, }, }; } } export class AddWidgetPreviewHeroBuilder { private heroImage?: WidgetField<"image">; setHeroImage(image: WidgetField<"image">): this { this.heroImage = image; return this; } build(): AddWidgetPreviewHeroLayout { if (!this.heroImage) { throw new Error("Hero Image is required for add_widget_preview_hero."); } return { layout: "add_widget_preview_hero", components: { hero_image: { fields: { image: this.heroImage } }, }, }; } } export class AddWidgetPreviewContainedBuilder { private containedImage?: WidgetField<"image">; setContainedImage(image: WidgetField<"image">): this { this.containedImage = image; return this; } build(): AddWidgetPreviewContainedLayout { if (!this.containedImage) { throw new Error("Contained Image is required for add_widget_preview_contained."); } return { layout: "add_widget_preview_contained", components: { contained_image: { fields: { image: this.containedImage } }, }, }; } } export class MiniProfileContainedStatBuilder { private stat?: StatFields; private containedImage?: WidgetField<"image">; setStat( text: WidgetField<"number" | "text">, label?: WidgetField<"number" | "text">, icon?: WidgetField<"image">, ): this { this.stat = { text, label, icon }; return this; } setContainedImage(image: WidgetField<"image">): this { this.containedImage = image; return this; } build(): MiniProfileContainedStatLayout { if (!this.stat) { throw new Error("Stat is required for mini_profile_contained_stat."); } if (!this.containedImage) { throw new Error("Contained Image is required for mini_profile_contained_stat."); } return { layout: "mini_profile_contained_stat", components: { stat: { fields: this.stat }, contained_image: { fields: { image: this.containedImage } }, }, }; } } export class ActivityAccessoryStatBuilder { private stat?: StatFields; setStat( text: WidgetField<"number" | "text">, label?: WidgetField<"number" | "text">, icon?: WidgetField<"image">, ): this { this.stat = { text, label, icon }; return this; } build(): ActivityAccessoryStatLayout { if (!this.stat) { throw new Error("Stat is required for activity_accessory_stat."); } return { layout: "activity_accessory_stat", components: { stat: { fields: this.stat }, }, }; } } export class WidgetPayloadBuilder { private payload: WidgetPayload; constructor(displayName: string) { this.payload = { display_name: displayName, // @ts-expect-error: defined later surfaces: {}, }; } widgetTop(layout: WidgetTopSurface): this { this.payload.surfaces.widget_top = layout; return this; } widgetBottom(layout: WidgetBottomSurface): this { this.payload.surfaces.widget_bottom = layout; return this; } miniProfile(layout: MiniProfileSurface): this { this.payload.surfaces.mini_profile = layout; return this; } activityAccessory(layout: ActivityAccessorySurface): this { this.payload.surfaces.activity_accessory = layout; return this; } addWidgetPreview(layout: AddWidgetPreviewSurface): this { this.payload.surfaces.add_widget_preview = layout; return this; } build(): WidgetPayload { return this.payload; } }