import React, { useMemo, useState } from 'react' import { archetypeCoachHint, combinationArchetypeLabel, sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes' import { METHOD_PROFILE_GUI_FIELDS, parseProfileJson, setFullProfileRawJson, updateProfileGuided, patchMethodProfile, readSlotProfilesV1, patchSlotTimingField, patchSlotAdvanceMode, normalizeAdvanceMode, parseComboRepSeriesCountUi, 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, omitPerSlotTiming = false, }) { 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 = !omitPerSlotTiming && 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 onSlotAdvanceChange = (slotIx, rawMode) => { const patched = patchMethodProfile(methodProfileJson || '{}', (d) => patchSlotAdvanceMode(d, slotIx, rawMode) ) if (!patched.ok) return onChangeMethodProfileJson(patched.json) setPresetHint(null) } const onSlotRepSeriesCount = (slotIx, rawStr) => { const trimmed = String(rawStr ?? '').trim() const effective = trimmed === '' ? '1' : trimmed const pn = parseInt(effective, 10) const clearIntra = !Number.isFinite(pn) || pn < 2 const patched = patchMethodProfile(methodProfileJson || '{}', (d) => { patchSlotTimingField(d, slotIx, 'rep_series_count', effective) if (clearIntra) patchSlotTimingField(d, slotIx, 'intra_rep_rest_sec', '') }) 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 ? (

{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 ? (
{arch === 'circuit_rotate_time' ? (
Schnellwahl:
) : null} {arch === 'time_domain_interval' ? (
Schnellwahl:
) : null} {fields.map((def) => { if (def.kind === 'bool') { const ck = !!profileObj[def.key] return ( ) } const v = profileObj[def.key] const str = v === undefined || v === null ? '' : typeof v === 'number' && Number.isFinite(v) ? String(v) : String(v) return (
{ const t = e.target.value if (t.trim() === '') applyGuided(def.key, '', 'int') else applyGuided(def.key, parseInt(t, 10), 'int') }} />
) })}
) : arch && 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.

) : null} {showSlotTiming ? (
Pro Station / Slot — Steuerung & Sekunden

Steuerung: zeitlich (Arbeits‑Countdown), Zielzahl Wiederholungen oder Coach‑geführt ohne Arbeitsuhr. Pausen/Wechsel bleiben unabhängig planbar. Felder können leer bleiben — z. B. nutzt der Zirkel erst die globalen Arbeit‑Sekunden.

{outlineSorted.map((slot, ordIdx) => { const siRaw = slot.slot_index const si = siRaw === '' || siRaw == null ? null : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10) if (!Number.isFinite(si)) return null const row = lookupSlotTiming(si) const ttl = ((slot.title || '').trim() || `Station ${ordIdx + 1}`).trim() const slotAdv = normalizeAdvanceMode(row.advance_mode) const serieLabel = slotAdv === 'timed' ? 'Wdh. ohne Wechsel' : slotAdv === 'rep' ? 'Wdh. / Serie' : 'Richtwert' const showMultiSeries = slotAdv === 'rep' || slotAdv === 'manual' const serienUi = parseComboRepSeriesCountUi(row.rep_series_count) const showInterSeriesPause = showMultiSeries && serienUi >= 2 const intraLabel = slotAdv === 'timed' ? 'Pause zwischen Wdh.' : 'Pause zw. Serien' return (
{ttl}
{slotAdv === 'timed' ? (
onSlotField(si, 'load_sec', e.target.value)} />
) : null}
onSlotField(si, 'consecutive_reps', e.target.value)} />
{showMultiSeries ? (
onSlotRepSeriesCount(si, e.target.value)} />
) : null} {slotAdv === 'timed' || showInterSeriesPause ? (
onSlotField(si, 'intra_rep_rest_sec', e.target.value)} />
) : null}
onSlotField(si, 'transition_after_sec', e.target.value)} />
{showMultiSeries && serienUi < 2 ? (

Wechsel (s) zur nächsten Station. „Pause zw. Serien“ nur ab 2 Serien.

) : null}
) })}
) : null} {showExpertSection ? (
{ if (ev.target.open) openAdvanced() }} > Support / Entwicklung: Rohdaten (JSON)

Für Migrationen und Sonderfälle. Geführte Felder setzen weiterhin gültige Standardschlüssel.