import { create } from "zustand"; import type { PlayerSession } from "../app/types"; import { requestOnlineJson } from "../app/onlineAccountClient"; import { DEFAULT_PARTY_BUFF_POLICY, normalizePartyBuffPolicy, type PartyBuffPolicy } from "./partyBuffPolicy"; interface PublishedPolicy { readonly revision: number; readonly updatedAt: number | null; readonly updatedBy?: string | null; readonly config: PartyBuffPolicy; } interface PolicyState extends PublishedPolicy { readonly draft: PartyBuffPolicy; readonly loading: boolean; readonly saving: boolean; readonly dirty: boolean; readonly error: string | null; refresh: (session?: PlayerSession | null) => Promise; replaceDraft: (draft: PartyBuffPolicy) => void; resetDraft: () => void; publish: (session: PlayerSession | null) => Promise; } const CACHE_KEY = "healer-man.party-buff-policy.v1"; function cached(): PublishedPolicy { try { const value = JSON.parse(window.localStorage.getItem(CACHE_KEY) ?? "null") as PublishedPolicy; if (!Number.isInteger(value.revision)) throw new Error("missing"); return { ...value, config: normalizePartyBuffPolicy(value.config) }; } catch { return { revision: 0, updatedAt: null, config: DEFAULT_PARTY_BUFF_POLICY }; } } function cache(payload: PublishedPolicy): void { try { window.localStorage.setItem(CACHE_KEY, JSON.stringify(payload)); } catch { /* optional cache */ } } const initial = cached(); export const usePartyBuffPolicyStore = create((set, get) => ({ ...initial, draft: initial.config, loading: false, saving: false, dirty: false, error: null, refresh: async (session = null) => { set({ loading: true, error: null }); try { const gm = Boolean(session?.accessToken && session.roles?.includes("gm")); const payload = await requestOnlineJson( gm ? "/api/gm/party-buff-policy" : "/api/party-buff-policy", {}, gm ? session?.accessToken : undefined, ); const normalized = { ...payload, config: normalizePartyBuffPolicy(payload.config) }; cache(normalized); set({ ...normalized, draft: normalized.config, loading: false, dirty: false }); } catch (error) { set({ loading: false, error: error instanceof Error ? error.message : "Could not load party buff policy." }); } }, replaceDraft: (draft) => set({ draft: normalizePartyBuffPolicy(draft), 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 payload = await requestOnlineJson("/api/gm/party-buff-policy", { method: "PUT", body: JSON.stringify({ expectedRevision: get().revision, config: get().draft }), }, session.accessToken); const normalized = { ...payload, config: normalizePartyBuffPolicy(payload.config) }; cache(normalized); set({ ...normalized, draft: normalized.config, saving: false, dirty: false }); return true; } catch (error) { set({ saving: false, error: error instanceof Error ? error.message : "Could not publish party buff policy." }); return false; } }, }));