import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
import { GripVertical, Pencil } 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,
  streamTabLabelFromIndices,
  streamsForParallelPhaseOrders,
  sectionIndicesForParallelStream,
  reorderWithinBucketIndices,
  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)
}

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 }) => 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 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 addWholeGroupPhase = () => {
    patch((prev) => {
      const nextPo = maxPhaseOrderIndexFromSections(prev) + 1
      const pl = defaultPlanLocWholeGroup(nextPo)
      const base = defaultSection(`Abschnitt ${prev.length + 1}`)
      return [...prev, { ...base, planLoc: pl }]
    })
  }

  const addParallelPhase = () => {
    patch((prev) => {
      const nextPo = maxPhaseOrderIndexFromSections(prev) + 1
      const pl = defaultPlanLocParallel(nextPo, 0)
      const base = defaultSection(`Abschnitt ${prev.length + 1}`)
      return [...prev, { ...base, planLoc: pl }]
    })
  }

  const addStreamToLastParallelPhase = () => {
    patch((prev) => {
      const par = (prev || []).filter((s) => s?.planLoc?.phaseKind === 'parallel')
      if (!par.length) return prev
      const maxP = Math.max(...par.map((s) => s.planLoc.phaseOrderIndex ?? 0))
      const inPhase = par.filter((s) => (s.planLoc.phaseOrderIndex ?? 0) === maxP)
      const distinctStreams = new Set(inPhase.map((s) => s.planLoc.parallelStreamOrderIndex ?? 0))
      if (distinctStreams.size >= MAX_PARALLEL_STREAMS_PER_PHASE) return prev
      const maxS = Math.max(...inPhase.map((s) => s.planLoc.parallelStreamOrderIndex ?? 0))
      const newSo = maxS + 1
      const tmpl = {
        ...inPhase[0].planLoc,
        parallelStreamOrderIndex: newSo,
        streamTitle: null,
        streamNotes: null,
        streamAssignedTrainerProfileIds: null,
      }
      const base = defaultSection(`Abschnitt ${prev.length + 1}`)
      return [...prev, { ...base, planLoc: tmpl }]
    })
  }

  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) => {
      const next = prev.filter((_, i) => i !== sIdx)
      return next.length ? next : [defaultSection()]
    })
  }

  /** Ganzgruppe: global tauschen; parallele Phase: nur innerhalb desselben Streams sortieren. */
  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
        const newPos = pos + dir
        if (newPos < 0 || newPos >= bucket.length) return p
        return reorderWithinBucketIndices(p, bucket, pos, newPos)
      }
      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({})
  /** { 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 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 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 fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1
    if (typeof fromSi !== 'number') return

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

    patch((prev) => reorderBlocksImmutable(prev, fromSi, insertBeforeIdx))
  }

  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 list = ensure(sections)

  const hasParallelPhase = useMemo(
    () => list.some((s) => s?.planLoc?.phaseKind === 'parallel'),
    [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])

  const cannotAddMoreStreams = useMemo(() => {
    const par = list.filter((s) => s?.planLoc?.phaseKind === 'parallel')
    if (!par.length) return true
    const maxP = Math.max(...par.map((s) => s.planLoc.phaseOrderIndex ?? 0))
    const inPhase = par.filter((s) => (s.planLoc.phaseOrderIndex ?? 0) === maxP)
    const distinct = new Set(inPhase.map((s) => s.planLoc.parallelStreamOrderIndex ?? 0))
    return distinct.size >= MAX_PARALLEL_STREAMS_PER_PHASE
  }, [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)
      return pos <= 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)
      return pos < 0 || pos >= bucket.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}
      {enableParallelPhaseControls ? (
        <div
          className="card"
          style={{
            marginBottom: '1rem',
            padding: '12px 14px',
            background: 'var(--surface2)',
            border: '1px solid var(--border, rgba(0,0,0,0.08))',
            borderRadius: '10px',
          }}
        >
          <div style={{ fontSize: '0.88rem', fontWeight: 600, marginBottom: '6px', color: 'var(--text1)' }}>
            Breakout: Phasen und parallele Streams
          </div>
          <p style={{ margin: '0 0 12px', fontSize: '0.8rem', color: 'var(--text2)', lineHeight: 1.5 }}>
            Legt fest, ob Abschnitte zur ganzen Gruppe oder zu parallelen Gruppen gehören. Pro paralleler Phase erscheinen
            Reiter je Stream — nur der aktive Stream ist sichtbar (farbig am linken Rand). „Abschnitt hinzufügen“ übernimmt
            die Zuordnung des letzten Abschnitts. Speichern erzeugt bei Bedarf automatisch den{' '}
            <code style={{ fontSize: '0.78em' }}>phases</code>-Payload fürs Backend.
          </p>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' }}>
            <button type="button" className="btn btn-secondary" onClick={addWholeGroupPhase}>
              Neue Ganzgruppen-Phase
            </button>
            <button type="button" className="btn btn-secondary" onClick={addParallelPhase}>
              Neue parallele Phase
            </button>
            <button
              type="button"
              className="btn btn-secondary"
              onClick={addStreamToLastParallelPhase}
              disabled={!hasParallelPhase || cannotAddMoreStreams}
              title={
                !hasParallelPhase
                  ? 'Zuerst eine parallele Phase anlegen'
                  : cannotAddMoreStreams
                    ? `Höchstens ${MAX_PARALLEL_STREAMS_PER_PHASE} Streams pro Phase`
                    : 'Weiterer Stream in der letzten parallelen Phase (höchster Phasen-Index)'
              }
            >
              Stream hinzufügen
            </button>
          </div>
        </div>
      ) : null}
      {list.map((sec, sIdx) => {
        const planMin = sectionPlannedMinutes(sec)
        const itemCount = sec.items?.length ?? 0
        const moduleLegend = planningCompactLegend ? sectionModuleLegendModel(sec.items) : []
        const bandActiveBefore = (bx) =>
          enableSectionDragReorder &&
          dropSectionBand &&
          dropSectionBand.slot === sectionToSlot &&
          dropSectionBand.beforeIdx === bx

        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 isFirstSectionOfParallelPhase =
          parallelPhaseOrder != null &&
          firstSectionIndexByParallelPhase.get(parallelPhaseOrder) === sIdx
        const streamVisual =
          enableParallelPhaseControls && pl?.phaseKind === 'parallel'
            ? parallelStreamVisual(pl.parallelStreamOrderIndex ?? 0)
            : null
        const allowSectionDragGrip =
          enableSectionDragReorder && !(enableParallelPhaseControls && pl?.phaseKind === 'parallel')

        return (
          <Fragment key={`secFrag-${sIdx}`}>
            {enableSectionDragReorder ? (
              <div
                className={'tu-section-dropband' + (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 ? (
              <div
                role="tablist"
                aria-label={`Parallele Streams · Phase ${parallelPhaseOrder}`}
                style={{
                  display: 'flex',
                  flexWrap: 'wrap',
                  gap: '6px',
                  marginBottom: '10px',
                  padding: '8px 10px',
                  background: 'var(--surface2)',
                  borderRadius: '10px',
                  border: '1px solid var(--border, rgba(0,0,0,0.08))',
                }}
              >
                {streamOrdersForParallelPhase.map((so) => {
                  const sel =
                    (parallelStreamTabByPhase[parallelPhaseOrder] ??
                      streamOrdersForParallelPhase[0] ??
                      0) === so
                  const pv = parallelStreamVisual(so)
                  const lab = streamTabLabelFromIndices(
                    list,
                    sectionIndicesForParallelStream(list, parallelPhaseOrder, so),
                  )
                  return (
                    <button
                      key={`p${parallelPhaseOrder}-tab-s${so}`}
                      type="button"
                      role="tab"
                      aria-selected={sel}
                      className="btn framework-ctrl framework-ctrl--xs"
                      style={{
                        margin: 0,
                        border: sel ? `2px solid ${pv.border}` : `1px solid ${pv.border}`,
                        background: sel ? pv.tabBgActive : pv.tabBg,
                        color: 'var(--text1)',
                        fontWeight: sel ? 600 : 500,
                      }}
                      onClick={() =>
                        setParallelStreamTabByPhase((prev) => ({ ...prev, [parallelPhaseOrder]: so }))
                      }
                    >
                      {lab}
                    </button>
                  )
                })}
              </div>
            ) : 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'
                    ? `Ganzgruppen-Phase ${sec.planLoc.phaseOrderIndex ?? 0}`
                    : `Parallel · Phase ${sec.planLoc.phaseOrderIndex ?? 0} · Stream ${sec.planLoc.parallelStreamOrderIndex ?? 0}`}
                </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>
            ) : null}
          </Fragment>
        )
      })}

      {enableSectionDragReorder ? (
        <div
          className={
            'tu-section-dropband tu-section-dropband--end' +
            (dropSectionBand &&
            dropSectionBand.slot === sectionToSlot &&
            dropSectionBand.beforeIdx === list.length
              ? ' tu-section-dropband--active'
              : '')
          }
          title="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}

      <button
        type="button"
        className="btn btn-secondary framework-ctrl framework-ctrl--xs"
        onClick={addSection}
      >
        + Abschnitt hinzufügen
      </button>

      {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>
  )
}
