/**
 * Geführtes method_profile für Kombinationsübungen — Felder nach method_archetype.
 * Unbekannte JSON-Schlüssel bleiben beim Zusammenführen erhalten (Erweiterbarkeit).
 */

const INT_MAX = 86400

/** Pro Station: zeitlich (Standard), mengenorientiert oder coachgeführt (ohne Arbeits-Countdown). */
export const SLOT_ADVANCE_MODES = Object.freeze(['timed', 'rep', 'manual'])

export function normalizeAdvanceMode(v) {
  const s = typeof v === 'string' ? v.trim().toLowerCase() : ''
  if (s === 'rep' || s === 'reps' || s === 'count') return 'rep'
  if (s === 'manual' || s === 'coach' || s === 'coach_led') return 'manual'
  return 'timed'
}

/**
 * Modus aus Roh-Zeile (Legacy ohne advance_mode; oder nur Ziel‑Wdh. ohne Sekunden).
 */
export function inferAdvanceModeFromStoredSlotRow(row) {
  if (!row || typeof row !== 'object') return 'timed'
  const explicitRaw = row.advance_mode
  if (explicitRaw !== undefined && explicitRaw !== null && String(explicitRaw).trim() !== '') {
    const e = normalizeAdvanceMode(explicitRaw)
    return e === 'rep' || e === 'manual' ? e : 'timed'
  }
  const load =
    row.load_sec !== undefined && row.load_sec !== null && row.load_sec !== ''
      ? normalizeOptionalNonNegInt(row.load_sec)
      : undefined
  const reps =
    row.consecutive_reps !== undefined && row.consecutive_reps !== null && row.consecutive_reps !== ''
      ? normalizeOptionalPositiveInt(row.consecutive_reps)
      : undefined

  if (load != null && reps == null) return 'timed'
  if (reps != null && load == null) return 'rep'
  if (load != null && reps != null) return 'timed'
  return 'timed'
}

/** UI: Serien-Anzahl aus Formularfeld; leer/ungültig ⇒ 1 (eine Serie). */
export function parseComboRepSeriesCountUi(raw) {
  if (raw === '' || raw === undefined || raw === null) return 1
  const n =
    typeof raw === 'number' && Number.isFinite(raw) ? Math.round(raw) : parseInt(String(raw).trim(), 10)
  if (!Number.isFinite(n) || n < 1) return 1
  return n
}

function parseProfileJson(raw) {
  if (typeof raw !== 'string' || !raw.trim()) return { ok: true, obj: {} }
  try {
    const p = JSON.parse(raw)
    if (!p || typeof p !== 'object' || Array.isArray(p)) {
      return { ok: false, error: 'Ablaufprofil muss ein JSON-Objekt sein.' }
    }
    return { ok: true, obj: { ...p } }
  } catch {
    return { ok: false, error: 'Ablaufprofil (JSON): Syntax ungültig.' }
  }
}

/** Pro Archetyp: UI-Feldbeschreibungen (Werte werden in method_profile geschrieben) */
export const METHOD_PROFILE_GUI_FIELDS = Object.freeze({
  sequence_linear: [
    {
      key: 'rounds',
      kind: 'int',
      label: 'Anzahl Gesamtdurchläufe (komplette Sequenz, alle Stationen nacheinander)',
      min: 1,
      max: 999,
    },
    {
      key: 'hint_step_duration_sec',
      kind: 'int',
      label: 'Orientierung: Sekunden je Station/Schritt (optional)',
      min: 5,
      max: INT_MAX,
    },
    {
      key: 'block_intro_sec',
      kind: 'int',
      label: 'Einführung / Demon am Block Gesamt (Sek., optional)',
      min: 0,
      max: INT_MAX,
    },
  ],
  circuit_rotate_time: [
    {
      key: 'rounds',
      kind: 'int',
      label:
        'Anzahl Gesamtdurchläufe (jede Station pro Sportler mehrfach beim Umlauf, z. B. 4 Stationen × 2 = zwei komplette Runden)',
      min: 1,
      max: 999,
    },
    {
      key: 'work_seconds',
      kind: 'int',
      label: 'Arbeitszeit pro Station (Sek.)',
      min: 5,
      max: INT_MAX,
    },
    {
      key: 'transition_seconds',
      kind: 'int',
      label: 'Wechsel / Rotation (Sek., optional)',
      min: 0,
      max: INT_MAX,
    },
    {
      key: 'rest_seconds',
      kind: 'int',
      label: 'Pause zwischen Runden oder Stationen-Folgen (Sek., optional)',
      min: 0,
      max: INT_MAX,
    },
  ],
  circuit_all_parallel: [
    {
      key: 'rounds',
      kind: 'int',
      label: 'Anzahl Durchläufe (wenn alle parallel dieselbe Rundenlogik haben, optional)',
      min: 1,
      max: 999,
    },
    {
      key: 'explain_before_seconds',
      kind: 'int',
      label: 'Zeitfenster Vorab‑Erklärung aller Stationen (Sek., optional)',
      min: 0,
      max: INT_MAX,
    },
    {
      key: 'simultaneous_start',
      kind: 'bool',
      label: 'Alle Stationen starten zusammen nach Erklärung',
    },
  ],
  station_parcour: [
    {
      key: 'rounds',
      kind: 'int',
      label: 'Anzahl Durchläufe des Parcours (Start Station 1, alle Bahnpunkte, Wiederholung bei Bedarf)',
      min: 1,
      max: 999,
    },
    {
      key: 'allow_free_visit_order',
      kind: 'bool',
      label: 'Reihenfolge der Besuche frei (Parcours / Abhaken-Logik später im Coach)',
    },
  ],
  pair_superset: [
    {
      key: 'switch_seconds',
      kind: 'int',
      label: 'Orientierung: Wechselpause A↔B (Sek., optional)',
      min: 0,
      max: INT_MAX,
    },
    {
      key: 'work_seconds_per_side',
      kind: 'int',
      label: 'Arbeit pro Rolle oder Seite (Sek., optional)',
      min: 5,
      max: INT_MAX,
    },
  ],
  time_domain_interval: [
    {
      key: 'work_seconds',
      kind: 'int',
      label: 'Intervall: Belastungszeit (Sek.)',
      min: 5,
      max: INT_MAX,
    },
    {
      key: 'rest_seconds',
      kind: 'int',
      label: 'Intervall: Erholungszeit (Sek., optional)',
      min: 0,
      max: INT_MAX,
    },
    {
      key: 'interval_rounds',
      kind: 'int',
      label: 'Anzahl Wiederholungen der Intervalldomäne (komplette Zyklen Arbeit/Pause)',
      min: 1,
      max: 999,
    },
  ],
  free_method_block: [],
})

function shortenComboGuiCaption(label) {
  const t = (label || '').trim()
  if (!t) return ''
  const cut = t.split('(')[0].trim()
  return cut.length > 52 ? `${cut.slice(0, 50)}…` : cut
}

/**
 * Globale Archetyp-Felder aus method_profile für Lesetext (Vorschau, Druck).
 * Ignoriert slot_profiles_v1 (kommt separat je Station).
 */
export function describeGlobalComboProfile(archetypeKey, profileObj) {
  const arch = typeof archetypeKey === 'string' ? archetypeKey.trim() : ''
  if (!profileObj || typeof profileObj !== 'object' || Array.isArray(profileObj)) return []
  const defs = METHOD_PROFILE_GUI_FIELDS[arch] || []
  const rows = []
  for (const def of defs) {
    const val = profileObj[def.key]
    if (val === undefined || val === null || val === '') continue
    if (def.kind === 'bool') {
      const on = val === true || val === 'true' || val === 1 || val === '1'
      rows.push({
        key: def.key,
        caption: shortenComboGuiCaption(def.label),
        detailLabel: def.label,
        value: on ? 'Ja' : 'Nein',
      })
    } else {
      rows.push({
        key: def.key,
        caption: shortenComboGuiCaption(def.label),
        detailLabel: def.label,
        value: String(val),
      })
    }
  }
  return rows
}

/**
 * Aktualisiert method_profile unter Beibehaltung nicht-GUI Schlüssel.
 */
export function updateProfileGuided(archetype, rawJson, key, parsedValue, kind) {
  const arch = typeof archetype === 'string' ? archetype.trim() : ''
  const parsed = parseProfileJson(rawJson)
  if (!parsed.ok) return parsed
  const next = { ...parsed.obj }

  if (kind === 'bool') {
    if (parsedValue) next[key] = true
    else delete next[key]
  } else if (kind === 'int') {
    if (parsedValue === null || parsedValue === undefined || parsedValue === '') {
      delete next[key]
    } else {
      const n = typeof parsedValue === 'number' ? parsedValue : parseInt(String(parsedValue), 10)
      if (!Number.isFinite(n)) delete next[key]
      else next[key] = n
    }
  }

  const outJson = JSON.stringify(next)
  return { ok: true, obj: next, json: outJson === '{}' ? '{}' : outJson }
}

export function setFullProfileRawJson(rawEditable) {
  const parsed = parseProfileJson(rawEditable)
  if (!parsed.ok) return parsed
  const j = JSON.stringify(parsed.obj)
  return { ok: true, obj: parsed.obj, json: j === '{}' ? '{}' : j }
}

/**
 * Pfad für slot_profiles_v1 und ähnliche strukturierte Erweiterungen.
 * Ungültiges JSON gibt { ok:false } zurück; mutator erhält geklontes Profil‑Objekt.
 */
export function patchMethodProfile(rawJson, mutator) {
  const parsed = parseProfileJson(rawJson || '{}')
  if (!parsed.ok) return parsed
  const draft = { ...parsed.obj }
  mutator(draft)
  try {
    const j = JSON.stringify(draft)
    return { ok: true, obj: draft, json: j === '{}' ? '{}' : j }
  } catch {
    return { ok: false, error: 'Ablaufprofil konnte nicht gespeichert werden.' }
  }
}

/** Normalisiert slot_profiles_v1 aus dem gespeicherten Profil */
export function readSlotProfilesV1(profileObj) {
  if (!profileObj || typeof profileObj !== 'object') return []
  const raw = profileObj.slot_profiles_v1
  if (!Array.isArray(raw)) return []
  return raw.map((row) => {
    if (!row || typeof row !== 'object') return null
    const si = Number(row.slot_index)
    const inferredMode = inferAdvanceModeFromStoredSlotRow(row)
    const out = {
      slot_index: Number.isFinite(si) ? si : 0,
      advance_mode: inferredMode,
      load_sec: normalizeOptionalNonNegInt(row.load_sec),
      consecutive_reps: normalizeOptionalPositiveInt(row.consecutive_reps),
      rep_series_count: normalizeOptionalPositiveInt(row.rep_series_count),
      intra_rep_rest_sec: normalizeOptionalNonNegInt(row.intra_rep_rest_sec),
      transition_after_sec: normalizeOptionalNonNegInt(row.transition_after_sec),
    }
    return out
  }).filter(Boolean)
}

/** Kurztext für Listen/strip (Coach „Plan:“ — gleiche Logik). */
export function summarizeSlotProfileBrief(r) {
  if (!r) return null
  const adv = r.advance_mode || 'timed'
  const bits = []
  if (adv === 'timed') {
    bits.push('Zeit')
    if (r.load_sec != null) bits.push(`${r.load_sec}s Arbeit`)
    if (r.consecutive_reps != null)
      bits.push(`${r.consecutive_reps}× Wdh. ohne Wechsel zur nächsten Station`)
  } else if (adv === 'rep') {
    bits.push('Ziel‑Wdh.')
    const nSer = r.rep_series_count != null && r.rep_series_count >= 1 ? r.rep_series_count : 1
    if (r.consecutive_reps != null) {
      if (nSer >= 2) bits.push(`${nSer} Serien à ${r.consecutive_reps}×`)
      else bits.push(`${r.consecutive_reps}×`)
    }
  } else {
    bits.push('Coach')
    const nSerMan = r.rep_series_count != null && r.rep_series_count >= 2 ? r.rep_series_count : 1
    if (r.consecutive_reps != null) {
      if (nSerMan >= 2) bits.push(`${nSerMan} Serien à Richtwert ${r.consecutive_reps}×`)
      else bits.push(`Richtwert ${r.consecutive_reps}×`)
    } else if (r.rep_series_count != null && r.rep_series_count >= 2) {
      bits.push(`${r.rep_series_count} Serien`)
    }
  }
  if (r.intra_rep_rest_sec != null) {
    if (adv === 'timed')
      bits.push(`Pause zw. Wdh. ${r.intra_rep_rest_sec}s (nicht Stationswechsel)`)
    else if (adv === 'rep' && r.rep_series_count != null && r.rep_series_count >= 2)
      bits.push(`Pause zw. Serien ${r.intra_rep_rest_sec}s`)
    else if (
      adv === 'manual' &&
      r.rep_series_count != null &&
      r.rep_series_count >= 2
    ) {
      bits.push(`Pause zw. Serien ${r.intra_rep_rest_sec}s`)
    }
  }
  if (r.transition_after_sec != null) bits.push(`Wechsel ${r.transition_after_sec}s`)
  return bits.join(' · ')
}

/**
 * Kompakte Stations-Belastung für die Plan-Klammer (links): Sekunden oder Wdh., nicht Slot-ID.
 */
export function stationPrimaryLoadLabel(slotRow) {
  if (!slotRow || typeof slotRow !== 'object') return null
  const adv = slotRow.advance_mode || 'timed'
  if (adv === 'timed') {
    if (slotRow.load_sec != null) return `${slotRow.load_sec}s`
    if (slotRow.consecutive_reps != null) return `${slotRow.consecutive_reps}×`
    return null
  }
  if (adv === 'rep') {
    if (slotRow.consecutive_reps != null) return `${slotRow.consecutive_reps}×`
    return null
  }
  if (adv === 'manual') {
    if (slotRow.consecutive_reps != null) return `~${slotRow.consecutive_reps}×`
    return null
  }
  return null
}

function globalTimingHintsForArchetype(arch, mp) {
  if (!mp || typeof mp !== 'object' || Array.isArray(mp)) return []
  const bits = []
  switch (arch) {
    case 'circuit_rotate_time':
      if (mp.work_seconds != null && mp.work_seconds !== '') bits.push(`${mp.work_seconds}s Arbeit je Station`)
      if (mp.transition_seconds != null && mp.transition_seconds !== '')
        bits.push(`Rotation ${mp.transition_seconds}s`)
      if (mp.rest_seconds != null && mp.rest_seconds !== '') bits.push(`Pause ${mp.rest_seconds}s`)
      if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Umlauf‑Runden`)
      break
    case 'sequence_linear':
      if (mp.hint_step_duration_sec != null && mp.hint_step_duration_sec !== '')
        bits.push(`~${mp.hint_step_duration_sec}s je Station`)
      if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Sequenz‑Durchläufe`)
      if (mp.block_intro_sec != null && mp.block_intro_sec !== '') bits.push(`Block‑Intro ${mp.block_intro_sec}s`)
      break
    case 'time_domain_interval':
      if (mp.work_seconds != null && mp.work_seconds !== '') bits.push(`${mp.work_seconds}s Intervall‑Arbeit`)
      if (mp.rest_seconds != null && mp.rest_seconds !== '') bits.push(`${mp.rest_seconds}s Erholung`)
      if (mp.interval_rounds != null && mp.interval_rounds !== '') bits.push(`${mp.interval_rounds} Intervall‑Zyklen`)
      break
    case 'pair_superset':
      if (mp.work_seconds_per_side != null && mp.work_seconds_per_side !== '')
        bits.push(`${mp.work_seconds_per_side}s Arbeit`)
      if (mp.switch_seconds != null && mp.switch_seconds !== '') bits.push(`Wechsel ${mp.switch_seconds}s`)
      break
    case 'station_parcour':
      if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Parcours‑Runden`)
      break
    case 'circuit_all_parallel':
      if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Runden`)
      if (mp.explain_before_seconds != null && mp.explain_before_seconds !== '')
        bits.push(`Erklärung ${mp.explain_before_seconds}s`)
      break
    default:
      break
  }
  return bits
}

function isWeakSlotTimingSummary(txt) {
  if (!txt || typeof txt !== 'string') return true
  const t = txt.trim()
  return t === 'Zeit' || t === 'Coach' || t === 'Ziel‑Wdh.'
}

/**
 * Stationszeile für Lesetext: Slot‑Zeiten + bei Bedarf globale Eckdaten (Zirkel‑Sekunden, Runden …).
 */
export function effectiveStationTimingSummary(archetypeKey, profileObj, slotRow) {
  const arch = typeof archetypeKey === 'string' ? archetypeKey.trim() : ''
  const mp = profileObj && typeof profileObj === 'object' && !Array.isArray(profileObj) ? profileObj : {}
  const slotTxt = summarizeSlotProfileBrief(slotRow)
  const hints = globalTimingHintsForArchetype(arch, mp)
  const hintStr = hints.join(' · ')

  if (!isWeakSlotTimingSummary(slotTxt)) {
    const extras = []
    for (const h of hints) {
      if (
        /Runden|Durchläufe|Zyklen|Umlauf/i.test(h) &&
        slotTxt &&
        !/Runden|Serien|×|\d+s Arbeit|\d+s Erholung|\d+s Intervall/i.test(slotTxt)
      ) {
        extras.push(h)
      }
    }
    return extras.length ? `${slotTxt} · ${extras.join(' · ')}` : slotTxt
  }

  if (hintStr) return hintStr
  if (slotTxt) return slotTxt
  return null
}

function normalizeOptionalNonNegInt(v) {
  if (v === '' || v === undefined || v === null) return undefined
  const n = typeof v === 'number' ? v : parseInt(String(v), 10)
  if (!Number.isFinite(n) || n < 0) return undefined
  return Math.round(n)
}

function normalizeOptionalPositiveInt(v) {
  const n = normalizeOptionalNonNegInt(v)
  if (n === undefined) return undefined
  if (n < 1) return undefined
  return n
}

const SLOT_TIMING_FIELDS = /** @type {const} */ ([
  'load_sec',
  'consecutive_reps',
  'rep_series_count',
  'intra_rep_rest_sec',
  'transition_after_sec',
])

function slotProfileRowShouldKeep(nextRow) {
  if (!nextRow || typeof nextRow !== 'object') return false
  const mode = normalizeAdvanceMode(nextRow.advance_mode)
  if (mode !== 'timed') return true
  return SLOT_TIMING_FIELDS.some((k) => nextRow[k] !== undefined && nextRow[k] !== null)
}

function writeSlotProfilesV1Arr(profileDraft, arr) {
  const sorted = [...arr].sort((a, b) => Number(a.slot_index) - Number(b.slot_index))
  if (sorted.length === 0) delete profileDraft.slot_profiles_v1
  else profileDraft.slot_profiles_v1 = sorted
}

/** Steuert Ende der Arbeitsphase: Zeit, Wiederholungsziel oder nur manuell weiter. */
export function patchSlotAdvanceMode(profileDraft, slotIndex, modeRaw) {
  const ix =
    typeof slotIndex === 'number' && Number.isFinite(slotIndex)
      ? slotIndex
      : parseInt(String(slotIndex), 10)
  if (!Number.isFinite(ix)) return

  const mode = normalizeAdvanceMode(modeRaw)
  let arr = Array.isArray(profileDraft.slot_profiles_v1) ? [...profileDraft.slot_profiles_v1] : []
  const found = arr.findIndex((r) => r && typeof r === 'object' && Number(r.slot_index) === ix)

  const nextRow = {}
  if (found >= 0 && arr[found] && typeof arr[found] === 'object') {
    Object.assign(nextRow, arr[found])
  }
  nextRow.slot_index = ix
  if (mode === 'timed') delete nextRow.advance_mode
  else {
    nextRow.advance_mode = mode
    delete nextRow.load_sec
  }

  let nextArr
  if (!slotProfileRowShouldKeep(nextRow)) {
    nextArr = found >= 0 ? arr.filter((_, i) => i !== found) : arr
  } else if (found >= 0) {
    nextArr = [...arr]
    nextArr[found] = nextRow
  } else {
    nextArr = [...arr, nextRow]
  }

  writeSlotProfilesV1Arr(profileDraft, nextArr)
}

/** '', null = Feld entfernen; sonst gültige Zahl setzen */
export function patchSlotTimingField(profileDraft, slotIndex, field, rawInput) {
  if (!SLOT_TIMING_FIELDS.includes(field)) return
  const ix =
    typeof slotIndex === 'number' && Number.isFinite(slotIndex)
      ? slotIndex
      : parseInt(String(slotIndex), 10)
  if (!Number.isFinite(ix)) return

  let arr = Array.isArray(profileDraft.slot_profiles_v1) ? [...profileDraft.slot_profiles_v1] : []
  let found = arr.findIndex((r) => r && typeof r === 'object' && Number(r.slot_index) === ix)

  const nextRow = {}
  if (found >= 0 && arr[found] && typeof arr[found] === 'object') {
    Object.assign(nextRow, arr[found])
  }
  nextRow.slot_index = ix

  if (rawInput === null || rawInput === undefined || String(rawInput).trim() === '') {
    delete nextRow[field]
  } else if (field === 'consecutive_reps' || field === 'rep_series_count') {
    const n = normalizeOptionalPositiveInt(rawInput)
    if (n === undefined) delete nextRow[field]
    else nextRow[field] = n
  } else {
    const n = normalizeOptionalNonNegInt(rawInput)
    if (n === undefined) delete nextRow[field]
    else nextRow[field] = n
  }

  const keep = slotProfileRowShouldKeep(nextRow)

  if (found >= 0) {
    if (!keep) {
      arr = arr.filter((_, i) => i !== found)
    } else {
      arr[found] = nextRow
    }
  } else if (keep) {
    arr.push(nextRow)
  }

  writeSlotProfilesV1Arr(profileDraft, arr)
}

/** Rotierender Zirkel: typische Ableitungen (setzt Sekunden konkret). */
export function applyCircuitRotateQuickRatio(profileDraft, preset) {
  const wRaw = profileDraft.work_seconds
  const work =
    typeof wRaw === 'number' && Number.isFinite(wRaw) ? Math.round(wRaw) : parseInt(String(wRaw), 10)
  if (!Number.isFinite(work) || work <= 0)
    return { ok: false, error: 'Zuerst Arbeitszeit pro Station (Sek.) setzen.' }
  profileDraft.timing_schema = profileDraft.timing_schema ?? 1
  if (preset === 'transition_equals_work') {
    profileDraft.transition_seconds = work
    return { ok: true }
  }
  if (preset === 'round_rest_equals_work') {
    profileDraft.rest_seconds = work
    return { ok: true }
  }
  if (preset === 'round_rest_two_thirds_work') {
    profileDraft.rest_seconds = Math.round((work * 2) / 3)
    return { ok: true }
  }
  return { ok: false, error: 'Unbekannte Schnellwahl.' }
}

export function applyIntervalDomainQuickRatio(profileDraft, preset) {
  const wRaw = profileDraft.work_seconds
  const work =
    typeof wRaw === 'number' && Number.isFinite(wRaw) ? Math.round(wRaw) : parseInt(String(wRaw), 10)
  if (!Number.isFinite(work) || work <= 0)
    return { ok: false, error: 'Zuerst Belastungszeit Intervall (Sek.) setzen.' }
  profileDraft.timing_schema = profileDraft.timing_schema ?? 1
  if (preset === 'rest_equals_work') {
    profileDraft.rest_seconds = work
    return { ok: true }
  }
  if (preset === 'rest_two_thirds_work') {
    profileDraft.rest_seconds = Math.round((work * 2) / 3)
    return { ok: true }
  }
  return { ok: false, error: 'Unbekannte Schnellwahl.' }
}

export { parseProfileJson, INT_MAX }
