export class SalesScenario { options; token = ''; state; handlers = new Map(); constructor(options) { this.options = options; } async start() { const state = await this.request(`/public/scenarios/${encodeURIComponent(this.options.scenarioKey)}/runs`, { method: 'POST', body: JSON.stringify({ public_key: this.options.publicKey, sop_id: this.options.sopId, input: this.options.input || {}, initial_values: this.options.initialValues || {}, external_ref: this.options.externalRef || '' }) }); if (state.session_token) this.token = state.session_token; this.setState(state); this.emit('ready', state); return state; } getState() { if (!this.state) throw new Error('SDK has not started'); return this.state; } getStage() { const stage = this.getState().node.stage; if (!stage) throw new Error('current node is not a script stage'); return stage; } async getCurrentNode() { return this.update(`/public/runs/${this.getState().run_id}/current`); } /** Answer stage questions without advancing. Answer locally on the client and submit the whole stage once: pass every answered script in scriptAnswers together with dimensionSelects and the content form in answers. */ async answer(options = {}) { const state = await this.update(`/public/runs/${this.getState().run_id}/answer`, { method: 'POST', body: JSON.stringify({ node_key: this.getState().node.node_key, script_key: options.scriptKey || '', option_keys: options.optionKeys || [], value: options.value || '', script_answers: options.scriptAnswers || [], dimension_selects: options.dimensionSelects || {}, answers: options.answers || {}, dimension_value_key: options.dimensionValueKey || '', selected: options.selected ?? false }) }); return state; } async submit(values) { const state = await this.update(`/public/runs/${this.getState().run_id}/submit`, { method: 'POST', body: JSON.stringify({ node_key: this.getState().node.node_key, answers: values }) }); this.emit('form_submit', { values, state }); return state; } async next() { return this.update(`/public/runs/${this.getState().run_id}/next`, { method: 'POST' }); } async back() { return this.update(`/public/runs/${this.getState().run_id}/back`, { method: 'POST' }); } async feedback(scriptKey, feedbackType, note = '') { await this.request(`/public/runs/${this.getState().run_id}/feedback`, { method: 'POST', body: JSON.stringify({ node_key: this.getState().node.node_key, script_key: scriptKey, feedback_type: feedbackType, note }) }); return true; } async finish(options = {}) { const state = await this.update(`/public/runs/${this.getState().run_id}/finish`, { method: 'POST', body: JSON.stringify({ result: options.result || '', final_result: options.finalResult ?? null }) }); this.emit('finish', state); return state; } async reset() { return this.update(`/public/runs/${this.getState().run_id}/reset`, { method: 'POST' }); } destroy() { this.handlers.clear(); this.token = ''; this.state = undefined; } on(event, handler) { const handlers = this.handlers.get(event) || new Set(); handlers.add(handler); this.handlers.set(event, handlers); return () => handlers.delete(handler); } async update(path, init = {}) { const state = await this.request(path, init); this.setState(state); return state; } setState(state) { this.state = state; this.emit('node_change', state.node); } emit(event, payload) { this.handlers.get(event)?.forEach(handler => handler(payload)); } async request(path, init) { try { const response = await fetch(`${(this.options.baseURL || '').replace(/\/$/, '')}${path}`, { ...init, headers: { 'Content-Type': 'application/json', ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}) } }); const body = await response.json(); if (!response.ok) throw new Error(body.message || `Request failed: ${response.status}`); return (body.data || body); } catch (error) { this.emit('error', error); throw error; } } } export function create(options) { return new SalesScenario(options); }