import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'
import api from '../utils/api'
import { useAuth } from '../context/AuthContext'
import { useToast } from '../context/ToastContext'
import { getTenantClubDependencyKey } from '../utils/activeClub'
import ExercisePickerModal from '../components/ExercisePickerModal'
import ExercisePeekModal from '../components/ExercisePeekModal'
import TrainingUnitFormShell from '../components/planning/TrainingUnitFormShell'
import TrainingPlanningModuleApplyModal from '../components/planning/TrainingPlanningModuleApplyModal'
import TrainingPublishToFrameworkModal from '../components/planning/TrainingPublishToFrameworkModal'
import SaveExercisesAsModuleModal from '../components/planning/SaveExercisesAsModuleModal'
import {
  defaultSection,
  enrichSectionsWithVariants,
  formSectionsFromPlanTemplateRows,
  hydrateExercisePlanningRow,
  insertTrainingModuleIntoPlanningSections,
  normalizeUnitToForm,
  templateSectionsPayloadFromFormSections,
} from '../utils/trainingUnitSectionsForm'
import {
  filterDirectoryExcludingLead,
} from '../utils/trainingPlanningPageHelpers'
import {
  createEmptyTrainingUnitFormData,
  buildTrainingUnitSavePayload,
  trainingUnitToFormFields,
  trainingUnitFormSnapshot,
  validateTrainingUnitFormForSave,
} from '../utils/trainingUnitEditorCore'
import PageFormEditorChrome from '../components/PageFormEditorChrome'
import UnsavedChangesPrompt from '../components/UnsavedChangesPrompt'
import { useBeforeUnloadWhen, useUnsavedChangesBlocker } from '../hooks/useUnsavedChangesBlocker'
import { buildPlanUnitEditPath, planningHubPathFromReturnState } from '../utils/planningUnitRoutes'

export default function TrainingUnitEditPage() {
  const { id: routeId } = useParams()
  const isNew = !routeId || routeId === 'new'
  const unitId = !isNew ? parseInt(routeId, 10) : NaN
  const navigate = useNavigate()
  const location = useLocation()
  const [searchParams] = useSearchParams()
  const { user } = useAuth()
  const toast = useToast()
  const tenantClubDepKey = useMemo(() => getTenantClubDependencyKey(user), [user])

  const [loading, setLoading] = useState(!isNew)
  const [saving, setSaving] = useState(false)
  const [groups, setGroups] = useState([])
  const [planTemplates, setPlanTemplates] = useState([])
  const [editingUnit, setEditingUnit] = useState(null)
  const [draftPlanTemplateId, setDraftPlanTemplateId] = useState('')
  const [sectionsEditMode, setSectionsEditMode] = useState('planning')
  const [clubDirectory, setClubDirectory] = useState([])
  const [formData, setFormData] = useState(() => createEmptyTrainingUnitFormData())
  const formRef = useRef(formData)
  formRef.current = formData

  const baselineRef = useRef(null)
  const [baselineReady, setBaselineReady] = useState(false)
  const [bypassDirty, setBypassDirty] = useState(false)

  const dirtySignature = trainingUnitFormSnapshot(formRef.current, {
    editingUnit,
    draftPlanTemplateId,
  })

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

  useEffect(() => {
    if (loading) return undefined
    const handle = window.setTimeout(() => {
      baselineRef.current = trainingUnitFormSnapshot(formRef.current, {
        editingUnit,
        draftPlanTemplateId,
      })
      setBaselineReady(true)
    }, 120)
    return () => clearTimeout(handle)
    // Baseline nur nach initialem Laden — nicht bei Template-/Form-Änderungen im Editor
    // eslint-disable-next-line react-hooks/exhaustive-deps -- editingUnit/draftPlanTemplateId bewusst ausgeschlossen
  }, [loading, isNew, unitId])

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

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

  const [exercisePickerOpen, setExercisePickerOpen] = useState(false)
  const [exercisePickerTarget, setExercisePickerTarget] = useState(null)
  const [planningPeekCtx, setPlanningPeekCtx] = useState(null)
  const [moduleApplyOpen, setModuleApplyOpen] = useState(false)
  const [moduleApplyBusy, setModuleApplyBusy] = useState(false)
  const [moduleApplyList, setModuleApplyList] = useState([])
  const [moduleApplyModuleId, setModuleApplyModuleId] = useState('')
  const [moduleApplySectionIx, setModuleApplySectionIx] = useState(0)
  const [moduleApplyInsertSlot, setModuleApplyInsertSlot] = useState('before:0')
  const [moduleApplyErr, setModuleApplyErr] = useState('')
  const [moduleApplyPlacementLocked, setModuleApplyPlacementLocked] = useState(false)
  const [moduleApplySearchQuery, setModuleApplySearchQuery] = useState('')
  const [modulePickPreview, setModulePickPreview] = useState({
    loading: false,
    moduleId: '',
    exercises: [],
    notes: 0,
    err: '',
  })
  const [publishFrameworkOpen, setPublishFrameworkOpen] = useState(false)
  const [saveModuleOpen, setSaveModuleOpen] = useState(false)

  const goBack = useCallback(() => {
    navigate(planningHubPathFromReturnState(location.state?.planningReturn))
  }, [location.state, navigate])

  const planningClubId = useMemo(() => {
    const gid = Number(formData.group_id)
    if (!Number.isFinite(gid) || gid < 1) return null
    const g = groups.find((x) => Number(x.id) === gid)
    if (!g?.club_id) return null
    const c = Number(g.club_id)
    return Number.isFinite(c) ? c : null
  }, [groups, formData.group_id])

  const moduleApplyFilteredList = useMemo(() => {
    const q = moduleApplySearchQuery.trim().toLowerCase().replace(/\s+/g, ' ')
    const words = q ? q.split(' ').filter(Boolean) : []
    const list = Array.isArray(moduleApplyList) ? moduleApplyList : []
    if (!words.length) return list
    return list.filter((m) => {
      const blob = [m.title, m.summary, m.goal, m.target_group_notes, m.deployment_context_notes]
        .map((x) => String(x ?? '').toLowerCase())
        .join('\n')
      return words.every((w) => blob.includes(w))
    })
  }, [moduleApplySearchQuery, moduleApplyList])

  const modulePlacementSummary = useMemo(() => {
    const secs = Array.isArray(formData.sections) ? formData.sections : []
    let si =
      typeof moduleApplySectionIx === 'number'
        ? moduleApplySectionIx
        : parseInt(String(moduleApplySectionIx), 10)
    if (!Number.isFinite(si)) si = 0
    si = Math.max(0, Math.min(si, secs.length ? secs.length - 1 : 0))
    const cap = secs[si]?.items?.length ?? 0
    let beforeIx = cap
    if (typeof moduleApplyInsertSlot === 'string' && moduleApplyInsertSlot.startsWith('before:')) {
      const zi = parseInt(moduleApplyInsertSlot.slice('before:'.length), 10)
      if (Number.isFinite(zi)) beforeIx = Math.min(Math.max(0, zi), cap)
    }
    const rawTitle = (secs[si]?.title || '').trim()
    const secTitle = rawTitle || `Abschnitt ${si + 1}`
    let positionDescription
    if (cap <= 0) positionDescription = 'als erste Einträge dieses Abschnitts'
    else if (beforeIx <= 0) positionDescription = 'vor dem ersten Eintrag dieses Abschnitts'
    else if (beforeIx >= cap) positionDescription = 'nach dem letzten Eintrag dieses Abschnitts'
    else positionDescription = `unmittelbar vor Eintrag ${beforeIx + 1} (${cap} Einträge im Abschnitt)`
    return { secTitle, positionDescription }
  }, [formData.sections, moduleApplySectionIx, moduleApplyInsertSlot])

  const moduleApplyTargetItems = useMemo(() => {
    const secs = formData.sections || []
    if (!secs.length) return []
    let ix =
      typeof moduleApplySectionIx === 'number'
        ? moduleApplySectionIx
        : parseInt(String(moduleApplySectionIx), 10)
    if (!Number.isFinite(ix)) ix = 0
    if (ix < 0 || ix >= secs.length) return []
    const sec = secs[ix]
    return Array.isArray(sec?.items) ? sec.items : []
  }, [formData.sections, moduleApplySectionIx])

  useEffect(() => {
    if (!moduleApplyOpen || !moduleApplyFilteredList.length) return
    if (moduleApplyFilteredList.some((m) => String(m.id) === String(moduleApplyModuleId))) return
    setModuleApplyModuleId(String(moduleApplyFilteredList[0].id))
  }, [moduleApplyOpen, moduleApplyFilteredList, moduleApplyModuleId])

  useEffect(() => {
    if (!moduleApplyOpen) {
      setModulePickPreview({
        loading: false,
        moduleId: '',
        exercises: [],
        notes: 0,
        err: '',
      })
      return undefined
    }
    const mid = parseInt(String(moduleApplyModuleId), 10)
    if (!Number.isFinite(mid) || mid < 1) {
      setModulePickPreview({
        loading: false,
        moduleId: '',
        exercises: [],
        notes: 0,
        err: '',
      })
      return undefined
    }
    let cancelled = false
    setModulePickPreview({
      loading: true,
      moduleId: String(mid),
      exercises: [],
      notes: 0,
      err: '',
    })
    ;(async () => {
      try {
        const detail = await api.getTrainingModule(mid)
        if (cancelled) return
        const itemsSorted = [...(detail.items ?? [])].sort(
          (a, b) => (a.order_index ?? 0) - (b.order_index ?? 0)
        )
        const uniqueEx = new Set()
        let notes = 0
        for (const row of itemsSorted) {
          if ((row.item_type || '') !== 'note') {
            const eid = row.exercise_id
            if (eid) uniqueEx.add(Number(eid))
            continue
          }
          const b = String(row.note_body ?? '').trim()
          if (b === '---') continue
          notes += 1
        }
        const titleById = new Map()
        await Promise.all(
          [...uniqueEx].map(async (eid) => {
            try {
              const ex = await api.getExercise(eid)
              titleById.set(eid, (ex?.title || '').trim() || `Übung #${eid}`)
            } catch {
              titleById.set(eid, `Übung #${eid}`)
            }
          })
        )
        if (cancelled) return
        const exTitlesInOrder = []
        for (const row of itemsSorted) {
          if ((row.item_type || '') !== 'exercise') continue
          const eid = Number(row.exercise_id)
          if (!Number.isFinite(eid)) continue
          exTitlesInOrder.push(titleById.get(eid) || `Übung #${eid}`)
        }
        setModulePickPreview({
          loading: false,
          moduleId: String(mid),
          exercises: exTitlesInOrder,
          notes,
          err: '',
        })
      } catch (e) {
        if (!cancelled) {
          setModulePickPreview({
            loading: false,
            moduleId: String(mid),
            exercises: [],
            notes: 0,
            err: e?.message || 'Vorschau fehlgeschlagen',
          })
        }
      }
    })()
    return () => {
      cancelled = true
    }
  }, [moduleApplyOpen, moduleApplyModuleId])

  useEffect(() => {
    if (planningClubId == null) {
      setClubDirectory([])
      return undefined
    }
    let cancelled = false
    ;(async () => {
      try {
        const d = await api.clubMembersDirectory(planningClubId)
        if (!cancelled) setClubDirectory(Array.isArray(d) ? d : [])
      } catch {
        if (!cancelled) setClubDirectory([])
      }
    })()
    return () => {
      cancelled = true
    }
  }, [planningClubId])

  const clubDirectoryForCo = useMemo(() => {
    let exclude = null
    const leadTrim = String(formData.lead_trainer_profile_id || '').trim()
    if (leadTrim) {
      const n = parseInt(leadTrim, 10)
      if (Number.isFinite(n)) exclude = n
    } else if (editingUnit?.effective_lead_trainer_profile_id != null) {
      exclude = Number(editingUnit.effective_lead_trainer_profile_id)
    } else {
      const gid = parseInt(formData.group_id || '0', 10)
      const g = groups.find((gr) => gr.id === gid)
      if (g?.trainer_id != null) exclude = Number(g.trainer_id)
    }
    return filterDirectoryExcludingLead(clubDirectory, exclude)
  }, [clubDirectory, formData.lead_trainer_profile_id, formData.group_id, editingUnit, groups])

  const loadCatalogs = useCallback(async () => {
    const [groupsData, tpl] = await Promise.all([
      api.listTrainingGroups({ status: 'active' }),
      api.listTrainingPlanTemplates(),
    ])
    setGroups(Array.isArray(groupsData) ? groupsData : [])
    setPlanTemplates(Array.isArray(tpl) ? tpl : [])
    return Array.isArray(groupsData) ? groupsData : []
  }, [])

  useEffect(() => {
    let cancelled = false
    async function init() {
      setLoading(true)
      try {
        const groupsData = await loadCatalogs()
        if (cancelled) return

        if (isNew) {
          const qGroup = searchParams.get('group') || ''
          const qDate = searchParams.get('date') || new Date().toISOString().slice(0, 10)
          const qTemplate = searchParams.get('template') || ''
          const gid =
            qGroup ||
            (groupsData.length === 1 ? String(groupsData[0].id) : '') ||
            (groupsData.find((g) => g.trainer_id === user?.id)
              ? String(groupsData.find((g) => g.trainer_id === user?.id).id)
              : '')
          const group = groupsData.find((g) => String(g.id) === String(gid))
          setEditingUnit(null)
          setDraftPlanTemplateId(qTemplate)
          setSectionsEditMode(searchParams.get('mode') === 'debrief' ? 'debrief' : 'planning')
          setFormData(
            createEmptyTrainingUnitFormData({
              groupId: gid,
              plannedDate: qDate,
              timeStart: group?.time_start?.slice(0, 5) || '',
              timeEnd: group?.time_end?.slice(0, 5) || '',
            })
          )
          if (qTemplate) {
            const tpl = await api.getTrainingPlanTemplate(parseInt(qTemplate, 10))
            if (!cancelled && tpl?.sections?.length) {
              setFormData((fd) => ({
                ...fd,
                sections: formSectionsFromPlanTemplateRows(tpl.sections),
              }))
            }
          }
        } else if (Number.isFinite(unitId)) {
          const fullUnit = await api.getTrainingUnit(unitId)
          if (cancelled) return
          setEditingUnit(fullUnit)
          setDraftPlanTemplateId(fullUnit.plan_template_id ? String(fullUnit.plan_template_id) : '')
          let sections = normalizeUnitToForm(fullUnit)
          sections = await enrichSectionsWithVariants(sections)
          if (cancelled) return
          setFormData(trainingUnitToFormFields(fullUnit, sections))
          const modeParam = searchParams.get('mode')
          setSectionsEditMode(
            modeParam === 'debrief' || fullUnit.status === 'completed' ? 'debrief' : 'planning'
          )
        }
      } catch (e) {
        if (!cancelled) toast.error(e.message || 'Laden fehlgeschlagen')
      } finally {
        if (!cancelled) setLoading(false)
      }
    }
    init()
    return () => {
      cancelled = true
    }
  }, [isNew, unitId, searchParams, loadCatalogs, user?.id, tenantClubDepKey, toast])

  const updateFormField = useCallback(
    (field, value) => {
      setFormData((prev) => {
        if (field !== 'lead_trainer_profile_id') {
          const patch = { ...prev, [field]: value }
          if (field === 'status' && value !== 'completed') patch.debrief_completed = false
          return patch
        }
        const ts = typeof value === 'string' ? value.trim() : String(value ?? '').trim()
        const strip = new Set()
        if (ts !== '') {
          const nid = parseInt(ts, 10)
          if (Number.isFinite(nid)) strip.add(nid)
        } else {
          const gidParsed = parseInt(prev.group_id || '0', 10)
          const gr =
            Number.isFinite(gidParsed) && gidParsed >= 1
              ? groups.find((xg) => xg.id === gidParsed)
              : null
          if (gr?.trainer_id != null) {
            const ht = Number(gr.trainer_id)
            if (Number.isFinite(ht)) strip.add(ht)
          }
        }
        return {
          ...prev,
          lead_trainer_profile_id: value,
          session_assistant_profile_ids: prev.session_assistant_profile_ids.filter((id) => !strip.has(id)),
        }
      })
    },
    [groups]
  )

  const applyTemplateFromSelect = async (templateId) => {
    setDraftPlanTemplateId(templateId)
    if (!templateId) return
    try {
      const tpl = await api.getTrainingPlanTemplate(parseInt(templateId, 10))
      setFormData((fd) => ({
        ...fd,
        sections: (tpl.sections || []).length
          ? formSectionsFromPlanTemplateRows(tpl.sections)
          : [defaultSection()],
      }))
    } catch (err) {
      toast.error('Vorlage laden: ' + err.message)
    }
  }

  const reloadUnitAfterSave = useCallback(
    async (savedId) => {
      const fullUnit = await api.getTrainingUnit(savedId)
      const nextDraftTemplateId = fullUnit.plan_template_id ? String(fullUnit.plan_template_id) : ''
      setEditingUnit(fullUnit)
      setDraftPlanTemplateId(nextDraftTemplateId)
      let sections = normalizeUnitToForm(fullUnit)
      sections = await enrichSectionsWithVariants(sections)
      const nextForm = trainingUnitToFormFields(fullUnit, sections)
      setFormData(nextForm)
      baselineRef.current = trainingUnitFormSnapshot(nextForm, {
        editingUnit: fullUnit,
        draftPlanTemplateId: nextDraftTemplateId,
      })
      setBypassDirty(false)
      if (!isNew && savedId !== unitId) {
        navigate(buildPlanUnitEditPath(savedId), { replace: true, state: location.state })
      }
    },
    [isNew, unitId, navigate, location.state]
  )

  const handleSubmit = useCallback(
    async (e, { closeAfter = true } = {}) => {
      e?.preventDefault?.()
      const v = validateTrainingUnitFormForSave(formData)
      if (!v.ok) {
        toast.error(v.message)
        return false
      }
      setSaving(true)
      try {
        const payload = buildTrainingUnitSavePayload(formData, {
          editingUnit,
          draftPlanTemplateId,
        })
        let savedUnit
        if (editingUnit) {
          savedUnit = await api.updateTrainingUnit(editingUnit.id, payload)
        } else {
          savedUnit = await api.createTrainingUnit(payload)
        }
        toast.success('Gespeichert.')
        if (closeAfter) {
          goBack()
        } else if (savedUnit?.id) {
          await reloadUnitAfterSave(savedUnit.id)
        }
        return true
      } catch (err) {
        toast.error('Fehler beim Speichern: ' + err.message)
        return false
      } finally {
        setSaving(false)
      }
    },
    [formData, editingUnit, draftPlanTemplateId, toast, goBack, reloadUnitAfterSave]
  )

  const handleUnsavedDialogSave = async () => {
    const ok = await handleSubmit(null, { closeAfter: false })
    if (ok) blocker.proceed()
  }

  const actionConfig = useMemo(
    () => ({
      formId: 'planning-unit-form',
      saving,
      isNew: !editingUnit,
      onSave: (e) => handleSubmit(e, { closeAfter: false }),
      onSaveAndClose: (e) => handleSubmit(e, { closeAfter: true }),
      onCancel: goBack,
      showSave: true,
      showSaveAndClose: true,
    }),
    [saving, editingUnit, handleSubmit, goBack]
  )

  const hubBackPath = planningHubPathFromReturnState(location.state?.planningReturn)
  const pageTitle = editingUnit ? 'Trainingseinheit bearbeiten' : 'Neue Trainingseinheit'

  const handleSaveAsTemplate = async (opts = {}) => {
    const name = window.prompt('Name für die neue Trainingsvorlage (nur Abschnitts-Gliederung):')
    if (!name?.trim()) return
    const descRaw = window.prompt('Kurzbeschreibung (optional, leer lassen zum Überspringen):')
    const visibility =
      typeof opts.visibility === 'string' && opts.visibility.trim()
        ? String(opts.visibility).trim().toLowerCase()
        : 'private'
    let club_id = opts.club_id != null && opts.club_id !== '' ? Number(opts.club_id) : null
    if (visibility === 'club') {
      if (!Number.isFinite(club_id) || club_id < 1) club_id = planningClubId
      if (!Number.isFinite(club_id) || club_id < 1) {
        toast.error('Bitte einen Verein wählen (Sichtbarkeit „Verein“).')
        return
      }
    } else {
      club_id = null
    }
    try {
      await api.createTrainingPlanTemplate({
        name: name.trim(),
        description: descRaw?.trim() ? descRaw.trim() : null,
        visibility,
        club_id: visibility === 'club' ? club_id : null,
        sections: templateSectionsPayloadFromFormSections(formData.sections),
      })
      toast.success('Vorlage gespeichert.')
    } catch (err) {
      toast.error('Speichern: ' + err.message)
    }
  }

  const openModuleApplyModal = useCallback(async (placement) => {
    setModuleApplyErr('')
    setModuleApplySearchQuery('')
    const placementLocked =
      placement != null &&
      typeof placement.sectionIndex === 'number' &&
      typeof placement.insertBeforeIndex === 'number'
    setModuleApplyPlacementLocked(placementLocked)
    const secs = formRef.current?.sections ?? []
    let secIx = 0
    let before = 0
    if (secs.length) {
      if (placement && typeof placement.sectionIndex === 'number') {
        secIx = Math.min(Math.max(0, placement.sectionIndex), secs.length - 1)
        const items = Array.isArray(secs[secIx]?.items) ? secs[secIx].items : []
        before =
          typeof placement.insertBeforeIndex === 'number'
            ? Math.min(Math.max(0, placement.insertBeforeIndex), items.length)
            : items.length
      } else {
        before = Array.isArray(secs[0]?.items) ? secs[0].items.length : 0
      }
    }
    setModuleApplySectionIx(secIx)
    setModuleApplyInsertSlot(`before:${before}`)
    setModuleApplyOpen(true)
    try {
      const list = await api.listTrainingModules()
      const arr = Array.isArray(list) ? list : []
      setModuleApplyList(arr)
      setModuleApplyModuleId(arr.length ? String(arr[0].id) : '')
    } catch (e) {
      setModuleApplyErr(e.message || 'Module konnten nicht geladen werden')
      setModuleApplyList([])
    }
  }, [])

  const onModuleApplySectionIndexChange = useCallback((newIx) => {
    setModuleApplySectionIx(newIx)
    const secsNow = formRef.current?.sections ?? []
    const len = Array.isArray(secsNow[newIx]?.items) ? secsNow[newIx].items.length : 0
    setModuleApplyInsertSlot(`before:${len}`)
  }, [])

  const handleApplyTrainingModuleConfirm = useCallback(async () => {
    const mid = parseInt(moduleApplyModuleId, 10)
    if (!Number.isFinite(mid)) {
      toast.error('Bitte ein Trainingsmodul wählen.')
      return
    }
    let secIx = parseInt(String(moduleApplySectionIx), 10)
    const baseSections = formRef.current?.sections ?? []
    if (!baseSections.length) return
    if (!Number.isFinite(secIx) || secIx < 0 || secIx >= baseSections.length) secIx = 0
    const itemCap = Array.isArray(baseSections[secIx]?.items) ? baseSections[secIx].items.length : 0
    let insertBefore = itemCap
    if (typeof moduleApplyInsertSlot === 'string' && moduleApplyInsertSlot.startsWith('before:')) {
      const zi = parseInt(moduleApplyInsertSlot.slice('before:'.length), 10)
      if (Number.isFinite(zi)) insertBefore = Math.min(Math.max(0, zi), itemCap)
    }
    setModuleApplyBusy(true)
    try {
      const detail = await api.getTrainingModule(mid)
      let nextSections = await insertTrainingModuleIntoPlanningSections({
        sections: baseSections,
        moduleDetail: detail,
        sectionIndex: secIx,
        insertBeforeItemIndex: insertBefore,
      })
      nextSections = await enrichSectionsWithVariants(nextSections)
      setFormData((fd) => ({ ...fd, sections: nextSections }))
      setModuleApplyOpen(false)
      setModuleApplyPlacementLocked(false)
    } catch (e) {
      setModuleApplyErr(e.message || 'Einfügen fehlgeschlagen')
    } finally {
      setModuleApplyBusy(false)
    }
  }, [moduleApplyModuleId, moduleApplySectionIx, moduleApplyInsertSlot, toast])

  const refreshPlanningSectionMeta = useCallback(async () => {
    const next = await enrichSectionsWithVariants(formRef.current.sections)
    setFormData((prev) => ({ ...prev, sections: next }))
  }, [])

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

  return (
    <PageFormEditorChrome
      testId="planning-unit-form"
      title={pageTitle}
      backTo={hubBackPath}
      backLabel="Trainingsplanung"
      actionConfig={actionConfig}
    >
      <TrainingUnitFormShell
        editingUnit={editingUnit}
        formData={formData}
        updateFormField={updateFormField}
        setFormData={setFormData}
        onSaveOnly={(e) => handleSubmit(e, { closeAfter: false })}
        onSaveAndClose={(e) => handleSubmit(e, { closeAfter: true })}
        draftPlanTemplateId={draftPlanTemplateId}
        onDraftTemplateSelect={applyTemplateFromSelect}
        planTemplates={planTemplates}
        clubDirectory={clubDirectory}
        clubDirectoryForCo={clubDirectoryForCo}
        planningClubId={planningClubId}
        user={user}
        onMetaRefresh={refreshPlanningSectionMeta}
        sectionsEditMode={sectionsEditMode}
        setSectionsEditMode={setSectionsEditMode}
        onSaveAsTemplate={handleSaveAsTemplate}
        onRequestPublishToFramework={() => editingUnit?.id && setPublishFrameworkOpen(true)}
        onRequestSaveAsModule={() => editingUnit?.id && setSaveModuleOpen(true)}
        onRequestTrainingModulePick={(ctx) => void openModuleApplyModal(ctx)}
        onRequestExercisePick={({ sectionIndex, itemIndex, insertBeforeIndex }) => {
          setExercisePickerTarget({
            sIdx: sectionIndex,
            iIdx: typeof itemIndex === 'number' ? itemIndex : undefined,
            insertBeforeIndex:
              typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
                ? insertBeforeIndex
                : undefined,
          })
          setExercisePickerOpen(true)
        }}
        onPeekExercise={(id, variantId, peekExtras) =>
          setPlanningPeekCtx({ exerciseId: id, variantId: variantId ?? null, peekExtras: peekExtras ?? null })
        }
      />

      <TrainingPlanningModuleApplyModal
        open={moduleApplyOpen}
        busy={moduleApplyBusy}
        err={moduleApplyErr}
        placementLocked={moduleApplyPlacementLocked}
        placementSummary={modulePlacementSummary}
        sections={formData.sections}
        sectionIx={moduleApplySectionIx}
        onSectionIndexChange={onModuleApplySectionIndexChange}
        insertSlot={moduleApplyInsertSlot}
        onInsertSlotChange={setModuleApplyInsertSlot}
        targetItems={moduleApplyTargetItems}
        searchQuery={moduleApplySearchQuery}
        onSearchQueryChange={setModuleApplySearchQuery}
        filteredList={moduleApplyFilteredList}
        fullList={moduleApplyList}
        selectedModuleId={moduleApplyModuleId}
        onSelectModuleId={setModuleApplyModuleId}
        modulePickPreview={modulePickPreview}
        onConfirm={handleApplyTrainingModuleConfirm}
        onCancel={() => {
          setModuleApplyOpen(false)
          setModuleApplyPlacementLocked(false)
        }}
      />

      <TrainingPublishToFrameworkModal
        open={publishFrameworkOpen}
        onClose={() => setPublishFrameworkOpen(false)}
        onSuccess={() => setPublishFrameworkOpen(false)}
        unitId={editingUnit?.id}
        planningModalClubId={planningClubId}
      />

      <SaveExercisesAsModuleModal
        open={saveModuleOpen}
        onClose={() => setSaveModuleOpen(false)}
        onSuccess={() => setSaveModuleOpen(false)}
        unitId={editingUnit?.id}
        planningModalClubId={planningClubId}
      />

      <ExercisePickerModal
        open={exercisePickerOpen}
        multiSelect
        enableQuickCreateDraft
        onClose={() => {
          setExercisePickerOpen(false)
          setExercisePickerTarget(null)
        }}
        onSelectExercises={async (picked) => {
          if (!exercisePickerTarget || !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 { sIdx, iIdx, insertBeforeIndex } = exercisePickerTarget
          setFormData((prev) => ({
            ...prev,
            sections: prev.sections.map((s, si) => {
              if (si !== sIdx) return s
              const items = [...(s.items || [])]
              if (typeof iIdx === 'number') {
                const [first, ...tail] = rows
                items[iIdx] = { ...items[iIdx], ...first, item_type: 'exercise' }
                if (tail.length) items.splice(iIdx + 1, 0, ...tail)
                return { ...s, items }
              }
              const at = Math.min(
                typeof insertBeforeIndex === 'number' ? insertBeforeIndex : items.length,
                items.length
              )
              items.splice(at, 0, ...rows)
              return { ...s, items }
            }),
          }))
          setExercisePickerOpen(false)
          setExercisePickerTarget(null)
        }}
      />

      <ExercisePeekModal
        open={planningPeekCtx != null}
        exerciseId={planningPeekCtx?.exerciseId}
        variantId={planningPeekCtx?.variantId ?? undefined}
        peekExtras={planningPeekCtx?.peekExtras ?? undefined}
        onClose={() => setPlanningPeekCtx(null)}
      />

      <UnsavedChangesPrompt
        blocker={blocker}
        isBusy={saving}
        onSave={handleUnsavedDialogSave}
        onDiscardWithoutSave={() => setBypassDirty(true)}
        detail="Du hast ungespeicherte Änderungen vorgenommen. Möchtest du die Seite wirklich verlassen?"
      />
    </PageFormEditorChrome>
  )
}
