import { defineStore } from 'pinia' import { api } from '../lib/api' import type { AdminDashboardResponse, AdminSeasonDetailResponse, ApproveNominationPayload, AdminSeasonListItem, CreateNominationPayload, CreateVotePayload, OverviewResponse, SeasonCategoriesResponse, UpdateSeasonPayload, UpsertCandidatePayload, UpsertCategoryPayload, WinnerArchiveResponse, } from '../types/awards' const fallbackOverview: OverviewResponse = { seasonId: 1, year: 2026, title: 'VTuber Star Awards 2026', showDate: '2026-01-24', currentPhase: 'Community Voting', isCommunityOnly: true, loginProvider: 'Twitch', timeline: [ { key: 'nomination', title: 'Nominierung', startsAt: '2026-05-01', endsAt: '2026-05-31', state: 'done' }, { key: 'voting', title: 'Voting', startsAt: '2026-06-01', endsAt: '2026-06-30', state: 'active' }, { key: 'review', title: 'Auswertung', startsAt: '2026-07-01', endsAt: '2026-07-10', state: 'upcoming' }, { key: 'show', title: 'Award Show', startsAt: '2026-07-20', endsAt: '2026-07-20', state: 'upcoming' }, ], featuredCategories: [ { id: 1, groupName: 'Main Awards', name: 'VTuber des Jahres', description: 'Die groesste Auszeichnung des Jahres.', maxNomineesPerUser: 3 }, { id: 2, groupName: 'Performance', name: 'Bestes Live Event', description: 'Events, Konzerte und Showformate.', maxNomineesPerUser: 3 }, { id: 3, groupName: 'Clips & Highlights', name: 'Clip des Jahres', description: 'Der lustigste oder emotionalste Clip.', maxNomineesPerUser: 3 }, ], winnersPreview: [ { year: 2025, category: 'VTuber des Jahres', winnerName: 'Hoshimi Miyu', winnerSlug: '@hoshimimiyu' }, { year: 2025, category: 'Bestes Live Event', winnerName: 'Kurainu 3D Live', winnerSlug: '@kurainu' }, { year: 2024, category: 'Clip des Jahres', winnerName: 'Pyonkichi Kingdom', winnerSlug: '@pyonkichikingdom' }, ], faq: [ { question: 'Wer kann nominieren und voten?', answer: 'Jede Person mit Twitch Login. Das Konto wird beim ersten Login implizit erstellt.' }, { question: 'Wie werden Gewinner bestimmt?', answer: 'Aktuell rein community-basiert. Eine Mischlogik kann spaeter aktiviert werden.' }, { question: 'Wer verwaltet Kategorien und Unterkategorien?', answer: 'Das Team pflegt diese pro Jahr im Admin-Bereich.' }, ], } const fallbackCategories: SeasonCategoriesResponse = { seasonId: 1, year: 2026, categories: [ { id: 1, name: 'VTuber des Jahres', groupName: 'Main Awards', description: 'Die Hauptkategorie fuer die praegendste Creator-Praesenz des Jahres.', maxNomineesPerUser: 3, candidates: [ { id: 1, displayName: 'Hoshimi Miyu', channelSlug: '@hoshimimiyu', platform: 'Twitch' }, { id: 2, displayName: 'Kurainu', channelSlug: '@kurainu', platform: 'Twitch' }, { id: 3, displayName: 'Shiro Ch.', channelSlug: '@shiroch', platform: 'Twitch' }, ], }, { id: 2, name: 'Bestes Live Event', groupName: 'Performance', description: 'Konzerte, Sonderformate und grosse Community-Shows.', maxNomineesPerUser: 3, candidates: [ { id: 4, displayName: 'Kurainu 3D Live', channelSlug: '@kurainu', platform: 'Twitch' }, { id: 5, displayName: 'Aoi Sakura Showcase', channelSlug: '@aoisakura', platform: 'YouTube' }, ], }, ], } const fallbackArchive: WinnerArchiveResponse = { year: 2025, items: [ { category: 'VTuber des Jahres', winnerName: 'Hoshimi Miyu', winnerSlug: '@hoshimimiyu' }, { category: 'Bestes Live Event', winnerName: 'Kurainu 3D Live', winnerSlug: '@kurainu' }, { category: 'Clip des Jahres', winnerName: 'Pyonkichi Kingdom', winnerSlug: '@pyonkichikingdom' }, ], } const fallbackAdmin: AdminDashboardResponse = { metrics: [ { label: 'Nominierungen', value: 12341, note: '+12.4% vs. gestern' }, { label: 'Votes', value: 587231, note: '+8.7% vs. gestern' }, { label: 'Kategorien', value: 28, note: 'aktiv im Jahr 2026' }, { label: 'Reviews offen', value: 47, note: '14 neu' }, ], activities: [ { label: 'Neue Nominierung in Best New VTuber', age: 'vor 2 Min.' }, { label: 'Clip-Dublette erkannt in Clip des Jahres', age: 'vor 7 Min.' }, { label: 'Alias-Merge fuer Hoshimi Miyu reviewt', age: 'vor 18 Min.' }, ], topCategories: [ { category: 'VTuber des Jahres', votes: 186321 }, { category: 'Bestes Live Event', votes: 132550 }, { category: 'Clip des Jahres', votes: 98210 }, ], riskFlags: [ { id: 1, source: 'vote', type: 'rapid_vote_updates', severity: 'high', status: 'open', summary: 'Mehrere Voting-Aenderungen in kurzer Zeit erkannt.', twitchUserId: 'demo_user', createdFromIp: '127.0.0.1', createdAt: '2026-06-17T08:40:00Z', metadataJson: '{"recentVoteSubmissions":3}', }, ], auditEntries: [ { id: 1, adminTwitchUserId: 'jayuhime_admin', actionType: 'category.update', entityType: 'category', entityId: '1', summary: 'Kategorie VTuber des Jahres wurde aktualisiert.', createdAt: '2026-06-17T08:32:00Z', }, ], } const fallbackAdminSeasons: AdminSeasonListItem[] = [ { id: 1, year: 2026, name: 'VTuber Star Awards 2026', currentPhase: 'Community Voting', isCurrent: true, categoryCount: 4 }, { id: 2, year: 2025, name: 'VTuber Star Awards 2025', currentPhase: 'Archived', isCurrent: false, categoryCount: 3 }, ] const fallbackAdminSeasonDetail: AdminSeasonDetailResponse = { id: 1, year: 2026, name: 'VTuber Star Awards 2026', currentPhase: 'Community Voting', isCurrent: true, categories: [ { id: 1, groupName: 'Main Awards', name: 'VTuber des Jahres', slug: 'vtuber-des-jahres', description: 'Die groesste Auszeichnung des Jahres.', sortOrder: 1, maxNomineesPerUser: 3, candidateCount: 3, }, { id: 2, groupName: 'Performance', name: 'Bestes Live Event', slug: 'bestes-live-event', description: 'Events, Konzerte und 3D-Shows.', sortOrder: 2, maxNomineesPerUser: 3, candidateCount: 2, }, ], candidates: [ { id: 1, categoryId: 1, displayName: 'Hoshimi Miyu', channelSlug: '@hoshimimiyu', platform: 'Twitch' }, { id: 2, categoryId: 1, displayName: 'Kurainu', channelSlug: '@kurainu', platform: 'Twitch' }, ], pendingNominations: [ { id: 1, categoryId: 1, categoryName: 'VTuber des Jahres', submittedByTwitchId: 'demo_user', candidateText: 'Session Nominee', createdAt: '2026-06-17T08:00:00Z', }, ], } const emptyAdmin: AdminDashboardResponse = { metrics: [], activities: [], topCategories: [], riskFlags: [], auditEntries: [], } const emptyAdminSeasons: AdminSeasonListItem[] = [] const emptyAdminSeasonDetail: AdminSeasonDetailResponse = { id: 0, year: 0, name: '', currentPhase: '', isCurrent: false, categories: [], candidates: [], pendingNominations: [], } export const useAwardsStore = defineStore('awards', { state: () => ({ overview: fallbackOverview as OverviewResponse, categories: fallbackCategories as SeasonCategoriesResponse, archive: fallbackArchive as WinnerArchiveResponse, admin: fallbackAdmin as AdminDashboardResponse, adminSeasons: fallbackAdminSeasons as AdminSeasonListItem[], adminSeasonDetail: fallbackAdminSeasonDetail as AdminSeasonDetailResponse, loading: false, apiMode: 'fallback' as 'api' | 'fallback', }), actions: { async loadHomeData() { this.loading = true try { this.overview = await api.getOverview() this.categories = await api.getSeasonCategories(this.overview.year) this.archive = await api.getWinnerArchive(this.overview.winnersPreview[0]?.year ?? this.overview.year - 1) this.apiMode = 'api' } catch { this.apiMode = 'fallback' } finally { this.loading = false } }, async loadArchive(year: number) { try { this.archive = await api.getWinnerArchive(year) this.apiMode = 'api' } catch { this.archive = { ...fallbackArchive, year } } }, async loadAdmin() { try { this.admin = await api.getAdminDashboard() this.adminSeasons = await api.getAdminSeasons() this.adminSeasonDetail = await api.getAdminSeasonDetail(this.adminSeasons[0]?.id ?? 1) this.apiMode = 'api' } catch { this.admin = emptyAdmin this.adminSeasons = emptyAdminSeasons this.adminSeasonDetail = emptyAdminSeasonDetail } }, async loadAdminSeasonDetail(seasonId: number) { try { this.adminSeasonDetail = await api.getAdminSeasonDetail(seasonId) this.apiMode = 'api' } catch { this.adminSeasonDetail = emptyAdminSeasonDetail } }, submitNomination(payload: CreateNominationPayload) { return api.submitNomination(payload) }, submitVote(payload: CreateVotePayload) { return api.submitVote(payload) }, async updateAdminSeason(seasonId: number, payload: UpdateSeasonPayload) { const result = await api.updateAdminSeason(seasonId, payload) await this.loadAdmin() return result }, async createAdminCategory(seasonId: number, payload: UpsertCategoryPayload) { const result = await api.createAdminCategory(seasonId, payload) await this.loadAdminSeasonDetail(seasonId) return result }, async updateAdminCategory(categoryId: number, seasonId: number, payload: UpsertCategoryPayload) { const result = await api.updateAdminCategory(categoryId, payload) await this.loadAdminSeasonDetail(seasonId) return result }, async createAdminCandidate(seasonId: number, payload: UpsertCandidatePayload) { const result = await api.createAdminCandidate(seasonId, payload) await this.loadAdminSeasonDetail(seasonId) return result }, async updateAdminCandidate(candidateId: number, seasonId: number, payload: UpsertCandidatePayload) { const result = await api.updateAdminCandidate(candidateId, payload) await this.loadAdminSeasonDetail(seasonId) return result }, async approveAdminNomination(nominationId: number, seasonId: number, payload: ApproveNominationPayload) { const result = await api.approveAdminNomination(nominationId, payload) await this.loadAdminSeasonDetail(seasonId) await this.loadAdmin() return result }, async rejectAdminNomination(nominationId: number, seasonId: number) { const result = await api.rejectAdminNomination(nominationId) await this.loadAdminSeasonDetail(seasonId) await this.loadAdmin() return result }, async resolveRiskFlag(riskFlagId: number, status = 'resolved') { const result = await api.resolveRiskFlag(riskFlagId, status) await this.loadAdmin() return result }, }, })