import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { GripVertical, Pencil, X } from 'lucide-react'
import CombinationMethodProfileEditor from './CombinationMethodProfileEditor'
import CombinationPlanBracket from './CombinationPlanBracket'
import { comboPlanningProfileJsonForEditor, effectiveComboMethodProfile } from '../utils/comboPlanningMethodProfile'
import { sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes'
import {
  cloneJsonSerializablePlanningProfile,
  comboSlotsOutlineForProfileEditor,
  defaultSection,
  defaultPlanLocWholeGroup,
  defaultPlanLocParallel,
  maxPhaseOrderIndexFromSections,
  buildPlanTargetOptions,
  planLocKey,
  MAX_PARALLEL_STREAMS_PER_PHASE,
  parallelStreamVisual,
  streamsForParallelPhaseOrders,
  sectionIndicesForParallelStream,
  reorderWithinBucketIndices,
  reorderWithoutIndices,
  parallelStreamBucketHasContent,
  dissolveParallelPhaseToWholeGroup,
  phaseRunsFromSections,
  swapAdjacentPhaseRuns,
  reorderBlocksImmutableWithPlanLoc,
  reorderSectionBeforeParallelRunAsWholeGroup,
  reorderSectionAsFirstInParallelStream,
  reorderBlockIntoParallelStreamEnd,
  globalInsertBeforeIndexForParallelStreamEnd,
  movePhaseRunUpByPhaseOrder,
  movePhaseRunDownByPhaseOrder,
  moveParallelPhaseRunToInsertBefore,
  afterSectionReorderParallelGuard,
  indicesOfParallelPhase,
  exerciseRow,
  noteRow,
  sectionPlannedMinutes,
} from '../utils/trainingUnitSectionsForm'
import api from '../utils/api'
import { isCompactTagLegendMode } from '../config/planningModuleUx'
import { useAuth } from '../context/AuthContext'

function stripPlanLocFromSection(s) {
  if (!s || typeof s !== 'object') return s
  const { planLoc: _ignored, ...rest } = s
  return rest
}

function planSelectOptionsForSection(sections, sIdx, baseOpts) {
  const sec = sections[sIdx]
  const k = planLocKey(sec?.planLoc)
  if (k && !baseOpts.some((o) => o.key === k)) {
    const pl = sec.planLoc
    const label =
      pl.phaseKind === 'parallel'
        ? `Parallel · Phase ${pl.phaseOrderIndex ?? 0} · Stream ${pl.parallelStreamOrderIndex ?? 0}`
        : `Ganzgruppe · Phase ${pl.phaseOrderIndex ?? 0}`
    return [...baseOpts, { key: k, label, template: { ...pl } }].sort((a, b) =>
      a.key.localeCompare(b.key, undefined, { numeric: true })
    )
  }
  return baseOpts
}

const DND_TU_ITEM = 'application/x-shinkan-training-unit-item'
const DND_TU_SECTION = 'application/x-shinkan-training-section-v1'

/** Optische Trennlinie: wird als normale Zwischen-Anmerkung gespeichert (Inhalt nur dieser Marker). */
const SECTION_INSERT_SEPARATOR_BODY = '---'

function normalizedPlanningModuleChainId(raw) {
  if (raw == null || raw === '') return null
  const n = typeof raw === 'number' ? raw : Number(raw)
  return Number.isFinite(n) && n >= 1 ? n : null
}

function dtHasType(e, mime) {
  const t = e?.dataTransfer?.types
  if (!t || !mime) return false
  if (typeof t.contains === 'function' && t.contains(mime)) return true
  return Array.from(t).includes(mime)
}

/** Visuelle Zuordnung der Einfügezeile zu Split- vs. Ganzgruppen-Bereich (nur Darstellung). */
function sectionDropBandRegionClass(sections, beforeIdx, enableParallel) {
  if (!enableParallel) return ''
  const n = sections?.length ?? 0
  const below = beforeIdx < n ? sections[beforeIdx] : null
  const above = beforeIdx > 0 ? sections[beforeIdx - 1] : null
  const po = (s) => s?.planLoc?.phaseOrderIndex ?? 0
  const aboveP = above?.planLoc?.phaseKind === 'parallel'
  const belowP = below?.planLoc?.phaseKind === 'parallel'
  const aboveW = above?.planLoc?.phaseKind === 'whole_group'
  const belowW = below?.planLoc?.phaseKind === 'whole_group'

  if (aboveP && belowP && po(above) === po(below)) {
    return ' tu-section-dropband--region-split'
  }
  if (aboveW && belowW) return ' tu-section-dropband--region-whole'
  if (aboveP && belowW) return ' tu-section-dropband--region-split-to-whole'
  if (aboveW && belowP) return ' tu-section-dropband--region-whole-to-split'
  if (!above && belowP) return ' tu-section-dropband--region-split'
  if (!below && aboveP) return ' tu-section-dropband--region-split-to-whole'
  if (!above && belowW) return ' tu-section-dropband--region-whole'
  if (!below && aboveW) return ' tu-section-dropband--region-whole'
  return ' tu-section-dropband--region-neutral'
}

function truncatePreview(text, max = 160) {
  const t = (text || '').replace(/\s+/g, ' ').trim()
  if (t.length <= max) return t
  return `${t.slice(0, max - 1)}…`
}

/** Liest den zusammenhängenden Lauf eines Moduls im Abschnitt (ab erstem Item mit dieser Herkunfts-ID). */
function gatherPlanningModuleOutline(items, startIdx, moduleId) {
  const exercises = []
  let notes = 0
  for (let j = startIdx; j < (items?.length ?? 0); j++) {
    const row = items[j]
    if (normalizedPlanningModuleChainId(row.source_training_module_id) !== moduleId) break
    if (row.item_type === 'note') {
      const bod = (row.note_body || '').trim()
      if (bod === SECTION_INSERT_SEPARATOR_BODY) continue
      notes += 1
      continue
    }
    const t =
      (row.exercise_title || '').trim() ||
      (row.exercise_id ? `Übung #${row.exercise_id}` : 'Übung')
    exercises.push(t)
  }
  return { exercises, notes }
}

const MODULE_OUTLINE_PREVIEW_MAX = 8

/** Statuszeile: nur Planungs‑Override vs. Katalog (Archetyp steht bereits links). */
function compactComboPlanningCaption(it) {
  const overridden =
    it.planning_method_profile != null &&
    typeof it.planning_method_profile === 'object' &&
    !Array.isArray(it.planning_method_profile)
  return overridden ? 'Planung angepasst' : 'wie Katalog'
}

/** Stabile Farbzurodnung aus Modul-ID (nur Darstellung). */
function planningModulePalette(moduleId) {
  const id = normalizedPlanningModuleChainId(moduleId)
  const n = id != null && id >= 1 ? Math.floor(Number(id)) : 1
  const golden = ((n >>> 0) * 2654435761 + n * 73856093) >>> 0
  const h = golden % 360
  const border = `hsl(${h} 52% 36%)`
  const soft = `hsl(${h} 42% 94%)`
  return { border, soft, hue: h }
}

function PlanningModuleRowTag({ moduleId, title }) {
  const p = planningModulePalette(moduleId)
  const lbl = truncatePreview(title || `Modul #${moduleId}`, 34).trim()
  const fullTitle = ((title || '').trim() || `Modul #${moduleId}`).trim()
  return (
    <span
      className="tu-planning-mod-tag"
      style={{ borderColor: p.border, backgroundColor: p.soft }}
      title={`${fullTitle} (Bibliotheks-ID ${moduleId})`}
    >
      <span className="tu-planning-mod-tag__dot" style={{ background: p.border }} aria-hidden />
      <span className="tu-planning-mod-tag__text">Aus Modul: {lbl}</span>
    </span>
  )
}

/** Eindeutige Module im Abschnitt mit Zählerständen für die Legende. */
function sectionModuleLegendModel(items) {
  const map = new Map()
  for (const row of items || []) {
    const id = normalizedPlanningModuleChainId(row.source_training_module_id)
    if (id == null) continue
    if (!map.has(id)) {
      map.set(id, {
        id,
        title: (((row.source_module_title || '').trim() || '') || `Modul #${id}`).trim(),
        exercises: 0,
        notes: 0,
      })
    }
    const agg = map.get(id)
    if ((row.item_type || '') === 'note') {
      const bod = ((row.note_body || '').trim() || '').trim()
      if (bod === SECTION_INSERT_SEPARATOR_BODY) continue
      agg.notes += 1
    } else {
      agg.exercises += 1
    }
  }
  return [...map.values()].sort((a, b) => a.id - b.id)
}

function renderModulePlanningHead(modBandTitle, modOutline, showModuleBand) {
  if (!showModuleBand || !modOutline) return null
  return (
    <div className="tu-module-bundle-head" role="group" aria-label={`Modul ${modBandTitle}`}>
      <div className="tu-module-bundle-head__stripe" aria-hidden />
      <div className="tu-module-bundle-head__main">
        <span className="tu-module-bundle-head__kicker">Aus Modul</span>
        <strong className="tu-module-bundle-head__title">{modBandTitle}</strong>
        {modOutline.exercises.length === 0 && modOutline.notes === 0 ? (
          <p className="tu-module-bundle-head__empty">Ohne strukturierten Inhalt angezeigt.</p>
        ) : (
          <ol className="tu-module-bundle-head__list" start={1}>
            {modOutline.exercises.slice(0, MODULE_OUTLINE_PREVIEW_MAX).map((tx, ox) => (
              <li key={`mo-li-${modBandTitle}-${ox}`}>{tx}</li>
            ))}
          </ol>
        )}
        {modOutline.exercises.length > MODULE_OUTLINE_PREVIEW_MAX ? (
          <p className="tu-module-bundle-head__more">
            … und noch {modOutline.exercises.length - MODULE_OUTLINE_PREVIEW_MAX}{' '}
            {modOutline.exercises.length - MODULE_OUTLINE_PREVIEW_MAX === 1 ? 'Übung' : 'Übungen'}
          </p>
        ) : null}
        {modOutline.notes > 0 ? (
          <p className="tu-module-bundle-head__meta">
            sowie {modOutline.notes}{' '}
            {modOutline.notes === 1 ? 'Zwischen-Hinweis' : 'Zwischen-Hinweise'}
          </p>
        ) : null}
      </div>
    </div>
  )
}

function reorderBlocksImmutable(blocks, fromI, toBeforeIdx) {
  const b = [...blocks]
  if (fromI < 0 || fromI >= b.length) return blocks
  const [moved] = b.splice(fromI, 1)
  let insertAt = toBeforeIdx
  if (fromI < toBeforeIdx) insertAt = toBeforeIdx - 1
  insertAt = Math.max(0, Math.min(insertAt, b.length))
  b.splice(insertAt, 0, moved)
  return b
}

/**
 * @param {(updater: (prev: Array) => Array) => void} props.onSectionsChange — wie React setState
 * @param {(p: { fromSlot: number, fromSectionIdx: number, toSlot: number, toSectionIdx: number, toParallelStream?: { po: number, so: number } }) => void} [props.onMoveSectionsAcrossSlots] — Rahmenprogramm: Abschnitt zwischen Slots verschieben
 */
export default function TrainingUnitSectionsEditor({
  sections,
  onSectionsChange,
  onRequestExercisePick,
  onRequestTrainingModulePick,
  onPeekExercise,
  showExecutionExtras = false,
  heading = 'Abschnitte & Übungen',
  hideHeading = false,
  headingAccessory = null,
  wideExerciseGrid = false,
  enableItemDragReorder = true,
  enableSectionDragReorder = true,
  slotIndex = null,
  onMoveSectionsAcrossSlots = null,
  /** Dünnes „+“ zwischen Einträge: Popup für Typ (Übung, Modul, …) */
  betweenInsertMenus = true,
  /** Trainingsplanung: Phasen/Streams anlegen und Abschnitte zuordnen */
  enableParallelPhaseControls = false,
}) {
  const { user } = useAuth()
  const planningCompactLegend = isCompactTagLegendMode(
    user?.training_planning_prefs?.module_display_mode
  )

  const ensure = (prev) =>
    prev && prev.length ? prev : [defaultSection()]

  const patch = useCallback(
    (updater) => {
      onSectionsChange((prev) => updater(ensure(prev)))
    },
    [onSectionsChange]
  )

  const sectionToSlot =
    slotIndex !== null && slotIndex !== undefined ? Number(slotIndex) : -1

  const list = ensure(sections)
  const useStreamTagDropUx =
    enableSectionDragReorder &&
    enableParallelPhaseControls &&
    list.some((s) => s?.planLoc?.phaseKind === 'parallel')

  const updateSectionField = (sIdx, field, val) => {
    patch((prev) =>
      prev.map((s, i) => (i === sIdx ? { ...s, [field]: val } : s))
    )
  }

  const addSection = () => {
    patch((prev) => {
      const base = defaultSection(`Abschnitt ${prev.length + 1}`)
      const last = prev[prev.length - 1]
      const next = last?.planLoc ? { ...base, planLoc: { ...last.planLoc } } : base
      return [...prev, next]
    })
  }

  const addParallelPhaseTwoStreams = () => {
    patch((prev) => {
      const nextPo = maxPhaseOrderIndexFromSections(prev) + 1
      const pl0 = defaultPlanLocParallel(nextPo, 0)
      const pl1 = defaultPlanLocParallel(nextPo, 1)
      const base0 = defaultSection(`Abschnitt ${prev.length + 1}`)
      const base1 = defaultSection(`Abschnitt ${prev.length + 2}`)
      return [...prev, { ...base0, planLoc: pl0 }, { ...base1, planLoc: pl1 }]
    })
  }

  const addStreamToParallelPhase = (phaseOrder) => {
    patch((prev) => {
      const po = Number(phaseOrder) || 0
      const par = (prev || []).filter(
        (s) => s?.planLoc?.phaseKind === 'parallel' && (s.planLoc.phaseOrderIndex ?? 0) === po
      )
      if (!par.length) return prev
      const distinct = new Set(par.map((s) => s.planLoc.parallelStreamOrderIndex ?? 0))
      if (distinct.size >= MAX_PARALLEL_STREAMS_PER_PHASE) return prev
      const maxS = Math.max(...par.map((s) => s.planLoc.parallelStreamOrderIndex ?? 0))
      const newSo = maxS + 1
      const tmpl = {
        ...par[0].planLoc,
        parallelStreamOrderIndex: newSo,
        streamTitle: null,
        streamNotes: null,
        streamAssignedTrainerProfileIds: null,
      }
      const base = defaultSection(`Abschnitt ${prev.length + 1}`)
      const inPhaseIdx = indicesOfParallelPhase(prev, po)
      const insertAfter = inPhaseIdx.length ? Math.max(...inPhaseIdx) : prev.length - 1
      return [
        ...prev.slice(0, insertAfter + 1),
        { ...base, planLoc: tmpl },
        ...prev.slice(insertAfter + 1),
      ]
    })
  }

  const addWholeGroupSection = () => {
    patch((prev) => {
      const L = ensure(prev)
      const wgs = L.filter((s) => s?.planLoc?.phaseKind === 'whole_group')
      let pl
      if (wgs.length) {
        const maxPo = Math.max(...wgs.map((s) => s.planLoc.phaseOrderIndex ?? 0))
        const sample = wgs.find((s) => (s.planLoc.phaseOrderIndex ?? 0) === maxPo)
        pl = { ...sample.planLoc }
      } else {
        const nextPo = maxPhaseOrderIndexFromSections(L) + 1
        pl = defaultPlanLocWholeGroup(nextPo)
      }
      const base = defaultSection(`Abschnitt ${L.length + 1}`)
      return [...L, { ...base, planLoc: pl }]
    })
  }

  const addSectionToParallelStream = (phaseOrder, streamOrder) => {
    patch((prev) => {
      const L = ensure(prev)
      const po = Number(phaseOrder) || 0
      const so = Number(streamOrder) || 0
      const idxs = sectionIndicesForParallelStream(L, po, so)
      const tmpl = idxs.length ? L[idxs[0]].planLoc : defaultPlanLocParallel(po, so)
      const pl = {
        ...tmpl,
        phaseKind: 'parallel',
        phaseOrderIndex: po,
        parallelStreamOrderIndex: so,
      }
      const base = defaultSection(`Abschnitt ${L.length + 1}`)
      if (!idxs.length) {
        return [...L, { ...base, planLoc: pl }]
      }
      const insertAfter = Math.max(...idxs)
      return [...L.slice(0, insertAfter + 1), { ...base, planLoc: pl }, ...L.slice(insertAfter + 1)]
    })
  }

  const updateParallelPhaseTitleAll = (phaseOrder, title) => {
    const po = Number(phaseOrder) || 0
    const v = title.trim() ? title.trim() : null
    patch((prev) =>
      prev.map((s) => {
        const L = s?.planLoc
        if (L?.phaseKind !== 'parallel' || (L.phaseOrderIndex ?? 0) !== po) return s
        return { ...s, planLoc: { ...L, phaseTitle: v } }
      })
    )
  }

  const updateParallelStreamTitleAll = (phaseOrder, streamOrder, title) => {
    const po = Number(phaseOrder) || 0
    const so = Number(streamOrder) || 0
    const v = title.trim() ? title.trim() : null
    patch((prev) =>
      prev.map((s) => {
        const L = s?.planLoc
        if (
          L?.phaseKind !== 'parallel' ||
          (L.phaseOrderIndex ?? 0) !== po ||
          (L.parallelStreamOrderIndex ?? 0) !== so
        ) {
          return s
        }
        return { ...s, planLoc: { ...L, streamTitle: v } }
      })
    )
  }

  const removeParallelStream = (phaseOrder, streamOrder) => {
    const po = Number(phaseOrder) || 0
    const so = Number(streamOrder) || 0
    const idxs = sectionIndicesForParallelStream(list, po, so)
    if (!idxs.length) return
    if (
      parallelStreamBucketHasContent(list, idxs, SECTION_INSERT_SEPARATOR_BODY) &&
      !window.confirm(
        'In diesem Stream sind Übungen oder Anmerkungen geplant. Stream wirklich löschen?'
      )
    ) {
      return
    }
    patch((prev) => {
      const L = ensure(prev)
      const beforeOrders = streamsForParallelPhaseOrders(L, po)
      const rm = sectionIndicesForParallelStream(L, po, so)
      if (!rm.length) return prev
      let next = reorderWithoutIndices(L, rm)
      const afterOrders = streamsForParallelPhaseOrders(next, po)
      if (beforeOrders.length >= 2 && afterOrders.length <= 1) {
        if (
          window.confirm(
            'Nur noch eine Gruppe in dieser Phase übrig. Parallelen Aufbau auflösen und alle Abschnitte als gemeinsame Ganzgruppen-Phase weiterführen?'
          )
        ) {
          next = dissolveParallelPhaseToWholeGroup(next, po)
        }
      }
      return next
    })
  }

  const applySectionPlanTarget = (sIdx, rawKey) => {
    patch((prev) => {
      if (!rawKey) {
        return prev.map((s, i) => (i === sIdx ? stripPlanLocFromSection(s) : s))
      }
      const opts = planSelectOptionsForSection(prev, sIdx, buildPlanTargetOptions(prev))
      const hit = opts.find((o) => o.key === rawKey)
      if (!hit) return prev
      const tpl = { ...hit.template }
      return prev.map((s, i) => (i === sIdx ? { ...s, planLoc: tpl } : s))
    })
  }

  const removeSection = (sIdx) => {
    patch((prev) => {
      let next = prev.filter((_, i) => i !== sIdx)
      next = next.length ? next : [defaultSection()]
      if (enableParallelPhaseControls) {
        next = afterSectionReorderParallelGuard(prev, next)
      }
      return next
    })
  }

  /** Ganzgruppe: global tauschen; parallele Phase: innerhalb Stream oder ganze Parallel-Phase am Stück */
  const moveSection = (sIdx, dir) => {
    patch((prev) => {
      const p = ensure(prev)
      const sec = p[sIdx]
      const L = sec?.planLoc
      if (L?.phaseKind === 'parallel') {
        const po = L.phaseOrderIndex ?? 0
        const so = L.parallelStreamOrderIndex ?? 0
        const bucket = sectionIndicesForParallelStream(p, po, so)
        const pos = bucket.indexOf(sIdx)
        if (pos < 0) return p
        if (dir < 0 && pos > 0) {
          const newPos = pos + dir
          return reorderWithinBucketIndices(p, bucket, pos, newPos)
        }
        if (dir > 0 && pos < bucket.length - 1) {
          const newPos = pos + dir
          return reorderWithinBucketIndices(p, bucket, pos, newPos)
        }
        if (dir < 0 && pos === 0) {
          const runs = phaseRunsFromSections(p)
          const rIdx = runs.findIndex((r) => r.phaseKind === 'parallel' && r.phaseOrderIndex === po)
          if (rIdx <= 0) return p
          return swapAdjacentPhaseRuns(p, rIdx - 1)
        }
        if (dir > 0 && pos === bucket.length - 1) {
          const runs = phaseRunsFromSections(p)
          const rIdx = runs.findIndex((r) => r.phaseKind === 'parallel' && r.phaseOrderIndex === po)
          if (rIdx < 0 || rIdx >= runs.length - 1) return p
          return swapAdjacentPhaseRuns(p, rIdx)
        }
        return p
      }
      const arr = [...p]
      const ta = sIdx + dir
      if (ta < 0 || ta >= arr.length) return arr
      ;[arr[sIdx], arr[ta]] = [arr[ta], arr[sIdx]]
      return arr
    })
  }

  const insertItemAt = useCallback(
    (sIdx, beforeIx, row) => {
      patch((prev) =>
        prev.map((s, i) => {
          if (i !== sIdx) return s
          const items = [...(s.items || [])]
          const ix = Math.max(
            0,
            Math.min(Number(beforeIx) || 0, items.length)
          )
          items.splice(ix, 0, row)
          return { ...s, items }
        })
      )
    },
    [patch]
  )

  const addItem = (sIdx, kind) => {
    patch((prev) =>
      prev.map((s, i) => {
        if (i !== sIdx) return s
        const items = [...(s.items || [])]
        items.push(kind === 'note' ? noteRow() : exerciseRow())
        return { ...s, items }
      })
    )
  }

  const removeItem = (sIdx, iIdx) => {
    patch((prev) =>
      prev.map((s, si) =>
        si !== sIdx ? s : { ...s, items: (s.items || []).filter((_, ii) => ii !== iIdx) }
      )
    )
  }

  const moveItem = (sIdx, iIdx, dir) => {
    patch((prev) =>
      prev.map((s, si) => {
        if (si !== sIdx) return s
        const items = [...(s.items || [])]
        const ta = iIdx + dir
        if (ta < 0 || ta >= items.length) return s
        ;[items[iIdx], items[ta]] = [items[ta], items[iIdx]]
        return { ...s, items }
      })
    )
  }

  const updateItem = (sIdx, iIdx, field, val) => {
    patch((prev) =>
      prev.map((s, si) =>
        si !== sIdx
          ? s
          : {
              ...s,
              items: (s.items || []).map((row, ii) =>
                ii === iIdx ? { ...row, [field]: val } : row
              ),
            }
      )
    )
  }

  const [textEdit, setTextEdit] = useState(null)
  /** Kombi: Ablaufprofil in Modal statt einzuklappender Karte */
  const [comboPlanningModal, setComboPlanningModal] = useState(null)
  /** Katalog-Stationen, falls Zeile noch keine `combination_slots` (vor Enrich o. Ä.) */
  const [modalComboSlotsFetched, setModalComboSlotsFetched] = useState(null)
  /** { sIdx: number, beforeIx: number } – Einfüge-Popup („+“ zwischen Zeilen) */
  const [insertChooser, setInsertChooser] = useState(null)
  const [draggingPos, setDraggingPos] = useState(null)
  const [dropTargetPos, setDropTargetPos] = useState(null)

  const [dropSectionBand, setDropSectionBand] = useState(null)
  /** Aktiver Reiter pro paralleler Phase (phaseOrder → streamOrder). */
  const [parallelStreamTabByPhase, setParallelStreamTabByPhase] = useState({})
  /** `${phaseOrder}:${streamOrder}` während Stream-Name bearbeitet wird */
  const [streamNameEditKey, setStreamNameEditKey] = useState(null)
  const [streamNameDraft, setStreamNameDraft] = useState('')
  const [phaseTitleEditPo, setPhaseTitleEditPo] = useState(null)
  const [phaseTitleDraft, setPhaseTitleDraft] = useState('')
  const skipStreamNameBlurSave = useRef(false)
  const skipPhaseTitleBlurSave = useRef(false)
  /** { slot: number, beforeIdx: number } */

  useEffect(() => {
    if (!textEdit) return
    const onKey = (e) => {
      if (e.key === 'Escape') setTextEdit(null)
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [textEdit])

  useEffect(() => {
    if (!insertChooser) return
    const onKey = (e) => {
      if (e.key === 'Escape') setInsertChooser(null)
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [insertChooser])

  useEffect(() => {
    if (!comboPlanningModal) return
    const onKey = (e) => {
      if (e.key === 'Escape') setComboPlanningModal(null)
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [comboPlanningModal])

  useEffect(() => {
    if (!comboPlanningModal) return
    const L = ensure(sections)
    const { sIdx, iIdx } = comboPlanningModal
    const row = L[sIdx]?.items?.[iIdx]
    const ok =
      row &&
      String(row.exercise_kind || '').toLowerCase().trim() === 'combination' &&
      row.exercise_id
    if (!ok) setComboPlanningModal(null)
  }, [sections, comboPlanningModal])

  useEffect(() => {
    if (!comboPlanningModal) {
      setModalComboSlotsFetched(null)
      return
    }
    const L = ensure(sections)
    const { sIdx, iIdx } = comboPlanningModal
    const cand = L[sIdx]?.items?.[iIdx]
    if (
      !cand ||
      String(cand.exercise_kind || '').toLowerCase().trim() !== 'combination' ||
      !cand.exercise_id
    ) {
      setModalComboSlotsFetched(null)
      return
    }
    const cached = cand.combination_slots
    if (Array.isArray(cached) && cached.length > 0) {
      setModalComboSlotsFetched(cached)
      return
    }
    let cancelled = false
    setModalComboSlotsFetched([])
    api.getExercise(cand.exercise_id).then((ex) => {
      if (cancelled) return
      setModalComboSlotsFetched(Array.isArray(ex?.combination_slots) ? ex.combination_slots : [])
    })
    return () => {
      cancelled = true
    }
  }, [sections, comboPlanningModal])

  const closeInsertChooser = useCallback(() => setInsertChooser(null), [])

  const insertSlotKeyPrefix =
    slotIndex !== null && slotIndex !== undefined ? `sl${slotIndex}-` : ''

  const clearSectionDnD = () => setDropSectionBand(null)

  const onParallelPhaseDragStart = (e, phaseOrderIndex) => {
    if (!enableSectionDragReorder || !enableParallelPhaseControls) return
    e.stopPropagation()
    try {
      e.dataTransfer.effectAllowed = 'move'
      e.dataTransfer.setData(
        DND_TU_SECTION,
        JSON.stringify({
          fromSlot: sectionToSlot,
          fromSectionIdx: null,
          phaseRunMove: { phaseOrderIndex },
        })
      )
    } catch {
      /* ignore */
    }
    setDropSectionBand(null)
  }

  const onSectionDragStart = (e, sIdx) => {
    if (!enableSectionDragReorder) return
    e.stopPropagation()
    try {
      e.dataTransfer.effectAllowed = 'move'
      e.dataTransfer.setData(
        DND_TU_SECTION,
        JSON.stringify({
          fromSlot: sectionToSlot,
          fromSectionIdx: sIdx,
        })
      )
    } catch {
      /* ignore */
    }
    setDropSectionBand(null)
  }

  const onSectionBandDragOver = (e, beforeIdx) => {
    if (!enableSectionDragReorder) return
    if (!dtHasType(e, DND_TU_SECTION)) return
    e.preventDefault()
    e.stopPropagation()
    try {
      e.dataTransfer.dropEffect = 'move'
    } catch {
      /* ignore */
    }
    setDropSectionBand({ slot: sectionToSlot, beforeIdx })
  }

  const onPhaseAboveSplitDragOver = (e, po) => {
    if (!enableSectionDragReorder || !enableParallelPhaseControls) return
    if (!dtHasType(e, DND_TU_SECTION)) return
    e.preventDefault()
    e.stopPropagation()
    try {
      e.dataTransfer.dropEffect = 'move'
    } catch {
      /* ignore */
    }
    setDropSectionBand({ slot: sectionToSlot, phaseAboveSplitPo: Number(po) || 0 })
  }

  const onPhaseBelowSplitDragOver = (e, po, so) => {
    if (!enableSectionDragReorder || !enableParallelPhaseControls) return
    if (!dtHasType(e, DND_TU_SECTION)) return
    e.preventDefault()
    e.stopPropagation()
    try {
      e.dataTransfer.dropEffect = 'move'
    } catch {
      /* ignore */
    }
    setDropSectionBand({
      slot: sectionToSlot,
      phaseBelowSplit: { po: Number(po) || 0, so: Number(so) || 0 },
    })
  }

  const applyParsedSectionDrop = (data) => {
    const phaseRunMove = data.phaseRunMove
    const fromSi = data.fromSectionIdx
    const fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1

    if (phaseRunMove != null && phaseRunMove.phaseOrderIndex != null) {
      return { kind: 'phaseRun', phaseRunMove, fromSlot }
    }
    if (typeof fromSi !== 'number') return null

    if (
      typeof onMoveSectionsAcrossSlots === 'function' &&
      sectionToSlot >= 0 &&
      fromSlot >= 0
    ) {
      return { kind: 'crossSlot', fromSi, fromSlot }
    }

    return { kind: 'local', fromSi }
  }

  const onPhaseAboveSplitDrop = (e, po) => {
    if (!enableSectionDragReorder || !enableParallelPhaseControls) return
    e.preventDefault()
    e.stopPropagation()
    clearSectionDnD()
    let raw = ''
    try {
      raw = e.dataTransfer.getData(DND_TU_SECTION)
    } catch {
      return
    }
    if (!raw) return
    let data
    try {
      data = JSON.parse(raw)
    } catch {
      return
    }
    const targetPo = Number(po) || 0
    const parsed = applyParsedSectionDrop(data)
    if (!parsed) return

    if (parsed.kind === 'phaseRun') {
      const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0
      if (dragPo === targetPo) return
      patch((prev) => {
        const idxs = indicesOfParallelPhase(prev, targetPo)
        const fg = idxs.length ? idxs[0] : -1
        if (fg < 0) return prev
        let next = moveParallelPhaseRunToInsertBefore(prev, dragPo, fg)
        if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
        return next
      })
      return
    }

    if (parsed.kind === 'crossSlot') return

    const { fromSi } = parsed
    patch((prev) => {
      let next = reorderSectionBeforeParallelRunAsWholeGroup(prev, fromSi, targetPo)
      if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
      return next
    })
  }

  const onPhaseBelowSplitDrop = (e, po, so) => {
    if (!enableSectionDragReorder || !enableParallelPhaseControls) return
    e.preventDefault()
    e.stopPropagation()
    clearSectionDnD()
    let raw = ''
    try {
      raw = e.dataTransfer.getData(DND_TU_SECTION)
    } catch {
      return
    }
    if (!raw) return
    let data
    try {
      data = JSON.parse(raw)
    } catch {
      return
    }
    const targetPo = Number(po) || 0
    const targetSo = Number(so) || 0
    const parsed = applyParsedSectionDrop(data)
    if (!parsed) return

    if (parsed.kind === 'phaseRun') {
      const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0
      if (dragPo === targetPo) return
      patch((prev) => {
        const idxs = indicesOfParallelPhase(prev, targetPo)
        const fg = idxs.length ? idxs[0] : -1
        if (fg < 0) return prev
        let next = moveParallelPhaseRunToInsertBefore(prev, dragPo, fg)
        if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
        return next
      })
      return
    }

    if (parsed.kind === 'crossSlot') return

    const { fromSi } = parsed
    patch((prev) => {
      let next = reorderSectionAsFirstInParallelStream(prev, fromSi, targetPo, targetSo)
      if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
      return next
    })
  }

  const onSectionBandDrop = (e, insertBeforeIdx) => {
    if (!enableSectionDragReorder) return
    e.preventDefault()
    e.stopPropagation()
    clearSectionDnD()
    let raw = ''
    try {
      raw = e.dataTransfer.getData(DND_TU_SECTION)
    } catch {
      return
    }
    if (!raw) return
    let data
    try {
      data = JSON.parse(raw)
    } catch {
      return
    }
    const fromSi = data.fromSectionIdx
    const phaseRunMove = data.phaseRunMove
    const fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1

    if (phaseRunMove != null && phaseRunMove.phaseOrderIndex != null) {
      patch((prev) => {
        const po = Number(phaseRunMove.phaseOrderIndex) || 0
        let next = moveParallelPhaseRunToInsertBefore(prev, po, insertBeforeIdx)
        if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
        return next
      })
      return
    }

    if (typeof fromSi !== 'number') return

    if (enableParallelPhaseControls) {
      const fromPl = list[fromSi]?.planLoc
      if (fromPl?.phaseKind === 'parallel' && insertBeforeIdx >= 0 && insertBeforeIdx < list.length) {
        const po = fromPl.phaseOrderIndex ?? 0
        const fromSo = fromPl.parallelStreamOrderIndex ?? 0
        const tabSo =
          parallelStreamTabByPhase[po] ?? streamsForParallelPhaseOrders(list, po)[0] ?? 0
        const targetPl = list[insertBeforeIdx]?.planLoc
        if (
          targetPl?.phaseKind === 'parallel' &&
          (targetPl.phaseOrderIndex ?? 0) === po &&
          (targetPl.parallelStreamOrderIndex ?? 0) !== fromSo &&
          tabSo === fromSo
        ) {
          return
        }
      }
    }

    if (
      enableParallelPhaseControls &&
      (insertBeforeIdx === fromSi || insertBeforeIdx === fromSi + 1)
    ) {
      return
    }

    if (
      typeof onMoveSectionsAcrossSlots === 'function' &&
      sectionToSlot >= 0 &&
      fromSlot >= 0
    ) {
      onMoveSectionsAcrossSlots({
        fromSlot,
        fromSectionIdx: fromSi,
        toSlot: sectionToSlot,
        toSectionIdx: insertBeforeIdx,
      })
      return
    }

    patch((prev) => {
      let next = enableParallelPhaseControls
        ? reorderBlocksImmutableWithPlanLoc(prev, fromSi, insertBeforeIdx)
        : reorderBlocksImmutable(prev, fromSi, insertBeforeIdx)
      if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
      return next
    })
  }

  const onStreamDropTargetDragOver = (e, phaseOrder, streamOrder) => {
    if (!enableSectionDragReorder || !useStreamTagDropUx) return
    if (!dtHasType(e, DND_TU_SECTION)) return
    e.preventDefault()
    e.stopPropagation()
    try {
      e.dataTransfer.dropEffect = 'move'
    } catch {
      /* ignore */
    }
    setDropSectionBand({
      slot: sectionToSlot,
      streamDrop: { po: Number(phaseOrder) || 0, so: Number(streamOrder) || 0 },
    })
  }

  const onStreamDropTargetDragLeave = (e) => {
    if (e.currentTarget.contains(e.relatedTarget)) return
    clearSectionDnD()
  }

  const onStreamDropTargetDrop = (e, phaseOrder, streamOrder) => {
    if (!enableSectionDragReorder || !useStreamTagDropUx) return
    e.preventDefault()
    e.stopPropagation()
    clearSectionDnD()
    let raw = ''
    try {
      raw = e.dataTransfer.getData(DND_TU_SECTION)
    } catch {
      return
    }
    if (!raw) return
    let data
    try {
      data = JSON.parse(raw)
    } catch {
      return
    }
    if (data.phaseRunMove != null && data.phaseRunMove.phaseOrderIndex != null) {
      return
    }
    const fromSi = data.fromSectionIdx
    
    if (typeof fromSi !== 'number') return

    const po = Number(phaseOrder) || 0
    const so = Number(streamOrder) || 0
    const toIdx = globalInsertBeforeIndexForParallelStreamEnd(list, po, so)

    const fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1
    if (
      typeof onMoveSectionsAcrossSlots === 'function' &&
      sectionToSlot >= 0 &&
      fromSlot >= 0
    ) {
      onMoveSectionsAcrossSlots({
        fromSlot,
        fromSectionIdx: fromSi,
        toSlot: sectionToSlot,
        toSectionIdx: toIdx,
        toParallelStream: { po, so },
      })
      return
    }

    patch((prev) => {
      let next = reorderBlockIntoParallelStreamEnd(prev, fromSi, po, so)
      if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
      return next
    })
  }

  const onItemDragStart = (e, sIdx, iIdx) => {
    if (!enableItemDragReorder) return
    e.stopPropagation()
    try {
      e.dataTransfer.effectAllowed = 'move'
      e.dataTransfer.setData(
        DND_TU_ITEM,
        JSON.stringify({ sectionIndex: sIdx, itemIndex: iIdx })
      )
    } catch {
      /* ignore */
    }
    setDraggingPos({ sIdx, iIdx })
  }

  const clearDragChrome = () => {
    setDraggingPos(null)
    setDropTargetPos(null)
  }

  const onItemDragOverRow = (e, sIdx, iIdx) => {
    if (!enableItemDragReorder) return
    if (!dtHasType(e, DND_TU_ITEM)) return
    e.preventDefault()
    e.stopPropagation()
    try {
      e.dataTransfer.dropEffect = 'move'
    } catch {
      /* ignore */
    }
    setDropTargetPos({ sIdx, iIdx })
  }

  const onItemDropRow = (e, toSIdx, toIdx) => {
    if (!enableItemDragReorder) return
    e.preventDefault()
    e.stopPropagation()
    let raw = ''
    try {
      raw = e.dataTransfer.getData(DND_TU_ITEM)
    } catch {
      clearDragChrome()
      return
    }
    if (!raw) {
      clearDragChrome()
      return
    }
    let data
    try {
      data = JSON.parse(raw)
    } catch {
      clearDragChrome()
      return
    }
    const fromS = data.sectionIndex
    const fromI = data.itemIndex
    if (typeof fromS !== 'number' || typeof fromI !== 'number') {
      clearDragChrome()
      return
    }
    if (fromS === toSIdx && fromI === toIdx) {
      clearDragChrome()
      return
    }

    patch((prev) => {
      const list = ensure(prev)
      if (
        fromS < 0 ||
        fromS >= list.length ||
        toSIdx < 0 ||
        toSIdx >= list.length ||
        typeof toIdx !== 'number'
      ) {
        return prev
      }

      const fromItems = [...(list[fromS].items || [])]
      if (fromI < 0 || fromI >= fromItems.length) return prev

      const moved = fromItems[fromI]
      fromItems.splice(fromI, 1)

      if (fromS === toSIdx) {
        let insertAt = toIdx
        if (fromI < toIdx) insertAt = toIdx - 1
        const bounded = Math.max(0, Math.min(insertAt, fromItems.length))
        fromItems.splice(bounded, 0, moved)
        return list.map((sec, i) => (i === fromS ? { ...sec, items: fromItems } : sec))
      }

      const toItems = [...(list[toSIdx].items || [])]
      const insertAt = Math.max(0, Math.min(toIdx, toItems.length))
      toItems.splice(insertAt, 0, moved)
      return list.map((sec, i) => {
        if (i === fromS) return { ...sec, items: fromItems }
        if (i === toSIdx) return { ...sec, items: toItems }
        return sec
      })
    })
    clearDragChrome()
  }

  const applyTextEdit = () => {
    if (!textEdit) return
    const { kind, sIdx, iIdx, draft } = textEdit
    if (kind === 'zwischen-note') {
      updateItem(sIdx, iIdx, 'note_body', draft)
    } else if (kind === 'exercise-notes') {
      updateItem(sIdx, iIdx, 'notes', draft)
    }
    setTextEdit(null)
  }

  const renderBetweenInsertBand = (sIdx, beforeIx, itemCount) => {
    const posLabel =
      beforeIx === 0
        ? 'vor dem ersten Eintrag'
        : beforeIx >= itemCount
          ? 'am Ende des Abschnitts'
          : `vor Eintrag ${beforeIx + 1}`
    return (
      <div className="tu-insert-slot">
        <button
          type="button"
          className="tu-insert-slot__btn"
          aria-haspopup="dialog"
          aria-label={`Inhalt einfügen (${posLabel})`}
          title={`Hier einfügen (${posLabel})`}
          onClick={() => setInsertChooser({ sIdx, beforeIx })}
        >
          +
        </button>
      </div>
    )
  }

  const planningPhaseRuns = useMemo(() => phaseRunsFromSections(list), [list])

  const firstSectionIndexByParallelPhase = useMemo(() => {
    const m = new Map()
    list.forEach((s, i) => {
      const L = s?.planLoc
      if (L?.phaseKind !== 'parallel') return
      const po = L.phaseOrderIndex ?? 0
      if (!m.has(po)) m.set(po, i)
    })
    return m
  }, [list])

  const parallelPhaseOrdersPresent = useMemo(() => {
    const set = new Set()
    for (const s of list) {
      if (s?.planLoc?.phaseKind === 'parallel') set.add(s.planLoc.phaseOrderIndex ?? 0)
    }
    return [...set].sort((a, b) => a - b)
  }, [list])

  useEffect(() => {
    if (!enableParallelPhaseControls || !parallelPhaseOrdersPresent.length) return
    setParallelStreamTabByPhase((prev) => {
      const next = { ...prev }
      let changed = false
      for (const po of parallelPhaseOrdersPresent) {
        const orders = streamsForParallelPhaseOrders(list, po)
        if (!orders.length) continue
        if (next[po] === undefined) {
          next[po] = orders[0]
          changed = true
        } else if (!orders.includes(next[po])) {
          next[po] = orders[0]
          changed = true
        }
      }
      for (const k of Object.keys(next)) {
        const poi = Number(k)
        if (!Number.isFinite(poi) || !parallelPhaseOrdersPresent.includes(poi)) {
          delete next[k]
          changed = true
        }
      }
      return changed ? next : prev
    })
  }, [list, parallelPhaseOrdersPresent, enableParallelPhaseControls])

  const sectionMoveDisabledUp = (sIdx) => {
    const sec = list[sIdx]
    const L = sec?.planLoc
    if (L?.phaseKind === 'parallel') {
      const po = L.phaseOrderIndex ?? 0
      const so = L.parallelStreamOrderIndex ?? 0
      const bucket = sectionIndicesForParallelStream(list, po, so)
      const pos = bucket.indexOf(sIdx)
      if (pos > 0) return false
      const rIdx = planningPhaseRuns.findIndex(
        (r) => r.phaseKind === 'parallel' && r.phaseOrderIndex === po
      )
      return rIdx <= 0
    }
    return sIdx === 0
  }

  const sectionMoveDisabledDown = (sIdx) => {
    const sec = list[sIdx]
    const L = sec?.planLoc
    if (L?.phaseKind === 'parallel') {
      const po = L.phaseOrderIndex ?? 0
      const so = L.parallelStreamOrderIndex ?? 0
      const bucket = sectionIndicesForParallelStream(list, po, so)
      const pos = bucket.indexOf(sIdx)
      if (pos >= 0 && pos < bucket.length - 1) return false
      const rIdx = planningPhaseRuns.findIndex(
        (r) => r.phaseKind === 'parallel' && r.phaseOrderIndex === po
      )
      return rIdx < 0 || rIdx >= planningPhaseRuns.length - 1
    }
    return sIdx === list.length - 1
  }

  const comboPlanningModalDerived = useMemo(() => {
    if (!comboPlanningModal) {
      return { item: null, sIdx: null, iIdx: null }
    }
    const { sIdx, iIdx } = comboPlanningModal
    const cand = list[sIdx]?.items?.[iIdx]
    if (
      cand &&
      String(cand.exercise_kind || '').toLowerCase().trim() === 'combination' &&
      cand.exercise_id
    ) {
      return { item: cand, sIdx, iIdx }
    }
    return { item: null, sIdx: null, iIdx: null }
  }, [list, comboPlanningModal])

  const comboPlanningModalItem = comboPlanningModalDerived.item
  const comboPlanningModalSX = comboPlanningModalDerived.sIdx
  const comboPlanningModalIX = comboPlanningModalDerived.iIdx

  const comboPlanningResolvedSlots = useMemo(() => {
    if (!comboPlanningModalItem) return []
    const c = comboPlanningModalItem.combination_slots
    if (Array.isArray(c) && c.length > 0) return c
    return Array.isArray(modalComboSlotsFetched) ? modalComboSlotsFetched : []
  }, [comboPlanningModalItem, modalComboSlotsFetched])

  const comboPlanningSlotsOutline = useMemo(
    () => comboSlotsOutlineForProfileEditor(comboPlanningResolvedSlots),
    [comboPlanningResolvedSlots]
  )

  const comboPlanningEffectiveProfile = useMemo(() => {
    if (!comboPlanningModalItem) return {}
    return effectiveComboMethodProfile(
      comboPlanningModalItem.catalog_method_profile || {},
      comboPlanningModalItem.planning_method_profile
    )
  }, [comboPlanningModalItem])

  return (
    <div
      className={
        'training-unit-sections-editor' +
        (wideExerciseGrid ? ' training-unit-sections-editor--wide' : '') +
        (enableItemDragReorder ? ' training-unit-sections-editor--item-drag' : '')
      }
    >
      {(!hideHeading || headingAccessory) ? (
        <div
          className="tu-editor-heading-toolbar"
          style={{
            display: 'flex',
            flexWrap: 'wrap',
            justifyContent: 'space-between',
            alignItems: 'center',
            gap: '10px',
            marginBottom: '0.75rem',
          }}
        >
          {!hideHeading ? (
            <h3 style={{ margin: 0, fontSize: '1rem', flex: '1 1 200px', minWidth: 0 }}>
              {heading}
            </h3>
          ) : headingAccessory ? (
            <span style={{ flex: '1 1 auto', minWidth: 0 }} />
          ) : null}
          {headingAccessory ? (
            <div
              style={{
                display: 'flex',
                flexWrap: 'wrap',
                gap: '8px',
                justifyContent: 'flex-end',
                alignItems: 'center',
              }}
            >
              {headingAccessory}
            </div>
          ) : null}
        </div>
      ) : null}
      {list.map((sec, sIdx) => {
        const planMin = sectionPlannedMinutes(sec)
        const itemCount = sec.items?.length ?? 0
        const moduleLegend = planningCompactLegend ? sectionModuleLegendModel(sec.items) : []
        const pl = sec?.planLoc
        const parallelPhaseOrder =
          enableParallelPhaseControls && pl?.phaseKind === 'parallel' ? pl.phaseOrderIndex ?? 0 : null
        const streamOrdersForParallelPhase =
          parallelPhaseOrder != null ? streamsForParallelPhaseOrders(list, parallelPhaseOrder) : []
        const activeParallelStream =
          parallelPhaseOrder != null
            ? parallelStreamTabByPhase[parallelPhaseOrder] ?? streamOrdersForParallelPhase[0] ?? 0
            : null
        const hideParallelSection =
          enableParallelPhaseControls &&
          pl?.phaseKind === 'parallel' &&
          (pl.parallelStreamOrderIndex ?? 0) !== activeParallelStream
        const firstGlobalIdxThisPhase =
          parallelPhaseOrder != null
            ? firstSectionIndexByParallelPhase.get(parallelPhaseOrder)
            : null
        const firstVisibleIdxActiveStream =
          parallelPhaseOrder != null && streamOrdersForParallelPhase.length
            ? sectionIndicesForParallelStream(
                list,
                parallelPhaseOrder,
                activeParallelStream
              )[0]
            : null
        const hideDropBandBeforeOrphanFirstVisible =
          parallelPhaseOrder != null &&
          firstVisibleIdxActiveStream != null &&
          sIdx === firstVisibleIdxActiveStream &&
          firstGlobalIdxThisPhase != null &&
          sIdx !== firstGlobalIdxThisPhase

        const isFirstSectionOfParallelPhase =
          parallelPhaseOrder != null &&
          firstSectionIndexByParallelPhase.get(parallelPhaseOrder) === sIdx
        const hideDropBeforeFirstParallelBecauseDedicatedSlot =
          enableParallelPhaseControls &&
          isFirstSectionOfParallelPhase &&
          pl?.phaseKind === 'parallel'
        const showSectionDropBandBefore =
          (pl?.phaseKind !== 'parallel' || !hideParallelSection) &&
          !hideDropBandBeforeOrphanFirstVisible &&
          !hideDropBeforeFirstParallelBecauseDedicatedSlot

        const bandActiveBefore = (bx) =>
          enableSectionDragReorder &&
          dropSectionBand &&
          dropSectionBand.slot === sectionToSlot &&
          dropSectionBand.beforeIdx === bx &&
          !dropSectionBand.streamDrop &&
          dropSectionBand.phaseAboveSplitPo == null &&
          !dropSectionBand.phaseBelowSplit

        const streamChipDropActive = (po, so) =>
          useStreamTagDropUx &&
          dropSectionBand?.slot === sectionToSlot &&
          dropSectionBand?.streamDrop?.po === po &&
          dropSectionBand?.streamDrop?.so === so

        const phaseAboveSplitDnd =
          parallelPhaseOrder != null &&
          dropSectionBand?.slot === sectionToSlot &&
          dropSectionBand?.phaseAboveSplitPo === parallelPhaseOrder
        const phaseBelowSplitDnd =
          parallelPhaseOrder != null &&
          dropSectionBand?.slot === sectionToSlot &&
          dropSectionBand?.phaseBelowSplit?.po === parallelPhaseOrder &&
          dropSectionBand?.phaseBelowSplit?.so === activeParallelStream

        const streamVisual =
          enableParallelPhaseControls && pl?.phaseKind === 'parallel'
            ? parallelStreamVisual(pl.parallelStreamOrderIndex ?? 0)
            : null
        const allowSectionDragGrip = enableSectionDragReorder

        return (
          <Fragment key={`secFrag-${sIdx}`}>
            {enableSectionDragReorder && showSectionDropBandBefore ? (
              <div
                className={
                  'tu-section-dropband' +
                  sectionDropBandRegionClass(list, sIdx, enableParallelPhaseControls) +
                  (bandActiveBefore(sIdx) ? ' tu-section-dropband--active' : '')
                }
                title="Abschnitt hier einfügen"
                onDragOver={(e) => {
                  if (!enableSectionDragReorder) return
                  if (!dtHasType(e, DND_TU_SECTION)) return
                  onSectionBandDragOver(e, sIdx)
                }}
                onDragLeave={(e) => {
                  if (e.currentTarget.contains(e.relatedTarget)) return
                  clearSectionDnD()
                }}
                onDrop={(e) => onSectionBandDrop(e, sIdx)}
              />
            ) : null}
            {isFirstSectionOfParallelPhase &&
            enableParallelPhaseControls &&
            streamOrdersForParallelPhase.length ? (
              <Fragment key={`phase-edge-${parallelPhaseOrder}`}>
                {enableSectionDragReorder ? (
                  <div
                    className={
                      'tu-section-dropband tu-phase-drop--above-split tu-section-dropband--phase-parallel-slot' +
                      sectionDropBandRegionClass(
                        list,
                        firstGlobalIdxThisPhase ?? sIdx,
                        enableParallelPhaseControls
                      ) +
                      (phaseAboveSplitDnd ? ' tu-section-dropband--active' : '')
                    }
                    title="Oberhalb der Split-Phase: Abschnitt in die Ganzgruppe ziehen"
                    aria-label="Dropzone Ganzgruppe oberhalb der Split-Phase"
                    onDragOver={(e) => onPhaseAboveSplitDragOver(e, parallelPhaseOrder)}
                    onDragLeave={(e) => {
                      if (e.currentTarget.contains(e.relatedTarget)) return
                      clearSectionDnD()
                    }}
                    onDrop={(e) => onPhaseAboveSplitDrop(e, parallelPhaseOrder)}
                  />
                ) : null}
              <div
                style={{
                  marginBottom: '12px',
                  padding: '10px 12px',
                  background: 'var(--surface2)',
                  borderRadius: '10px',
                  border: '1px solid var(--border, rgba(0,0,0,0.08))',
                }}
              >
                <div
                  style={{
                    display: 'flex',
                    flexWrap: 'wrap',
                    gap: '8px',
                    alignItems: 'center',
                    marginBottom: '10px',
                  }}
                >
                  {enableSectionDragReorder ? (
                    <span
                      className="tu-sec-drag-grip"
                      draggable
                      onDragStart={(e) => onParallelPhaseDragStart(e, parallelPhaseOrder)}
                      role="button"
                      tabIndex={0}
                      aria-label="Parallele Phase ziehen"
                      title="Gesamte parallele Phase an neue Planposition ziehen"
                    >
                      <GripVertical size={16} strokeWidth={2} aria-hidden />
                    </span>
                  ) : null}
                  <label
                    className="form-label"
                    style={{ fontSize: '0.78rem', marginBottom: 0, flex: '0 0 auto' }}
                  >
                    Phase
                  </label>
                  {(() => {
                    const hi = firstSectionIndexByParallelPhase.get(parallelPhaseOrder)
                    const phaseTitleStr =
                      hi != null && list[hi]?.planLoc?.phaseTitle != null
                        ? String(list[hi].planLoc.phaseTitle)
                        : ''
                    const editingPhase = phaseTitleEditPo === parallelPhaseOrder
                    return editingPhase ? (
                      <input
                        className="form-input"
                        style={{ flex: '2 1 200px', maxWidth: '320px', marginBottom: 0 }}
                        autoFocus
                        value={phaseTitleDraft}
                        onChange={(e) => setPhaseTitleDraft(e.target.value)}
                        onBlur={() => {
                          if (!skipPhaseTitleBlurSave.current) {
                            updateParallelPhaseTitleAll(parallelPhaseOrder, phaseTitleDraft)
                          }
                          skipPhaseTitleBlurSave.current = false
                          setPhaseTitleEditPo(null)
                        }}
                        onKeyDown={(e) => {
                          if (e.key === 'Enter') e.currentTarget.blur()
                          if (e.key === 'Escape') {
                            skipPhaseTitleBlurSave.current = true
                            setPhaseTitleEditPo(null)
                            e.currentTarget.blur()
                          }
                        }}
                        placeholder="Bezeichnung der Phase (z. B. Drill-Runde)"
                      />
                    ) : (
                      <>
                        <span
                          style={{
                            flex: '2 1 200px',
                            maxWidth: '320px',
                            fontSize: '0.86rem',
                            fontWeight: 600,
                            color: 'var(--text1)',
                            lineHeight: 1.35,
                            padding: '6px 4px',
                          }}
                        >
                          {(phaseTitleStr || '').trim() ||
                            `Phase ${parallelPhaseOrder} · Namen per Stift bearbeiten`}
                        </span>
                        <button
                          type="button"
                          className="tu-icon-btn"
                          style={{ padding: '6px', color: 'var(--text2)' }}
                          aria-label="Phasen-Bezeichnung bearbeiten"
                          title="Namen bearbeiten"
                          onClick={() => {
                            setPhaseTitleEditPo(parallelPhaseOrder)
                            setPhaseTitleDraft(phaseTitleStr)
                          }}
                        >
                          <Pencil size={16} strokeWidth={2} aria-hidden />
                        </button>
                      </>
                    )
                  })()}
                  {(() => {
                    const prRunIdx = planningPhaseRuns.findIndex(
                      (r) => r.phaseKind === 'parallel' && r.phaseOrderIndex === parallelPhaseOrder
                    )
                    const chipPhaseUpDis = prRunIdx <= 0
                    const chipPhaseDownDis =
                      prRunIdx < 0 || prRunIdx >= planningPhaseRuns.length - 1
                    return (
                      <div
                        style={{
                          display: 'inline-flex',
                          gap: '4px',
                          alignItems: 'center',
                          marginRight: '2px',
                        }}
                      >
                        <button
                          type="button"
                          className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                          disabled={chipPhaseUpDis}
                          aria-label="Parallelen Block nach oben"
                          title="Gesamten parallelen Block im Plan nach oben schieben"
                          onClick={() =>
                            patch((p) => movePhaseRunUpByPhaseOrder(p, parallelPhaseOrder))
                          }
                          style={{ padding: '4px 10px' }}
                        >
                          ▲
                        </button>
                        <button
                          type="button"
                          className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                          disabled={chipPhaseDownDis}
                          aria-label="Parallelen Block nach unten"
                          title="Gesamten parallelen Block im Plan nach unten schieben"
                          onClick={() =>
                            patch((p) => movePhaseRunDownByPhaseOrder(p, parallelPhaseOrder))
                          }
                          style={{ padding: '4px 10px' }}
                        >
                          ▼
                        </button>
                      </div>
                    )
                  })()}
                  <button
                    type="button"
                    className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                    onClick={() => addStreamToParallelPhase(parallelPhaseOrder)}
                    disabled={streamOrdersForParallelPhase.length >= MAX_PARALLEL_STREAMS_PER_PHASE}
                    title={
                      streamOrdersForParallelPhase.length >= MAX_PARALLEL_STREAMS_PER_PHASE
                        ? `Höchstens ${MAX_PARALLEL_STREAMS_PER_PHASE} Streams`
                        : 'Weitere parallele Gruppe'
                    }
                  >
                    + Stream
                  </button>
                  <button
                    type="button"
                    className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                    onClick={() =>
                      addSectionToParallelStream(parallelPhaseOrder, activeParallelStream ?? 0)
                    }
                    title="Neuer Abschnitt im gerade gewählten Stream"
                  >
                    + Abschnitt in diesem Stream
                  </button>
                </div>
                <div
                  role="tablist"
                  aria-label={`Streams · Phase ${parallelPhaseOrder}`}
                  style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'stretch' }}
                >
                  {streamOrdersForParallelPhase.map((so) => {
                    const sel =
                      (parallelStreamTabByPhase[parallelPhaseOrder] ??
                        streamOrdersForParallelPhase[0] ??
                        0) === so
                    const pv = parallelStreamVisual(so)
                    const si = sectionIndicesForParallelStream(list, parallelPhaseOrder, so)
                    const titleSource = si.length ? list[si[0]]?.planLoc?.streamTitle : null
                    const streamName = titleSource != null ? String(titleSource) : ''
                    const editKey = `${parallelPhaseOrder}:${so}`
                    const editingStream = streamNameEditKey === editKey
                    return (
                      <div
                        key={`p${parallelPhaseOrder}-chip-s${so}`}
                        className={
                          'tu-stream-chip-pill' +
                          (streamChipDropActive(parallelPhaseOrder, so)
                            ? ' tu-stream-chip-pill--drop-active'
                            : '')
                        }
                        style={{
                          display: 'inline-flex',
                          alignItems: 'center',
                          gap: '4px',
                          padding: '4px 6px 4px 8px',
                          borderRadius: '999px',
                          border: sel ? `2px solid ${pv.border}` : `1px solid ${pv.border}`,
                          background: sel ? pv.tabBgActive : pv.tabBg,
                          maxWidth: '100%',
                        }}
                        title={
                          useStreamTagDropUx
                            ? 'Gruppe wählen oder Abschnitt hierher ziehen'
                            : undefined
                        }
                        onDragOver={
                          useStreamTagDropUx
                            ? (e) => onStreamDropTargetDragOver(e, parallelPhaseOrder, so)
                            : undefined
                        }
                        onDragLeave={
                          useStreamTagDropUx ? onStreamDropTargetDragLeave : undefined
                        }
                        onDrop={
                          useStreamTagDropUx
                            ? (e) => onStreamDropTargetDrop(e, parallelPhaseOrder, so)
                            : undefined
                        }
                      >
                        {editingStream ? (
                          <input
                            className="form-input"
                            autoFocus
                            style={{
                              minWidth: '7rem',
                              maxWidth: '12rem',
                              margin: 0,
                              padding: '4px 8px',
                              fontSize: '0.8rem',
                              borderRadius: '8px',
                            }}
                            value={streamNameDraft}
                            onChange={(e) => setStreamNameDraft(e.target.value)}
                            onBlur={() => {
                              if (!skipStreamNameBlurSave.current) {
                                updateParallelStreamTitleAll(parallelPhaseOrder, so, streamNameDraft)
                              }
                              skipStreamNameBlurSave.current = false
                              setStreamNameEditKey(null)
                            }}
                            onKeyDown={(e) => {
                              if (e.key === 'Enter') e.currentTarget.blur()
                              if (e.key === 'Escape') {
                                skipStreamNameBlurSave.current = true
                                setStreamNameEditKey(null)
                                e.currentTarget.blur()
                              }
                            }}
                            onClick={(e) => e.stopPropagation()}
                            placeholder={`Gruppe ${so + 1}`}
                            aria-label={`Name Gruppe ${so + 1}`}
                          />
                        ) : (
                          <button
                            type="button"
                            role="tab"
                            aria-selected={sel}
                            style={{
                              flex: '1 1 auto',
                              minWidth: '5.5rem',
                              maxWidth: '12rem',
                              margin: 0,
                              padding: '6px 10px',
                              fontSize: '0.85rem',
                              fontWeight: sel ? 600 : 500,
                              border: 'none',
                              background: 'transparent',
                              color: 'var(--text1)',
                              textAlign: 'left',
                              cursor: 'pointer',
                              borderRadius: '8px',
                            }}
                            onClick={() =>
                              setParallelStreamTabByPhase((prev) => ({
                                ...prev,
                                [parallelPhaseOrder]: so,
                              }))
                            }
                          >
                            {(streamName || '').trim() || `Gruppe ${so + 1}`}
                          </button>
                        )}
                        <button
                          type="button"
                          className="tu-icon-btn"
                          style={{
                            flex: '0 0 auto',
                            padding: '4px',
                            color: 'var(--text2)',
                            borderRadius: '8px',
                          }}
                          title="Gruppennamen bearbeiten"
                          aria-label="Gruppennamen bearbeiten"
                          onClick={(e) => {
                            e.stopPropagation()
                            setStreamNameEditKey(editKey)
                            setStreamNameDraft(streamName)
                          }}
                        >
                          <Pencil size={15} strokeWidth={2} aria-hidden />
                        </button>
                        <button
                          type="button"
                          className="tu-icon-btn"
                          style={{
                            flex: '0 0 auto',
                            padding: '4px',
                            color: 'var(--text2)',
                            borderRadius: '8px',
                          }}
                          title="Stream entfernen"
                          aria-label={`Stream ${so + 1} entfernen`}
                          onClick={(e) => {
                            e.stopPropagation()
                            removeParallelStream(parallelPhaseOrder, so)
                          }}
                        >
                          <X size={16} strokeWidth={2} aria-hidden />
                        </button>
                      </div>
                    )
                  })}
                </div>
              </div>
                {enableSectionDragReorder ? (
                  <div
                    className={
                      'tu-section-dropband tu-phase-drop--below-split tu-section-dropband--phase-parallel-slot' +
                      sectionDropBandRegionClass(
                        list,
                        firstVisibleIdxActiveStream ?? firstGlobalIdxThisPhase ?? sIdx,
                        enableParallelPhaseControls
                      ) +
                      (phaseBelowSplitDnd ? ' tu-section-dropband--active' : '')
                    }
                    title="Erster Abschnitt in dieser Gruppe (hier vorne einfügen)"
                    aria-label="Dropzone: erster Slot der gewählten Splitgruppe unter dem Split-Kopf"
                    onDragOver={(e) =>
                      onPhaseBelowSplitDragOver(
                        e,
                        parallelPhaseOrder,
                        activeParallelStream ?? 0
                      )
                    }
                    onDragLeave={(e) => {
                      if (e.currentTarget.contains(e.relatedTarget)) return
                      clearSectionDnD()
                    }}
                    onDrop={(e) =>
                      onPhaseBelowSplitDrop(
                        e,
                        parallelPhaseOrder,
                        activeParallelStream ?? 0
                      )
                    }
                  />
                ) : null}
              </Fragment>
            ) : null}
            {!hideParallelSection ? (
            <>
            <div
              className="tu-section-shell"
              style={{
                marginBottom: '1rem',
                padding: '0.75rem',
                background: streamVisual ? streamVisual.soft : 'var(--surface2)',
                borderRadius: '10px',
                border: streamVisual
                  ? `1px solid ${streamVisual.border}`
                  : '1px solid var(--border, rgba(0,0,0,0.08))',
                borderLeft: streamVisual ? `5px solid ${streamVisual.border}` : undefined,
              }}
            >
              <div
                style={{
                  display: 'flex',
                  flexWrap: 'wrap',
                  gap: '0.5rem',
                  marginBottom: '0.5rem',
                  alignItems: 'flex-start',
                }}
              >
                {allowSectionDragGrip ? (
                  <span
                    className="tu-sec-drag-grip"
                    draggable
                    onDragStart={(e) => onSectionDragStart(e, sIdx)}
                    role="button"
                    tabIndex={0}
                    aria-label="Abschnitt ziehen"
                    title="Abschnitt ziehen"
                  >
                    <GripVertical size={16} strokeWidth={2} aria-hidden />
                  </span>
                ) : null}
                <input
                  className="form-input"
                  style={{ flex: '2 1 180px', marginBottom: 0 }}
                  value={sec.title}
                  onChange={(e) => updateSectionField(sIdx, 'title', e.target.value)}
                  placeholder="Abschnittstitel (z. B. Aufwärmen)"
                />
                <div style={{ display: 'flex', gap: '4px', alignSelf: 'center' }}>
                  <button
                    type="button"
                    aria-label="Abschnitt hoch"
                    onClick={() => moveSection(sIdx, -1)}
                    disabled={sectionMoveDisabledUp(sIdx)}
                    style={{
                      padding: '4px 10px',
                      opacity: sectionMoveDisabledUp(sIdx) ? 0.35 : 1,
                    }}
                  >
                    ▲
                  </button>
                  <button
                    type="button"
                    aria-label="Abschnitt runter"
                    onClick={() => moveSection(sIdx, 1)}
                    disabled={sectionMoveDisabledDown(sIdx)}
                    style={{
                      padding: '4px 10px',
                      opacity: sectionMoveDisabledDown(sIdx) ? 0.35 : 1,
                    }}
                  >
                    ▼
                  </button>
                </div>
                <button
                  type="button"
                  className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                  onClick={() => removeSection(sIdx)}
                >
                  Abschnitt entfernen
                </button>
              </div>
              {enableParallelPhaseControls && sec.planLoc ? (
                <p
                  style={{
                    fontSize: '0.75rem',
                    color: 'var(--text2)',
                    margin: '0 0 8px',
                    fontWeight: 500,
                  }}
                >
                  {sec.planLoc.phaseKind === 'whole_group'
                    ? (() => {
                        const pt = sec.planLoc.phaseTitle
                        const po = sec.planLoc.phaseOrderIndex ?? 0
                        return pt != null && String(pt).trim()
                          ? `Ganzgruppe: ${String(pt).trim()} (Phase ${po})`
                          : `Ganzgruppen-Phase ${po}`
                      })()
                    : (() => {
                        const pt = sec.planLoc.phaseTitle
                        const st = sec.planLoc.streamTitle
                        const po = sec.planLoc.phaseOrderIndex ?? 0
                        const so = sec.planLoc.parallelStreamOrderIndex ?? 0
                        const phaseLbl =
                          pt != null && String(pt).trim() ? String(pt).trim() : `Phase ${po}`
                        const streamLbl =
                          st != null && String(st).trim() ? String(st).trim() : `Gruppe ${so + 1}`
                        return `Parallel · ${phaseLbl} · ${streamLbl}`
                      })()}
                </p>
              ) : null}
              <textarea
                className="form-input"
                rows={2}
                value={sec.guidance_notes}
                onChange={(e) =>
                  updateSectionField(sIdx, 'guidance_notes', e.target.value)
                }
                placeholder="Hinweise zum Abschnitt (Material, Zeit, Zielrichtung …)"
              />
              {enableParallelPhaseControls ? (
                <div className="form-row" style={{ marginTop: '10px', marginBottom: '2px' }}>
                  <label className="form-label" style={{ fontSize: '0.78rem' }}>
                    Zuordnung
                  </label>
                  <select
                    className="form-input"
                    value={planLocKey(sec.planLoc)}
                    onChange={(e) => applySectionPlanTarget(sIdx, e.target.value)}
                  >
                    <option value="">Standard — eine Ganzgruppe (klassischer Ablauf)</option>
                    {planSelectOptionsForSection(list, sIdx, buildPlanTargetOptions(list)).map((o) => (
                      <option key={o.key} value={o.key}>
                        {o.label}
                      </option>
                    ))}
                  </select>
                </div>
              ) : null}
              {planMin > 0 && (
                <p style={{ fontSize: '0.78rem', color: 'var(--text2)', marginTop: '0.35rem' }}>
                  Geplant in diesem Abschnitt: ca. {planMin} Min. (Übungen)
                </p>
              )}

              {betweenInsertMenus ? renderBetweenInsertBand(sIdx, 0, itemCount) : null}

              {(sec.items || []).map((it, iIdx) => {
                const dropHere =
                  enableItemDragReorder &&
                  dropTargetPos?.sIdx === sIdx &&
                  dropTargetPos?.iIdx === iIdx
                const dragHere =
                  enableItemDragReorder &&
                  draggingPos?.sIdx === sIdx &&
                  draggingPos?.iIdx === iIdx
                const rowCommon =
                  'tu-item-row' +
                  (dropHere ? ' tu-item-row--drop-target' : '') +
                  (dragHere ? ' tu-item-row--dragging' : '')

                const dndRowProps = enableItemDragReorder
                  ? {
                      onDragOverCapture: (ev) => onItemDragOverRow(ev, sIdx, iIdx),
                      onDrop: (ev) => onItemDropRow(ev, sIdx, iIdx),
                    }
                  : {}

                const prevIt = iIdx > 0 ? sec.items[iIdx - 1] : null
                const curMn = normalizedPlanningModuleChainId(it.source_training_module_id)
                const showModuleBand =
                  curMn != null && curMn !== normalizedPlanningModuleChainId(prevIt?.source_training_module_id)
                const modBandTitle =
                  (it.source_module_title || '').trim() ||
                  (curMn != null ? `Modul #${curMn}` : '')

                const modOutline =
                  !planningCompactLegend &&
                  showModuleBand &&
                  curMn != null
                    ? gatherPlanningModuleOutline(sec.items, iIdx, curMn)
                    : null
                const fromModClass =
                  curMn != null
                    ? planningCompactLegend
                      ? ' tu-item-row--from-module-soft'
                      : ' tu-item-row--from-module'
                    : ''
                const modBorderVarStyle =
                  planningCompactLegend && curMn != null
                    ? { '--tu-mod-border': planningModulePalette(curMn).border }
                    : undefined

                if (it.item_type === 'note') {
                  const isSepLine = (it.note_body || '').trim() === SECTION_INSERT_SEPARATOR_BODY
                  const notePv = truncatePreview(it.note_body || '', 260)
                  const noteHasText = Boolean((it.note_body || '').trim()) && !isSepLine
                  return (
                    <Fragment key={`${insertSlotKeyPrefix}sec-${sIdx}-blk-${iIdx}`}>
                      {!planningCompactLegend &&
                        renderModulePlanningHead(modBandTitle, modOutline, showModuleBand)}
                      <div
                        className={
                          `${rowCommon} tu-item-row--note` +
                          (isSepLine ? ' tu-item-row--separator-note' : '') +
                          fromModClass
                        }
                        {...dndRowProps}
                        style={modBorderVarStyle}
                      >
                      {enableItemDragReorder ? (
                        <span
                          className="tu-row-grip"
                          draggable
                          onDragStart={(e) => onItemDragStart(e, sIdx, iIdx)}
                          onDragEnd={clearDragChrome}
                          role="button"
                          tabIndex={0}
                          aria-label="Eintrag ziehen"
                        >
                          <GripVertical size={15} strokeWidth={2} aria-hidden />
                        </span>
                      ) : null}
                      <div className="tu-item-row__nudge">
                        <button
                          type="button"
                          aria-label="Eintrag nach oben"
                          onClick={() => moveItem(sIdx, iIdx, -1)}
                          disabled={iIdx === 0}
                        >
                          ▲
                        </button>
                        <button
                          type="button"
                          aria-label="Eintrag nach unten"
                          onClick={() => moveItem(sIdx, iIdx, 1)}
                          disabled={iIdx === sec.items.length - 1}
                        >
                          ▼
                        </button>
                      </div>
                      <div className="tu-item-row__body tu-item-row__body--note">
                        {!isSepLine && planningCompactLegend && curMn ? (
                          <PlanningModuleRowTag moduleId={curMn} title={modBandTitle} />
                        ) : null}
                        <span className="tu-item-row__meta-label">
                          {isSepLine ? 'Trennung' : 'Zwischen-Anmerkung'}
                        </span>
                        {isSepLine ? (
                          <div
                            className="tu-item-row__separator-line"
                            role="separator"
                            aria-label="Trennlinie im Ablauf"
                          />
                        ) : (
                          <p
                            className={`tu-item-row__preview tu-item-row__preview--clamp${noteHasText ? '' : ' tu-item-row__preview--empty'}`}
                            title={noteHasText ? (it.note_body || '').trim() : undefined}
                          >
                            {noteHasText ? notePv : '—'}
                          </p>
                        )}
                      </div>
                      <button
                        type="button"
                        className="tu-icon-btn"
                        title={isSepLine ? 'Trennung bearbeiten' : 'Zwischen-Anmerkung bearbeiten'}
                        aria-label={isSepLine ? 'Trennung bearbeiten' : 'Zwischen-Anmerkung bearbeiten'}
                        onClick={() =>
                          setTextEdit({
                            kind: 'zwischen-note',
                            sIdx,
                            iIdx,
                            draft: it.note_body || '',
                          })
                        }
                      >
                        <Pencil size={15} strokeWidth={2} aria-hidden />
                      </button>
                      <button
                        type="button"
                        className="tu-item-row__remove"
                        title="Entfernen"
                        aria-label={isSepLine ? 'Trennung entfernen' : 'Zwischen-Anmerkung entfernen'}
                        onClick={() => removeItem(sIdx, iIdx)}
                      >
                        ✗
                      </button>
                    </div>
                    {betweenInsertMenus ? renderBetweenInsertBand(sIdx, iIdx + 1, itemCount) : null}
                    </Fragment>
                  )
                }

                const variantOpts = Array.isArray(it.variants) ? it.variants : []
                const exTitle =
                  it.exercise_title || (it.exercise_id ? `Übung #${it.exercise_id}` : '')
                const isCombination =
                  String(it.exercise_kind || 'simple').toLowerCase().trim() === 'combination'
                const annotPrev = truncatePreview(it.notes || '', 220)
                const annotHasText = Boolean((it.notes || '').trim())
                const hasVariants = !isCombination && variantOpts.length > 0 && it.exercise_id
                const variantIdPeek =
                  it.exercise_variant_id === '' || it.exercise_variant_id == null
                    ? undefined
                    : Number(it.exercise_variant_id)

                const stripArchRaw =
                  isCombination && it.exercise_id ? String(it.catalog_method_archetype || '').trim() : ''
                const stripMpEff =
                  isCombination && it.exercise_id
                    ? effectiveComboMethodProfile(
                        it.catalog_method_profile || {},
                        it.planning_method_profile,
                      )
                    : null

                return (
                  <Fragment key={`${insertSlotKeyPrefix}sec-${sIdx}-blk-${iIdx}`}>
                    {!planningCompactLegend &&
                      renderModulePlanningHead(modBandTitle, modOutline, showModuleBand)}
                    <div
                      className={`${rowCommon} tu-item-row--exercise${fromModClass}${
                        isCombination && it.exercise_id ? ' tu-item-row--combo' : ''
                      }`}
                      {...dndRowProps}
                      style={modBorderVarStyle}
                    >
                    <div className="tu-item-row__mainline">
                      {enableItemDragReorder ? (
                        <span
                          className="tu-row-grip"
                          draggable
                          onDragStart={(e) => onItemDragStart(e, sIdx, iIdx)}
                          onDragEnd={clearDragChrome}
                          role="button"
                          tabIndex={0}
                          aria-label="Eintrag ziehen"
                        >
                          <GripVertical size={15} strokeWidth={2} aria-hidden />
                        </span>
                      ) : null}
                      <div className="tu-item-row__nudge">
                        <button
                          type="button"
                          aria-label="Eintrag nach oben"
                          onClick={() => moveItem(sIdx, iIdx, -1)}
                          disabled={iIdx === 0}
                        >
                          ▲
                        </button>
                        <button
                          type="button"
                          aria-label="Eintrag nach unten"
                          onClick={() => moveItem(sIdx, iIdx, 1)}
                          disabled={iIdx === sec.items.length - 1}
                        >
                          ▼
                        </button>
                      </div>
                      <div className="tu-item-row__body tu-item-row__body--exercise">
                        <div className="tu-ex-title-line">
                          {exTitle ? (
                            <strong className="tu-ex-title">{exTitle}</strong>
                          ) : (
                            <span className="tu-ex-title-placeholder">Keine Übung gewählt</span>
                          )}
                          {isCombination ? (
                            <span
                              className="exercise-tag"
                              style={{
                                marginLeft: 8,
                                fontSize: '11px',
                                alignSelf: 'center',
                                background: 'var(--accent-soft)',
                                color: 'var(--accent-dark)',
                              }}
                            >
                              Kombination
                            </span>
                          ) : null}
                          {planningCompactLegend && curMn ? (
                            <PlanningModuleRowTag moduleId={curMn} title={modBandTitle} />
                          ) : null}
                          <span className="tu-ex-inline-actions">
                            <button
                              type="button"
                              className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                              onClick={() =>
                                onRequestExercisePick?.({
                                  sectionIndex: sIdx,
                                  itemIndex: iIdx,
                                })
                              }
                            >
                              {exTitle ? 'Wechseln' : 'Übung suchen…'}
                            </button>
                            {it.exercise_id && onPeekExercise ? (
                              <button
                                type="button"
                                className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                                onClick={() =>
                                  onPeekExercise(
                                    Number(it.exercise_id),
                                    variantIdPeek,
                                    isCombination
                                      ? {
                                          catalog_method_profile: it.catalog_method_profile,
                                          planning_method_profile: it.planning_method_profile,
                                        }
                                      : undefined,
                                  )
                                }
                              >
                                Vorschau
                              </button>
                            ) : null}
                          </span>
                        </div>
                        <div className="tu-ex-meta-line">
                          {hasVariants ? (
                            <select
                              className={`form-input tu-ex-variant-select${
                                wideExerciseGrid ? ' tu-ex-variant-select--wide' : ''
                              }`}
                              value={
                                it.exercise_variant_id === '' ||
                                it.exercise_variant_id == null
                                  ? ''
                                  : String(it.exercise_variant_id)
                              }
                              onChange={(e) => {
                                const raw = e.target.value
                                updateItem(
                                  sIdx,
                                  iIdx,
                                  'exercise_variant_id',
                                  raw === '' ? '' : parseInt(raw, 10)
                                )
                              }}
                              title="Übungsvariante"
                            >
                              <option value="">Stammübung</option>
                              {variantOpts.map((v) => (
                                <option key={v.id} value={v.id}>
                                  {v.variant_name || `Variante #${v.id}`}
                                </option>
                              ))}
                            </select>
                          ) : null}
                          <div className="tu-ex-annot">
                            <span
                              className={`tu-item-row__preview tu-ex-annot__text${annotHasText ? '' : ' tu-item-row__preview--empty'}`}
                              title={annotHasText ? (it.notes || '').trim() : undefined}
                            >
                              {annotHasText ? annotPrev : '—'}
                            </span>
                            <button
                              type="button"
                              className="tu-icon-btn"
                              title="Anmerkung zur Übung"
                              aria-label="Anmerkung zur Übung bearbeiten"
                              onClick={() =>
                                setTextEdit({
                                  kind: 'exercise-notes',
                                  sIdx,
                                  iIdx,
                                  draft: it.notes || '',
                                })
                              }
                            >
                              <Pencil size={15} strokeWidth={2} aria-hidden />
                            </button>
                          </div>
                        </div>
                      </div>
                      <div className="tu-item-row__side">
                        <input
                          type="number"
                          className="form-input tu-ex-duration"
                          min={1}
                          value={it.planned_duration_min}
                          onChange={(e) =>
                            updateItem(sIdx, iIdx, 'planned_duration_min', e.target.value)
                          }
                          placeholder="Min"
                          title="Geplante Dauer (Minuten)"
                        />
                        <button
                          type="button"
                          className="tu-item-row__remove"
                          title="Übung entfernen"
                          aria-label="Übung entfernen"
                          onClick={() => removeItem(sIdx, iIdx)}
                        >
                          ✗
                        </button>
                      </div>
                    </div>

                    {isCombination && it.exercise_id ? (
                      <div className="tu-combo-planning-strip">
                        <div className="tu-combo-planning-strip__toolbar">
                          <button
                            type="button"
                            className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                            aria-haspopup="dialog"
                            aria-label="Ablaufprofil Kombination für diese Planung bearbeiten"
                            onClick={() => setComboPlanningModal({ sIdx, iIdx })}
                          >
                            Ablauf bearbeiten…
                          </button>
                        </div>
                        {(it.combination_slots || []).length > 0 ? (
                          <div className="tu-combo-planning-strip__bracket-wrap">
                            <CombinationPlanBracket
                              className="combo-plan-bracket--planning-embed"
                              methodArchetype={stripArchRaw}
                              methodProfile={stripMpEff || {}}
                              combinationSlots={sortCombinationSlotsForDisplay(it.combination_slots)}
                              planningAdjusted={
                                it.planning_method_profile != null &&
                                typeof it.planning_method_profile === 'object' &&
                                !Array.isArray(it.planning_method_profile)
                              }
                              candidateInteraction={onPeekExercise ? 'button' : 'none'}
                              onCandidatePeek={
                                onPeekExercise
                                  ? (exId) => onPeekExercise(Number(exId), null, undefined)
                                  : undefined
                              }
                            />
                          </div>
                        ) : (
                          <div
                            className="tu-combo-planning-strip__meta tu-combo-planning-strip__meta--fallback"
                            title="Stationen aus dem Katalog — nach ersten Laden oder wenn die Kombination noch keine Slots hat."
                          >
                            <div style={{ fontSize: '0.74rem', color: 'var(--text3)', fontStyle: 'italic', margin: 0 }}>
                              Stationen werden geladen oder die Kombination hat im Katalog noch keine Stationsliste …
                            </div>
                          </div>
                        )}
                      </div>
                    ) : null}

                    {showExecutionExtras ? (
                      <div className="tu-ex-debrief">
                        <div className="tu-ex-debrief__grow">
                          <span className="tu-item-row__meta-label">Abweichungen beim Durchführen</span>
                          <textarea
                            className="form-input tu-ex-debrief__textarea"
                            rows={3}
                            value={it.modifications || ''}
                            onChange={(e) =>
                              updateItem(sIdx, iIdx, 'modifications', e.target.value)
                            }
                            placeholder="Was lief anders? Anpassungen für spätere Planung…"
                          />
                        </div>
                        <div className="tu-ex-debrief__ist">
                          <span className="tu-item-row__meta-label">Ist (Min)</span>
                          <input
                            type="number"
                            className="form-input tu-ex-duration"
                            min={1}
                            value={it.actual_duration_min}
                            onChange={(e) =>
                              updateItem(sIdx, iIdx, 'actual_duration_min', e.target.value)
                            }
                            placeholder="IST"
                            title="Tatsächliche Dauer (Minuten); dieselbe Spaltenbreite wie „Min“ (Plan) oben"
                          />
                        </div>
                      </div>
                    ) : null}
                  </div>
                  {betweenInsertMenus ? renderBetweenInsertBand(sIdx, iIdx + 1, itemCount) : null}
                  </Fragment>
                )
              })}

              {enableItemDragReorder ? (
                <div
                  className={`tu-item-append-drop${
                    dropTargetPos?.sIdx === sIdx && dropTargetPos?.iIdx === itemCount
                      ? ' tu-item-append-drop--active'
                      : ''
                  }`}
                  title="Hierhin ziehen, um nach unten einzufügen"
                  onDragOverCapture={(e) => onItemDragOverRow(e, sIdx, itemCount)}
                  onDrop={(e) => onItemDropRow(e, sIdx, itemCount)}
                />
              ) : null}

              <div style={{ marginTop: '0.65rem' }}>
                {betweenInsertMenus ? (
                  <p style={{ margin: 0, fontSize: '0.8rem', color: 'var(--text3)', lineHeight: 1.45, maxWidth: '42rem' }}>
                    Über die +-Zeilen zwischen den Einträgen fügst du an der gewünschten Stelle Inhalte ein. Reihenfolge
                    weiter per Ziehen oder den Pfeiltasten ändern.
                  </p>
                ) : (
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                    <button
                      type="button"
                      className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                      onClick={() => onRequestExercisePick?.({ sectionIndex: sIdx })}
                    >
                      + Übung
                    </button>
                    <button
                      type="button"
                      className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                      onClick={() => addItem(sIdx, 'note')}
                    >
                      + Anmerkung
                    </button>
                  </div>
                )}
              </div>

              {moduleLegend.length ? (
                <div
                  className="tu-section-mod-legend"
                  aria-label="Liste der eingebundenen Trainingsmodule in diesem Abschnitt"
                >
                  <div className="tu-section-mod-legend__caption">Übernommene Module im Abschnitt</div>
                  <ul className="tu-section-mod-legend__list">
                    {moduleLegend.map((e) => {
                      const pal = planningModulePalette(e.id)
                      return (
                        <li key={`mod-leg-${sIdx}-${e.id}`} className="tu-section-mod-legend__item">
                          <span
                            className="tu-section-mod-legend__swatch"
                            style={{ background: pal.border }}
                            title={`Farbe wie an den Zeilen (Modul #${e.id})`}
                            aria-hidden
                          />
                          <span className="tu-section-mod-legend__text">
                            <span className="tu-section-mod-legend__title">
                              {(e.title || '').trim() || `Modul #${e.id}`}
                            </span>
                            <span className="tu-section-mod-legend__meta">
                              ID {e.id} · {e.exercises}{' '}
                              {e.exercises === 1 ? 'Übung' : 'Übungen'}
                              {e.notes > 0 ? (
                                <>
                                  {' '}
                                  · {e.notes} {e.notes === 1 ? 'Zwischen-Hinweis' : 'Zwischen-Hinweise'}
                                </>
                              ) : null}
                            </span>
                          </span>
                        </li>
                      )
                    })}
                  </ul>
                </div>
              ) : null}
            </div>
            {useStreamTagDropUx && pl?.phaseKind === 'parallel' && parallelPhaseOrder != null
              ? (() => {
                  const bucket = sectionIndicesForParallelStream(
                    list,
                    parallelPhaseOrder,
                    pl.parallelStreamOrderIndex ?? 0
                  )
                  if (!bucket.length || bucket[bucket.length - 1] !== sIdx) return null
                  const pvA = parallelStreamVisual(pl.parallelStreamOrderIndex ?? 0)
                  const soA = pl.parallelStreamOrderIndex ?? 0
                  const sd = dropSectionBand?.streamDrop
                  const appendBandActive =
                    !!sd &&
                    dropSectionBand?.slot === sectionToSlot &&
                    sd.po === parallelPhaseOrder &&
                    sd.so === soA
                  return (
                    <div
                      className="tu-section-stream-append"
                      style={{
                        marginBottom: '1rem',
                        padding: '0.45rem 0.65rem',
                        borderRadius: '10px',
                        border: `1px dashed ${pvA.border}`,
                        background: pvA.soft,
                        borderLeft: `5px solid ${pvA.border}`,
                        boxSizing: 'border-box',
                      }}
                    >
                      <div
                        className={
                          'tu-section-stream-append__drop tu-section-dropband tu-section-dropband--region-split' +
                          (appendBandActive ? ' tu-section-dropband--active' : '')
                        }
                        title="Abschnitt am Ende dieser Gruppe einfügen (per Ziehen)"
                        aria-label="Dropzone: Abschnitt ans Ende dieser parallelen Gruppe"
                        onDragOver={(e) =>
                          onStreamDropTargetDragOver(e, parallelPhaseOrder, soA)
                        }
                        onDragLeave={onStreamDropTargetDragLeave}
                        onDrop={(e) =>
                          onStreamDropTargetDrop(e, parallelPhaseOrder, soA)
                        }
                      />
                    </div>
                  )
                })()
              : null}
            </>
            ) : null}
          </Fragment>
        )
      })}

      {enableSectionDragReorder ? (
        <div
          className={
            'tu-section-dropband tu-section-dropband--end' +
            (useStreamTagDropUx ? ' tu-section-dropband--whole-plan-end' : '') +
            (!useStreamTagDropUx
              ? sectionDropBandRegionClass(list, list.length, enableParallelPhaseControls)
              : '') +
            (dropSectionBand &&
            dropSectionBand.slot === sectionToSlot &&
            dropSectionBand.beforeIdx === list.length &&
            !dropSectionBand.streamDrop
              ? ' tu-section-dropband--active'
              : '')
          }
          title={
            useStreamTagDropUx
              ? 'Hier ablegen: neuer Abschnitt für die Ganzgruppe (am Planende)'
              : 'Abschnitt am Ende einfügen'
          }
          onDragOver={(e) => {
            if (!dtHasType(e, DND_TU_SECTION)) return
            onSectionBandDragOver(e, list.length)
          }}
          onDragLeave={(e) => {
            if (e.currentTarget.contains(e.relatedTarget)) return
            clearSectionDnD()
          }}
          onDrop={(e) => onSectionBandDrop(e, list.length)}
        />
      ) : null}

      <div
        style={{
          display: 'flex',
          flexWrap: 'wrap',
          gap: '8px',
          alignItems: 'center',
          marginTop: '0.75rem',
        }}
      >
        {enableParallelPhaseControls ? (
          <>
            <button
              type="button"
              className="btn btn-secondary framework-ctrl framework-ctrl--xs"
              onClick={addParallelPhaseTwoStreams}
              disabled={
                list.length > 0 &&
                list[list.length - 1]?.planLoc?.phaseKind === 'parallel'
              }
              title={
                list.length > 0 && list[list.length - 1]?.planLoc?.phaseKind === 'parallel'
                  ? 'Splitten direkt nach einem parallelen Block ist un\u00fcblich. Zuerst eine Ganzgruppen-Phase oder einen Ganzgruppen-Abschnitt anf\u00fcgen, dann erneut splitten.'
                  : 'Zwei parallele Gruppen mit je einem Abschnitt anlegen'
              }
            >
              Gruppen splitten
            </button>
            <button
              type="button"
              className="btn btn-secondary framework-ctrl framework-ctrl--xs"
              onClick={addWholeGroupSection}
              title="Neuer Abschnitt für die gemeinsame Gruppe (legt bei Bedarf eine neue Ganzgruppen-Phase an)"
            >
              + Abschnitt (Ganzgruppe)
            </button>
          </>
        ) : (
          <button
            type="button"
            className="btn btn-secondary framework-ctrl framework-ctrl--xs"
            onClick={addSection}
            title="Abschnitt am Ende anfügen"
          >
            + Abschnitt hinzufügen
          </button>
        )}
      </div>

      {insertChooser ? (
        <div
          className="tu-textedit-backdrop"
          role="presentation"
          onMouseDown={(e) => {
            if (e.target === e.currentTarget) closeInsertChooser()
          }}
        >
          <div
            className="tu-textedit-panel"
            role="dialog"
            aria-modal="true"
            aria-labelledby="tu-insert-chooser-title"
            onMouseDown={(e) => e.stopPropagation()}
          >
            <h4 id="tu-insert-chooser-title" className="tu-textedit-title">
              An dieser Stelle einfügen
            </h4>
            <p style={{ margin: '0 0 0.75rem', fontSize: '0.86rem', color: 'var(--text2)', lineHeight: 1.45 }}>
              Die neue Zeile erscheint genau hier; Reihenfolge kannst du wie gewohnt per Ziehen oder Pfeilen
              ändern.
            </p>
            <div className="tu-insert-chooser-actions">
              <button
                type="button"
                className="btn btn-primary tu-insert-chooser-actions__full"
                onClick={() => {
                  const { sIdx, beforeIx } = insertChooser
                  closeInsertChooser()
                  onRequestExercisePick?.({
                    sectionIndex: sIdx,
                    insertBeforeIndex: beforeIx,
                  })
                }}
              >
                Übung auswählen …
              </button>
              {onRequestTrainingModulePick ? (
                <button
                  type="button"
                  className="btn btn-secondary tu-insert-chooser-actions__full"
                  onClick={() => {
                    const ctx = { ...insertChooser }
                    closeInsertChooser()
                    onRequestTrainingModulePick({
                      sectionIndex: ctx.sIdx,
                      insertBeforeIndex: ctx.beforeIx,
                    })
                  }}
                >
                  Trainingsmodul …
                </button>
              ) : null}
              <button
                type="button"
                className="btn btn-secondary tu-insert-chooser-actions__full"
                onClick={() => {
                  const { sIdx, beforeIx } = insertChooser
                  insertItemAt(sIdx, beforeIx, noteRow())
                  closeInsertChooser()
                }}
              >
                Zwischen-Anmerkung
              </button>
              <button
                type="button"
                className="btn btn-secondary tu-insert-chooser-actions__full"
                onClick={() => {
                  const { sIdx, beforeIx } = insertChooser
                  const r = noteRow()
                  r.note_body = SECTION_INSERT_SEPARATOR_BODY
                  insertItemAt(sIdx, beforeIx, r)
                  closeInsertChooser()
                }}
              >
                Trennlinie
              </button>
              <button type="button" className="btn btn-secondary tu-insert-chooser-actions__full" onClick={closeInsertChooser}>
                Abbrechen
              </button>
            </div>
          </div>
        </div>
      ) : null}

      {comboPlanningModalItem != null &&
      comboPlanningModalSX != null &&
      comboPlanningModalIX != null ? (
        <div
          className="admin-modal-backdrop combo-planning-edit-backdrop"
          role="presentation"
          onMouseDown={(e) => {
            if (e.target === e.currentTarget) setComboPlanningModal(null)
          }}
        >
          <div
            className="admin-modal-sheet combo-planning-edit-sheet"
            role="dialog"
            aria-modal="true"
            aria-labelledby="tu-combo-planning-title"
            onMouseDown={(e) => e.stopPropagation()}
          >
            <div className="admin-modal-sheet__header">
              <div style={{ minWidth: 0 }}>
                <h3 id="tu-combo-planning-title" className="admin-modal-sheet__title">
                  {(comboPlanningModalItem.exercise_title || '').trim() ||
                    `Kombination #${comboPlanningModalItem.exercise_id}`}
                </h3>
                <p style={{ margin: '6px 0 0', fontSize: '0.82rem', color: 'var(--text2)', lineHeight: 1.4 }}>
                  Planung für diesen Termin · {compactComboPlanningCaption(comboPlanningModalItem)}
                </p>
              </div>
              <button
                type="button"
                className="btn btn-secondary admin-modal-sheet__close"
                onClick={() => setComboPlanningModal(null)}
              >
                Schließen
              </button>
            </div>
            <div className="admin-modal-sheet__body">
              <div className="combo-planning-edit-toolbar">
                <button
                  type="button"
                  className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                  onClick={() =>
                    updateItem(comboPlanningModalSX, comboPlanningModalIX, 'planning_method_profile', null)
                  }
                >
                  Planung wie Katalog
                </button>
                <button
                  type="button"
                  className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                  title="Bearbeitbare Kopie der Katalog-Vorgaben für diese Einheit setzen"
                  onClick={() =>
                    updateItem(comboPlanningModalSX, comboPlanningModalIX, 'planning_method_profile', {
                      ...(comboPlanningModalItem.catalog_method_profile || {}),
                    })
                  }
                >
                  Aus Katalog kopieren …
                </button>
              </div>
              <p className="combo-planning-edit-hint">
                Vorschau unten entspricht der effektiven Planung (Katalog oder Anpassung). Stationen und Einzelübungen
                kommen aus dem Katalog; hier änderst du nur Zeiten, Runden und Steuerung für diese Einheit.
              </p>
              {comboPlanningResolvedSlots.length > 0 ? (
                <div style={{ marginBottom: 18 }}>
                  <CombinationPlanBracket
                    methodArchetype={(comboPlanningModalItem.catalog_method_archetype || '').trim()}
                    methodProfile={comboPlanningEffectiveProfile}
                    combinationSlots={comboPlanningResolvedSlots}
                    planningAdjusted={
                      comboPlanningModalItem.planning_method_profile != null &&
                      typeof comboPlanningModalItem.planning_method_profile === 'object' &&
                      !Array.isArray(comboPlanningModalItem.planning_method_profile)
                    }
                    candidateInteraction={onPeekExercise ? 'button' : 'none'}
                    onCandidatePeek={
                      onPeekExercise
                        ? (exId) => onPeekExercise(Number(exId), null, undefined)
                        : undefined
                    }
                  />
                </div>
              ) : (
                <p style={{ margin: '0 0 18px', fontSize: '0.82rem', color: 'var(--text3)', lineHeight: 1.45 }}>
                  Stationen werden geladen … oder die Kombination hat im Katalog keine Stationsliste.
                </p>
              )}
              <div className="combo-planning-edit-card">
                <h4 className="combo-planning-edit-card__title">Globale und stationsbezogene Anpassungen</h4>
                <CombinationMethodProfileEditor
                  plannerMode
                  methodArchetype={(comboPlanningModalItem.catalog_method_archetype || '').trim()}
                  methodProfileJson={comboPlanningProfileJsonForEditor(
                    comboPlanningModalItem.catalog_method_profile || {},
                    comboPlanningModalItem.planning_method_profile
                  )}
                  onChangeMethodProfileJson={(json) => {
                    try {
                      const obj = JSON.parse(json || '{}')
                      if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
                        const cleaned = cloneJsonSerializablePlanningProfile(obj) ?? {}
                        updateItem(
                          comboPlanningModalSX,
                          comboPlanningModalIX,
                          'planning_method_profile',
                          Object.keys(cleaned).length ? cleaned : null,
                        )
                      }
                    } catch {
                      /* Ungültiges JSON — Hinweis im Editor */
                    }
                  }}
                  comboSlotsOutline={comboPlanningSlotsOutline}
                />
              </div>
            </div>
          </div>
        </div>
      ) : null}

      {textEdit ? (
        <div
          className="tu-textedit-backdrop"
          role="presentation"
          onMouseDown={(e) => {
            if (e.target === e.currentTarget) setTextEdit(null)
          }}
        >
          <div
            className="tu-textedit-panel"
            role="dialog"
            aria-modal="true"
            aria-labelledby="tu-textedit-title"
            onMouseDown={(e) => e.stopPropagation()}
          >
            <h4 id="tu-textedit-title" className="tu-textedit-title">
              {textEdit.kind === 'zwischen-note'
                ? 'Zwischen-Anmerkung'
                : 'Anmerkung zur Übung'}
            </h4>
            <textarea
              className="form-input tu-textedit-textarea"
              rows={5}
              value={textEdit.draft}
              onChange={(e) =>
                setTextEdit((prev) => (prev ? { ...prev, draft: e.target.value } : prev))
              }
              placeholder={
                textEdit.kind === 'zwischen-note'
                  ? 'Hinweise zwischen Übungen …'
                  : 'Kurze Anmerkung zur Übung'
              }
            />
            <div className="tu-textedit-actions">
              <button type="button" className="btn btn-primary" onClick={applyTextEdit}>
                Übernehmen
              </button>
              <button
                type="button"
                className="btn btn-secondary"
                onClick={() => setTextEdit(null)}
              >
                Abbrechen
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  )
}
