/** * Geführtes method_profile für Kombinationsübungen — Felder nach method_archetype. * Unbekannte JSON-Schlüssel bleiben beim Zusammenführen erhalten (Erweiterbarkeit). */ const INT_MAX = 86400 function parseProfileJson(raw) { if (typeof raw !== 'string' || !raw.trim()) return { ok: true, obj: {} } try { const p = JSON.parse(raw) if (!p || typeof p !== 'object' || Array.isArray(p)) { return { ok: false, error: 'Ablaufprofil muss ein JSON-Objekt sein.' } } return { ok: true, obj: { ...p } } } catch { return { ok: false, error: 'Ablaufprofil (JSON): Syntax ungültig.' } } } /** Pro Archetyp: UI-Feldbeschreibungen (Werte werden in method_profile geschrieben) */ export const METHOD_PROFILE_GUI_FIELDS = Object.freeze({ sequence_linear: [ { key: 'hint_step_duration_sec', kind: 'int', label: 'Orientierung: Sekunden je Station/Schritt (optional)', min: 5, max: INT_MAX, }, { key: 'block_intro_sec', kind: 'int', label: 'Einführung / Demon am Block Gesamt (Sek., optional)', min: 0, max: INT_MAX, }, ], circuit_rotate_time: [ { key: 'work_seconds', kind: 'int', label: 'Arbeitszeit pro Station (Sek.)', min: 5, max: INT_MAX, }, { key: 'transition_seconds', kind: 'int', label: 'Wechsel / Rotation (Sek., optional)', min: 0, max: INT_MAX, }, { key: 'rest_seconds', kind: 'int', label: 'Pause zwischen Runden oder Stationen-Folgen (Sek., optional)', min: 0, max: INT_MAX, }, { key: 'rounds', kind: 'int', label: 'Runden (optional, wenn alle Station je Runde angefahren werden)', min: 1, max: 999, }, ], circuit_all_parallel: [ { key: 'explain_before_seconds', kind: 'int', label: 'Zeitfenster Vorab‑Erklärung aller Stationen (Sek., optional)', min: 0, max: INT_MAX, }, { key: 'simultaneous_start', kind: 'bool', label: 'Alle Stationen starten zusammen nach Erklärung', }, ], station_parcour: [ { key: 'allow_free_visit_order', kind: 'bool', label: 'Reihenfolge der Besuche frei (Parcours / Abhaken-Logik später im Coach)', }, ], pair_superset: [ { key: 'switch_seconds', kind: 'int', label: 'Orientierung: Wechselpause A↔B (Sek., optional)', min: 0, max: INT_MAX, }, { key: 'work_seconds_per_side', kind: 'int', label: 'Arbeit pro Rolle oder Seite (Sek., optional)', min: 5, max: INT_MAX, }, ], time_domain_interval: [ { key: 'work_seconds', kind: 'int', label: 'Intervall: Belastungszeit (Sek.)', min: 5, max: INT_MAX, }, { key: 'rest_seconds', kind: 'int', label: 'Intervall: Erholungszeit (Sek., optional)', min: 0, max: INT_MAX, }, { key: 'interval_rounds', kind: 'int', label: 'Anzahl Wiederholungen / Runden der Domäne (optional)', min: 1, max: 999, }, ], free_method_block: [], }) /** * Aktualisiert method_profile unter Beibehaltung nicht-GUI Schlüssel. */ export function updateProfileGuided(archetype, rawJson, key, parsedValue, kind) { const arch = typeof archetype === 'string' ? archetype.trim() : '' const parsed = parseProfileJson(rawJson) if (!parsed.ok) return parsed const next = { ...parsed.obj } if (kind === 'bool') { if (parsedValue) next[key] = true else delete next[key] } else if (kind === 'int') { if (parsedValue === null || parsedValue === undefined || parsedValue === '') { delete next[key] } else { const n = typeof parsedValue === 'number' ? parsedValue : parseInt(String(parsedValue), 10) if (!Number.isFinite(n)) delete next[key] else next[key] = n } } const outJson = JSON.stringify(next) return { ok: true, obj: next, json: outJson === '{}' ? '{}' : outJson } } export function setFullProfileRawJson(rawEditable) { const parsed = parseProfileJson(rawEditable) if (!parsed.ok) return parsed const j = JSON.stringify(parsed.obj) return { ok: true, obj: parsed.obj, json: j === '{}' ? '{}' : j } } export { parseProfileJson, INT_MAX }