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

const DEFAULT_ACTION_CONFIRM = {
  open: false,
  url: '',
  title: 'Please confirm',
  message: '',
  confirmLabel: 'Confirm',
  cancelLabel: 'Cancel',
  confirmTone: 'danger',
  noteEnabled: false,
  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),
    type: type?.label || formData.type || 'Seasonal',
    badge_label: formData.badge_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),
  }
}

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: '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: 'seo',
    label: 'SEO',
    icon: 'fa-solid fa-magnifying-glass-chart',
    description: 'Search and social metadata that ships with the world page.',
  },
]

const WORLD_EDITOR_TAB_FIELDS = {
  basics: ['title', 'slug', 'tagline', 'summary', 'description'],
  structure: ['relations', 'section_order_json', 'section_visibility_json'],
  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', 'is_featured', 'is_recurring', 'recurrence_key', 'recurrence_rule', 'edition_year'],
  presentation: ['theme_key', 'accent_color', 'accent_color_secondary', 'background_motif', 'icon_name', 'cover_path', 'og_image_path', 'cta_label', 'cta_url', 'badge_label', 'badge_description', 'badge_url', 'related_tags_json'],
  seo: ['seo_title', 'seo_description'],
}

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 WorldEditorActionPanel({ formProcessing, isEditing, publishUrl, 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}
        {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>
  )
}

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 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) => ({
    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,
    query: relation.preview?.title || '',
  }))) : []

  const form = useForm({
    title: world?.title || '',
    slug: world?.slug || '',
    tagline: world?.tagline || '',
    summary: world?.summary || '',
    description: world?.description || '',
    cover_path: world?.cover_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),
    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_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 || '',
    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 || '',
    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 [activeTab, setActiveTab] = useState('basics')
  const [temporaryMediaPaths, setTemporaryMediaPaths] = useState({
    cover: '',
    og: '',
  })

  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 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 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 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 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 '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 'seo':
        meta = form.data.seo_title || form.data.seo_description ? 'Configured' : 'Optional metadata'
        break
      default:
        meta = ''
    }

    return {
      ...tab,
      meta,
      errorCount: tabErrorCounts[tab.id] || 0,
    }
  }), [form.data.participation_mode, form.data.title, form.data.relations.length, form.data.is_recurring, form.data.seo_description, form.data.seo_title, reviewQueue?.counts?.live, reviewQueue?.counts?.pending, selectedTheme?.label, tabErrorCounts])
  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 [actionBusy, setActionBusy] = useState(false)

  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: '', og: '' })
      },
    }

    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('')
  }

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

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

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

    const payload = actionConfirm.noteEnabled ? { review_note: actionReviewNote } : {}

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

  const runDuplicateAction = (url, promptText) => {
    openActionConfirm({
      url,
      title: 'Duplicate world?',
      message: promptText,
      confirmLabel: 'Continue',
      cancelLabel: 'Cancel',
      confirmTone: 'accent',
      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,
    })
  }

  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>}
                </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 === '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="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 surfaces and homepage spotlight" size={20} variant="accent" />
                    <div className="mt-2 text-xs leading-5 text-slate-500">Enable this when the world should be eligible for promoted placement beyond its own public URL.</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}
                />

                <WorldDuplicateActionMenu
                  duplicateUrl={duplicateActions?.duplicateUrl}
                  newEditionUrl={duplicateActions?.newEditionUrl}
                  canCreateEdition={Boolean(duplicateActions?.canCreateEdition)}
                  onDuplicate={() => runDuplicateAction(duplicateActions?.duplicateUrl, 'Duplicate this world into a new draft?')}
                  onCreateEdition={() => runDuplicateAction(duplicateActions?.newEditionUrl, 'Create the next edition draft from this world?')}
                />
              </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}
                              {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.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 || '' }))
                    }}
                  />

                  <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">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 === '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}
        </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}
            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}
      />

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