import { create } from 'zustand' import { scriptsApi } from '@/api' import type { ScriptCategoryResponse, ScriptTemplateListItem, ScriptTemplateDetail, ScriptParametersSchema, } from '@/types' interface ScriptGeneratorState { // Template browsing categories: ScriptCategoryResponse[] templates: ScriptTemplateListItem[] selectedTemplate: ScriptTemplateDetail | null searchQuery: string activeCategoryId: string | null // null = "All" tabFilters: { mine?: boolean; shared?: boolean } // current tab's ownership filter isLoadingTemplates: boolean // drives skeleton in ScriptTemplateList isLoadingDetail: boolean // drives spinner in ScriptConfigurePane // Form paramValues: Record // keyed by ScriptParameter.key; booleans as 'true'/'false' formErrors: Record // keyed by ScriptParameter.key // Output generatedScript: string | null generationId: string | null generationWarnings: string[] isGenerating: boolean generateError: string | null // Actions loadCategories: () => Promise loadTemplates: (filters?: { mine?: boolean; shared?: boolean }) => Promise selectTemplate: (id: string) => Promise setCategory: (id: string | null) => void setSearch: (query: string) => void setParamValue: (key: string, value: string) => void validate: () => boolean generate: (sessionId?: string) => Promise clearOutput: () => void reset: () => void } export const useScriptGeneratorStore = create()((set, get) => ({ // Initial state categories: [], templates: [], selectedTemplate: null, searchQuery: '', activeCategoryId: null, tabFilters: {}, isLoadingTemplates: false, isLoadingDetail: false, paramValues: {}, formErrors: {}, generatedScript: null, generationId: null, generationWarnings: [], isGenerating: false, generateError: null, loadCategories: async () => { try { const categories = await scriptsApi.getCategories() set({ categories }) } catch { // silently ignore — categories remain empty, UI degrades gracefully } }, loadTemplates: async (filters) => { // When filters are provided (e.g. tab change), persist them so that // subsequent setCategory/setSearch calls reuse the same ownership filter. const resolvedFilters = filters !== undefined ? filters : get().tabFilters if (filters !== undefined) set({ tabFilters: filters }) set({ isLoadingTemplates: true }) try { const { activeCategoryId, categories, searchQuery } = get() const category = categories.find(c => c.id === activeCategoryId) const params: { category_slug?: string; search?: string; mine?: boolean; shared?: boolean } = {} if (category) params.category_slug = category.slug if (searchQuery) params.search = searchQuery if (resolvedFilters.mine) params.mine = true if (resolvedFilters.shared) params.shared = true const templates = await scriptsApi.getTemplates(params) set({ templates, isLoadingTemplates: false }) } catch { set({ isLoadingTemplates: false }) } }, selectTemplate: async (id: string) => { set({ isLoadingDetail: true }) try { const detail = await scriptsApi.getTemplateDetail(id) // Populate paramValues from parameter defaults const schema = detail.parameters_schema as ScriptParametersSchema const parameters = schema?.parameters ?? [] const paramValues: Record = {} for (const param of parameters) { const d = param.default if (d === null || d === undefined) paramValues[param.key] = '' else if (typeof d === 'boolean') paramValues[param.key] = d ? 'true' : 'false' else paramValues[param.key] = String(d) } set({ selectedTemplate: detail, paramValues, formErrors: {}, generatedScript: null, generationId: null, generationWarnings: [], generateError: null, isLoadingDetail: false, }) } catch { set({ isLoadingDetail: false }) } }, setCategory: (id: string | null) => { set({ activeCategoryId: id }) get().loadTemplates() }, setSearch: (query: string) => { set({ searchQuery: query }) get().loadTemplates() }, setParamValue: (key: string, value: string) => { set(state => ({ paramValues: { ...state.paramValues, [key]: value }, formErrors: { ...state.formErrors, [key]: '' }, // clear error on change })) }, validate: () => { const { selectedTemplate, paramValues } = get() if (!selectedTemplate) return true const schema = selectedTemplate.parameters_schema as ScriptParametersSchema const parameters = schema?.parameters ?? [] const errors: Record = {} for (const param of parameters) { if (param.required && !paramValues[param.key]) { errors[param.key] = `${param.label} is required` } } set({ formErrors: errors }) return Object.keys(errors).length === 0 }, generate: async (sessionId?: string) => { const { selectedTemplate, paramValues } = get() if (!selectedTemplate) return if (!get().validate()) return set({ isGenerating: true, generateError: null }) try { const response = await scriptsApi.generate({ template_id: selectedTemplate.id, parameters: paramValues, ...(sessionId ? { session_id: sessionId } : {}), }) set({ generatedScript: response.script, generationId: response.id, generationWarnings: response.warnings, isGenerating: false, }) } catch (error: unknown) { const axiosErr = error as { response?: { data?: { detail?: string } } } const message = axiosErr.response?.data?.detail ?? 'Failed to generate script' set({ generateError: message, isGenerating: false }) } }, clearOutput: () => { set({ generatedScript: null, generationId: null, generationWarnings: [], generateError: null, }) }, // Exposed for Phase 3 callers (session execution context). // Does NOT clear selectedTemplate, categories, templates, or browsing state. reset: () => { set({ paramValues: {}, formErrors: {}, generatedScript: null, generationId: null, generationWarnings: [], generateError: null, }) }, }))