import React, { useMemo, useState } from 'react' import { archetypeCoachHint, combinationArchetypeLabel, sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes' import { METHOD_PROFILE_GUI_FIELDS, parseProfileJson, setFullProfileRawJson, updateProfileGuided, patchMethodProfile, readSlotProfilesV1, patchSlotTimingField, applyCircuitRotateQuickRatio, applyIntervalDomainQuickRatio, } from '../utils/combinationMethodProfileUi' function clampInt(n, min, max) { if (!Number.isFinite(n)) return null let x = n if (typeof min === 'number' && x < min) x = min if (typeof max === 'number' && x > max) x = max return Math.round(x) } /** Archetypen mit klar bezifferbarer Stationslogik · alle mit Slot-Liste sinnvoll */ const ARCHETYPES_WITH_SLOT_TIMING = new Set([ 'circuit_rotate_time', 'sequence_linear', 'station_parcour', 'time_domain_interval', 'circuit_all_parallel', 'pair_superset', 'free_method_block', ]) /** * Kombination: geführtes method_profile (+ optional Stationszeilen, ohne JSON für Trainer). * * @param {boolean} [props.plannerMode] — z. B. Planungs‑Override: keine Roh‑JSON‑Sektion. * @param {boolean} [props.allowExpertJson] — wenn true und nicht plannerMode: Roh‑JSON (Support). * @param {{ slot_index?: number|string, title?: string }[]} [props.comboSlotsOutline] — für Slot‑Felder aus der Übung */ export default function CombinationMethodProfileEditor({ methodArchetype, methodProfileJson, onChangeMethodProfileJson, plannerMode = false, allowExpertJson = false, comboSlotsOutline = null, }) { const arch = typeof methodArchetype === 'string' ? methodArchetype.trim() : '' const fieldsGui = METHOD_PROFILE_GUI_FIELDS[arch] const fields = Array.isArray(fieldsGui) ? fieldsGui : null const parseState = useMemo(() => parseProfileJson(methodProfileJson || '{}'), [methodProfileJson]) const [rawOpenError, setRawOpenError] = useState(null) const [rawDraft, setRawDraft] = useState(null) const [presetHint, setPresetHint] = useState(null) const profileObj = parseState.ok ? parseState.obj : {} const outlineSorted = useMemo(() => { if (!comboSlotsOutline || !Array.isArray(comboSlotsOutline) || comboSlotsOutline.length === 0) return [] return sortCombinationSlotsForDisplay(comboSlotsOutline) }, [comboSlotsOutline]) const showSlotTiming = ARCHETYPES_WITH_SLOT_TIMING.has(arch) && outlineSorted.length > 0 const slotRowsModel = useMemo(() => readSlotProfilesV1(profileObj), [profileObj]) const lookupSlotTiming = (slotIndex) => slotRowsModel.find((r) => Number(r.slot_index) === Number(slotIndex)) || {} const applyGuided = (key, value, kind) => { if (kind === 'bool') { const res = updateProfileGuided(arch, methodProfileJson || '{}', key, value, 'bool') if (!res.ok) return onChangeMethodProfileJson(res.json) setPresetHint(null) return } if (value === '' || value === undefined || value === null) { const res = updateProfileGuided(arch, methodProfileJson || '{}', key, '', 'int') if (!res.ok) return onChangeMethodProfileJson(res.json) setPresetHint(null) return } const num = typeof value === 'number' ? value : parseInt(String(value), 10) if (!Number.isFinite(num)) return const def = METHOD_PROFILE_GUI_FIELDS[arch]?.find((f) => f.key === key && f.kind === 'int') const c = clampInt(num, def?.min, def?.max) if (c == null) return const res = updateProfileGuided(arch, methodProfileJson || '{}', key, c, 'int') if (!res.ok) return onChangeMethodProfileJson(res.json) setPresetHint(null) } const onSlotField = (slotIx, field, rawStr) => { const patched = patchMethodProfile(methodProfileJson || '{}', (d) => patchSlotTimingField(d, slotIx, field, rawStr) ) if (!patched.ok) return onChangeMethodProfileJson(patched.json) setPresetHint(null) } const runCircuitPreset = (presetId) => { const r = patchMethodProfile(methodProfileJson || '{}', (draft) => { const pr = applyCircuitRotateQuickRatio(draft, presetId) if (!pr.ok) setPresetHint(pr.error || '') else setPresetHint(null) }) if (!r.ok) return onChangeMethodProfileJson(r.json) } const runIntervalPreset = (presetId) => { const r = patchMethodProfile(methodProfileJson || '{}', (draft) => { const pr = applyIntervalDomainQuickRatio(draft, presetId) if (!pr.ok) setPresetHint(pr.error || '') else setPresetHint(null) }) if (!r.ok) return onChangeMethodProfileJson(r.json) } const archeLabel = arch ? combinationArchetypeLabel(arch) : null const openAdvanced = () => { setRawOpenError(null) const p = parseProfileJson(methodProfileJson || '{}') setRawDraft(p.ok ? JSON.stringify(p.obj, null, 2) : String(methodProfileJson || '')) } const showExpertSection = allowExpertJson && !plannerMode return (
{presetHint}
) : null} {arch ? (Coach & Planung:{' '} {archeLabel && archeLabel !== arch ? `${archeLabel} · ` : ''} {archetypeCoachHint(arch)}
) : (Wähle einen Methoden‑Archetyp — besonders beim freien Methodenblock stehen alle typischen Stations‑Zeiten zur Verfügung. Ohne Archetyp keine geführten Eingaben.
)} {!parseState.ok ? ({parseState.error}
) : null} {fields && fields.length > 0 ? (Dieser Archetyp ist für maximal flexible Stationsblöcke gedacht — die Zeit‑Eckdaten sind unten je Station möglich. Freitexte der Kombination beschreiben alles Organisatorische, was nicht in Sekunden gefasst wird.
Belastungsdauer, wie oft die Übung an der gleichen Station hintereinander, kurze Pause dazwischen, Zeit bis zur nächsten Station. Felder können leer bleiben — z. B. nutzt der Zirkel oben erst die globalen Arbeit‑/ Rotations‑Sekunden.
Für Migrationen und Sonderfälle. Geführte Felder setzen weiterhin gültige Standardschlüssel.