import { create } from "zustand"; import type { PlayerSession } from "../app/types"; import { requestOnlineJson } from "../app/onlineAccountClient"; import { DEFAULT_COMPANION_COOLDOWN_POLICY, installCompanionCooldownPolicy, normalizeCompanionCooldownPolicy, type CompanionCooldownPolicy, } from "./companionCooldownPolicy"; interface PublishedCompanionCooldownPolicy { readonly revision: number; readonly updatedAt: number | null; readonly updatedBy?: string | null; readonly config: CompanionCooldownPolicy; } interface CompanionCooldownPolicyState extends PublishedCompanionCooldownPolicy { readonly draft: CompanionCooldownPolicy; readonly loading: boolean; readonly saving: boolean; readonly dirty: boolean; readonly error: string | null; refresh: (session?: PlayerSession | null) => Promise; replaceDraft: (draft: CompanionCooldownPolicy) => void; resetDraft: () => void; publish: (session: PlayerSession | null) => Promise; } const CACHE_KEY = "healer-man.companion-cooldown-policy.v1"; function cached(): PublishedCompanionCooldownPolicy { try { const value = JSON.parse(window.localStorage.getItem(CACHE_KEY) ?? "null") as Partial | null; if (!value || !Number.isInteger(value.revision)) throw new Error("missing"); return { revision: value.revision!, updatedAt: value.updatedAt ?? null, config: normalizeCompanionCooldownPolicy(value.config) }; } catch { return { revision: 0, updatedAt: null, config: DEFAULT_COMPANION_COOLDOWN_POLICY }; } } function cache(payload: PublishedCompanionCooldownPolicy): void { try { window.localStorage.setItem(CACHE_KEY, JSON.stringify(payload)); } catch { /* optional cache */ } } const initial = cached(); installCompanionCooldownPolicy(initial.config); export const useCompanionCooldownPolicyStore = 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/companion-cooldown-policy" : "/api/companion-cooldown-policy", {}, gm ? session?.accessToken : undefined, ); const normalized = { ...payload, config: normalizeCompanionCooldownPolicy(payload.config) }; cache(normalized); installCompanionCooldownPolicy(normalized.config); set({ ...normalized, draft: normalized.config, loading: false, dirty: false }); } catch (error) { set({ loading: false, error: error instanceof Error ? error.message : "Could not load companion cooldown policy." }); } }, replaceDraft: (draft) => set({ draft: normalizeCompanionCooldownPolicy(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/companion-cooldown-policy", { method: "PUT", body: JSON.stringify({ expectedRevision: get().revision, config: get().draft }), }, session.accessToken); const normalized = { ...payload, config: normalizeCompanionCooldownPolicy(payload.config) }; cache(normalized); installCompanionCooldownPolicy(normalized.config); 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 companion cooldown policy." }); return false; } }, }));