import { create } from "zustand"; import { requestOnlineJson } from "./onlineAccountClient"; import type { PlayerSession } from "./types"; import { DEFAULT_DUNGEON_QUEUE_POLICY, normalizeDungeonQueuePolicy, type DungeonQueueMode, type DungeonQueuePolicy, } from "../game/dungeonQueuePolicy"; interface PublishedPolicy { readonly revision: number; readonly updatedAt: number | null; readonly updatedBy?: string | null; readonly config: DungeonQueuePolicy; } interface PolicyState extends PublishedPolicy { readonly draft: DungeonQueuePolicy; readonly loading: boolean; readonly saving: boolean; readonly dirty: boolean; readonly error: string | null; refresh: (session: PlayerSession | null) => Promise; setEnabled: (mode: DungeonQueueMode, dungeonId: string, enabled: boolean) => void; resetDraft: () => void; publish: (session: PlayerSession | null) => Promise; } const CACHE_KEY = "healer-man.dungeon-queue-policy.v1"; function cached(): PublishedPolicy { try { const value = JSON.parse(globalThis.localStorage?.getItem(CACHE_KEY) ?? "null") as PublishedPolicy; if (!Number.isInteger(value.revision)) throw new Error("missing"); return { ...value, config: normalizeDungeonQueuePolicy(value.config) }; } catch { return { revision: 0, updatedAt: null, config: DEFAULT_DUNGEON_QUEUE_POLICY }; } } function cache(value: PublishedPolicy): void { try { globalThis.localStorage?.setItem(CACHE_KEY, JSON.stringify(value)); } catch { /* offline cache is optional */ } } function normalized(value: PublishedPolicy): PublishedPolicy { return { ...value, config: normalizeDungeonQueuePolicy(value.config) }; } /** Fetch for each random roll so a newly published GM setting is used without a reload. */ export async function loadDungeonQueuePolicy(): Promise { try { const value = normalized(await requestOnlineJson("/api/dungeon-queue-policy")); cache(value); useDungeonQueuePolicyStore.setState((state) => state.dirty ? state : { ...value, draft: value.config }); return value.config; } catch { return cached().config; } } const initial = cached(); export const useDungeonQueuePolicyStore = create((set, get) => ({ ...initial, draft: initial.config, loading: false, saving: false, dirty: false, error: null, refresh: async (session) => { set({ loading: true, error: null }); try { const gm = Boolean(session?.accessToken && session.roles?.includes("gm")); const value = normalized(await requestOnlineJson( gm ? "/api/gm/dungeon-queue-policy" : "/api/dungeon-queue-policy", {}, gm ? session?.accessToken : undefined, )); cache(value); set({ ...value, draft: value.config, loading: false, dirty: false }); } catch (error) { set({ loading: false, error: error instanceof Error ? error.message : "Could not load dungeon queue settings." }); } }, setEnabled: (mode, dungeonId, enabled) => set((state) => { const ids = new Set(state.draft.disabled[mode]); if (enabled) ids.delete(dungeonId); else ids.add(dungeonId); return { draft: { schemaVersion: 1, disabled: { ...state.draft.disabled, [mode]: [...ids].sort() } }, dirty: true, error: null, }; }), resetDraft: () => set((state) => ({ draft: state.config, dirty: false, error: null })), publish: async (session) => { if (!session?.accessToken || !session.roles?.includes("gm")) { set({ error: "Game Master access is required." }); return false; } set({ saving: true, error: null }); try { const value = normalized(await requestOnlineJson("/api/gm/dungeon-queue-policy", { method: "PUT", body: JSON.stringify({ expectedRevision: get().revision, config: get().draft }), }, session.accessToken)); cache(value); set({ ...value, draft: value.config, saving: false, dirty: false }); return true; } catch (error) { set({ saving: false, error: error instanceof Error ? error.message : "Could not publish dungeon queue settings." }); return false; } }, }));