import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'
import api from '../utils/api'
import ExercisePickerModal from '../components/ExercisePickerModal'
import ExercisePeekModal from '../components/ExercisePeekModal'
import TrainingUnitSectionsEditor from '../components/TrainingUnitSectionsEditor'
import PageSectionNav from '../components/PageSectionNav'
import FormActionBar from '../components/FormActionBar'
import { useToast } from '../context/ToastContext'
import UnsavedChangesPrompt from '../components/UnsavedChangesPrompt'
import { useBeforeUnloadWhen, useUnsavedChangesBlocker } from '../hooks/useUnsavedChangesBlocker'
import {
  defaultSection,
  normalizeUnitToForm,
  enrichSectionsWithVariants,
  buildPlanPayloadForSave,
  hydrateExercisePlanningRow,
  reorderBlockIntoParallelStreamEnd,
  indicesOfParallelPhase,
  reorderSectionBeforeParallelRunAsWholeGroup,
  reorderSectionAsFirstInParallelStream,
} from '../utils/trainingUnitSectionsForm'

const DND_FW_SLOT = 'application/x-shinkan-framework-slot'

/** Unter dieser Breite: 2 Tabs (Stammdaten | Plan); darüber: alles untereinander */
const FRAMEWORK_DESKTOP_MIN_PX = 900

function reorderArray(arr, from, to) {
  if (from === to || from < 0 || from >= arr.length) return [...arr]
  const next = [...arr]
  const [it] = next.splice(from, 1)
  const t = Math.max(0, Math.min(to, next.length))
  next.splice(t, 0, it)
  return next
}

function slotChipLabel(slot, idx) {
  const t = (slot?.title || '').trim()
  return t || `Session ${idx + 1}`
}

function emptyGoal() {
  return { title: '', notes: '' }
}


function emptySlot() {
  return { title: '', notes: '', sections: [defaultSection('Ablauf')] }
}

async function enrichFrameworkSlotSections(slots) {
  const out = []
  for (const s of slots || []) {
    const baseSecs = s.sections && s.sections.length ? s.sections : [defaultSection('Ablauf')]
    out.push({
      ...s,
      sections: await enrichSectionsWithVariants(baseSecs),
    })
  }
  return out
}

function goalHoverText(g) {
  const t = (g.title || '').trim() || 'Ohne Titel'
  const n = (g.notes || '').trim()
  if (!n) return t
  const combined = `${t} — ${n}`
  return combined.length > 280 ? `${combined.slice(0, 277)}…` : combined
}

function defaultForm() {
  return {
    title: '',
    description: '',
    focus_area_id: '',
    style_direction_id: '',
    training_type_ids: [],
    target_group_ids: [],
    planned_period_start: '',
    planned_period_end: '',
    visibility: 'private',
    club_id: '',
    goals: [emptyGoal()],
    slots: [],
  }
}

function frameworkDraftSnapshot(fm) {
  const goalsNorm = (fm.goals || []).map((g) => ({
    t: (g.title || '').trim(),
    n: (g.notes || '').trim(),
  }))
  const slotsNorm = (fm.slots || []).map((s) => ({
    title: (s.title || '').trim(),
    notes: (s.notes || '').trim(),
    sections: s.sections,
  }))
  return JSON.stringify({
    title: (fm.title || '').trim(),
    description: (fm.description || '').trim(),
    focus_area_id: fm.focus_area_id || '',
    style_direction_id: fm.style_direction_id || '',
    training_type_ids: [...(fm.training_type_ids || [])].map(String).sort(),
    target_group_ids: [...(fm.target_group_ids || [])].map(String).sort(),
    planned_period_start: fm.planned_period_start || '',
    planned_period_end: fm.planned_period_end || '',
    visibility: (fm.visibility || '').trim(),
    club_id: (fm.club_id || '').trim(),
    goals: goalsNorm,
    slots: slotsNorm,
  })
}

function serverFrameworkToForm(fw) {
  const goalsIn = Array.isArray(fw.goals) && fw.goals.length ? fw.goals : [emptyGoal()]
  return {
    title: fw.title || '',
    description: fw.description || '',
    focus_area_id: fw.focus_area_id != null ? String(fw.focus_area_id) : '',
    style_direction_id: fw.style_direction_id != null ? String(fw.style_direction_id) : '',
    training_type_ids: Array.isArray(fw.training_type_ids)
      ? fw.training_type_ids.map((x) => String(x))
      : [],
    target_group_ids: Array.isArray(fw.target_group_ids)
      ? fw.target_group_ids.map((x) => String(x))
      : [],
    planned_period_start: fw.planned_period_start || '',
    planned_period_end: fw.planned_period_end || '',
    visibility: fw.visibility || 'private',
    club_id: fw.club_id != null ? String(fw.club_id) : '',
    goals: goalsIn.map((g) => ({
      title: g.title || '',
      notes: g.notes || '',
    })),
    slots: (fw.slots || []).map((s) => ({
      title: s.title || '',
      notes: s.notes || '',
      sections: normalizeUnitToForm({
        sections: s.sections,
        exercises: s.exercises,
        phases: s.phases,
      }),
    })),
  }
}

function buildApiPayload(form) {
  const goals = (form.goals || [])
    .map((g, i) => ({
      sort_order: i,
      title: (g.title || '').trim(),
      notes: (g.notes || '').trim() || null,
    }))
    .filter((g) => g.title)
  if (goals.length === 0) {
    throw new Error('Mindestens ein Entwicklungsziel mit Titel ist erforderlich.')
  }

  const slots = (form.slots || []).map((s, si) => {
    const secList = s.sections && s.sections.length ? s.sections : [defaultSection('Ablauf')]
    const plan = buildPlanPayloadForSave(secList)
    const base = {
      sort_order: si,
      title: (s.title || '').trim() || null,
      notes: (s.notes || '').trim() || null,
    }
    if (plan.phases) {
      return { ...base, phases: plan.phases }
    }
    return { ...base, sections: plan.sections }
  })

  const focusAreaId =
    form.focus_area_id && !Number.isNaN(parseInt(form.focus_area_id, 10))
      ? parseInt(form.focus_area_id, 10)
      : null
  const styleDirectionId =
    form.style_direction_id && !Number.isNaN(parseInt(form.style_direction_id, 10))
      ? parseInt(form.style_direction_id, 10)
      : null

  const training_type_ids = (form.training_type_ids || [])
    .map((x) => parseInt(String(x), 10))
    .filter((n) => !Number.isNaN(n) && n > 0)
  const target_group_ids = (form.target_group_ids || [])
    .map((x) => parseInt(String(x), 10))
    .filter((n) => !Number.isNaN(n) && n > 0)

  const clubId =
    form.club_id && !Number.isNaN(parseInt(form.club_id, 10))
      ? parseInt(form.club_id, 10)
      : null

  return {
    title: (form.title || '').trim(),
    description: (form.description || '').trim() || null,
    focus_area_id: focusAreaId,
    style_direction_id: styleDirectionId,
    training_type_ids,
    target_group_ids,
    planned_period_start: form.planned_period_start || null,
    planned_period_end: form.planned_period_end || null,
    visibility: form.visibility || 'private',
    club_id: clubId,
    goals,
    slots,
  }
}

export default function TrainingFrameworkProgramEditPage() {
  const { id: idParam } = useParams()
  const location = useLocation()
  const navigate = useNavigate()
  /** Route `…/framework-programs/new` hat kein dynamisches `:id` — useParams ist dann leer. */
  const isNew = /\/framework-programs\/new\/?$/.test(location.pathname)

  const [loading, setLoading] = useState(!isNew)
  const [saving, setSaving] = useState(false)
  const [form, setForm] = useState(defaultForm())
  const [clubs, setClubs] = useState([])
  const [focusAreas, setFocusAreas] = useState([])
  const [styleDirections, setStyleDirections] = useState([])
  const [trainingTypesCatalog, setTrainingTypesCatalog] = useState([])
  const [targetGroupsCatalog, setTargetGroupsCatalog] = useState([])
  const [sectionPickerCtx, setSectionPickerCtx] = useState(null)
  const [peekCtx, setPeekCtx] = useState(null)
  const [editingGoalIdx, setEditingGoalIdx] = useState(null)
  const [goalMenuGi, setGoalMenuGi] = useState(null)
  /** Nur schmal: Stammdaten | Plan (Ziele übereinander, darunter Slots) — Desktop: alles sichtbar */
  const [frameworkTab, setFrameworkTab] = useState('meta')
  const [desktopLayout, setDesktopLayout] = useState(
    typeof window !== 'undefined'
      ? window.matchMedia(`(min-width: ${FRAMEWORK_DESKTOP_MIN_PX}px)`).matches
      : false
  )
  /** Schmale Ansicht: welcher Session-Slot gerade die volle Breite nutzt (Chip-Navigation) */
  const [mobileSlotIdx, setMobileSlotIdx] = useState(0)

  const toast = useToast()
  const baselineRef = useRef(null)
  const latestFormRef = useRef(form)
  latestFormRef.current = form
  const [baselineReady, setBaselineReady] = useState(false)
  const [bypassDirty, setBypassDirty] = useState(false)

  const dirtySignature = frameworkDraftSnapshot(form)

  useEffect(() => {
    baselineRef.current = null
    setBaselineReady(false)
    setBypassDirty(false)
  }, [idParam, isNew])

  useEffect(() => {
    if (loading) return
    const handle = window.setTimeout(() => {
      baselineRef.current = frameworkDraftSnapshot(latestFormRef.current)
      setBaselineReady(true)
    }, 120)
    return () => clearTimeout(handle)
  }, [loading, idParam, isNew])

  const formDirtyEffective =
    baselineReady &&
    baselineRef.current != null &&
    !bypassDirty &&
    !loading &&
    dirtySignature !== baselineRef.current

  const blocker = useUnsavedChangesBlocker(Boolean(formDirtyEffective && !saving))
  useBeforeUnloadWhen(Boolean(formDirtyEffective && !saving))

  useEffect(() => {
    const mq = window.matchMedia(`(min-width: ${FRAMEWORK_DESKTOP_MIN_PX}px)`)
    const apply = () => setDesktopLayout(!!mq.matches)
    apply()
    mq.addEventListener('change', apply)
    return () => mq.removeEventListener('change', apply)
  }, [])

  useEffect(() => {
    const onPointerDown = (e) => {
      const t = e.target
      if (t.closest?.('.framework-popmenu-anchor')) return
      setGoalMenuGi(null)
    }
    document.addEventListener('pointerdown', onPointerDown, true)
    return () => document.removeEventListener('pointerdown', onPointerDown, true)
  }, [])

  const loadMeta = useCallback(async () => {
    try {
      const [cl, fa, sd, tt, tg] = await Promise.all([
        api.listClubs(),
        api.listFocusAreas({ status: 'active' }),
        api.listStyleDirections({ status: 'active' }),
        api.listTrainingTypes({ status: 'active' }),
        api.listTargetGroups({ status: 'active' }),
      ])
      setClubs(Array.isArray(cl) ? cl : [])
      setFocusAreas(Array.isArray(fa) ? fa : [])
      setStyleDirections(Array.isArray(sd) ? sd : [])
      setTrainingTypesCatalog(Array.isArray(tt) ? tt : [])
      setTargetGroupsCatalog(Array.isArray(tg) ? tg : [])
    } catch {
      setClubs([])
      setFocusAreas([])
      setStyleDirections([])
      setTrainingTypesCatalog([])
      setTargetGroupsCatalog([])
    }
  }, [])

  useEffect(() => {
    loadMeta()
  }, [loadMeta])

  useEffect(() => {
    setMobileSlotIdx(0)
  }, [idParam, isNew])

  useEffect(() => {
    if (isNew) {
      setForm(defaultForm())
      setLoading(false)
      return
    }
    const fid = parseInt(idParam, 10)
    if (Number.isNaN(fid)) {
      navigate('/planning/framework-programs', { replace: true })
      return
    }
    setLoading(true)
    let cancelled = false
    ;(async () => {
      try {
        const fw = await api.getTrainingFrameworkProgram(fid)
        if (cancelled) return
        let next = serverFrameworkToForm(fw)
        next = { ...next, slots: await enrichFrameworkSlotSections(next.slots) }
        setForm(next)
      } catch (e) {
        toast.error(e.message || 'Laden fehlgeschlagen')
        navigate('/planning/framework-programs')
      } finally {
        if (!cancelled) setLoading(false)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [isNew, idParam, navigate, location.pathname])

  const updateField = (key, val) => {
    setForm((prev) => ({ ...prev, [key]: val }))
  }

  const moveGoal = (idx, dir) => {
    setEditingGoalIdx(null)
    setGoalMenuGi(null)
    setForm((prev) => {
      const j = idx + dir
      if (j < 0 || j >= prev.goals.length) return prev
      const g = [...prev.goals]
      ;[g[idx], g[j]] = [g[j], g[idx]]
      return { ...prev, goals: g }
    })
  }

  const addGoal = () => {
    let newIdx = 0
    setForm((prev) => {
      const goals = [...prev.goals, emptyGoal()]
      newIdx = goals.length - 1
      return { ...prev, goals }
    })
    setEditingGoalIdx(newIdx)
    setGoalMenuGi(null)
  }

  const removeGoal = (idx) => {
    setForm((prev) => {
      const g = prev.goals.filter((_, i) => i !== idx)
      return { ...prev, goals: g.length ? g : [emptyGoal()] }
    })
    setEditingGoalIdx(null)
    setGoalMenuGi(null)
  }

  const moveSlot = (idx, dir) => {
    setForm((prev) => {
      const j = idx + dir
      if (j < 0 || j >= prev.slots.length) return prev
      const sl = [...prev.slots]
      ;[sl[idx], sl[j]] = [sl[j], sl[idx]]
      setMobileSlotIdx((mi) => {
        if (mi === idx) return j
        if (mi === j) return idx
        return mi
      })
      return { ...prev, slots: sl }
    })
  }

  const addSlot = () => {
    setForm((prev) => {
      const slots = [...prev.slots, emptySlot()]
      setMobileSlotIdx(slots.length - 1)
      return { ...prev, slots }
    })
  }

  const removeSlot = (idx) => {
    const n = form.slots.length
    setMobileSlotIdx((mi) => {
      if (idx < mi) return Math.max(0, mi - 1)
      if (idx === mi) return Math.min(mi, Math.max(0, n - 2))
      return mi
    })
    setForm((prev) => ({ ...prev, slots: prev.slots.filter((_, i) => i !== idx) }))
  }

  const slotField = (sIdx, key, val) => {
    setForm((prev) => ({
      ...prev,
      slots: prev.slots.map((s, i) => (i === sIdx ? { ...s, [key]: val } : s)),
    }))
  }

  const performFrameworkSave = async ({ fromUnsavedDialog = false, closeAfter = false } = {}) => {
    if (!(form.title || '').trim()) {
      toast.error('Titel ist Pflichtfeld.')
      return false
    }
    let payload
    try {
      payload = buildApiPayload(form)
    } catch (e) {
      toast.error(e.message || 'Validierung')
      return false
    }
    if (!payload.title) {
      toast.error('Titel ist Pflichtfeld.')
      return false
    }
    setSaving(true)
    try {
      if (isNew) {
        const created = await api.createTrainingFrameworkProgram(payload)
        toast.success('Rahmenprogramm angelegt.')
        if (closeAfter) {
          navigate('/planning/framework-programs')
        } else if (!fromUnsavedDialog) {
          navigate(`/planning/framework-programs/${created.id}`, { replace: true })
        }
        return true
      }
      const fid = parseInt(idParam, 10)
      await api.updateTrainingFrameworkProgram(fid, payload)
      const refreshed = await api.getTrainingFrameworkProgram(fid)
      let next = serverFrameworkToForm(refreshed)
      next = { ...next, slots: await enrichFrameworkSlotSections(next.slots) }
      setForm(next)
      baselineRef.current = frameworkDraftSnapshot(next)
      setBypassDirty(false)
      setBaselineReady(true)
      toast.success('Gespeichert.')
      if (closeAfter) navigate('/planning/framework-programs')
      return true
    } catch (e) {
      toast.error(e.message || 'Speichern fehlgeschlagen')
      return false
    } finally {
      setSaving(false)
    }
  }

  const handleSave = async () => {
    await performFrameworkSave({ fromUnsavedDialog: false, closeAfter: false })
  }

  const handleSaveAndClose = async () => {
    await performFrameworkSave({ fromUnsavedDialog: false, closeAfter: true })
  }

  const handleUnsavedDialogSave = async () => {
    const ok = await performFrameworkSave({ fromUnsavedDialog: true })
    if (ok) blocker.proceed()
  }

  async function handleDelete() {
    if (isNew) return
    const fid = parseInt(idParam, 10)
    if (!confirm('Dieses Rahmenprogramm wirklich löschen?')) return
    try {
      await api.deleteTrainingFrameworkProgram(fid)
      navigate('/planning/framework-programs')
    } catch (e) {
      toast.error(e.message || 'Löschen fehlgeschlagen')
    }
  }

  const onSlotDragStart = (e, slotIdx) => {
    e.stopPropagation()
    e.dataTransfer.effectAllowed = 'move'
    e.dataTransfer.setData(DND_FW_SLOT, JSON.stringify({ slotIdx }))
  }

  const onSlotColumnDragOver = (e) => {
    e.preventDefault()
    e.dataTransfer.dropEffect = 'move'
  }

  const onSlotColumnDrop = (e, targetSi) => {
    e.preventDefault()
    const slotRaw = e.dataTransfer.getData(DND_FW_SLOT)
    if (!slotRaw) return
    const { slotIdx } = JSON.parse(slotRaw)
    if (slotIdx !== targetSi) {
      setForm((prev) => ({ ...prev, slots: reorderArray([...prev.slots], slotIdx, targetSi) }))
    }
  }

  const panelActive = (key) => {
    if (desktopLayout) return true
    if (key === 'meta') return frameworkTab === 'meta'
    if (key === 'plan') return frameworkTab === 'plan'
    return false
  }

  /** Schmale Ansicht: Sichtbarkeit per Inline (falls globales CSS nicht greift / altes Bundle) */
  const panelVisibilityStyle = (key) =>
    desktopLayout ? undefined : { display: panelActive(key) ? 'block' : 'none' }

  const trainingTypesFiltered = useMemo(() => {
    if (!form.focus_area_id) return trainingTypesCatalog
    return trainingTypesCatalog.filter(
      (t) => !t.focus_area_id || String(t.focus_area_id) === String(form.focus_area_id)
    )
  }, [trainingTypesCatalog, form.focus_area_id])

  useEffect(() => {
    if (!form.focus_area_id || trainingTypesCatalog.length === 0) return
    const allowed = new Set(trainingTypesFiltered.map((t) => String(t.id)))
    setForm((prev) => {
      const cur = prev.training_type_ids || []
      const next = cur.filter((id) => allowed.has(String(id)))
      if (next.length === cur.length) return prev
      return { ...prev, training_type_ids: next }
    })
  }, [form.focus_area_id, trainingTypesCatalog.length, trainingTypesFiltered])

  const toggleTrainingTypeId = (tid) => {
    const idStr = String(tid)
    setForm((prev) => {
      const s = new Set(prev.training_type_ids || [])
      if (s.has(idStr)) s.delete(idStr)
      else s.add(idStr)
      return { ...prev, training_type_ids: [...s].sort((a, b) => Number(a) - Number(b)) }
    })
  }

  const toggleTargetGroupId = (gid) => {
    const idStr = String(gid)
    setForm((prev) => {
      const s = new Set(prev.target_group_ids || [])
      if (s.has(idStr)) s.delete(idStr)
      else s.add(idStr)
      return { ...prev, target_group_ids: [...s].sort((a, b) => Number(a) - Number(b)) }
    })
  }

  const moveSectionsAcrossFrameworkSlots = useCallback(
    (payload) => {
      const {
        fromSlot,
        fromSectionIdx,
        toSlot,
        toSectionIdx,
        toParallelStream,
        parallelPhaseRunOrderIndex,
        insertBeforeParallelInTarget,
        firstInParallelStreamInTarget,
      } = payload
      setForm((prev) => {
        const slots = prev.slots.map((sl) => ({
          ...sl,
          sections: [...((sl.sections && sl.sections.length) ? sl.sections : [defaultSection('Ablauf')])],
        }))
        if (
          typeof fromSlot !== 'number' ||
          typeof toSlot !== 'number' ||
          fromSlot < 0 ||
          toSlot < 0 ||
          fromSlot >= slots.length ||
          toSlot >= slots.length
        ) {
          return prev
        }

        const fromSecs = slots[fromSlot].sections
        const toSecs = slots[toSlot].sections

        const applyParallelStreamEnd =
          toParallelStream != null && toParallelStream.po != null && toParallelStream.so != null
            ? (secs, insertedAt) => {
                const po = Number(toParallelStream.po) || 0
                const so = Number(toParallelStream.so) || 0
                return reorderBlockIntoParallelStreamEnd(secs, insertedAt, po, so)
              }
            : null

        /** Gesamten Parallel-Lauf aus fromSlot an toSectionIdx in toSlot legen */
        if (parallelPhaseRunOrderIndex != null && parallelPhaseRunOrderIndex !== '') {
          const po = Number(parallelPhaseRunOrderIndex) || 0
          const idxs = indicesOfParallelPhase(fromSecs, po)
          if (!idxs.length) {
            return prev
          }
          const blocks = idxs.map((i) => fromSecs[i])
          for (const i of [...idxs].sort((a, b) => b - a)) {
            fromSecs.splice(i, 1)
          }
          if (fromSlot === toSlot) {
            let insertAt = Number(toSectionIdx) || 0
            for (const i of idxs) {
              if (i < insertAt) insertAt -= 1
            }
            insertAt = Math.max(0, Math.min(insertAt, fromSecs.length))
            fromSecs.splice(insertAt, 0, ...blocks)
            slots[fromSlot].sections = fromSecs
            return { ...prev, slots }
          }
          const insertAt = Math.max(0, Math.min(Number(toSectionIdx) || 0, toSecs.length))
          toSecs.splice(insertAt, 0, ...blocks)
          slots[toSlot].sections = toSecs
          return { ...prev, slots }
        }

        if (
          typeof fromSectionIdx !== 'number' ||
          fromSectionIdx < 0 ||
          fromSectionIdx >= fromSecs.length
        ) {
          return prev
        }

        const [block] = fromSecs.splice(fromSectionIdx, 1)

        if (fromSlot === toSlot) {
          let insertAt = toSectionIdx
          if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1
          insertAt = Math.max(0, Math.min(insertAt, fromSecs.length))
          fromSecs.splice(insertAt, 0, block)
          if (applyParallelStreamEnd) {
            slots[fromSlot].sections = applyParallelStreamEnd(fromSecs, insertAt)
          } else {
            slots[fromSlot].sections = fromSecs
          }
          return { ...prev, slots }
        }

        if (insertBeforeParallelInTarget != null && insertBeforeParallelInTarget !== '') {
          const tpo = Number(insertBeforeParallelInTarget) || 0
          const pIdxs = indicesOfParallelPhase(toSecs, tpo)
          const ins = pIdxs.length ? pIdxs[0] : toSecs.length
          toSecs.splice(ins, 0, block)
          slots[toSlot].sections = reorderSectionBeforeParallelRunAsWholeGroup(toSecs, ins, tpo)
          return { ...prev, slots }
        }

        if (
          firstInParallelStreamInTarget != null &&
          firstInParallelStreamInTarget.po != null &&
          firstInParallelStreamInTarget.so != null
        ) {
          const fpo = Number(firstInParallelStreamInTarget.po) || 0
          const fso = Number(firstInParallelStreamInTarget.so) || 0
          const nextSecs = [...toSecs, block]
          const movedFromI = nextSecs.length - 1
          slots[toSlot].sections = reorderSectionAsFirstInParallelStream(
            nextSecs,
            movedFromI,
            fpo,
            fso
          )
          return { ...prev, slots }
        }

        const ia = Math.max(0, Math.min(toSectionIdx, toSecs.length))
        toSecs.splice(ia, 0, block)
        if (applyParallelStreamEnd) {
          slots[toSlot].sections = applyParallelStreamEnd(toSecs, ia)
        } else {
          slots[toSlot].sections = toSecs
        }
        return { ...prev, slots }
      })
    },
    []
  )

  const slotChipButtons = (opts) =>
    form.slots.map((slot, si) => {
      const isActive = si === mobileSlotIdx
      const sel = opts?.tabSemantics
      const baseClass = `framework-slot-chip${isActive ? ' framework-slot-chip--active' : ''}`
      const attrs = sel
        ? { role: 'tab', id: `fw-slot-chip-${si}`, 'aria-selected': isActive }
        : { 'aria-pressed': isActive }
      return (
        <button key={si} type="button" {...attrs} className={baseClass} onClick={() => setMobileSlotIdx(si)} title={slotChipLabel(slot, si)}>
          {slotChipLabel(slot, si)}
        </button>
      )
    })

  const renderFrameworkSlotCard = (si) => {
    const slot = form.slots[si]
    if (!slot) return null
    return (
      <div
        key={si}
        className={`card framework-slot-card${!desktopLayout ? ' framework-slot-card--mobile-single' : ''}`}
        onDragOver={desktopLayout ? onSlotColumnDragOver : undefined}
        onDrop={desktopLayout ? (e) => onSlotColumnDrop(e, si) : undefined}
      >
        <div className="framework-slot-card__head">
          {desktopLayout ? (
            <span
              role="button"
              tabIndex={0}
              className="framework-slot-card__drag-handle"
              draggable
              onDragStart={(e) => onSlotDragStart(e, si)}
              aria-label="Slot ziehen: Reihenfolge ändern"
              title="Slot ziehen (Reihenfolge)"
            >
              ⋮⋮
            </span>
          ) : null}
          <div className="framework-slot-card__head-main">
            <span className="framework-slot-card__slot-label">Session {si + 1}</span>
            <input
              className="form-input framework-slot-card__title-input"
              value={slot.title}
              onChange={(e) => slotField(si, 'title', e.target.value)}
              placeholder={`z. B. Woche ${si + 1}`}
            />
          </div>
          <div className="framework-slot-card__slot-actions">
            <button
              type="button"
              className="btn btn-secondary framework-ctrl framework-ctrl--xs"
              onClick={() => moveSlot(si, -1)}
              aria-label="Slot nach links / oben"
            >
              ↑
            </button>
            <button
              type="button"
              className="btn btn-secondary framework-ctrl framework-ctrl--xs"
              onClick={() => moveSlot(si, 1)}
              aria-label="Slot nach rechts / unten"
            >
              ↓
            </button>
            <button
              type="button"
              className="btn btn-secondary framework-ctrl framework-ctrl--xs"
              onClick={() => removeSlot(si)}
            >
              ×
            </button>
          </div>
        </div>

        <details className="framework-slot-details">
          <summary className="framework-slot-details__summary">Notizen (Session)</summary>
          <div className="form-row">
            <label className="form-label">Notizen</label>
            <textarea
              className="form-input"
              rows={2}
              value={slot.notes}
              onChange={(e) => slotField(si, 'notes', e.target.value)}
            />
          </div>
        </details>

        <div className="framework-slot-card__plan-editor" style={{ marginTop: '0.65rem', minHeight: '120px' }}>
          <TrainingUnitSectionsEditor
            heading={`Ablauf · Session ${si + 1}`}
            sections={slot.sections}
            betweenInsertMenus={false}
            showExecutionExtras={false}
            wideExerciseGrid
            slotIndex={si}
            enableParallelPhaseControls
            onMoveSectionsAcrossSlots={moveSectionsAcrossFrameworkSlots}
            onSectionsChange={(updater) => {
              setForm((prev) => ({
                ...prev,
                slots: prev.slots.map((sl, ii) =>
                  ii !== si
                    ? sl
                    : {
                        ...sl,
                        sections: updater(
                          sl.sections && sl.sections.length ? sl.sections : [defaultSection('Ablauf')]
                        ),
                      }
                ),
              }))
            }}
            onRequestExercisePick={({ sectionIndex, itemIndex, insertBeforeIndex }) =>
              setSectionPickerCtx({
                slotIdx: si,
                sectionIndex,
                itemIndex: typeof itemIndex === 'number' ? itemIndex : undefined,
                insertBeforeIndex:
                  typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
                    ? insertBeforeIndex
                    : undefined,
              })
            }
            onPeekExercise={(id, variantId) =>
              setPeekCtx({ exerciseId: id, variantId: variantId ?? null })
            }
          />
        </div>
      </div>
    )
  }

  if (loading) {
    return (
      <div className="app-page" style={{ padding: '2rem 0', textAlign: 'center' }}>
        <div className="spinner" />
        <p>Laden…</p>
      </div>
    )
  }

  return (
    <div className="app-page">
      <div className="framework-edit">
        <p style={{ marginBottom: '0.75rem' }}>
          <Link to="/planning/framework-programs" style={{ color: 'var(--accent-dark)' }}>
            ← Alle Rahmenprogramme
          </Link>
        </p>

        <h1 style={{ marginBottom: '0.75rem' }}>{isNew ? 'Neues Rahmenprogramm' : 'Rahmenprogramm bearbeiten'}</h1>

        <details className="framework-edit-intro">
          <summary className="framework-edit-intro__summary">
            Kurz erklärt: Was ist ein Rahmenprogramm?
          </summary>
          <div className="framework-edit-intro__body">
            <strong style={{ color: 'var(--text1)' }}>Rahmenprogramm (Bibliothek):</strong> Wiederverwendbare Vorlage mit
            Zielen und Session‑Slots. Die <strong>Zuordnung zu Gruppe oder Kalendertermin</strong> erfolgt aus der{' '}
            <strong>Gruppen‑Planung</strong> („Übernahme“). Pro Slot planst du den Ablauf wie bei einer Trainingsseinheit:{' '}
            <strong>Abschnitte</strong>, optional <strong>Ganzgruppen- und parallele Phasen (Breakout)</strong>, Übungen mit Varianten und Dauer, <strong>Zwischen‑Anmerkungen</strong>. Abschnitte kannst du per Ziehen auch in eine andere Session legen.
          </div>
        </details>

        <div className="framework-edit__tabbar">
          <PageSectionNav
            ariaLabel="Bereiche"
            value={frameworkTab}
            onChange={setFrameworkTab}
            items={[
              { id: 'meta', label: 'Stammdaten' },
              { id: 'plan', label: 'Plan (Ziele & Sessions)' },
            ]}
            className="page-section-nav--embedded framework-edit__section-nav"
          />
        </div>

        <div
          className={
            'framework-edit__panel framework-edit__panel--meta card' +
            (panelActive('meta') ? ' framework-edit__panel--active' : '')
          }
          style={{ marginBottom: '1rem', ...(panelVisibilityStyle('meta') || {}) }}
        >
          <h3 className="card-title">Stammdaten</h3>
          <div className="form-row">
            <label className="form-label">Titel *</label>
            <input
              className="form-input"
              value={form.title}
              onChange={(e) => updateField('title', e.target.value)}
              placeholder="z. B. Vorbereitung Gürtelprüfung — 8 Wochen"
            />
          </div>
          <div className="form-row">
            <label className="form-label">Beschreibung</label>
            <textarea
              className="form-input"
              rows={3}
              value={form.description}
              onChange={(e) => updateField('description', e.target.value)}
            />
          </div>
          <div className="form-row">
            <label className="form-label">Fokusbereich (optional)</label>
            <select
              className="form-input"
              value={form.focus_area_id}
              onChange={(e) => updateField('focus_area_id', e.target.value)}
            >
              <option value="">— keiner —</option>
              {focusAreas.map((fa) => (
                <option key={fa.id} value={String(fa.id)}>
                  {fa.name}
                </option>
              ))}
            </select>
            <p className="form-sub">Hilft beim Filtern der Trainingsarten und bei der späteren Zuordnung in der Planung.</p>
          </div>
          <div className="form-row">
            <label className="form-label">Stilrichtung (optional)</label>
            <select
              className="form-input"
              value={form.style_direction_id}
              onChange={(e) => updateField('style_direction_id', e.target.value)}
            >
              <option value="">— keine —</option>
              {styleDirections.map((sd) => (
                <option key={sd.id} value={String(sd.id)}>
                  {sd.name}
                </option>
              ))}
            </select>
          </div>
          <div className="form-row">
            <label className="form-label">Trainingsarten (optional, Mehrfachwahl)</label>
            <div className="framework-catalog-checkgrid">
              {trainingTypesFiltered.length === 0 ? (
                <p className="form-sub" style={{ marginTop: 0 }}>
                  Keine Einträge im Katalog — oder Fokusbereich wählen, um zu filtern.
                </p>
              ) : (
                trainingTypesFiltered.map((t) => (
                  <label key={t.id} className="framework-catalog-check">
                    <input
                      type="checkbox"
                      checked={(form.training_type_ids || []).includes(String(t.id))}
                      onChange={() => toggleTrainingTypeId(t.id)}
                    />
                    <span>{t.name}</span>
                  </label>
                ))
              )}
            </div>
          </div>
          <div className="form-row">
            <label className="form-label">Zielgruppen (optional, Mehrfachwahl)</label>
            <div className="framework-catalog-checkgrid">
              {targetGroupsCatalog.length === 0 ? (
                <p className="form-sub" style={{ marginTop: 0 }}>
                  Keine Zielgruppen im Katalog.
                </p>
              ) : (
                targetGroupsCatalog.map((tg) => (
                  <label key={tg.id} className="framework-catalog-check">
                    <input
                      type="checkbox"
                      checked={(form.target_group_ids || []).includes(String(tg.id))}
                      onChange={() => toggleTargetGroupId(tg.id)}
                    />
                    <span>{tg.name}</span>
                  </label>
                ))
              )}
            </div>
          </div>

          <div className="responsive-grid-2" style={{ marginBottom: '16px' }}>
            <div>
              <label className="form-label">Zeitraum von</label>
              <input
                type="date"
                className="form-input"
                value={form.planned_period_start}
                onChange={(e) => updateField('planned_period_start', e.target.value)}
              />
            </div>
            <div>
              <label className="form-label">Zeitraum bis</label>
              <input
                type="date"
                className="form-input"
                value={form.planned_period_end}
                onChange={(e) => updateField('planned_period_end', e.target.value)}
              />
            </div>
          </div>

          <div className="responsive-grid-2" style={{ marginBottom: '16px' }}>
            <div>
              <label className="form-label">Sichtbarkeit</label>
              <select
                className="form-input"
                value={form.visibility}
                onChange={(e) => updateField('visibility', e.target.value)}
              >
                <option value="private">Privat</option>
                <option value="club">Verein</option>
                <option value="official">Offiziell</option>
              </select>
            </div>
            <div>
              <label className="form-label">Verein (optional)</label>
              <select
                className="form-input"
                value={form.club_id}
                onChange={(e) => updateField('club_id', e.target.value)}
              >
                <option value="">— keiner —</option>
                {clubs.map((c) => (
                  <option key={c.id} value={String(c.id)}>
                    {c.name}
                  </option>
                ))}
              </select>
            </div>
          </div>
        </div>

        <div
          className={
            'framework-edit__panel framework-edit__panel--plan' +
            (panelActive('plan') ? ' framework-edit__panel--active' : '')
          }
          style={{ marginBottom: '1rem', ...(panelVisibilityStyle('plan') || {}) }}
        >
          <div className="framework-edit__plan-stack">
            <div className="card framework-plan-goals">
              <div
                style={{
                  display: 'flex',
                  flexWrap: 'wrap',
                  gap: '8px',
                  justifyContent: 'space-between',
                  alignItems: 'center',
                  marginBottom: '0.6rem',
                }}
              >
                <h3 className="card-title" style={{ marginBottom: 0 }}>
                  Entwicklungsziele
                </h3>
                <button type="button" className="btn btn-secondary framework-ctrl framework-ctrl--xs" onClick={addGoal}>
                  + Ziel
                </button>
              </div>
              <p className="framework-goal-chips__hint">
                Ziele als Tags — Tippen zum Bearbeiten; am Desktop zeigt der <strong>Titel</strong>-Tooltip die Notizen mit. Am
                Handy: <strong>⋯</strong> oder Bearbeiten.
              </p>
              <div className="framework-goal-chips">
                {(form.goals || []).map((g, gi) => (
                  <div key={gi} className="framework-popmenu-anchor framework-goal-chip-wrap">
                    <button
                      type="button"
                      className={
                        'framework-goal-chip' + (editingGoalIdx === gi ? ' framework-goal-chip--active' : '')
                      }
                      title={goalHoverText(g)}
                      onClick={() => {
                        setEditingGoalIdx(gi)
                        setGoalMenuGi(null)
                      }}
                    >
                      <span className="framework-goal-chip__text">
                        {(g.title || '').trim() || `Ziel ${gi + 1}`}
                      </span>
                    </button>
                    <button
                      type="button"
                      className="framework-goal-chip__kebab"
                      aria-label="Ziel-Menü"
                      aria-expanded={goalMenuGi === gi}
                      onClick={(e) => {
                        e.stopPropagation()
                        setGoalMenuGi((prev) => (prev === gi ? null : gi))
                      }}
                    >
                      ⋮
                    </button>
                    {goalMenuGi === gi ? (
                      <ul className="framework-popmenu" role="menu">
                        <li>
                          <button
                            type="button"
                            role="menuitem"
                            className="framework-popmenu__item"
                            onClick={() => {
                              setEditingGoalIdx(gi)
                              setGoalMenuGi(null)
                            }}
                          >
                            Bearbeiten
                          </button>
                        </li>
                        <li>
                          <button
                            type="button"
                            role="menuitem"
                            className="framework-popmenu__item"
                            onClick={() => {
                              moveGoal(gi, -1)
                              setGoalMenuGi(null)
                            }}
                          >
                            Nach vorn in der Liste
                          </button>
                        </li>
                        <li>
                          <button
                            type="button"
                            role="menuitem"
                            className="framework-popmenu__item"
                            onClick={() => {
                              moveGoal(gi, 1)
                              setGoalMenuGi(null)
                            }}
                          >
                            Nach hinten in der Liste
                          </button>
                        </li>
                        <li>
                          <button
                            type="button"
                            role="menuitem"
                            className="framework-popmenu__item framework-popmenu__item--danger"
                            onClick={() => removeGoal(gi)}
                          >
                            Entfernen
                          </button>
                        </li>
                      </ul>
                    ) : null}
                  </div>
                ))}
              </div>
              {editingGoalIdx != null && form.goals[editingGoalIdx] != null ? (
                <div className="framework-goal-editor">
                  <div className="form-row">
                    <label className="form-label">Titel *</label>
                    <input
                      className="form-input"
                      value={form.goals[editingGoalIdx].title}
                      onChange={(e) =>
                        setForm((prev) => ({
                          ...prev,
                          goals: prev.goals.map((x, i) =>
                            i === editingGoalIdx ? { ...x, title: e.target.value } : x
                          ),
                        }))
                      }
                    />
                  </div>
                  <div className="form-row" style={{ marginBottom: 10 }}>
                    <label className="form-label">Notizen</label>
                    <textarea
                      className="form-input"
                      rows={2}
                      value={form.goals[editingGoalIdx].notes}
                      onChange={(e) =>
                        setForm((prev) => ({
                          ...prev,
                          goals: prev.goals.map((x, i) =>
                            i === editingGoalIdx ? { ...x, notes: e.target.value } : x
                          ),
                        }))
                      }
                    />
                  </div>
                  <button
                    type="button"
                    className="btn btn-secondary framework-ctrl framework-ctrl--xs"
                    onClick={() => setEditingGoalIdx(null)}
                  >
                    Fertig
                  </button>
                </div>
              ) : null}
            </div>

            <div className="card framework-plan-slots" style={{ marginBottom: '1.5rem' }}>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
                <h3 className="card-title" style={{ marginBottom: 0 }}>
                  Session‑Slots & Ablauf
                </h3>
                <button type="button" className="btn btn-secondary framework-ctrl framework-ctrl--xs" onClick={addSlot}>
                  + Slot
                </button>
              </div>

              {form.slots.length === 0 ? (
                <p style={{ color: 'var(--text2)', fontSize: '0.9rem' }}>
                  Noch keine Slots — mit <strong>+ Slot</strong> legst du Spalten an (z. B. „Woche 1“). In jedem Slot
                  strukturierst du wie in der Trainingsplanung: Abschnitte, Übungen, Anmerkungen.
                </p>
              ) : (
                <p
                  className="framework-slots-hint"
                  style={{ fontSize: '0.8rem', color: 'var(--text3)', marginBottom: '10px', lineHeight: 1.45 }}
                >
                  {desktopLayout ? (
                    <>
                      Reihenfolge der Spalten per Griff (⋮⋮) ziehen oder mit ↑ / ↓ verschieben; Inhalt eines Slots wie
                      in der Planung bearbeiten (Abschnitte, Übungen, Anmerkungen).
                    </>
                  ) : (
                    <>
                      Sessions oben oder unten per Chips wählen — ein Slot nutzt die volle Breite. Reihenfolge mit ↑ / ↓;
                      bearbeiten wie in der Planung.
                    </>
                  )}
                </p>
              )}

              {form.slots.length > 0 ? (
                !desktopLayout ? (
                  <div className="framework-slots-board-outer framework-slots-board-outer--mobile-single">
                    <div
                      className="framework-slot-chips-bar framework-slot-chips-bar--top"
                      role="tablist"
                      aria-label="Sessions"
                    >
                      {slotChipButtons({ tabSemantics: true })}
                    </div>
                    <div className="framework-slot-mobile-panel" tabIndex={-1}>
                      {renderFrameworkSlotCard(mobileSlotIdx)}
                    </div>
                    <div
                      className="framework-slot-chips-bar framework-slot-chips-bar--bottom"
                      role="group"
                      aria-label="Sessions (Anwahl unten)"
                    >
                      {slotChipButtons({ tabSemantics: false })}
                    </div>
                  </div>
                ) : (
                  <div className="framework-slots-board-outer framework-slots-board-outer--desktop">
                    <div className="framework-slots-board framework-slots-board--desktop-wide">
                      {form.slots.map((_, si) => renderFrameworkSlotCard(si))}
                    </div>
                  </div>
                )
              ) : null}
            </div>
          </div>
        </div>

        <FormActionBar
          isNew={isNew}
          saving={saving}
          onSave={handleSave}
          onSaveAndClose={handleSaveAndClose}
          onCancel={() => navigate('/planning/framework-programs')}
          cancelLabel="Abbrechen"
        />
        {!isNew ? (
          <button type="button" className="btn btn-secondary" onClick={handleDelete} style={{ marginTop: '10px' }}>
            Löschen
          </button>
        ) : null}
      </div>

      <ExercisePickerModal
        open={sectionPickerCtx != null}
        multiSelect
        enableQuickCreateDraft
        onClose={() => setSectionPickerCtx(null)}
        onSelectExercises={async (picked) => {
          if (!sectionPickerCtx || !picked?.length) return
          const rows = []
          for (const ex of picked) {
            const row = await hydrateExercisePlanningRow(ex)
            if (row) rows.push(row)
          }
          if (!rows.length) return
          const { slotIdx, sectionIndex: sIdx, itemIndex: iIdx, insertBeforeIndex } = sectionPickerCtx
          setForm((prev) => ({
            ...prev,
            slots: prev.slots.map((sl, ii) => {
              if (ii !== slotIdx) return sl
              const baseSecs = sl.sections && sl.sections.length ? sl.sections : [defaultSection('Ablauf')]
              return {
                ...sl,
                sections: baseSecs.map((sec, si) => {
                  if (si !== sIdx) return sec
                  const items = [...(sec.items || [])]
                  if (typeof iIdx === 'number') {
                    const cur = items[iIdx]
                    if (!cur || cur.item_type !== 'exercise') return sec
                    const [first, ...tail] = rows
                    items[iIdx] = {
                      ...cur,
                      exercise_id: first.exercise_id,
                      exercise_variant_id: first.exercise_variant_id,
                      exercise_title: first.exercise_title,
                      variants: first.variants,
                    }
                    if (tail.length) items.splice(iIdx + 1, 0, ...tail)
                    return { ...sec, items }
                  }
                  const rawAt =
                    typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
                      ? insertBeforeIndex
                      : items.length
                  const at = Math.max(0, Math.min(rawAt, items.length))
                  items.splice(at, 0, ...rows)
                  return { ...sec, items }
                }),
              }
            }),
          }))
          setSectionPickerCtx(null)
        }}
      />

      <ExercisePeekModal
        key={peekCtx != null ? String(peekCtx.exerciseId) : 'fw-peek-closed'}
        open={peekCtx != null}
        exerciseId={peekCtx?.exerciseId || 0}
        variantId={peekCtx?.variantId ?? undefined}
        onClose={() => setPeekCtx(null)}
      />
      <UnsavedChangesPrompt
        blocker={blocker}
        isBusy={saving}
        onSave={handleUnsavedDialogSave}
        onDiscardWithoutSave={() => setBypassDirty(true)}
      />
    </div>
  )
}
