import React, { useEffect, useMemo, useState } from 'react'
import { router, useForm, usePage } from '@inertiajs/react'
import StudioLayout from '../../Layouts/StudioLayout'
import RichTextEditor from '../../components/forum/RichTextEditor'
import { Checkbox, DateTimePicker, NovaSelect } from '../../components/ui'
import NovaConfirmDialog from '../../components/ui/NovaConfirmDialog'
import WorldDuplicateActionMenu from '../../components/worlds/editor/WorldDuplicateActionMenu'
import WorldRecapArticlePickerModal from '../../components/worlds/editor/WorldRecapArticlePickerModal'
import WorldLinkedChallengePickerModal from '../../components/worlds/editor/WorldLinkedChallengePickerModal'
import WorldMiniPreviewPanel from '../../components/worlds/editor/WorldMiniPreviewPanel'
import WorldRecurrenceHelper from '../../components/worlds/editor/WorldRecurrenceHelper'
import WorldRelationCard from '../../components/worlds/editor/WorldRelationCard'
import WorldRelationPickerModal from '../../components/worlds/editor/WorldRelationPickerModal'
import WorldSectionToggleList from '../../components/worlds/editor/WorldSectionToggleList'
import WorldMediaUploadField from '../../components/worlds/editor/WorldMediaUploadField'
import WorldAnalyticsPanel from '../../components/worlds/editor/analytics/WorldAnalyticsPanel'
import WorldSuggestionsPanel from '../../components/worlds/editor/suggestions/WorldSuggestionsPanel'
import WorldSummaryCard from '../../components/worlds/editor/WorldSummaryCard'
import WorldThemePresetHelper from '../../components/worlds/editor/WorldThemePresetHelper'

function toDateTimeLocal(value) {
  if (!value) return ''
  return String(value).slice(0, 16)
}

function arraysEqual(left, right) {
  if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
    return false
  }

  return left.every((entry, index) => entry === right[index])
}

function normalizeRelations(relations) {
  return (Array.isArray(relations) ? relations : []).map((relation, index) => ({
    ...relation,
    sort_order: index,
  }))
}

function relationForEditor(relation) {
  return {
    ...relation,
    sort_order: Number(relation?.sort_order || 0),
    is_featured: Boolean(relation?.is_featured),
    preview: relation?.preview || null,
    query: relation?.query || relation?.preview?.title || '',
  }
}

function upsertRelation(relations, nextRelation) {
  const items = Array.isArray(relations) ? relations : []
  const normalized = relationForEditor(nextRelation)
  const existingIndex = items.findIndex((relation) => relation.related_type === normalized.related_type && Number(relation.related_id) === Number(normalized.related_id))

  if (existingIndex === -1) {
    return normalizeRelations([...items, normalized])
  }

  return normalizeRelations(items.map((relation, index) => (index === existingIndex ? relationForEditor({ ...relation, ...normalized }) : relation)))
}

function initialSectionVisibility(sectionOptions, worldVisibility) {
  const defaults = Object.fromEntries((Array.isArray(sectionOptions) ? sectionOptions : []).map((option) => [option.value, true]))
  return { ...defaults, ...(worldVisibility || {}) }
}

function buildDefaultRelation(sectionOptions, relationTypeOptions, existingCount = 0) {
  const firstSection = sectionOptions?.[0]
  return {
    section_key: firstSection?.value || 'featured_artworks',
    related_type: firstSection?.relation_types?.[0] || relationTypeOptions?.[0]?.value || 'artwork',
    related_id: '',
    context_label: '',
    sort_order: existingCount,
    is_featured: false,
    preview: null,
    query: '',
  }
}

function resolveMediaUrl(path, fallbackUrl = '', filesBaseUrl = '') {
  if (!path) return fallbackUrl || ''
  if (String(path).startsWith('http://') || String(path).startsWith('https://') || String(path).startsWith('/')) {
    return path
  }

  if (fallbackUrl) {
    return fallbackUrl
  }

  if (filesBaseUrl) {
    return `${String(filesBaseUrl).replace(/\/$/, '')}/${String(path).replace(/^\//, '')}`
  }

  return path
}

function formatCompactNumber(value) {
  const number = Number(value || 0)

  if (!Number.isFinite(number)) {
    return '0'
  }

  return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(number)
}

const DEFAULT_ACTION_CONFIRM = {
  open: false,
  url: '',
  title: 'Please confirm',
  message: '',
  confirmLabel: 'Confirm',
  cancelLabel: 'Cancel',
  confirmTone: 'danger',
  noteEnabled: false,
  copyModeEnabled: false,
  copyModeOptions: [],
  defaultCopyMode: 'with_relations',
  preserveScroll: true,
}

function buildPreviewWorld(formData, world, themeOptions, typeOptions, filesBaseUrl) {
  const theme = (themeOptions || []).find((item) => item.value === formData.theme_key) || null
  const type = (typeOptions || []).find((item) => item.value === formData.type)

  return {
    title: formData.title,
    tagline: formData.tagline,
    summary: formData.summary,
    description: formData.description,
    cover_url: resolveMediaUrl(formData.cover_path, world?.cover_path === formData.cover_path ? world?.cover_url || '' : '', filesBaseUrl),
    teaser_image_url: resolveMediaUrl(formData.teaser_image_path, world?.teaser_image_path === formData.teaser_image_path ? world?.teaser_image_url || '' : '', filesBaseUrl),
    type: type?.label || formData.type || 'Seasonal',
    badge_label: formData.badge_label,
    campaign_label: formData.campaign_label,
    badge_description: formData.badge_description,
    cta_label: formData.cta_label,
    accent_color: formData.accent_color || theme?.accent_color || '#38bdf8',
    accent_color_secondary: formData.accent_color_secondary || theme?.accent_color_secondary || '#0f172a',
    background_motif: formData.background_motif || theme?.background_motif || 'atmosphere',
    icon_name: formData.icon_name || theme?.icon_name || 'fa-solid fa-sparkles',
    is_featured: Boolean(formData.is_featured),
    is_active_campaign: Boolean(formData.is_active_campaign),
    is_homepage_featured: Boolean(formData.is_homepage_featured),
  }
}

const WORLD_EDITOR_TABS = [
  {
    id: 'basics',
    label: 'Basics',
    icon: 'fa-solid fa-pen-ruler',
    description: 'Core editorial copy, title, and public story framing for the world.',
  },
  {
    id: 'structure',
    label: 'Structure',
    icon: 'fa-solid fa-diagram-project',
    description: 'Curated relations and section ordering that shape the public world composition.',
  },
  {
    id: 'suggestions',
    label: 'Suggestions',
    icon: 'fa-solid fa-wand-magic-sparkles',
    description: 'Editorial assist candidates scored from theme, submissions, challenge context, and recurring-world signals.',
  },
  {
    id: 'community',
    label: 'Community',
    icon: 'fa-solid fa-people-group',
    description: 'Submission settings and the moderation queue for creator participation.',
  },
  {
    id: 'publishing',
    label: 'Publishing',
    icon: 'fa-solid fa-calendar-check',
    description: 'Status, schedule, recurrence, and edition management controls.',
  },
  {
    id: 'presentation',
    label: 'Presentation',
    icon: 'fa-solid fa-swatchbook',
    description: 'Theme preset, visual identity, media assets, CTA, and badge surface copy.',
  },
  {
    id: 'recap',
    label: 'Recap',
    icon: 'fa-solid fa-stars',
    description: 'Shape the archive-facing recap with editorial framing, linked recap story, and publish-ready summary state.',
  },
  {
    id: 'seo',
    label: 'SEO',
    icon: 'fa-solid fa-magnifying-glass-chart',
    description: 'Search and social metadata that ships with the world page.',
  },
  {
    id: 'analytics',
    label: 'Analytics',
    icon: 'fa-solid fa-chart-column',
    description: 'Traffic, source surfaces, engagement, participation, challenge energy, and recurring-edition comparison.',
  },
]

const WORLD_EDITOR_TAB_FIELDS = {
  basics: ['title', 'slug', 'tagline', 'summary', 'description'],
  structure: ['relations', 'section_order_json', 'section_visibility_json', 'linked_challenge_id', 'show_linked_challenge_section', 'show_linked_challenge_entries', 'show_linked_challenge_winners', 'show_linked_challenge_finalists', 'auto_grant_challenge_world_rewards', 'challenge_teaser_override', 'hidden_linked_challenge_artwork_ids_json'],
  suggestions: [],
  community: ['accepts_submissions', 'participation_mode', 'submission_starts_at', 'submission_ends_at', 'submission_note_enabled', 'community_section_enabled', 'allow_readd_after_removal', 'submission_guidelines'],
  publishing: ['type', 'status', 'published_at', 'starts_at', 'ends_at', 'promotion_starts_at', 'promotion_ends_at', 'is_featured', 'is_active_campaign', 'is_homepage_featured', 'campaign_priority', 'is_recurring', 'recurrence_key', 'recurrence_rule', 'edition_year'],
  presentation: ['theme_key', 'accent_color', 'accent_color_secondary', 'background_motif', 'icon_name', 'cover_path', 'teaser_image_path', 'og_image_path', 'teaser_title', 'teaser_summary', 'cta_label', 'cta_url', 'badge_label', 'campaign_label', 'badge_description', 'badge_url', 'related_tags_json'],
  recap: ['recap_status', 'recap_title', 'recap_summary', 'recap_intro', 'recap_editor_note', 'recap_cover_path', 'recap_article_id'],
  seo: ['seo_title', 'seo_description'],
  analytics: [],
}

const PARTICIPATION_MODE_OPTIONS = [
  { value: 'manual_approval', label: 'Manual approval', description: 'Creators can add artworks, but each one starts pending until a moderator approves it.' },
  { value: 'auto_add', label: 'Auto add', description: 'Eligible artworks go live in the community section immediately.' },
  { value: 'closed', label: 'Closed', description: 'Hide this world from creator participation surfaces.' },
]

function errorBelongsToTab(tabId, errorKey) {
  const prefixes = WORLD_EDITOR_TAB_FIELDS[tabId] || []

  return prefixes.some((prefix) => errorKey === prefix || errorKey.startsWith(`${prefix}.`) || errorKey.startsWith(`${prefix}[`))
}

function WorldEditorSection({ title, description, actions = null, children }) {
  return (
    <div className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
      <div className="flex items-start justify-between gap-4">
        <div>
          <h2 className="text-xl font-semibold text-white">{title}</h2>
          {description ? <p className="mt-1 text-sm leading-6 text-slate-400">{description}</p> : null}
        </div>
        {actions}
      </div>
      <div className="mt-5">{children}</div>
    </div>
  )
}

function LinkedChallengeCard({ challenge, onChange, onClear }) {
  return (
    <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
      {challenge ? (
        <div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
          <div className="flex min-w-0 gap-4">
            <div className="h-16 w-16 shrink-0 overflow-hidden rounded-[20px] border border-white/10 bg-slate-950">
              {challenge.image ? <img src={challenge.image} alt="" className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-slate-500"><i className="fa-solid fa-trophy" /></div>}
            </div>
            <div className="min-w-0">
              <div className="flex flex-wrap items-center gap-2">
                <div className="text-base font-semibold text-white">{challenge.title}</div>
                {challenge.entity_label ? <span className="rounded-full border border-sky-300/20 bg-sky-400/10 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-sky-100">{challenge.entity_label}</span> : null}
              </div>
              {challenge.subtitle ? <div className="mt-1 text-xs uppercase tracking-[0.14em] text-slate-500">{challenge.subtitle}</div> : null}
              {challenge.description ? <div className="mt-2 text-sm leading-6 text-slate-400">{challenge.description}</div> : null}
              {Array.isArray(challenge.meta) && challenge.meta.length > 0 ? <div className="mt-2 flex flex-wrap gap-2 text-xs text-slate-500">{challenge.meta.map((entry) => <span key={entry}>{entry}</span>)}</div> : null}
            </div>
          </div>
          <div className="flex shrink-0 flex-wrap gap-2">
            <button type="button" onClick={onChange} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Change</button>
            <button type="button" onClick={onClear} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-slate-200">Clear</button>
          </div>
        </div>
      ) : (
        <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
          <div>
            <div className="text-sm font-semibold text-white">No primary challenge linked</div>
            <p className="mt-1 text-sm leading-6 text-slate-400">Link one group challenge when this world should automatically surface challenge status, entries, and winner context.</p>
          </div>
          <button type="button" onClick={onChange} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Select challenge</button>
        </div>
      )}
    </div>
  )
}

function WorldEditorActionPanel({ formProcessing, isEditing, publishUrl, publishRecapUrl, canPublishRecap, recapStatusLabel, archiveUrl, publicUrl }) {
  return (
    <div className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
      <div className="flex items-start justify-between gap-4">
        <div>
          <h2 className="text-xl font-semibold text-white">Campaign actions</h2>
          <p className="mt-1 text-sm leading-6 text-slate-400">Keep save, publish, archive, and public-page actions reachable while reviewing the campaign summary.</p>
        </div>
        <div className="hidden rounded-full border border-white/10 bg-black/20 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-400 xl:inline-flex">Sticky</div>
      </div>

      <div className="mt-5 grid gap-3 sm:grid-cols-2">
        <button type="submit" disabled={formProcessing} className="inline-flex items-center justify-center gap-2 rounded-2xl bg-white px-5 py-3 text-sm font-semibold text-slate-950 transition hover:bg-sky-100 disabled:opacity-60">{formProcessing ? 'Saving…' : (isEditing ? 'Save world' : 'Create world')}</button>
        {publishUrl ? <button type="button" onClick={() => router.post(publishUrl)} className="inline-flex items-center justify-center gap-2 rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-5 py-3 text-sm font-semibold text-emerald-100">Publish</button> : null}
        {publishRecapUrl ? <button type="button" onClick={() => router.post(publishRecapUrl)} disabled={!canPublishRecap} className="inline-flex items-center justify-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 disabled:cursor-not-allowed disabled:opacity-50">{recapStatusLabel === 'Published recap' ? 'Refresh recap snapshot' : 'Publish recap'}</button> : null}
        {archiveUrl ? <button type="button" onClick={() => router.post(archiveUrl)} className="inline-flex items-center justify-center gap-2 rounded-2xl border border-amber-300/20 bg-amber-400/10 px-5 py-3 text-sm font-semibold text-amber-100">Archive</button> : null}
        {publicUrl ? <a href={publicUrl} className="inline-flex items-center justify-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white">Public page</a> : null}
      </div>
    </div>
  )
}

function LinkedChallengeEntryVisibilityManager({ challenge, hiddenIds, onToggle, error = '' }) {
  const items = Array.isArray(challenge?.entry_preview_items) ? challenge.entry_preview_items : []

  if (!challenge || items.length === 0) {
    return null
  }

  return (
    <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
      <div className="flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
        <div>
          <div className="text-sm font-semibold text-white">Entry visibility overrides</div>
          <p className="mt-1 text-sm leading-6 text-slate-400">Hide specific linked challenge entries from the derived world feed when moderation or editorial context requires it.</p>
        </div>
        <div className="text-xs uppercase tracking-[0.16em] text-slate-500">{hiddenIds.length} hidden</div>
      </div>

      <div className="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
        {items.map((item) => {
          const hidden = hiddenIds.includes(item.id)
          const statusLabel = item.status === 'winner' ? 'Winner' : item.status === 'finalist' ? 'Finalist' : 'Entry'

          return (
            <button
              key={item.id}
              type="button"
              onClick={() => onToggle(item.id)}
              className={`flex items-center gap-3 rounded-[20px] border px-3 py-3 text-left transition ${hidden ? 'border-amber-300/25 bg-amber-400/10' : 'border-white/10 bg-white/[0.03] hover:bg-white/[0.05]'}`}
            >
              <div className="h-14 w-14 shrink-0 overflow-hidden rounded-2xl border border-white/10 bg-slate-950">
                {item.image ? <img src={item.image} alt="" className="h-full w-full object-cover" /> : <div className="flex h-full w-full items-center justify-center text-slate-500"><i className="fa-solid fa-image" /></div>}
              </div>
              <div className="min-w-0 flex-1">
                <div className="truncate text-sm font-semibold text-white">{item.title}</div>
                <div className="mt-1 text-[11px] uppercase tracking-[0.16em] text-slate-500">{statusLabel}</div>
                <div className={`mt-2 text-xs font-semibold ${hidden ? 'text-amber-100' : 'text-emerald-100'}`}>{hidden ? 'Hidden from world entries' : 'Visible on world page'}</div>
              </div>
            </button>
          )
        })}
      </div>

      {error ? <div className="mt-3 text-sm text-rose-300">{error}</div> : null}
    </div>
  )
}

export default function StudioWorldEditor() {
  const { props } = usePage()
  const world = props.world || null
  const filesBaseUrl = props.mediaSupport?.files_base_url || ''
  const sectionOptions = props.sectionOptions || []
  const relationTypeOptions = props.relationTypeOptions || []
  const themeOptions = props.themeOptions || []
  const typeOptions = props.typeOptions || []
  const duplicateActions = props.duplicateActions || null
  const suggestions = props.suggestions || null
  const reviewQueue = world?.submission_review_queue || { counts: { pending: 0, live: 0, removed: 0, blocked: 0, featured: 0 }, items: [] }

  const initialRelations = Array.isArray(world?.relations) ? normalizeRelations(world.relations.map((relation) => relationForEditor({
    section_key: relation.section_key || sectionOptions?.[0]?.value || 'featured_artworks',
    related_type: relation.related_type || relationTypeOptions?.[0]?.value || 'artwork',
    related_id: relation.related_id || '',
    context_label: relation.context_label || '',
    sort_order: relation.sort_order || 0,
    is_featured: Boolean(relation.is_featured),
    preview: relation.preview || null,
  }))) : []

  const form = useForm({
    title: world?.title || '',
    slug: world?.slug || '',
    tagline: world?.tagline || '',
    summary: world?.summary || '',
    teaser_title: world?.teaser_title || '',
    teaser_summary: world?.teaser_summary || '',
    description: world?.description || '',
    cover_path: world?.cover_path || '',
    teaser_image_path: world?.teaser_image_path || '',
    theme_key: world?.theme_key ?? '',
    accent_color: world?.accent_color || '',
    accent_color_secondary: world?.accent_color_secondary || '',
    background_motif: world?.background_motif || '',
    icon_name: world?.icon_name || '',
    status: world?.status || 'draft',
    type: world?.type || 'seasonal',
    published_at: toDateTimeLocal(world?.published_at),
    starts_at: toDateTimeLocal(world?.starts_at),
    ends_at: toDateTimeLocal(world?.ends_at),
    promotion_starts_at: toDateTimeLocal(world?.promotion_starts_at),
    promotion_ends_at: toDateTimeLocal(world?.promotion_ends_at),
    accepts_submissions: Boolean(world?.accepts_submissions),
    participation_mode: world?.participation_mode || (world?.accepts_submissions ? 'manual_approval' : 'closed'),
    submission_starts_at: toDateTimeLocal(world?.submission_starts_at),
    submission_ends_at: toDateTimeLocal(world?.submission_ends_at),
    submission_note_enabled: world?.submission_note_enabled !== false,
    community_section_enabled: world?.community_section_enabled !== false,
    allow_readd_after_removal: world?.allow_readd_after_removal !== false,
    is_featured: Boolean(world?.is_featured),
    is_active_campaign: Boolean(world?.is_active_campaign),
    is_homepage_featured: Boolean(world?.is_homepage_featured),
    campaign_priority: world?.campaign_priority ?? '',
    is_recurring: Boolean(world?.is_recurring),
    recurrence_key: world?.recurrence_key || '',
    recurrence_rule: world?.recurrence_rule || '',
    edition_year: world?.edition_year || '',
    cta_label: world?.cta_label || '',
    cta_url: world?.cta_url || '',
    badge_label: world?.badge_label || '',
    campaign_label: world?.campaign_label || '',
    badge_description: world?.badge_description || '',
    submission_guidelines: world?.submission_guidelines || '',
    badge_url: world?.badge_url || '',
    seo_title: world?.seo_title || '',
    seo_description: world?.seo_description || '',
    og_image_path: world?.og_image_path || '',
    recap_status: world?.recap_status || 'draft',
    recap_title: world?.recap_title || '',
    recap_summary: world?.recap_summary || '',
    recap_intro: world?.recap_intro || '',
    recap_editor_note: world?.recap_editor_note || '',
    recap_cover_path: world?.recap_cover_path || '',
    recap_article_id: world?.recap_article_id || '',
    linked_challenge_id: world?.linked_challenge_id || '',
    show_linked_challenge_section: world?.show_linked_challenge_section !== false,
    show_linked_challenge_entries: world?.show_linked_challenge_entries !== false,
    show_linked_challenge_winners: world?.show_linked_challenge_winners !== false,
    show_linked_challenge_finalists: world?.show_linked_challenge_finalists !== false,
    auto_grant_challenge_world_rewards: world?.auto_grant_challenge_world_rewards !== false,
    challenge_teaser_override: world?.challenge_teaser_override || '',
    hidden_linked_challenge_artwork_ids_json: Array.isArray(world?.hidden_linked_challenge_artwork_ids_json) ? world.hidden_linked_challenge_artwork_ids_json : [],
    related_tags_json: Array.isArray(world?.related_tags_json) ? world.related_tags_json : [],
    section_order_json: Array.isArray(world?.section_order_json) && world.section_order_json.length > 0 ? world.section_order_json : sectionOptions.map((option) => option.value),
    section_visibility_json: initialSectionVisibility(sectionOptions, world?.section_visibility_json),
    relations: initialRelations,
  })

  const [pickerState, setPickerState] = useState({ open: false, index: null })
  const [linkedChallengePickerOpen, setLinkedChallengePickerOpen] = useState(false)
  const [linkedChallengePreview, setLinkedChallengePreview] = useState(world?.linked_challenge || null)
  const [recapArticlePickerOpen, setRecapArticlePickerOpen] = useState(false)
  const [recapArticlePreview, setRecapArticlePreview] = useState(world?.recap_article || null)
  const [activeTab, setActiveTab] = useState('basics')
  const [temporaryMediaPaths, setTemporaryMediaPaths] = useState({
    cover: '',
    teaser: '',
    og: '',
    recap: '',
  })

  const themeMap = useMemo(() => Object.fromEntries(themeOptions.map((option) => [option.value, option])), [themeOptions])
  const sectionMap = useMemo(() => Object.fromEntries(sectionOptions.map((option) => [option.value, option])), [sectionOptions])
  const tagString = useMemo(() => (Array.isArray(form.data.related_tags_json) ? form.data.related_tags_json.join(', ') : ''), [form.data.related_tags_json])
  const linkedChallengeEntryPreviewItems = useMemo(() => Array.isArray(linkedChallengePreview?.entry_preview_items) ? linkedChallengePreview.entry_preview_items : [], [linkedChallengePreview])

  const selectedTheme = form.data.theme_key ? themeMap[form.data.theme_key] : null
  const previewWorld = useMemo(() => buildPreviewWorld(form.data, world, themeOptions, typeOptions, filesBaseUrl), [filesBaseUrl, form.data, world, themeOptions, typeOptions])
  const recapCoverPreviewUrl = useMemo(() => resolveMediaUrl(form.data.recap_cover_path, world?.recap_cover_path === form.data.recap_cover_path ? world?.recap_cover_url || '' : '', filesBaseUrl), [filesBaseUrl, form.data.recap_cover_path, world?.recap_cover_path, world?.recap_cover_url])

  const relationCounts = useMemo(() => form.data.relations.reduce((counts, relation) => ({ ...counts, [relation.section_key]: (counts[relation.section_key] || 0) + 1 }), {}), [form.data.relations])
  const enabledSectionsCount = useMemo(() => Object.values(form.data.section_visibility_json || {}).filter(Boolean).length, [form.data.section_visibility_json])
  const defaultAnalyticsRange = world?.analytics?.default_range || '30d'
  const analyticsSummary = world?.analytics?.ranges?.[defaultAnalyticsRange]?.summary || null

  const previewSections = useMemo(() => {
    const visibleKeys = (form.data.section_order_json || []).filter((key) => form.data.section_visibility_json?.[key] !== false)
    return visibleKeys.map((key) => ({
      key,
      label: sectionMap[key]?.label || key,
      count: form.data.relations.filter((relation) => relation.section_key === key).length,
      items: form.data.relations.filter((relation) => relation.section_key === key).map((relation) => relation.preview).filter(Boolean).slice(0, 3),
    }))
  }, [form.data.section_order_json, form.data.section_visibility_json, form.data.relations, sectionMap])

  const recapStatsSnapshot = world?.recap_stats_snapshot || null
  const recapStatsSummary = recapStatsSnapshot?.summary || null
  const canPublishRecap = Boolean(world && (world.status === 'archived' || (world.ends_at && new Date(world.ends_at) < new Date())))

  const errorEntries = Object.entries(form.errors || {})
  const tabErrorCounts = useMemo(() => WORLD_EDITOR_TABS.reduce((counts, tab) => ({
    ...counts,
    [tab.id]: errorEntries.filter(([key]) => errorBelongsToTab(tab.id, key)).length,
  }), {}), [errorEntries])
  const editorTabs = useMemo(() => WORLD_EDITOR_TABS.map((tab) => {
    let meta = ''

    switch (tab.id) {
      case 'basics':
        meta = form.data.title ? 'Named and writable' : 'Needs title'
        break
      case 'structure':
        meta = form.data.relations.length > 0 ? `${form.data.relations.length} relation${form.data.relations.length === 1 ? '' : 's'}` : 'No relations yet'
        break
      case 'suggestions':
        meta = !world
          ? 'Save to unlock'
          : `${suggestions?.summary?.available_count || 0} ready · ${suggestions?.summary?.pinned_count || 0} pinned`
        break
      case 'community':
        meta = form.data.participation_mode === 'closed'
          ? 'Closed to creators'
          : form.data.participation_mode === 'auto_add'
            ? `${reviewQueue?.counts?.live || 0} live now`
            : `${reviewQueue?.counts?.pending || 0} pending review`
        break
      case 'publishing':
        meta = form.data.is_recurring ? 'Recurring workflow' : 'Single edition'
        break
      case 'presentation':
        meta = selectedTheme?.label || 'Custom identity'
        break
      case 'recap':
        meta = form.data.recap_title || recapArticlePreview?.title
          ? `${form.data.recap_status === 'published' ? 'Published' : 'Draft'} recap`
          : 'Optional archive layer'
        break
      case 'seo':
        meta = form.data.seo_title || form.data.seo_description ? 'Configured' : 'Optional metadata'
        break
      case 'analytics':
        meta = analyticsSummary?.views > 0 ? `${analyticsSummary.views} tracked views` : (world ? 'Ready for measurement' : 'Available after create')
        break
      default:
        meta = ''
    }

    return {
      ...tab,
      meta,
      errorCount: tabErrorCounts[tab.id] || 0,
    }
  }), [analyticsSummary?.views, form.data.participation_mode, form.data.recap_status, form.data.recap_title, form.data.title, form.data.relations.length, form.data.is_recurring, form.data.seo_description, form.data.seo_title, recapArticlePreview?.title, reviewQueue?.counts?.live, reviewQueue?.counts?.pending, selectedTheme?.label, suggestions?.summary?.available_count, suggestions?.summary?.pinned_count, tabErrorCounts, world])
  const firstErrorTab = useMemo(() => editorTabs.find((tab) => tab.errorCount > 0) || null, [editorTabs])
  const currentTab = editorTabs.find((tab) => tab.id === activeTab) || editorTabs[0]
  const editingRelation = pickerState.index === null ? buildDefaultRelation(sectionOptions, relationTypeOptions, form.data.relations.length) : form.data.relations[pickerState.index]
  const [actionConfirm, setActionConfirm] = useState(DEFAULT_ACTION_CONFIRM)
  const [actionReviewNote, setActionReviewNote] = useState('')
  const [actionCopyMode, setActionCopyMode] = useState(DEFAULT_ACTION_CONFIRM.defaultCopyMode)
  const [actionBusy, setActionBusy] = useState(false)
  const [suggestionBusyKey, setSuggestionBusyKey] = useState('')
  const [suggestionNotice, setSuggestionNotice] = useState('')

  useEffect(() => {
    if (firstErrorTab && firstErrorTab.id !== activeTab) {
      setActiveTab(firstErrorTab.id)
    }
  }, [activeTab, firstErrorTab])

  const searchEntities = async (type, query) => {
    const url = new URL(props.entitySearchUrl, window.location.origin)
    url.searchParams.set('type', type)
    url.searchParams.set('q', query)

    const response = await fetch(url.toString(), {
      headers: { Accept: 'application/json' },
      credentials: 'same-origin',
    })

    if (!response.ok) {
      return []
    }

    const payload = await response.json()
    return Array.isArray(payload.items) ? payload.items : []
  }

  const submit = (event) => {
    event.preventDefault()

    const options = {
      onSuccess: () => {
        setTemporaryMediaPaths({ cover: '', teaser: '', og: '', recap: '' })
      },
    }

    if (props.updateUrl) {
      form.patch(props.updateUrl, options)
      return
    }

    form.post(props.storeUrl, options)
  }

  const deleteTemporaryMediaPath = async (path) => {
    if (!props.mediaSupport?.delete_url || !path) return

    await fetch(props.mediaSupport.delete_url, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
        Accept: 'application/json',
      },
      credentials: 'same-origin',
      body: JSON.stringify({
        path,
        world_id: world?.id || undefined,
      }),
    })
  }

  const applyCoverToOg = async () => {
    if (!form.data.cover_path) return

    const currentOgPath = form.data.og_image_path
    const shouldDeleteTemporaryOg = temporaryMediaPaths.og !== '' && temporaryMediaPaths.og === currentOgPath && currentOgPath !== form.data.cover_path

    if (shouldDeleteTemporaryOg) {
      try {
        await deleteTemporaryMediaPath(currentOgPath)
      } catch {
        // Leave the field update available even if cleanup fails.
      }
    }

    form.setData('og_image_path', form.data.cover_path)
    setTemporaryMediaPaths((current) => ({ ...current, og: '' }))
  }

  const handleThemeChange = (nextThemeKey, force = false) => {
    const nextTheme = themeMap[nextThemeKey]
    const previousTheme = themeMap[form.data.theme_key]
    const currentTags = Array.isArray(form.data.related_tags_json) ? form.data.related_tags_json : []

    const shouldReplace = (currentValue, nextValue, previousValue) => {
      if (!nextValue) return currentValue
      if (force || !currentValue || currentValue === previousValue) return nextValue
      return currentValue
    }

    const nextData = {
      ...form.data,
      theme_key: String(nextThemeKey || ''),
      accent_color: shouldReplace(form.data.accent_color, nextTheme?.accent_color || '', previousTheme?.accent_color || ''),
      accent_color_secondary: shouldReplace(form.data.accent_color_secondary, nextTheme?.accent_color_secondary || '', previousTheme?.accent_color_secondary || ''),
      background_motif: shouldReplace(form.data.background_motif, nextTheme?.background_motif || '', previousTheme?.background_motif || ''),
      icon_name: shouldReplace(form.data.icon_name, nextTheme?.icon_name || '', previousTheme?.icon_name || ''),
      badge_label: shouldReplace(form.data.badge_label, nextTheme?.suggested_badge_label || '', previousTheme?.suggested_badge_label || ''),
      cta_label: shouldReplace(form.data.cta_label, nextTheme?.suggested_cta_label || '', previousTheme?.suggested_cta_label || ''),
      related_tags_json: force || currentTags.length === 0 || arraysEqual(currentTags, previousTheme?.related_tags_json || [])
        ? [...(nextTheme?.related_tags_json || [])]
        : currentTags,
    }

    form.setData(nextData)
  }

  const openRelationPicker = (index = null) => setPickerState({ open: true, index })
  const closeRelationPicker = () => setPickerState({ open: false, index: null })
  const saveRelation = (relation) => {
    const nextRelations = pickerState.index === null
      ? normalizeRelations([...form.data.relations, relation])
      : normalizeRelations(form.data.relations.map((item, index) => (index === pickerState.index ? relation : item)))

    form.setData('relations', nextRelations)
    closeRelationPicker()
  }

  const removeRelation = (index) => form.setData('relations', normalizeRelations(form.data.relations.filter((_, relationIndex) => relationIndex !== index)))
  const moveRelation = (index, delta) => {
    const nextIndex = index + delta
    if (nextIndex < 0 || nextIndex >= form.data.relations.length) return
    const next = [...form.data.relations]
    const [entry] = next.splice(index, 1)
    next.splice(nextIndex, 0, entry)
    form.setData('relations', normalizeRelations(next))
  }

  const updateSectionControls = (nextOrder, nextVisibility) => {
    form.setData({
      ...form.data,
      section_order_json: nextOrder,
      section_visibility_json: nextVisibility,
    })
  }

  const closeActionConfirm = () => {
    if (actionBusy) return
    setActionConfirm(DEFAULT_ACTION_CONFIRM)
    setActionReviewNote('')
    setActionCopyMode(DEFAULT_ACTION_CONFIRM.defaultCopyMode)
  }

  const saveLinkedChallenge = (challenge) => {
    setLinkedChallengePreview(challenge)
    form.setData({
      ...form.data,
      linked_challenge_id: challenge?.id || '',
      hidden_linked_challenge_artwork_ids_json: [],
    })
    setLinkedChallengePickerOpen(false)
  }


  const saveRecapArticle = (article) => {
    setRecapArticlePreview(article)
    form.setData({
      ...form.data,
      recap_article_id: article?.id || '',
    })
    setRecapArticlePickerOpen(false)
  }

  const clearRecapArticle = () => {
    setRecapArticlePreview(null)
    form.setData('recap_article_id', '')
  }
  const clearLinkedChallenge = () => {
    setLinkedChallengePreview(null)
    form.setData({
      ...form.data,
      linked_challenge_id: '',
      challenge_teaser_override: '',
      hidden_linked_challenge_artwork_ids_json: [],
    })
  }

  const toggleHiddenLinkedChallengeEntry = (artworkId) => {
    const currentIds = Array.isArray(form.data.hidden_linked_challenge_artwork_ids_json) ? form.data.hidden_linked_challenge_artwork_ids_json : []
    const nextIds = currentIds.includes(artworkId)
      ? currentIds.filter((id) => id !== artworkId)
      : [...currentIds, artworkId]

    form.setData('hidden_linked_challenge_artwork_ids_json', nextIds)
  }

  const openActionConfirm = (config) => {
    if (!config?.url) return

    setActionReviewNote('')
    setActionCopyMode(config.defaultCopyMode || DEFAULT_ACTION_CONFIRM.defaultCopyMode)
    setActionConfirm({
      ...DEFAULT_ACTION_CONFIRM,
      ...config,
      open: true,
    })
  }

  const confirmAction = () => {
    if (!actionConfirm.url || actionBusy) return

    const payload = {
      ...(actionConfirm.noteEnabled ? { review_note: actionReviewNote } : {}),
      ...(actionConfirm.copyModeEnabled ? { copy_mode: actionCopyMode } : {}),
    }

    setActionBusy(true)
    router.post(actionConfirm.url, payload, {
      preserveScroll: actionConfirm.preserveScroll,
      onSuccess: () => {
        setActionConfirm(DEFAULT_ACTION_CONFIRM)
        setActionReviewNote('')
      },
      onFinish: () => {
        setActionBusy(false)
      },
    })
  }

  const runDuplicateAction = (url, promptText, copyModeOptions = []) => {
    openActionConfirm({
      url,
      title: 'Duplicate world?',
      message: promptText,
      confirmLabel: 'Continue',
      cancelLabel: 'Cancel',
      confirmTone: 'accent',
      copyModeEnabled: copyModeOptions.length > 0,
      copyModeOptions,
      defaultCopyMode: copyModeOptions.find((option) => option.value === 'with_relations')?.value || copyModeOptions[0]?.value || 'with_relations',
      preserveScroll: false,
    })
  }

  const runSubmissionAction = (url, promptText, options = {}) => {
    const {
      title = 'Confirm submission action',
      confirmLabel = 'Confirm',
      cancelLabel = 'Cancel',
      confirmTone = 'accent',
      noteEnabled = false,
    } = options

    openActionConfirm({
      url,
      title,
      message: promptText,
      confirmLabel,
      cancelLabel,
      confirmTone,
      noteEnabled,
      preserveScroll: true,
    })
  }

  const postSuggestionAction = async (url, payload) => {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
        Accept: 'application/json',
      },
      credentials: 'same-origin',
      body: JSON.stringify(payload),
    })

    const data = await response.json().catch(() => ({}))

    if (!response.ok) {
      throw new Error(data?.message || 'Suggestion action failed.')
    }

    return data
  }

  const refreshSuggestions = () => {
    if (!world) return

    router.reload({
      only: ['suggestions'],
      preserveScroll: true,
      preserveState: true,
    })
  }

  const handleSuggestionAction = async (item, action, payload = {}, afterSuccess = null) => {
    const url = props.suggestionActions?.[action]
    if (!url || !item) return

    setSuggestionBusyKey(item.key)

    try {
      const response = await postSuggestionAction(url, {
        related_type: item.entity_type,
        related_id: item.entity_id,
        ...payload,
      })

      if (typeof afterSuccess === 'function') {
        afterSuccess(response)
      }

      setSuggestionNotice(response?.message || 'Suggestion updated.')
      refreshSuggestions()
    } catch (error) {
      setSuggestionNotice(error?.message || 'Suggestion action failed.')
    } finally {
      setSuggestionBusyKey('')
    }
  }

  const addSuggestionRelation = (item, sectionKey, isFeatured) => handleSuggestionAction(item, 'add', {
    section_key: sectionKey,
    is_featured: isFeatured,
  }, (response) => {
    if (response?.relation) {
      form.setData('relations', upsertRelation(form.data.relations, response.relation))
    }
  })

  const pinSuggestion = (item, sectionKey) => handleSuggestionAction(item, 'pin', {
    section_key: sectionKey,
  })

  const dismissSuggestion = (item) => handleSuggestionAction(item, 'dismiss')

  const markSuggestionNotRelevant = (item) => handleSuggestionAction(item, 'notRelevant')

  const restoreSuggestion = (item) => handleSuggestionAction(item, 'restore')

  return (
    <StudioLayout title={props.title} subtitle={props.description}>
      <form onSubmit={submit} className="grid gap-6 xl:grid-cols-[minmax(0,1.04fr)_minmax(360px,0.96fr)]">
        <section className="space-y-6">
          {errorEntries.length > 0 ? (
            <div className="rounded-[28px] border border-rose-300/20 bg-rose-400/10 p-5 text-sm text-rose-100">
              <div className="font-semibold">Fix the highlighted editor issues before publishing.</div>
              {firstErrorTab ? <div className="mt-2 text-sm text-rose-100/90">The first blocked section is <span className="font-semibold">{firstErrorTab.label}</span>.</div> : null}
              <div className="mt-3 grid gap-1 text-sm leading-6">
                {errorEntries.slice(0, 8).map(([key, message]) => <div key={key}>{message}</div>)}
              </div>
            </div>
          ) : null}

          <div className="rounded-[28px] border border-white/10 bg-white/[0.03] p-3">
            <div className="grid gap-2 md:grid-cols-2 2xl:grid-cols-3">
              {editorTabs.map((tab) => {
                const isActive = activeTab === tab.id

                return (
                  <button
                    key={tab.id}
                    type="button"
                    onClick={() => setActiveTab(tab.id)}
                    role="tab"
                    aria-selected={isActive}
                    className={[
                      'rounded-[22px] border px-4 py-3 text-left transition',
                      isActive
                        ? 'border-sky-300/30 bg-sky-400/12 text-white shadow-[0_16px_40px_rgba(14,165,233,0.12)]'
                        : 'border-white/10 bg-black/20 text-slate-300 hover:border-white/15 hover:bg-white/[0.05]',
                    ].join(' ')}
                  >
                    <div className="flex items-start justify-between gap-3">
                      <div className="min-w-0">
                        <div className="flex items-center gap-2">
                          <i className={`${tab.icon} text-[11px] ${isActive ? 'text-sky-100' : 'text-slate-500'}`} aria-hidden="true" />
                          <span className="text-sm font-semibold">{tab.label}</span>
                        </div>
                        <div className="mt-1 text-xs leading-5 text-slate-400">{tab.meta}</div>
                      </div>
                      <div className="flex shrink-0 items-center gap-2">
                        {tab.errorCount > 0 ? <span className="rounded-full border border-rose-300/20 bg-rose-400/12 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-rose-100">{tab.errorCount}</span> : null}
                        {isActive ? <span className="rounded-full border border-sky-300/25 bg-sky-400/12 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] text-sky-100">Active</span> : null}
                      </div>
                    </div>
                  </button>
                )
              })}
            </div>
          </div>

          {activeTab === 'basics' ? (
            <WorldEditorSection title="World basics" description="Shape the title, summary, and long-form story before you worry about composition or scheduling.">
              <div className="grid gap-4">
                <div className="grid gap-4 md:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Title</span>
                    <input value={form.data.title} onChange={(event) => form.setData('title', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    <span className="text-xs leading-5 text-slate-500">Use a campaign-ready title that will still read clearly in homepage spotlight, preview, and archive contexts.</span>
                  </label>
                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Slug</span>
                    <input value={form.data.slug} onChange={(event) => form.setData('slug', event.target.value)} placeholder="optional-manual-slug" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    <span className="text-xs leading-5 text-slate-500">Keep this short and durable. It becomes the public world URL and should survive future editorial updates.</span>
                  </label>
                </div>

                <div className="grid gap-4 md:grid-cols-2">
                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Tagline</span>
                    <input value={form.data.tagline} onChange={(event) => form.setData('tagline', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                  </label>
                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Summary</span>
                    <input value={form.data.summary} onChange={(event) => form.setData('summary', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                  </label>
                </div>

                <div className="grid gap-2 text-sm text-slate-300">
                  <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Description</span>
                  <RichTextEditor
                    content={form.data.description}
                    onChange={(nextValue) => form.setData('description', nextValue)}
                    placeholder="Build the world story with rich formatting, links, emoji, and campaign callouts."
                    error={form.errors.description}
                    minHeight={18}
                    autofocus={false}
                  />
                </div>
              </div>
            </WorldEditorSection>
          ) : null}

          {activeTab === 'structure' ? (
            <>
              <WorldEditorSection
                title="Curated relations"
                description="Attach explicit artworks, collections, creators, groups, news, challenges, events, releases, and cards as editorial composition blocks."
                actions={<button type="button" onClick={() => openRelationPicker(null)} className="rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Add relation</button>}
              >
                <div className="grid gap-4">
                  {form.data.relations.length > 0 ? form.data.relations.map((relation, index) => (
                    <WorldRelationCard
                      key={`${relation.related_type}-${relation.related_id || index}-${index}`}
                      relation={relation}
                      index={index}
                      total={form.data.relations.length}
                      sectionLabel={sectionMap[relation.section_key]?.label || relation.section_key}
                      onEdit={() => openRelationPicker(index)}
                      onRemove={() => removeRelation(index)}
                      onMove={moveRelation}
                    />
                  )) : <div className="rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-5 text-sm text-slate-400">No curated relations attached yet. Add relations to turn the world into a real campaign hub instead of a static shell.</div>}

                  {activeTab === 'suggestions' ? (
                    <WorldSuggestionsPanel
                      suggestions={suggestions}
                      notice={suggestionNotice}
                      worldExists={Boolean(world)}
                      busyKey={suggestionBusyKey}
                      onAddFeatured={(item, sectionKey) => addSuggestionRelation(item, sectionKey, true)}
                      onAddSection={(item, sectionKey, isFeatured = false) => addSuggestionRelation(item, sectionKey, isFeatured)}
                      onPin={pinSuggestion}
                      onDismiss={dismissSuggestion}
                      onNotRelevant={markSuggestionNotRelevant}
                      onRestore={restoreSuggestion}
                    />
                  ) : null}
                </div>
              </WorldEditorSection>

              <WorldEditorSection title="Linked challenge automation" description="Use one primary challenge to drive a dedicated challenge panel, entry rail, winner carry-over, and campaign freshness across public surfaces.">
                <div className="grid gap-4">
                  <LinkedChallengeCard challenge={linkedChallengePreview} onChange={() => setLinkedChallengePickerOpen(true)} onClear={clearLinkedChallenge} />

                  <div className="grid gap-3 md:grid-cols-2">
                    <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                      <Checkbox checked={form.data.show_linked_challenge_section} onChange={(event) => form.setData('show_linked_challenge_section', event.target.checked)} label="Show challenge panel on the world page" size={20} variant="accent" />
                    </div>
                    <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                      <Checkbox checked={form.data.show_linked_challenge_entries} onChange={(event) => form.setData('show_linked_challenge_entries', event.target.checked)} label="Show linked challenge entries rail" size={20} variant="accent" />
                    </div>
                    <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                      <Checkbox checked={form.data.show_linked_challenge_winners} onChange={(event) => form.setData('show_linked_challenge_winners', event.target.checked)} label="Show linked challenge winner section" size={20} variant="accent" />
                    </div>
                    <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                      <Checkbox checked={form.data.auto_grant_challenge_world_rewards} onChange={(event) => form.setData('auto_grant_challenge_world_rewards', event.target.checked)} label="Auto-grant winner and finalist rewards from the linked challenge" size={20} variant="accent" />
                    </div>
                  </div>

                  <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                    <Checkbox checked={form.data.show_linked_challenge_finalists} onChange={(event) => form.setData('show_linked_challenge_finalists', event.target.checked)} label="Show linked challenge finalists when they exist" size={20} variant="accent" />
                    <div className="mt-2 text-xs leading-5 text-slate-500">Finalists now come from structured challenge outcomes, so worlds can surface them automatically without waiting for manual recap edits.</div>
                  </div>

                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Challenge teaser override</span>
                    <textarea value={form.data.challenge_teaser_override} onChange={(event) => form.setData('challenge_teaser_override', event.target.value)} rows={4} placeholder="Optional world-specific framing for the linked challenge panel." className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    <span className="text-xs leading-5 text-slate-500">Leave this blank to reuse the challenge summary automatically. Use it when the world needs a more editorial campaign voice than the raw challenge copy.</span>
                  </label>

                  <LinkedChallengeEntryVisibilityManager
                    challenge={linkedChallengePreview}
                    hiddenIds={Array.isArray(form.data.hidden_linked_challenge_artwork_ids_json) ? form.data.hidden_linked_challenge_artwork_ids_json : []}
                    onToggle={toggleHiddenLinkedChallengeEntry}
                    error={form.errors.hidden_linked_challenge_artwork_ids_json}
                  />

                  {linkedChallengePreview && linkedChallengeEntryPreviewItems.length === 0 ? <div className="rounded-2xl border border-dashed border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-400">No visible challenge entries are available yet for per-entry overrides.</div> : null}
                </div>
              </WorldEditorSection>

              <WorldEditorSection title="Sections" description="Reorder and hide public sections without losing their underlying data or editorial intent.">
                <WorldSectionToggleList
                  sectionOptions={sectionOptions}
                  order={form.data.section_order_json}
                  visibility={form.data.section_visibility_json}
                  relationCounts={relationCounts}
                  onChange={updateSectionControls}
                />
              </WorldEditorSection>
            </>
          ) : null}

          {activeTab === 'suggestions' ? (
            <WorldSuggestionsPanel
              suggestions={suggestions}
              notice={suggestionNotice}
              worldExists={Boolean(world)}
              busyKey={suggestionBusyKey}
              onAddFeatured={(item, sectionKey) => addSuggestionRelation(item, sectionKey, true)}
              onAddSection={(item, sectionKey, isFeatured = false) => addSuggestionRelation(item, sectionKey, isFeatured)}
              onPin={pinSuggestion}
              onDismiss={dismissSuggestion}
              onNotRelevant={markSuggestionNotRelevant}
              onRestore={restoreSuggestion}
            />
          ) : null}

          {activeTab === 'publishing' ? (
            <WorldEditorSection title="Publishing and timing" description="Control the public state, campaign dates, and recurrence lifecycle without mixing in community settings.">
              <div className="mt-5 grid gap-4">
                <div className="grid gap-4 md:grid-cols-2">
                  <NovaSelect label="Type" value={form.data.type || null} onChange={(nextValue) => form.setData('type', String(nextValue || ''))} options={typeOptions} searchable={false} className="bg-black/20" />
                  <NovaSelect label="Workflow status" value={form.data.status || null} onChange={(nextValue) => form.setData('status', String(nextValue || ''))} options={props.statusOptions || []} searchable={false} className="bg-black/20" />
                </div>

                <DateTimePicker label="Publish at" value={form.data.published_at} onChange={(nextValue) => form.setData('published_at', nextValue)} placeholder="Pick an automatic publish date" clearable className="bg-black/20" />
                <div className="-mt-1 text-xs leading-5 text-slate-500">A future publish date keeps the world in the editorial pipeline until the release moment arrives automatically.</div>

                <div className="grid gap-4 md:grid-cols-2">
                  <DateTimePicker label="Starts at" value={form.data.starts_at} onChange={(nextValue) => form.setData('starts_at', nextValue)} placeholder="Pick the campaign start" clearable className="bg-black/20" />
                  <DateTimePicker label="Ends at" value={form.data.ends_at} onChange={(nextValue) => form.setData('ends_at', nextValue)} placeholder="Pick the campaign end" clearable className="bg-black/20" />
                </div>
                <div className="-mt-1 text-xs leading-5 text-slate-500">The campaign window drives whether the world reads as upcoming, active, or finished on public surfaces.</div>

                <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                  <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Activation and promotion</div>
                  <div className="mt-2 text-sm leading-6 text-slate-300">Use campaign activation for platform-level surfacing. Homepage feature is a stronger spotlight signal on top of that base state.</div>

                  <div className="mt-4 grid gap-3 md:grid-cols-2">
                    <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                      <Checkbox
                        checked={form.data.is_active_campaign}
                        onChange={(event) => form.setData({
                          ...form.data,
                          is_active_campaign: event.target.checked,
                          is_homepage_featured: event.target.checked ? form.data.is_homepage_featured : false,
                        })}
                        label="Mark as active campaign"
                        size={20}
                        variant="accent"
                      />
                      <div className="mt-2 text-xs leading-5 text-slate-500">Active campaigns become eligible for stronger public surfacing on homepage, upload, and promoted worlds views.</div>
                    </div>
                    <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                      <Checkbox
                        checked={form.data.is_homepage_featured}
                        onChange={(event) => form.setData({
                          ...form.data,
                          is_homepage_featured: event.target.checked,
                          is_active_campaign: event.target.checked ? true : form.data.is_active_campaign,
                        })}
                        label="Feature on homepage spotlight"
                        size={20}
                        variant="accent"
                      />
                      <div className="mt-2 text-xs leading-5 text-slate-500">Use this when the campaign should compete for the primary homepage spotlight position.</div>
                    </div>
                  </div>

                  <div className="mt-4 grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Campaign priority</span>
                      <input type="number" min="0" max="9999" value={form.data.campaign_priority} onChange={(event) => form.setData('campaign_priority', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Higher values win when multiple campaigns are active at once.</span>
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Campaign label</span>
                      <input value={form.data.campaign_label} onChange={(event) => form.setData('campaign_label', event.target.value)} placeholder="Spring campaign, Returning seasonal world" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                  </div>

                  <div className="mt-4 grid gap-4 md:grid-cols-2">
                    <DateTimePicker label="Promotion starts" value={form.data.promotion_starts_at} onChange={(nextValue) => form.setData('promotion_starts_at', nextValue)} placeholder="Optional promotion start" clearable className="bg-black/20" />
                    <DateTimePicker label="Promotion ends" value={form.data.promotion_ends_at} onChange={(nextValue) => form.setData('promotion_ends_at', nextValue)} placeholder="Optional promotion end" clearable className="bg-black/20" />
                  </div>
                  <div className="mt-2 text-xs leading-5 text-slate-500">Promotion dates control when the world is highlighted beyond its own page. Leave them blank to use the main campaign window.</div>
                </div>

                <div className="grid gap-3 md:grid-cols-2">
                  <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                    <Checkbox checked={form.data.is_featured} onChange={(event) => form.setData('is_featured', event.target.checked)} label="Feature on Worlds lists and supporting rails" size={20} variant="accent" />
                    <div className="mt-2 text-xs leading-5 text-slate-500">Keep this for secondary featured placement on worlds surfaces. Homepage spotlighting is controlled separately by active campaign and homepage feature.</div>
                  </div>
                  <div className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3">
                    <Checkbox checked={form.data.is_recurring} onChange={(event) => form.setData('is_recurring', event.target.checked)} label="Recurring world" size={20} variant="accent" />
                    <div className="mt-2 text-xs leading-5 text-slate-500">Use recurrence for annual or repeatable campaigns so new editions can be rolled forward cleanly.</div>
                  </div>
                </div>

                <div className="grid gap-4 md:grid-cols-2">
                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recurrence key</span>
                    <input value={form.data.recurrence_key} onChange={(event) => form.setData('recurrence_key', event.target.value)} placeholder="halloween, retro-month" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                  </label>
                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Edition year</span>
                    <input type="number" min="2000" max="2100" value={form.data.edition_year} onChange={(event) => form.setData('edition_year', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                  </label>
                </div>

                <label className="grid gap-2 text-sm text-slate-300">
                  <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recurrence rule</span>
                  <input value={form.data.recurrence_rule} onChange={(event) => form.setData('recurrence_rule', event.target.value)} placeholder="annual:10, annual:12, campaign-window" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                </label>

                <WorldRecurrenceHelper
                  enabled={form.data.is_recurring}
                  recurrenceKey={form.data.recurrence_key}
                  editionYear={form.data.edition_year}
                  recurrenceKeyError={form.errors.recurrence_key}
                  editionYearError={form.errors.edition_year}
                />

                {world?.is_recurring ? (
                  <div className="rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-300">
                    <div className="flex flex-wrap items-start justify-between gap-4">
                      <div>
                        <div className="text-sm font-semibold text-white">Recurring family status</div>
                        <p className="mt-1 max-w-2xl leading-6 text-slate-400">Track whether this editor is pointing at the canonical family edition, an archived edition, or a sibling inside the same recurrence group.</p>
                      </div>
                      <div className="flex flex-wrap gap-2">
                        <span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-200">{world.family_title || world.recurrence_key}</span>
                        <span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-200">Edition {world.edition_year || 'Draft'}</span>
                        <span className={`rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] ${world.is_canonical_edition ? 'border-emerald-300/20 bg-emerald-400/12 text-emerald-100' : 'border-amber-300/20 bg-amber-400/12 text-amber-100'}`}>{world.is_canonical_edition ? 'Canonical family edition' : 'Archive or sibling edition'}</span>
                      </div>
                    </div>

                    <div className="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Family key</div>
                        <div className="mt-2 font-semibold text-white">{world.recurrence_key}</div>
                      </div>
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Editions</div>
                        <div className="mt-2 font-semibold text-white">{world.family_edition_count || 1}</div>
                      </div>
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Archive count</div>
                        <div className="mt-2 font-semibold text-white">{world.archive_edition_count || 0}</div>
                      </div>
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Current public edition</div>
                        <div className="mt-2 font-semibold text-white">{world.current_edition?.title || 'None published yet'}</div>
                      </div>
                    </div>

                    <div className="mt-4 grid gap-3 md:grid-cols-3">
                      <a href={world.family_url || '#'} className={`rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 ${world.family_url ? 'text-white' : 'pointer-events-none text-slate-500'}`}>
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Family route</div>
                        <div className="mt-2 text-sm font-semibold">{world.family_url ? 'Open canonical family page' : 'Unavailable'}</div>
                      </a>
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Previous edition</div>
                        <div className="mt-2 text-sm font-semibold text-white">{world.previous_edition?.title || 'None'}</div>
                      </div>
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Next edition</div>
                        <div className="mt-2 text-sm font-semibold text-white">{world.next_edition?.title || 'None yet'}</div>
                      </div>
                    </div>
                  </div>
                ) : null}

                <WorldDuplicateActionMenu
                  duplicateUrl={duplicateActions?.duplicateUrl}
                  newEditionUrl={duplicateActions?.newEditionUrl}
                  canCreateEdition={Boolean(duplicateActions?.canCreateEdition)}
                  copyModeCount={Math.max(duplicateActions?.duplicateModeOptions?.length || 0, duplicateActions?.newEditionModeOptions?.length || 0)}
                  onDuplicate={() => runDuplicateAction(duplicateActions?.duplicateUrl, 'Duplicate this world into a new draft?', duplicateActions?.duplicateModeOptions || [])}
                  onCreateEdition={() => runDuplicateAction(duplicateActions?.newEditionUrl, 'Create the next edition draft from this world?', duplicateActions?.newEditionModeOptions || [])}
                />
              </div>
            </WorldEditorSection>
          ) : null}

          {activeTab === 'community' ? (
            <WorldEditorSection title="Community participation" description="Choose how creators can join the world, define the participation window, and moderate community placements without touching curated relations.">
              <div className="grid gap-4">
                <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                  <div className="grid gap-4">
                    <div>
                      <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Participation mode</div>
                      <div className="mt-2 text-sm leading-6 text-slate-300">Pick whether creator artworks need approval, go live automatically, or stay hidden from creator participation surfaces.</div>
                    </div>

                    <NovaSelect
                      label="Creator participation"
                      value={form.data.participation_mode || 'closed'}
                      onChange={(nextValue) => {
                        const nextMode = String(nextValue || 'closed')
                        form.setData({
                          ...form.data,
                          participation_mode: nextMode,
                          accepts_submissions: nextMode !== 'closed',
                        })
                      }}
                      options={PARTICIPATION_MODE_OPTIONS}
                      searchable={false}
                      className="bg-black/20"
                    />

                    <div className="grid gap-3 md:grid-cols-2">
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <Checkbox checked={form.data.submission_note_enabled} onChange={(event) => form.setData('submission_note_enabled', event.target.checked)} label="Allow creator notes" size={20} variant="accent" />
                      </div>
                      <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                        <Checkbox checked={form.data.community_section_enabled} onChange={(event) => form.setData('community_section_enabled', event.target.checked)} label="Show community section on the public world page" size={20} variant="accent" />
                      </div>
                    </div>

                    <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3">
                      <Checkbox checked={form.data.allow_readd_after_removal} onChange={(event) => form.setData('allow_readd_after_removal', event.target.checked)} label="Allow creators to re-add artworks after removal" size={20} variant="accent" />
                      <div className="mt-2 text-xs leading-5 text-slate-500">Blocked artworks still stay locked until a moderator unblocks them.</div>
                    </div>
                  </div>

                  <div className="mt-4 grid gap-4 md:grid-cols-2">
                    <DateTimePicker label="Submission opens" value={form.data.submission_starts_at} onChange={(nextValue) => form.setData('submission_starts_at', nextValue)} placeholder="Optional submission start" clearable className="bg-black/20" />
                    <DateTimePicker label="Submission closes" value={form.data.submission_ends_at} onChange={(nextValue) => form.setData('submission_ends_at', nextValue)} placeholder="Optional submission end" clearable className="bg-black/20" />
                  </div>

                  <label className="mt-4 grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Creator guidelines</span>
                    <textarea value={form.data.submission_guidelines} onChange={(event) => form.setData('submission_guidelines', event.target.value)} rows={5} className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" placeholder="Explain what qualifies, what you want creators to submit, and any world-specific expectations." />
                    <span className="text-xs leading-5 text-slate-500">These notes are shown to creators in upload and artwork-edit submission surfaces.</span>
                  </label>
                </div>

                {world ? (
                  <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                    <div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
                      <div>
                        <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Submission review queue</div>
                        <div className="mt-2 text-sm leading-6 text-slate-300">Moderate creator participation without converting those artworks into curated world relations.</div>
                      </div>
                      <div className="flex flex-wrap gap-2 text-xs">
                        {[
                          ['Pending', reviewQueue?.counts?.pending || 0, 'border-sky-300/25 bg-sky-400/10 text-sky-100'],
                          ['Live', reviewQueue?.counts?.live || 0, 'border-emerald-300/25 bg-emerald-400/10 text-emerald-100'],
                          ['Featured', reviewQueue?.counts?.featured || 0, 'border-amber-300/25 bg-amber-400/10 text-amber-100'],
                          ['Removed', reviewQueue?.counts?.removed || 0, 'border-orange-300/25 bg-orange-400/10 text-orange-100'],
                          ['Blocked', reviewQueue?.counts?.blocked || 0, 'border-rose-300/25 bg-rose-400/10 text-rose-100'],
                        ].map(([label, count, tone]) => (
                          <span key={label} className={`rounded-full border px-3 py-1 font-semibold uppercase tracking-[0.14em] ${tone}`}>{label}: {count}</span>
                        ))}
                      </div>
                    </div>

                    <div className="mt-4 grid gap-3">
                      {Array.isArray(reviewQueue?.items) && reviewQueue.items.length > 0 ? reviewQueue.items.map((item) => (
                        <div key={item.id} className="rounded-2xl border border-white/10 bg-white/[0.03] p-4">
                          <div className="flex flex-col gap-4 xl:flex-row">
                            <div className="h-24 w-24 shrink-0 overflow-hidden rounded-[20px] border border-white/10 bg-slate-950/80">
                              {item.artwork?.thumbnail_url ? <img src={item.artwork.thumbnail_url} alt="" className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-slate-500"><i className="fa-solid fa-image" /></div>}
                            </div>

                            <div className="min-w-0 flex-1">
                              <div className="flex flex-wrap items-center gap-2">
                                <a href={item.artwork?.edit_url || item.artwork?.url || '#'} className="text-base font-semibold text-white hover:text-sky-100">{item.artwork?.title || 'Untitled artwork'}</a>
                                <span className="rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-300">{item.status_label}</span>
                              </div>
                              <div className="mt-1 text-xs uppercase tracking-[0.16em] text-slate-500">{item.submitted_by?.name || item.artwork?.creator_name || 'Unknown creator'}</div>
                              {Array.isArray(item.artwork?.meta) && item.artwork.meta.length > 0 ? <div className="mt-3 flex flex-wrap gap-2">{item.artwork.meta.map((entry) => <span key={entry} className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs text-slate-300">{entry}</span>)}</div> : null}
                              {Array.isArray(item.world_rewards) && item.world_rewards.length > 0 ? (
                                <div className="mt-3 flex flex-wrap gap-2">
                                  {item.world_rewards.map((reward) => (
                                    <span key={reward.id} className="rounded-full border border-sky-300/20 bg-sky-400/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.14em] text-sky-100">{reward.reward_label}</span>
                                  ))}
                                </div>
                              ) : null}
                              {item.note ? <div className="mt-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm leading-6 text-slate-300"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Creator note</div><div className="mt-2">{item.note}</div></div> : null}
                              {item.reviewer_note ? <div className="mt-3 rounded-2xl border border-amber-300/20 bg-amber-400/10 px-4 py-3 text-sm leading-6 text-amber-50"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100/80">Moderator note</div><div className="mt-2">{item.reviewer_note}</div></div> : null}
                            </div>

                            <div className="flex shrink-0 flex-wrap gap-2 xl:w-[220px] xl:flex-col">
                              {item.status === 'pending' ? <button type="button" onClick={() => runSubmissionAction(item.actions?.approve, 'Approve this submission and make it live?', { title: 'Approve submission?', confirmLabel: 'Approve' })} className="rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-sm font-semibold text-emerald-100">Approve</button> : null}
                              {item.status === 'removed' ? <button type="button" onClick={() => runSubmissionAction(item.actions?.restore, 'Restore this submission to the live community section?', { title: 'Restore submission?', confirmLabel: 'Restore' })} className="rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-sm font-semibold text-emerald-100">Restore</button> : null}
                              {item.status === 'blocked' ? <button type="button" onClick={() => runSubmissionAction(item.actions?.unblock, 'Unblock this artwork so it can be restored or re-added later?', { title: 'Unblock artwork?', confirmLabel: 'Unblock', confirmTone: 'accent' })} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Unblock</button> : null}
                              {item.status !== 'blocked' ? <button type="button" onClick={() => runSubmissionAction(item.is_featured ? item.actions?.unfeature : item.actions?.feature, item.is_featured ? 'Remove this artwork from featured community placement?' : 'Feature this artwork in the public community section?', { title: item.is_featured ? 'Unfeature artwork?' : 'Feature artwork?', confirmLabel: item.is_featured ? 'Unfeature' : 'Feature' })} className="rounded-2xl border border-amber-300/20 bg-amber-400/10 px-4 py-2 text-sm font-semibold text-amber-100">{item.is_featured ? 'Unfeature' : 'Feature'}</button> : null}
                              {item.status !== 'pending' && item.status !== 'blocked' ? <button type="button" onClick={() => runSubmissionAction(item.actions?.pending, 'Return this submission to pending review?', { title: 'Return to pending?', confirmLabel: 'Set pending' })} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Set pending</button> : null}
                              {item.can_grant_manual_rewards ? <button type="button" onClick={() => runSubmissionAction(item.actions?.grant_rewards?.winner, 'Grant winner recognition to this creator for this world edition?', { title: 'Grant winner reward?', confirmLabel: 'Grant winner', noteEnabled: true })} className="rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-2 text-sm font-semibold text-emerald-100">Winner</button> : null}
                              {item.can_grant_manual_rewards ? <button type="button" onClick={() => runSubmissionAction(item.actions?.grant_rewards?.finalist, 'Grant finalist recognition to this creator for this world edition?', { title: 'Grant finalist reward?', confirmLabel: 'Grant finalist', noteEnabled: true })} className="rounded-2xl border border-violet-300/20 bg-violet-400/10 px-4 py-2 text-sm font-semibold text-violet-100">Finalist</button> : null}
                              {item.can_grant_manual_rewards ? <button type="button" onClick={() => runSubmissionAction(item.actions?.grant_rewards?.spotlight, 'Grant spotlight recognition to this creator for this world edition?', { title: 'Grant spotlight reward?', confirmLabel: 'Grant spotlight', noteEnabled: true })} className="rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100">Spotlight</button> : null}
                              {Array.isArray(item.world_rewards) && item.world_rewards.some((reward) => reward.reward_type === 'winner') ? <button type="button" onClick={() => runSubmissionAction(item.actions?.revoke_rewards?.winner, 'Revoke winner recognition for this creator in this world edition?', { title: 'Revoke winner reward?', confirmLabel: 'Revoke winner', confirmTone: 'danger' })} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-slate-200">Revoke winner</button> : null}
                              {Array.isArray(item.world_rewards) && item.world_rewards.some((reward) => reward.reward_type === 'finalist') ? <button type="button" onClick={() => runSubmissionAction(item.actions?.revoke_rewards?.finalist, 'Revoke finalist recognition for this creator in this world edition?', { title: 'Revoke finalist reward?', confirmLabel: 'Revoke finalist', confirmTone: 'danger' })} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-slate-200">Revoke finalist</button> : null}
                              {Array.isArray(item.world_rewards) && item.world_rewards.some((reward) => reward.reward_type === 'spotlight') ? <button type="button" onClick={() => runSubmissionAction(item.actions?.revoke_rewards?.spotlight, 'Revoke spotlight recognition for this creator in this world edition?', { title: 'Revoke spotlight reward?', confirmLabel: 'Revoke spotlight', confirmTone: 'danger' })} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-slate-200">Revoke spotlight</button> : null}
                              {item.status !== 'removed' && item.status !== 'blocked' ? <button type="button" onClick={() => runSubmissionAction(item.actions?.remove, 'Remove this artwork from community participation for this world?', { title: 'Remove artwork?', confirmLabel: 'Remove', cancelLabel: 'Keep live', confirmTone: 'danger', noteEnabled: true })} className="rounded-2xl border border-orange-300/20 bg-orange-400/10 px-4 py-2 text-sm font-semibold text-orange-100">Remove</button> : null}
                              {item.status !== 'blocked' ? <button type="button" onClick={() => runSubmissionAction(item.actions?.block, 'Block this artwork from re-adding to this world until a moderator clears it?', { title: 'Block artwork?', confirmLabel: 'Block', cancelLabel: 'Cancel', confirmTone: 'danger', noteEnabled: true })} className="rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-2 text-sm font-semibold text-rose-100">Block</button> : null}
                            </div>
                          </div>
                        </div>
                      )) : <div className="rounded-2xl border border-dashed border-white/10 bg-white/[0.02] p-4 text-sm text-slate-400">No submissions have been attached to this world yet.</div>}
                    </div>
                  </div>
                ) : null}
              </div>
            </WorldEditorSection>
          ) : null}

          {activeTab === 'presentation' ? (
            <>
              <WorldEditorSection title="Theme and identity" description="Apply theme presets and tune the visual language that drives the world’s mood and recognition.">
                <div className="mt-5 grid gap-4">
                  <NovaSelect
                    label="Theme preset"
                    value={form.data.theme_key || null}
                    onChange={(nextValue) => handleThemeChange(String(nextValue || ''))}
                    options={[{ value: '', label: 'No preset' }, ...themeOptions]}
                    searchable={false}
                    className="bg-black/20"
                  />

                  <WorldThemePresetHelper theme={selectedTheme} onApply={() => handleThemeChange(form.data.theme_key, true)} />

                  <div className="grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Accent color</span>
                      <input value={form.data.accent_color} onChange={(event) => form.setData('accent_color', event.target.value)} placeholder="#f97316" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Secondary accent</span>
                      <input value={form.data.accent_color_secondary} onChange={(event) => form.setData('accent_color_secondary', event.target.value)} placeholder="#0f172a" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                  </div>

                  <div className="grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Background motif</span>
                      <input value={form.data.background_motif} onChange={(event) => form.setData('background_motif', event.target.value)} placeholder="embers, frost, scanlines" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Icon class</span>
                      <input value={form.data.icon_name} onChange={(event) => form.setData('icon_name', event.target.value)} placeholder="fa-solid fa-ghost" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                  </div>
                </div>
              </WorldEditorSection>

              <WorldEditorSection title="Media assets" description={props.mediaSupport?.helper_text}>
                <div className="mt-5 grid gap-4">
                  <WorldMediaUploadField
                    label="Cover image"
                    slot="cover"
                    value={form.data.cover_path}
                    previewUrl={resolveMediaUrl(form.data.cover_path, world?.cover_path === form.data.cover_path ? world?.cover_url || '' : '', filesBaseUrl)}
                    emptyLabel="No cover selected"
                    helperText="Use the strongest hero asset you have so the world reads clearly in spotlight, cards, and preview states."
                    uploadUrl={props.mediaSupport?.upload_url}
                    deleteUrl={props.mediaSupport?.delete_url}
                    worldId={world?.id || null}
                    isTemporaryValue={temporaryMediaPaths.cover !== '' && temporaryMediaPaths.cover === form.data.cover_path}
                    accept={(props.mediaSupport?.accepted_mime_types || []).join(',') || 'image/jpeg,image/png,image/webp'}
                    maxFileSizeMb={props.mediaSupport?.max_file_size_mb || 6}
                    onChange={({ path }) => {
                      form.setData('cover_path', path)
                      setTemporaryMediaPaths((current) => ({ ...current, cover: path || '' }))
                    }}
                  />

                  <WorldMediaUploadField
                    label="Teaser image"
                    slot="teaser"
                    value={form.data.teaser_image_path}
                    previewUrl={resolveMediaUrl(form.data.teaser_image_path, world?.teaser_image_path === form.data.teaser_image_path ? world?.teaser_image_url || '' : '', filesBaseUrl)}
                    emptyLabel="Falls back to cover image when blank"
                    helperText="Use a promo-specific image when homepage, upload, or world index surfaces need a tighter campaign crop than the full world cover."
                    uploadUrl={props.mediaSupport?.upload_url}
                    deleteUrl={props.mediaSupport?.delete_url}
                    worldId={world?.id || null}
                    isTemporaryValue={temporaryMediaPaths.teaser !== '' && temporaryMediaPaths.teaser === form.data.teaser_image_path}
                    accept={(props.mediaSupport?.accepted_mime_types || []).join(',') || 'image/jpeg,image/png,image/webp'}
                    maxFileSizeMb={props.mediaSupport?.max_file_size_mb || 6}
                    onChange={({ path }) => {
                      form.setData('teaser_image_path', path)
                      setTemporaryMediaPaths((current) => ({ ...current, teaser: path || '' }))
                    }}
                  />

                  <div className="grid gap-3">
                    <WorldMediaUploadField
                      label="OG image"
                      slot="og"
                      value={form.data.og_image_path}
                      previewUrl={resolveMediaUrl(form.data.og_image_path, world?.og_image_path === form.data.og_image_path ? world?.og_image_url || '' : '', filesBaseUrl)}
                      emptyLabel="Falls back to cover image when blank"
                      helperText="Upload a dedicated social share image, or leave it blank and use the cover image fallback."
                      uploadUrl={props.mediaSupport?.upload_url}
                      deleteUrl={props.mediaSupport?.delete_url}
                      worldId={world?.id || null}
                      isTemporaryValue={temporaryMediaPaths.og !== '' && temporaryMediaPaths.og === form.data.og_image_path}
                      accept={(props.mediaSupport?.accepted_mime_types || []).join(',') || 'image/jpeg,image/png,image/webp'}
                      maxFileSizeMb={props.mediaSupport?.max_file_size_mb || 6}
                      onChange={({ path }) => {
                        form.setData('og_image_path', path)
                        setTemporaryMediaPaths((current) => ({ ...current, og: path || '' }))
                      }}
                    />

                    <div className="flex justify-end">
                      <button
                        type="button"
                        onClick={applyCoverToOg}
                        disabled={!form.data.cover_path}
                        className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm font-semibold text-white disabled:opacity-50"
                      >
                        Use cover
                      </button>
                    </div>
                  </div>

                  <div className="grid gap-4 md:grid-cols-2">
                    <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                      <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Cover preview</div>
                      <div className="mt-3 h-40 overflow-hidden rounded-[20px] border border-white/10 bg-slate-950">{previewWorld.cover_url ? <img src={previewWorld.cover_url} alt="" className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-sm text-slate-500">No cover selected</div>}</div>
                    </div>
                    <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                      <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">OG preview</div>
                      <div className="mt-3 h-40 overflow-hidden rounded-[20px] border border-white/10 bg-slate-950">{form.data.og_image_path || previewWorld.cover_url ? <img src={resolveMediaUrl(form.data.og_image_path || form.data.cover_path, form.data.og_image_path ? (world?.og_image_path === form.data.og_image_path ? world?.og_image_url || '' : '') : (world?.cover_path === form.data.cover_path ? world?.cover_url || '' : ''), filesBaseUrl)} alt="" className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-sm text-slate-500">Falls back to cover image when blank</div>}</div>
                    </div>
                  </div>
                </div>
              </WorldEditorSection>

              <WorldEditorSection title="CTA and badge" description="Handle the promotional copy and campaign affordances that sit around the world, not inside its core story.">
                <div className="mt-5 grid gap-4">
                  <div className="grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Teaser title</span>
                      <input value={form.data.teaser_title} onChange={(event) => form.setData('teaser_title', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Optional. Use this for homepage, upload, and world-card headlines without changing the canonical world title.</span>
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Teaser summary</span>
                      <textarea value={form.data.teaser_summary} onChange={(event) => form.setData('teaser_summary', event.target.value)} rows={4} className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                  </div>

                  <div className="grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">CTA label</span>
                      <input value={form.data.cta_label} onChange={(event) => form.setData('cta_label', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Keep this action specific to the campaign intent: explore, join, enter, submit, or discover.</span>
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">CTA URL</span>
                      <input value={form.data.cta_url} onChange={(event) => form.setData('cta_url', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Point this at the destination that matches the world, such as a challenge, collection, landing page, or participation flow.</span>
                    </label>
                  </div>

                  <div className="grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Badge label</span>
                      <input value={form.data.badge_label} onChange={(event) => form.setData('badge_label', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Use concise editorial language that still reads cleanly in the hero, cards, and spotlight surfaces.</span>
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Badge URL</span>
                      <input value={form.data.badge_url} onChange={(event) => form.setData('badge_url', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Optional. Use this when the badge should send visitors to a participation or explanation destination.</span>
                    </label>
                  </div>

                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Badge description</span>
                    <textarea value={form.data.badge_description} onChange={(event) => form.setData('badge_description', event.target.value)} rows={4} className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                  </label>

                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Related tags</span>
                    <input value={tagString} onChange={(event) => form.setData('related_tags_json', event.target.value.split(',').map((tag) => tag.trim()).filter(Boolean))} placeholder="halloween, spooky, demoscene" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    <span className="text-xs leading-5 text-slate-500">Use these to reinforce the campaign identity and keep recurring presets consistent across editions.</span>
                  </label>
                </div>
              </WorldEditorSection>
            </>
          ) : null}

          {activeTab === 'recap' ? (
            <>
              <WorldEditorSection title="Recap framing" description="Prepare the archive-facing editorial layer that turns an ended edition into a meaningful recap instead of a quiet archive shell.">
                <div className="grid gap-4">
                  <div className="grid gap-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-start">
                    <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                      <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recap workflow</div>
                      <div className="mt-3 flex flex-wrap gap-2">
                        <span className={`rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] ${form.data.recap_status === 'published' ? 'border-sky-300/25 bg-sky-400/10 text-sky-100' : 'border-white/10 bg-white/[0.04] text-slate-200'}`}>{form.data.recap_status === 'published' ? 'Published recap' : 'Draft recap'}</span>
                        {world?.recap_published_at ? <span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-200">Published {new Date(world.recap_published_at).toLocaleDateString()}</span> : null}
                        {canPublishRecap ? <span className="rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-emerald-100">Ready to publish</span> : <span className="rounded-full border border-amber-300/20 bg-amber-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100">Publish after the edition ends</span>}
                      </div>
                      <p className="mt-3 text-sm leading-6 text-slate-300">Recap stays editorial but data-backed. Curated world relations, challenge outcomes, community highlights, and analytics snapshots all flow into the public recap once you publish it.</p>
                    </div>

                    <div className="rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm text-slate-300">
                      <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Primary recap metrics</div>
                      <div className="mt-3 grid gap-3">
                        {recapStatsSummary ? (
                          <>
                            <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Views</div><div className="mt-2 text-lg font-semibold text-white">{formatCompactNumber(recapStatsSummary.views)}</div></div>
                            <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Submissions</div><div className="mt-2 text-lg font-semibold text-white">{formatCompactNumber(recapStatsSummary.submissions)}</div></div>
                            <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3"><div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recognitions</div><div className="mt-2 text-lg font-semibold text-white">{formatCompactNumber(recapStatsSummary.reward_grants)}</div></div>
                          </>
                        ) : <div className="rounded-2xl border border-dashed border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-slate-400">A recap snapshot will be captured when you publish the recap.</div>}
                      </div>
                    </div>
                  </div>

                  <div className="grid gap-4 md:grid-cols-2">
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recap title</span>
                      <input value={form.data.recap_title} onChange={(event) => form.setData('recap_title', event.target.value)} placeholder="Halloween World 2026 recap" className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                      <span className="text-xs leading-5 text-slate-500">Leave blank to fall back to “{form.data.title || 'World'} recap”.</span>
                    </label>
                    <label className="grid gap-2 text-sm text-slate-300">
                      <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recap summary</span>
                      <textarea value={form.data.recap_summary} onChange={(event) => form.setData('recap_summary', event.target.value)} rows={4} placeholder="Short archive-facing summary for the recap hero." className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    </label>
                  </div>

                  <div className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Recap intro</span>
                    <RichTextEditor
                      content={form.data.recap_intro}
                      onChange={(nextValue) => form.setData('recap_intro', nextValue)}
                      placeholder="Write the archive-facing intro that frames what made this edition memorable."
                      error={form.errors.recap_intro}
                      minHeight={14}
                      autofocus={false}
                    />
                  </div>

                  <WorldMediaUploadField
                    label="Recap cover"
                    slot="recap_cover"
                    value={form.data.recap_cover_path}
                    previewUrl={recapCoverPreviewUrl}
                    emptyLabel="Falls back to the main world cover when blank"
                    helperText="Optional. Use a calmer archive-facing image when the recap should feel distinct from the live campaign hero."
                    uploadUrl={props.mediaSupport?.upload_url}
                    deleteUrl={props.mediaSupport?.delete_url}
                    worldId={world?.id || null}
                    isTemporaryValue={temporaryMediaPaths.recap !== '' && temporaryMediaPaths.recap === form.data.recap_cover_path}
                    accept={(props.mediaSupport?.accepted_mime_types || []).join(',') || 'image/jpeg,image/png,image/webp'}
                    maxFileSizeMb={props.mediaSupport?.max_file_size_mb || 6}
                    onChange={({ path }) => {
                      form.setData('recap_cover_path', path)
                      setTemporaryMediaPaths((current) => ({ ...current, recap: path || '' }))
                    }}
                  />

                  <label className="grid gap-2 text-sm text-slate-300">
                    <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Editor note</span>
                    <textarea value={form.data.recap_editor_note} onChange={(event) => form.setData('recap_editor_note', event.target.value)} rows={4} placeholder="Internal notes for editorial context, follow-up cleanup, or recap publishing reminders." className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                    <span className="text-xs leading-5 text-slate-500">Internal only. This note is stored with the recap draft but is not shown on the public recap page.</span>
                  </label>
                </div>
              </WorldEditorSection>

              <WorldEditorSection title="Recap article and publish state" description="Link one editorial story when the recap should point visitors toward a fuller write-up, results article, or reflective editorial piece.">
                <div className="grid gap-4">
                  <div className="rounded-[24px] border border-white/10 bg-black/20 p-4">
                    {recapArticlePreview ? (
                      <div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
                        <div className="flex min-w-0 gap-4">
                          <div className="h-16 w-16 shrink-0 overflow-hidden rounded-[20px] border border-white/10 bg-slate-950">
                            {recapArticlePreview.image ? <img src={recapArticlePreview.image} alt="" className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-slate-500"><i className="fa-solid fa-newspaper" /></div>}
                          </div>
                          <div className="min-w-0">
                            <div className="text-base font-semibold text-white">{recapArticlePreview.title}</div>
                            {recapArticlePreview.subtitle ? <div className="mt-1 text-xs uppercase tracking-[0.14em] text-slate-500">{recapArticlePreview.subtitle}</div> : null}
                            {recapArticlePreview.description ? <div className="mt-2 text-sm leading-6 text-slate-400">{recapArticlePreview.description}</div> : null}
                          </div>
                        </div>
                        <div className="flex shrink-0 flex-wrap gap-2">
                          <button type="button" onClick={() => setRecapArticlePickerOpen(true)} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Change</button>
                          <button type="button" onClick={clearRecapArticle} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-slate-200">Clear</button>
                        </div>
                      </div>
                    ) : (
                      <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
                        <div>
                          <div className="text-sm font-semibold text-white">No recap article linked</div>
                          <p className="mt-1 text-sm leading-6 text-slate-400">Link an article when this recap should push visitors toward a fuller editorial recap, results post, or archive story.</p>
                        </div>
                        <button type="button" onClick={() => setRecapArticlePickerOpen(true)} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Select article</button>
                      </div>
                    )}
                  </div>

                  <div className="rounded-[24px] border border-white/10 bg-black/20 p-4 text-sm leading-6 text-slate-300">
                    <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Publish behavior</div>
                    <p className="mt-2">Publishing recap captures a stats snapshot and makes the recap-first archive layout public for ended editions. You can keep refining the draft copy before that point.</p>
                  </div>
                </div>
              </WorldEditorSection>
            </>
          ) : null}

          {activeTab === 'seo' ? (
            <WorldEditorSection title="SEO" description="Set metadata that travels with the world into search results, previews, and social cards.">
              <div className="mt-5 grid gap-4">
                <label className="grid gap-2 text-sm text-slate-300">
                  <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">SEO title</span>
                  <input value={form.data.seo_title} onChange={(event) => form.setData('seo_title', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                </label>
                <label className="grid gap-2 text-sm text-slate-300">
                  <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">SEO description</span>
                  <textarea value={form.data.seo_description} onChange={(event) => form.setData('seo_description', event.target.value)} rows={4} className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
                </label>
              </div>
            </WorldEditorSection>
          ) : null}

          {activeTab === 'analytics' ? (
            <WorldEditorSection title="World analytics" description="Use campaign-useful metrics to judge traffic, editorial engagement, participation quality, challenge impact, and recurring-edition performance.">
              <WorldAnalyticsPanel analytics={world?.analytics || null} world={world} />
            </WorldEditorSection>
          ) : null}
        </section>

        <section className="space-y-6 xl:sticky xl:top-6 xl:max-h-[calc(100vh-48px)] xl:self-start xl:overflow-y-auto xl:overscroll-contain xl:pr-1 nova-scrollbar">
          <WorldEditorActionPanel
            formProcessing={form.processing}
            isEditing={Boolean(props.updateUrl)}
            publishUrl={props.publishUrl}
            publishRecapUrl={props.publishRecapUrl}
            canPublishRecap={canPublishRecap}
            recapStatusLabel={world?.recap_status_label || (form.data.recap_status === 'published' ? 'Published recap' : 'Draft recap')}
            archiveUrl={props.archiveUrl}
            publicUrl={world?.urls?.public}
          />

          <WorldSummaryCard world={form.data} themeLabel={selectedTheme?.label || ''} relationCount={form.data.relations.length} enabledSectionsCount={enabledSectionsCount} />

          <div className="rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
            <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Current focus</div>
            <div className="mt-2 flex items-center gap-2 text-white">
              <i className={`${currentTab?.icon || 'fa-solid fa-pen-ruler'} text-[11px] text-sky-200`} aria-hidden="true" />
              <span className="text-base font-semibold">{currentTab?.label || 'Editor'}</span>
            </div>
            <p className="mt-3 text-sm leading-6 text-slate-300">{currentTab?.description}</p>
          </div>

          <WorldMiniPreviewPanel world={previewWorld} sections={previewSections} previewUrl={props.previewUrl} />
        </section>
      </form>

      <WorldRelationPickerModal
        open={pickerState.open}
        onClose={closeRelationPicker}
        onSave={saveRelation}
        initialRelation={editingRelation}
        sectionOptions={sectionOptions}
        relationTypeOptions={relationTypeOptions}
        searchEntities={searchEntities}
      />

      <WorldLinkedChallengePickerModal
        open={linkedChallengePickerOpen}
        onClose={() => setLinkedChallengePickerOpen(false)}
        onSave={saveLinkedChallenge}
        initialChallenge={linkedChallengePreview}
        searchEntities={searchEntities}
      />

      <WorldRecapArticlePickerModal
        open={recapArticlePickerOpen}
        onClose={() => setRecapArticlePickerOpen(false)}
        onSave={saveRecapArticle}
        initialArticle={recapArticlePreview}
        searchEntities={searchEntities}
      />

      <NovaConfirmDialog
        open={actionConfirm.open}
        title={actionConfirm.title}
        message={actionConfirm.message}
        confirmLabel={actionConfirm.confirmLabel}
        cancelLabel={actionConfirm.cancelLabel}
        confirmTone={actionConfirm.confirmTone}
        onConfirm={confirmAction}
        onClose={closeActionConfirm}
        busy={actionBusy}
      >
        {actionConfirm.copyModeEnabled ? (
          <div className="grid gap-2">
            <div className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Copy mode</div>
            <div className="grid gap-2">
              {(actionConfirm.copyModeOptions || []).map((option) => (
                <label key={option.value} className="flex cursor-pointer items-start gap-3 rounded-[20px] border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-300">
                  <input
                    type="radio"
                    name="world-copy-mode"
                    value={option.value}
                    checked={actionCopyMode === option.value}
                    onChange={(event) => setActionCopyMode(event.target.value)}
                    className="mt-1"
                  />
                  <span>
                    <span className="block font-semibold text-white">{option.label}</span>
                    {option.description ? <span className="mt-1 block text-xs leading-5 text-slate-400">{option.description}</span> : null}
                  </span>
                </label>
              ))}
            </div>
          </div>
        ) : null}
        {actionConfirm.noteEnabled ? (
          <label className="grid gap-2 text-sm text-slate-300">
            <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Moderator note</span>
            <textarea
              value={actionReviewNote}
              onChange={(event) => setActionReviewNote(event.target.value)}
              rows={4}
              className="rounded-[24px] border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
              placeholder="Explain the moderation decision so creators understand what changed."
            />
          </label>
        ) : null}
      </NovaConfirmDialog>
    </StudioLayout>
  )
}