import { create } from 'zustand'; import { api } from '../lib/api.ts'; import type { Message } from './message.ts'; export interface Thread { id: string; server_id: string; parent_channel_id: string; name: string; type: 'thread'; category: string | null; position: number; archived_at: string | null; auto_archive_duration: number; message_count: number; last_message_at: string | null; created_at: string; tag_ids?: string[]; } export interface ForumTag { id: string; tag_id: string; name: string; emoji: string | null; color: string | null; } export interface CreateThreadData { name: string; message_id?: string; tag_ids?: string[]; } interface ThreadState { threadsByParent: Record; tagsByForum: Record; activeThreadId: string | null; messagesByThread: Record; isLoading: boolean; error: string | null; fetchThreads: (parentChannelId: string) => Promise; createThread: (parentChannelId: string, data: CreateThreadData) => Promise; archiveThread: (threadId: string) => Promise; setActiveThread: (id: string | null) => void; fetchThreadMessages: (threadId: string, before?: string) => Promise; sendThreadMessage: (threadId: string, content: string) => Promise; addThreadMessage: (message: Message) => void; fetchForumTags: (forumChannelId: string) => Promise; createForumTag: (forumChannelId: string, data: { name: string; emoji?: string; color?: string }) => Promise; deleteForumTag: (tagId: string) => Promise; } export const useThreadStore = create((set) => ({ threadsByParent: {}, tagsByForum: {}, activeThreadId: null, messagesByThread: {}, isLoading: false, error: null, fetchThreads: async (parentChannelId) => { set({ isLoading: true, error: null }); try { const threads = await api.get(`/channels/${parentChannelId}/threads`); set((state) => ({ threadsByParent: { ...state.threadsByParent, [parentChannelId]: Array.isArray(threads) ? threads : [] }, isLoading: false, })); } catch (error) { set({ isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch threads' }); } }, createThread: async (parentChannelId, data) => { const thread = await api.post(`/channels/${parentChannelId}/threads`, data); set((state) => { const list = state.threadsByParent[parentChannelId] || []; return { threadsByParent: { ...state.threadsByParent, [parentChannelId]: [...list, thread] }, activeThreadId: thread.id, }; }); return thread; }, archiveThread: async (threadId) => { await api.patch(`/threads/${threadId}`, { archived: true }); set((state) => { const next: Record = {}; for (const parentId of Object.keys(state.threadsByParent)) { next[parentId] = state.threadsByParent[parentId].map((t) => t.id === threadId ? { ...t, archived_at: new Date().toISOString() } : t, ); } return { threadsByParent: next }; }); }, setActiveThread: (id) => set({ activeThreadId: id }), fetchThreadMessages: async (threadId, before) => { set({ isLoading: true, error: null }); try { const params = before ? `?before=${encodeURIComponent(before)}` : ''; const raw = await api.get(`/channels/${threadId}/messages${params}`); // ponytail: API returns DESC (newest first), reverse to oldest-first const msgs = (Array.isArray(raw) ? raw : []).reverse(); set((state) => ({ messagesByThread: { ...state.messagesByThread, [threadId]: msgs }, isLoading: false, })); } catch (error) { set({ isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch thread messages' }); } }, sendThreadMessage: async (threadId, content) => { const message = await api.post(`/channels/${threadId}/messages`, { content }); set((state) => { const list = state.messagesByThread[threadId] || []; return { messagesByThread: { ...state.messagesByThread, [threadId]: [...list, message] } }; }); return message; }, addThreadMessage: (message) => set((state) => { const list = state.messagesByThread[message.channel_id] || []; if (list.some((m) => m.id === message.id)) return state; return { messagesByThread: { ...state.messagesByThread, [message.channel_id]: [...list, message] } }; }), fetchForumTags: async (forumChannelId) => { const tags = await api.get(`/channels/${forumChannelId}/forum-tags`); set((state) => ({ tagsByForum: { ...state.tagsByForum, [forumChannelId]: Array.isArray(tags) ? tags : [] }, })); }, createForumTag: async (forumChannelId, data) => { const tag = await api.post(`/channels/${forumChannelId}/forum-tags`, data); set((state) => { const list = state.tagsByForum[forumChannelId] || []; return { tagsByForum: { ...state.tagsByForum, [forumChannelId]: [...list, tag] } }; }); return tag; }, deleteForumTag: async (tagId) => { await api.delete(`/forum-tags/${tagId}`); set((state) => { const next: Record = {}; for (const forumId of Object.keys(state.tagsByForum)) { next[forumId] = state.tagsByForum[forumId].filter((t) => t.id !== tagId && t.tag_id !== tagId); } return { tagsByForum: next }; }); }, }));