import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useEditor, EditorContent } from '@tiptap/react'
import { Node, mergeAttributes } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'
import { Table } from '@tiptap/extension-table'
import { TableRow } from '@tiptap/extension-table-row'
import { TableHeader } from '@tiptap/extension-table-header'
import { TableCell } from '@tiptap/extension-table-cell'
import Placeholder from '@tiptap/extension-placeholder'
import Underline from '@tiptap/extension-underline'
import Mention from '@tiptap/extension-mention'
import mentionSuggestion from './mentionSuggestion'
import EmojiPicker from './EmojiPicker'
import RichImage from './RichImageNode'
import RichCompare from './RichCompareNode'
import RichTableControls, { TableInsertDialog } from './RichTableControls'

function ToolbarBtn({ onClick, active, disabled, title, children, className = '' }) {
  return (
    <button
      type="button"
      onMouseDown={(event) => {
        event.preventDefault()
      }}
      onClick={onClick}
      disabled={disabled}
      title={title}
      className={[
        'inline-flex h-8 w-8 items-center justify-center rounded-lg text-sm transition-colors',
        'focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-400',
        active
          ? 'bg-sky-600/25 text-sky-300'
          : 'text-zinc-400 hover:bg-white/[0.06] hover:text-zinc-200',
        disabled && 'pointer-events-none opacity-30',
        className,
      ].filter(Boolean).join(' ')}
    >
      {children}
    </button>
  )
}

function Divider() {
  return <div className="mx-1 h-5 w-px bg-white/10" />
}

function getRootFontSizePx() {
  if (typeof window === 'undefined') {
    return 16
  }

  return Number.parseFloat(window.getComputedStyle(document.documentElement).fontSize) || 16
}

function formatViewportHeightLabel(value) {
  const rounded = Number(value || 0)
  const displayValue = Number.isInteger(rounded) ? rounded : Number(rounded.toFixed(1))

  return `${displayValue}rem`
}

function normalizeHttpUrl(rawValue) {
  const trimmed = String(rawValue || '').trim()
  if (trimmed === '') {
    return null
  }

  const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
    ? trimmed
    : `https://${trimmed.replace(/^\/+/, '')}`

  try {
    const parsed = new URL(withProtocol)
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return null
    }

    return parsed.toString()
  } catch {
    return null
  }
}

function normalizeVideoEmbedUrl(rawValue) {
  const normalized = normalizeHttpUrl(rawValue)
  if (!normalized) {
    return null
  }

  const parsed = new URL(normalized)
  const host = parsed.hostname.replace(/^www\./i, '').toLowerCase()
  const path = parsed.pathname

  if (host === 'youtu.be') {
    const videoId = path.replace(/^\//, '').split('/')[0]
    return videoId ? `https://www.youtube.com/embed/${videoId}` : normalized
  }

  if (host === 'youtube.com' || host === 'm.youtube.com') {
    if (path === '/watch') {
      const videoId = parsed.searchParams.get('v')
      return videoId ? `https://www.youtube.com/embed/${videoId}` : normalized
    }

    const pathMatch = path.match(/^\/(embed|shorts|live)\/([^/?#]+)/i)
    if (pathMatch?.[2]) {
      return `https://www.youtube.com/embed/${pathMatch[2]}`
    }
  }

  return normalized
}

function detectSocialPlatform(rawUrl) {
  const normalized = normalizeHttpUrl(rawUrl)
  if (!normalized) {
    return { platform: 'social', label: 'Social post', url: null }
  }

  const host = new URL(normalized).hostname.replace(/^www\./i, '').toLowerCase()

  if (host.includes('instagram.')) return { platform: 'instagram', label: 'Instagram post', url: normalized }
  if (host.includes('facebook.')) return { platform: 'facebook', label: 'Facebook post', url: normalized }
  if (host.includes('tiktok.')) return { platform: 'tiktok', label: 'TikTok post', url: normalized }
  if (host.includes('twitter.') || host.includes('x.com')) return { platform: 'x', label: 'X post', url: normalized }
  if (host.includes('linkedin.')) return { platform: 'linkedin', label: 'LinkedIn post', url: normalized }
  if (host.includes('threads.')) return { platform: 'threads', label: 'Threads post', url: normalized }
  if (host.includes('pinterest.')) return { platform: 'pinterest', label: 'Pinterest pin', url: normalized }

  return { platform: 'social', label: 'Social post', url: normalized }
}

const ArtworkEmbed = Node.create({
  name: 'artworkEmbed',
  group: 'block',
  atom: true,

  addAttributes() {
    return {
      title: { default: 'Artwork' },
      url: { default: '' },
      thumb: { default: '' },
    }
  },

  parseHTML() {
    return [{ tag: 'figure[data-artwork-embed]' }]
  },

  renderHTML({ HTMLAttributes }) {
    const preview = []

    if (HTMLAttributes.thumb) {
      preview.push([
        'img',
        {
          src: HTMLAttributes.thumb,
          alt: HTMLAttributes.title || 'Artwork',
          class: 'block h-auto w-full object-cover',
          loading: 'lazy',
        },
      ])
    }

    preview.push([
      'figcaption',
      { class: 'news-embed-caption' },
      HTMLAttributes.title || 'Artwork',
    ])

    return [
      'figure',
      mergeAttributes(HTMLAttributes, {
        'data-artwork-embed': 'true',
        class: 'news-embed news-embed-artwork',
      }),
      [
        'a',
        {
          href: HTMLAttributes.url || '#',
          class: 'news-embed-link',
          rel: 'noopener noreferrer nofollow',
          target: '_blank',
        },
        ...preview,
      ],
    ]
  },
})

const VideoEmbed = Node.create({
  name: 'videoEmbed',
  group: 'block',
  atom: true,

  addAttributes() {
    return {
      src: { default: '' },
      url: { default: '' },
      title: { default: 'Embedded video' },
    }
  },

  parseHTML() {
    return [{ tag: 'figure[data-video-embed]' }]
  },

  renderHTML({ HTMLAttributes }) {
    return [
      'figure',
      mergeAttributes(HTMLAttributes, {
        'data-video-embed': 'true',
        class: 'news-embed news-embed-video w-full',
      }),
      [
        'iframe',
        {
          src: HTMLAttributes.src || '',
          title: HTMLAttributes.title || 'Embedded video',
          loading: 'lazy',
          frameborder: '0',
          allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share',
          allowfullscreen: 'true',
          referrerpolicy: 'strict-origin-when-cross-origin',
        },
      ],
    ]
  },
})

const SocialEmbed = Node.create({
  name: 'socialEmbed',
  group: 'block',
  atom: true,

  addAttributes() {
    return {
      url: { default: '' },
      platform: { default: 'social' },
      label: { default: 'Social post' },
    }
  },

  parseHTML() {
    return [{ tag: 'figure[data-social-embed]' }]
  },

  renderHTML({ HTMLAttributes }) {
    const platform = String(HTMLAttributes.platform || 'social')
    const url = HTMLAttributes.url || '#'
    const label = HTMLAttributes.label || 'Social post'

    if (platform === 'instagram') {
      return [
        'figure',
        mergeAttributes(HTMLAttributes, {
          'data-social-embed': 'true',
          'data-platform': platform,
          class: 'news-embed news-embed-social',
        }),
        [
          'blockquote',
          {
            class: 'instagram-media',
            'data-instgrm-captioned': 'true',
            'data-instgrm-permalink': url,
            'data-instgrm-version': '14',
          },
          [
            'a',
            { href: url, rel: 'noopener noreferrer nofollow', target: '_blank' },
            label,
          ],
        ],
      ]
    }

    if (platform === 'facebook') {
      return [
        'figure',
        mergeAttributes(HTMLAttributes, {
          'data-social-embed': 'true',
          'data-platform': platform,
          class: 'news-embed news-embed-social',
        }),
        [
          'div',
          { class: 'fb-post', 'data-href': url, 'data-show-text': 'true' },
          [
            'blockquote',
            { cite: url, class: 'fb-xfbml-parse-ignore' },
            [
              'a',
              { href: url, rel: 'noopener noreferrer nofollow', target: '_blank' },
              label,
            ],
          ],
        ],
      ]
    }

    if (platform === 'tiktok') {
      return [
        'figure',
        mergeAttributes(HTMLAttributes, {
          'data-social-embed': 'true',
          'data-platform': platform,
          class: 'news-embed news-embed-social',
        }),
        [
          'blockquote',
          { class: 'tiktok-embed', cite: url },
          [
            'section',
            null,
            [
              'a',
              { href: url, rel: 'noopener noreferrer nofollow', target: '_blank' },
              label,
            ],
          ],
        ],
      ]
    }

    if (platform === 'x') {
      return [
        'figure',
        mergeAttributes(HTMLAttributes, {
          'data-social-embed': 'true',
          'data-platform': platform,
          class: 'news-embed news-embed-social',
        }),
        [
          'blockquote',
          { class: 'twitter-tweet' },
          [
            'a',
            { href: url, rel: 'noopener noreferrer nofollow', target: '_blank' },
            label,
          ],
        ],
      ]
    }

    return [
      'figure',
      mergeAttributes(HTMLAttributes, {
        'data-social-embed': 'true',
        'data-platform': platform,
        class: 'news-embed news-embed-social',
      }),
      [
        'a',
        {
          href: url,
          class: 'news-embed-link',
          rel: 'noopener noreferrer nofollow',
          target: '_blank',
        },
        ['span', { class: 'news-embed-badge' }, label],
        ['span', { class: 'news-embed-url' }, url],
      ],
    ]
  },
})

function ArtworkPickerDialog({
  open,
  query,
  items,
  loading,
  onQueryChange,
  onClose,
  onSearch,
  onSelect,
}) {
  if (!open) return null

  return createPortal(
    <div
      className="fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md"
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose?.()
        }
      }}
      role="presentation"
    >
      <div className="w-full max-w-3xl overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]">
        <div className="border-b border-white/[0.06] px-6 py-5">
          <div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35">Artwork embed</div>
          <h3 className="mt-2 text-lg font-semibold text-white">Choose artwork</h3>
          <p className="mt-2 text-sm leading-6 text-white/65">Search existing artworks and insert a linked artwork card into the News article body.</p>
        </div>

        <div className="border-b border-white/[0.06] px-6 py-4">
          <div className="flex gap-3">
            <input
              value={query}
              onChange={(event) => onQueryChange?.(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === 'Enter') {
                  event.preventDefault()
                  onSearch?.()
                }
              }}
              placeholder="Search by title, slug, or creator"
              className="min-w-0 flex-1 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
            />
            <button type="button" onClick={onSearch} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15">
              Search
            </button>
          </div>
        </div>

        <div className="nova-scrollbar max-h-[60vh] overflow-y-auto px-6 py-5">
          {loading ? <div className="rounded-2xl border border-white/10 bg-black/20 p-4 text-sm text-slate-300">Searching artworks…</div> : null}
          {!loading && (!Array.isArray(items) || items.length === 0) ? <div className="rounded-2xl border border-dashed border-white/10 bg-white/[0.02] p-4 text-sm text-slate-400">No artworks found yet. Try a broader title or creator search.</div> : null}
          {!loading && Array.isArray(items) && items.length > 0 ? (
            <div className="grid gap-3">
              {items.map((item) => {
                const previewImage = item.image || item.avatar || ''

                return (
                  <button
                    key={`${item.entity_type || 'artwork'}-${item.id}`}
                    type="button"
                    onClick={() => onSelect?.(item)}
                    className="flex items-center gap-4 rounded-[24px] border border-white/10 bg-black/20 p-3 text-left transition hover:border-white/20 hover:bg-white/[0.04]"
                  >
                    <div className="h-20 w-28 shrink-0 overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03]">
                      {previewImage ? <img src={previewImage} alt={item.title} className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-xs uppercase tracking-[0.18em] text-slate-500">No thumb</div>}
                    </div>
                    <div className="min-w-0 flex-1">
                      <div className="text-sm font-semibold text-white">{item.title}</div>
                      {item.subtitle ? <div className="mt-1 text-xs uppercase tracking-[0.14em] text-slate-500">{item.subtitle}</div> : null}
                      {item.description ? <div className="mt-2 line-clamp-2 text-xs leading-5 text-slate-400">{item.description}</div> : null}
                    </div>
                  </button>
                )
              })}
            </div>
          ) : null}
        </div>

        <div className="flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
          <button type="button" onClick={onClose} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
            Cancel
          </button>
        </div>
      </div>
    </div>,
    document.body,
  )
}

function MediaImageDialog({
  open,
  uploading,
  error,
  previewUrl,
  imageUrl,
  altText,
  uploadLabel,
  helperText,
  allowUpload,
  onClose,
  onImageUrlChange,
  onAltTextChange,
  onPickFile,
  onBrowseAssets,
  onInsert,
  onClearUploaded,
}) {
  const inputRef = useRef(null)

  if (!open) return null

  return createPortal(
    <div
      className="fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md"
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose?.()
        }
      }}
      role="presentation"
    >
      <div className="w-full max-w-4xl overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]">
        <div className="border-b border-white/[0.06] px-6 py-5">
          <div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35">Media</div>
          <h3 className="mt-2 text-lg font-semibold text-white">Add image</h3>
          <p className="mt-2 text-sm leading-6 text-white/65">Upload, drag, paste, or link an image without leaving the editor.</p>
        </div>

        <div className="grid gap-6 px-6 py-5 lg:grid-cols-[minmax(0,1fr)_320px]">
          <div className="grid gap-4">
            {allowUpload ? (
              <div
                role="button"
                tabIndex={0}
                onClick={() => !uploading && inputRef.current?.click()}
                onKeyDown={(event) => {
                  if (uploading) return
                  if (event.key === 'Enter' || event.key === ' ') {
                    event.preventDefault()
                    inputRef.current?.click()
                  }
                }}
                onDragOver={(event) => {
                  event.preventDefault()
                }}
                onDrop={(event) => {
                  event.preventDefault()
                  void onPickFile?.(event.dataTransfer?.files?.[0] || null)
                }}
                className={[
                  'rounded-[24px] border border-dashed px-5 py-5 transition outline-none',
                  uploading
                    ? 'cursor-progress border-sky-300/35 bg-sky-400/10'
                    : 'cursor-pointer border-white/10 bg-black/20 hover:border-white/20 hover:bg-white/[0.04]',
                ].join(' ')}
              >
                <div className="flex items-start gap-4">
                  <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-sky-300/20 bg-sky-400/10 text-sky-100">
                    <i className={`fa-solid ${uploading ? 'fa-circle-notch fa-spin' : 'fa-cloud-arrow-up'}`} />
                  </div>
                  <div>
                    <div className="text-sm font-semibold text-white">{uploading ? 'Uploading image…' : uploadLabel || 'Drop image here or browse'}</div>
                    <div className="mt-1 text-xs leading-5 text-slate-400">{helperText}</div>
                    <div className="mt-2 text-xs leading-5 text-slate-500">You can also paste an image directly into the editor or drag one into the writing area.</div>
                  </div>
                </div>
                <input
                  ref={inputRef}
                  type="file"
                  accept="image/jpeg,image/png,image/webp"
                  className="hidden"
                  onChange={(event) => {
                    void onPickFile?.(event.target.files?.[0] || null)
                    event.target.value = ''
                  }}
                />
              </div>
            ) : null}

            <label className="grid gap-2 text-sm text-slate-300">
              <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Image URL</span>
              <input
                value={imageUrl}
                onChange={(event) => onImageUrlChange?.(event.target.value)}
                placeholder="https://files.skinbase.org/..."
                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">Alt text</span>
              <input
                value={altText}
                onChange={(event) => onAltTextChange?.(event.target.value)}
                placeholder="Describe what the image shows"
                className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
              />
            </label>

            {error ? <div className="rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100">{error}</div> : null}
          </div>

          <div className="grid gap-4">
            <div className="overflow-hidden rounded-[24px] border border-white/10 bg-slate-950">
              {previewUrl ? (
                <img src={previewUrl} alt="Media preview" className="h-64 w-full object-cover" />
              ) : (
                <div className="flex h-64 items-center justify-center px-6 text-center text-sm text-slate-500">No media selected yet.</div>
              )}
            </div>

            <div className="flex flex-wrap gap-2">
              {allowUpload ? (
                <button type="button" onClick={() => inputRef.current?.click()} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
                  Browse
                </button>
              ) : null}
              {onBrowseAssets ? (
                <button type="button" onClick={onBrowseAssets} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
                  Choose from assets
                </button>
              ) : null}
              {previewUrl ? (
                <button type="button" onClick={() => onClearUploaded?.()} className="rounded-full border border-white/10 bg-transparent px-4 py-2 text-sm font-semibold text-slate-300 transition hover:bg-white/[0.04]">
                  Clear
                </button>
              ) : null}
            </div>
          </div>
        </div>

        <div className="flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
          <button type="button" onClick={onClose} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
            Cancel
          </button>
          <button type="button" onClick={onInsert} disabled={!previewUrl || uploading} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2.5 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:cursor-not-allowed disabled:opacity-40">
            Insert image
          </button>
        </div>
      </div>
    </div>,
    document.body,
  )
}

function CompareImageDialog({
  open,
  uploading,
  error,
  subtitle,
  leftImage,
  rightImage,
  allowUpload,
  onClose,
  onSubtitleChange,
  onLeftAltTextChange,
  onRightAltTextChange,
  onLeftPickFile,
  onRightPickFile,
  onLeftBrowseAssets,
  onRightBrowseAssets,
  onLeftClear,
  onRightClear,
  onInsert,
}) {
  const leftInputRef = useRef(null)
  const rightInputRef = useRef(null)

  if (!open) return null

  const renderSide = (sideLabel, image, inputRef, onPickFile, onClear, onAltTextChange, onBrowseAssets) => (
    <div
      role="button"
      tabIndex={0}
      onClick={(event) => {
        if (event.target !== event.currentTarget) {
          return
        }

        if (!uploading && allowUpload) {
          inputRef.current?.click()
        }
      }}
      onKeyDown={(event) => {
        if (event.target !== event.currentTarget) {
          return
        }

        if (uploading || !allowUpload) return
        if (event.key === 'Enter' || event.key === ' ') {
          event.preventDefault()
          inputRef.current?.click()
        }
      }}
      onDragOver={(event) => {
        event.preventDefault()
      }}
      onDrop={(event) => {
        event.preventDefault()
        void onPickFile?.(event.dataTransfer?.files?.[0] || null)
      }}
      className={[
        'grid gap-4 rounded-[24px] border border-dashed px-5 py-5 transition outline-none',
        uploading
          ? 'cursor-progress border-sky-300/35 bg-sky-400/10'
          : 'cursor-pointer border-white/10 bg-black/20 hover:border-white/20 hover:bg-white/[0.04]',
      ].join(' ')}
    >
      <div className="flex items-start gap-4">
        <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-sky-300/20 bg-sky-400/10 text-sky-100">
          <i className={`fa-solid ${uploading ? 'fa-circle-notch fa-spin' : 'fa-images'}`} />
        </div>
        <div className="min-w-0 flex-1">
          <div className="text-sm font-semibold text-white">{sideLabel}</div>
          <div className="mt-1 text-xs leading-5 text-slate-400">Upload the image for this side of the comparison.</div>
        </div>
      </div>

      <div className="overflow-hidden rounded-[20px] border border-white/10 bg-slate-950">
        {image.previewUrl ? (
          <img src={image.previewUrl} alt={`${sideLabel} preview`} className="h-56 w-full object-cover" />
        ) : (
          <div className="flex h-56 items-center justify-center px-6 text-center text-sm text-slate-500">No image selected yet.</div>
        )}
      </div>

      <label className="grid gap-2 text-sm text-slate-300" onClick={(event) => event.stopPropagation()}>
        <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Alt text</span>
        <input
          value={image.altText}
          onChange={(event) => onAltTextChange?.(event.target.value)}
          placeholder={`Describe the ${sideLabel.toLowerCase()} image`}
          className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
        />
      </label>

      <div className="flex flex-wrap gap-2" onClick={(event) => event.stopPropagation()}>
        {allowUpload ? (
          <button
            type="button"
            onClick={(event) => {
              event.stopPropagation()
              inputRef.current?.click()
            }}
            className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]"
          >
            Browse
          </button>
        ) : null}
        {onBrowseAssets ? (
          <button
            type="button"
            onClick={(event) => {
              event.stopPropagation()
              onBrowseAssets()
            }}
            className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]"
          >
            Choose from assets
          </button>
        ) : null}
        {image.previewUrl ? (
          <button
            type="button"
            onClick={(event) => {
              event.stopPropagation()
              onClear?.()
            }}
            className="rounded-full border border-white/10 bg-transparent px-4 py-2 text-sm font-semibold text-slate-300 transition hover:bg-white/[0.04]"
          >
            Clear
          </button>
        ) : null}
      </div>

      <input
        ref={inputRef}
        type="file"
        accept="image/jpeg,image/png,image/webp"
        className="hidden"
        onChange={(event) => {
          void onPickFile?.(event.target.files?.[0] || null)
          event.target.value = ''
        }}
      />
    </div>
  )

  return createPortal(
    <div
      className="fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md"
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose?.()
        }
      }}
      role="presentation"
    >
      <div className="w-full max-w-6xl overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]">
        <div className="border-b border-white/[0.06] px-6 py-5">
          <div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35">Image comparison</div>
          <h3 className="mt-2 text-lg font-semibold text-white">Add side-by-side images</h3>
          <p className="mt-2 text-sm leading-6 text-white/65">Upload two images, add alt text for each, and include a subtitle under the comparison.</p>
        </div>

        <div className="grid gap-6 px-6 py-5 xl:grid-cols-2">
          {renderSide('Left image', leftImage, leftInputRef, onLeftPickFile, onLeftClear, onLeftAltTextChange, onLeftBrowseAssets)}
          {renderSide('Right image', rightImage, rightInputRef, onRightPickFile, onRightClear, onRightAltTextChange, onRightBrowseAssets)}
        </div>

        <div className="border-t border-white/[0.06] px-6 py-5">
          <label className="grid gap-2 text-sm text-slate-300">
            <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Subtitle</span>
            <input
              value={subtitle}
              onChange={(event) => onSubtitleChange?.(event.target.value)}
              placeholder="Visible subtitle below the comparison"
              className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
            />
          </label>

          {error ? <div className="mt-4 rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100">{error}</div> : null}
        </div>

        <div className="flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
          <button type="button" onClick={onClose} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
            Cancel
          </button>
          <button type="button" onClick={onInsert} disabled={!leftImage.previewUrl || !rightImage.previewUrl || uploading} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-2.5 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15 disabled:cursor-not-allowed disabled:opacity-40">
            Insert comparison
          </button>
        </div>
      </div>
    </div>,
    document.body,
  )
}

function AssetPickerDialog({
  open,
  searchQuery,
  assets,
  loading,
  error,
  pagination,
  onClose,
  onRefresh,
  onSearchQueryChange,
  onSearch,
  onPreviousPage,
  onNextPage,
  onSelect,
}) {
  if (!open) return null

  return createPortal(
    <div
      className="fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md"
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose?.()
        }
      }}
      role="presentation"
    >
      <div className="w-full max-w-6xl overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]">
        <div className="flex items-start justify-between gap-4 border-b border-white/[0.06] px-6 py-5">
          <div>
            <div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35">Academy assets</div>
            <h3 className="mt-2 text-lg font-semibold text-white">Choose an uploaded image</h3>
            <p className="mt-2 text-sm leading-6 text-white/65">Pick from previously uploaded Academy lesson images instead of uploading a new file.</p>
          </div>

          <div className="flex flex-wrap items-center gap-2">
            <button type="button" onClick={onRefresh} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
              Refresh
            </button>
          </div>
        </div>

        <div className="border-b border-white/[0.06] px-6 py-4">
          <div className="flex flex-col gap-3 lg:flex-row lg:items-center">
            <input
              value={searchQuery}
              onChange={(event) => onSearchQueryChange?.(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === 'Enter') {
                  event.preventDefault()
                  onSearch?.()
                }
              }}
              placeholder="Search academy assets"
              className="min-w-0 flex-1 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
            />
            <button type="button" onClick={onSearch} className="rounded-2xl border border-sky-300/20 bg-sky-400/10 px-4 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15">
              Search
            </button>
          </div>
        </div>

        <div className="nova-scrollbar max-h-[68vh] overflow-y-auto px-6 py-5">
          {loading ? <div className="rounded-2xl border border-white/10 bg-black/20 p-4 text-sm text-slate-300">Loading academy assets…</div> : null}
          {error ? <div className="mb-4 rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100">{error}</div> : null}
          {!loading && (!Array.isArray(assets) || assets.length === 0) ? <div className="rounded-2xl border border-dashed border-white/10 bg-white/[0.02] p-4 text-sm text-slate-400">No academy images found yet. Upload one first.</div> : null}

          {!loading && Array.isArray(assets) && assets.length > 0 ? (
            <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
              {assets.map((asset) => (
                <button
                  key={asset.path}
                  type="button"
                  onClick={() => onSelect?.(asset)}
                  className="group overflow-hidden rounded-[24px] border border-white/10 bg-black/20 text-left transition hover:border-sky-300/30 hover:bg-white/[0.04]"
                >
                  <div className="aspect-[4/3] overflow-hidden bg-slate-950">
                    <img src={asset.url} alt={asset.name || 'Academy asset'} className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.02]" loading="lazy" />
                  </div>
                  <div className="space-y-2 p-4">
                    <div className="text-sm font-semibold text-white">{asset.name || 'Academy image'}</div>
                    <div className="flex flex-wrap gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">
                      {asset.slot ? <span className="rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1">{asset.slot}</span> : null}
                      {asset.modified_at ? <span className="rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1">{new Date(asset.modified_at).toLocaleDateString()}</span> : null}
                    </div>
                  </div>
                </button>
              ))}
            </div>
          ) : null}
        </div>

        <div className="flex flex-wrap items-center justify-between gap-3 border-t border-white/[0.06] px-6 py-4">
          <div className="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
            Page {pagination?.page || 1} of {pagination?.last_page || 1}
            {typeof pagination?.total === 'number' ? ` · ${pagination.total} assets` : ''}
          </div>
          <div className="flex items-center gap-2">
            <button
              type="button"
              onClick={onPreviousPage}
              disabled={(pagination?.page || 1) <= 1 || loading}
              className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08] disabled:cursor-not-allowed disabled:opacity-40"
            >
              Previous
            </button>
            <button
              type="button"
              onClick={onNextPage}
              disabled={!pagination?.has_more || loading}
              className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08] disabled:cursor-not-allowed disabled:opacity-40"
            >
              Next
            </button>
          </div>
        </div>

        <div className="flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
          <button type="button" onClick={onClose} className="rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
            Cancel
          </button>
        </div>
      </div>
    </div>,
    document.body,
  )
}

function Toolbar({
  editor,
  advancedNews = false,
  activeSourceMode = null,
  sourceModeLabel = 'HTML',
  sourceModeTitle = 'View or edit source HTML',
  secondarySourceModeLabel = null,
  secondarySourceModeTitle = '',
  showStructureOutlines = false,
  showComparisonTool = false,
  fullHeightMode = false,
  onToggleSourceMode,
  onToggleSecondarySourceMode,
  onToggleStructureOutlines,
  onInsertArtwork,
  onInsertImage,
  onInsertComparison,
  onInsertTable,
  onInsertSocialEmbed,
  onInsertVideoEmbed,
  onInsertHashtag,
  editorViewportHeight,
  onIncreaseEditorViewportHeight,
  onDecreaseEditorViewportHeight,
  onToggleFullHeightMode,
}) {
  if (!editor) return null

  const addLink = useCallback(() => {
    const prev = editor.getAttributes('link').href
    const url = window.prompt('URL', prev ?? 'https://')
    if (url === null) return
    if (url === '') {
      editor.chain().focus().extendMarkRange('link').unsetLink().run()
    } else {
      editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
    }
  }, [editor])

  return (
    <div className="flex flex-wrap items-center gap-0.5 border-b border-white/[0.06] px-2.5 py-2">
      <ToolbarBtn onClick={() => editor.chain().focus().toggleBold().run()} active={editor.isActive('bold')} title="Bold (Ctrl+B)">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M6 4h8a4 4 0 014 4 4 4 0 01-4 4H6zm0 8h9a4 4 0 014 4 4 4 0 01-4 4H6z"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive('italic')} title="Italic (Ctrl+I)">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleUnderline().run()} active={editor.isActive('underline')} title="Underline (Ctrl+U)">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 3v7a6 6 0 006 6 6 6 0 006-6V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleStrike().run()} active={editor.isActive('strike')} title="Strikethrough">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="4" y1="12" x2="20" y2="12"/><path d="M17.5 7.5c0-2-1.5-3.5-5.5-3.5S6.5 5.5 6.5 7.5c0 4 11 4 11 8 0 2-1.5 3.5-5.5 3.5s-5.5-1.5-5.5-3.5"/></svg>
      </ToolbarBtn>

      <Divider />

      <ToolbarBtn onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive('heading', { level: 2 })} title="Heading 2">
        <span className="text-xs font-bold">H2</span>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive('heading', { level: 3 })} title="Heading 3">
        <span className="text-xs font-bold">H3</span>
      </ToolbarBtn>

      <Divider />

      <ToolbarBtn onClick={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive('bulletList')} title="Bullet list">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4.5" cy="6" r="1" fill="currentColor"/><circle cx="4.5" cy="12" r="1" fill="currentColor"/><circle cx="4.5" cy="18" r="1" fill="currentColor"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive('orderedList')} title="Numbered list">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><text x="3" y="8" fontSize="7" fill="currentColor" stroke="none" fontFamily="sans-serif">1</text><text x="3" y="14" fontSize="7" fill="currentColor" stroke="none" fontFamily="sans-serif">2</text><text x="3" y="20" fontSize="7" fill="currentColor" stroke="none" fontFamily="sans-serif">3</text></svg>
      </ToolbarBtn>

      <Divider />

      <ToolbarBtn onClick={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive('blockquote')} title="Quote">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M4.583 17.321C3.553 16.227 3 15 3 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311C9.591 11.68 11 13.24 11 15.14c0 .94-.36 1.84-1.001 2.503A3.34 3.34 0 017.559 18.6a3.77 3.77 0 01-2.976-.879zm10.4 0C13.953 16.227 13.4 15 13.4 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.986.169 3.395 1.729 3.395 3.629 0 .94-.36 1.84-1.001 2.503a3.34 3.34 0 01-2.44.957 3.77 3.77 0 01-2.976-.879z"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleCodeBlock().run()} active={editor.isActive('codeBlock')} title="Code block">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={() => editor.chain().focus().toggleCode().run()} active={editor.isActive('code')} title="Inline code">
        <span className="font-mono text-[11px] font-bold">{'{}'}</span>
      </ToolbarBtn>

      <Divider />

      <ToolbarBtn onClick={addLink} active={editor.isActive('link')} title="Link">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>
      </ToolbarBtn>
      <ToolbarBtn onClick={onInsertImage} title="Insert image">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
      </ToolbarBtn>
      {showComparisonTool ? (
        <ToolbarBtn onClick={onInsertComparison} title="Insert image comparison">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="8" height="16" rx="1.5"/><rect x="13" y="4" width="8" height="16" rx="1.5"/><path d="M9 8h1M14 8h1M9 16h1M14 16h1"/></svg>
        </ToolbarBtn>
      ) : null}
      <ToolbarBtn onClick={onInsertTable} title="Insert table">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/></svg>
      </ToolbarBtn>

      <Divider />

      <ToolbarBtn onClick={() => editor.chain().focus().setHorizontalRule().run()} title="Horizontal rule">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="3" y1="12" x2="21" y2="12"/></svg>
      </ToolbarBtn>
      <EmojiPicker editor={editor} />
      <ToolbarBtn onClick={() => editor.chain().focus().insertContent('@').run()} title="Mention a user (type @username)">
        <span className="text-xs font-bold">@</span>
      </ToolbarBtn>

      {advancedNews ? (
        <>
          <Divider />
          <ToolbarBtn onClick={onToggleSourceMode} active={activeSourceMode === 'primary'} title={sourceModeTitle} className="w-auto px-2.5">
            <span className="text-[11px] font-bold uppercase tracking-[0.14em]">{sourceModeLabel}</span>
          </ToolbarBtn>
          {secondarySourceModeLabel ? (
            <ToolbarBtn onClick={onToggleSecondarySourceMode} active={activeSourceMode === 'secondary'} title={secondarySourceModeTitle} className="w-auto px-2.5">
              <span className="text-[11px] font-bold uppercase tracking-[0.14em]">{secondarySourceModeLabel}</span>
            </ToolbarBtn>
          ) : null}
          <ToolbarBtn onClick={onToggleStructureOutlines} active={showStructureOutlines} title="Outline blocks (p, div, figure, list)" className="w-auto px-2.5">
            <span className="text-[11px] font-bold uppercase tracking-[0.14em]">DOM</span>
          </ToolbarBtn>
          <ToolbarBtn onClick={onInsertArtwork} title="Embed artwork" className="w-auto px-2.5">
            <span className="text-[11px] font-bold uppercase tracking-[0.14em]">Art</span>
          </ToolbarBtn>
          <ToolbarBtn onClick={onInsertSocialEmbed} title="Embed social post" className="w-auto px-2.5">
            <span className="text-[11px] font-bold uppercase tracking-[0.14em]">Social</span>
          </ToolbarBtn>
          <ToolbarBtn onClick={onInsertVideoEmbed} title="Embed YouTube" className="w-auto px-2.5">
            <span className="text-[11px] font-bold uppercase tracking-[0.14em]">YT</span>
          </ToolbarBtn>
          <ToolbarBtn onClick={onInsertHashtag} title="Insert hashtag" className="w-auto px-2.5">
            <span className="text-xs font-bold">#</span>
          </ToolbarBtn>
        </>
      ) : null}

      <div className="ml-auto flex items-center gap-0.5">
        <ToolbarBtn onClick={onDecreaseEditorViewportHeight} title="Shorter editor" className="w-auto px-2.5">
          <span className="text-[11px] font-bold uppercase tracking-[0.14em]">A-</span>
        </ToolbarBtn>
        <div className="mx-1 flex min-w-[5.25rem] items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400">
          {formatViewportHeightLabel(editorViewportHeight)}
        </div>
        <ToolbarBtn onClick={onIncreaseEditorViewportHeight} title="Taller editor" className="w-auto px-2.5">
          <span className="text-[11px] font-bold uppercase tracking-[0.14em]">A+</span>
        </ToolbarBtn>
        <ToolbarBtn onClick={onToggleFullHeightMode} active={fullHeightMode} title={fullHeightMode ? 'Exit full height editor' : 'Expand editor to full browser size'} className="w-auto px-2.5">
          <span className="text-[11px] font-bold uppercase tracking-[0.14em]">Fit</span>
        </ToolbarBtn>
        <ToolbarBtn onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().undo()} title="Undo (Ctrl+Z)">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 102.13-9.36L1 10"/></svg>
        </ToolbarBtn>
        <ToolbarBtn onClick={() => editor.chain().focus().redo().run()} disabled={!editor.can().redo()} title="Redo (Ctrl+Shift+Z)">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.13-9.36L23 10"/></svg>
        </ToolbarBtn>
      </div>
    </div>
  )
}

export default function RichTextEditor({
  content = '',
  onChange,
  placeholder = 'Write something…',
  error,
  minHeight = 12,
  maxHeightRem = 42,
  autofocus = false,
  advancedNews = false,
  sourceModeLabel = 'HTML',
  sourceModeTitle = 'View or edit source HTML',
  sourceModeDescription = 'Edit the stored article HTML directly. Saving while in this mode keeps the HTML exactly as written here.',
  secondarySourceModeLabel = null,
  secondarySourceModeTitle = '',
  secondarySourceModeDescription = '',
  secondarySourceModeValue = null,
  onSecondarySourceModeValueChange = null,
  searchEntities = null,
  mediaSupport = null,
}) {
  const viewportStorageKey = 'rich-text-editor.viewport-height'
  const viewportMinHeight = Math.max(minHeight + 6, 18)
  const viewportMaxHeight = Math.max(viewportMinHeight, Number(maxHeightRem) || 42)
  const viewportStep = 4
  const [activeSourceMode, setActiveSourceMode] = useState(null)
  const [fullHeightMode, setFullHeightMode] = useState(false)
  const [sourceValue, setSourceValue] = useState(String(content || ''))
  const [showStructureOutlines, setShowStructureOutlines] = useState(false)
  const [helperMessage, setHelperMessage] = useState('')
  const [artworkPickerOpen, setArtworkPickerOpen] = useState(false)
  const [artworkQuery, setArtworkQuery] = useState('')
  const [artworkResults, setArtworkResults] = useState([])
  const [artworkLoading, setArtworkLoading] = useState(false)
  const [mediaDialogOpen, setMediaDialogOpen] = useState(false)
  const [mediaUploading, setMediaUploading] = useState(false)
  const [mediaError, setMediaError] = useState('')
  const [mediaUrlValue, setMediaUrlValue] = useState('')
  const [mediaPreviewUrl, setMediaPreviewUrl] = useState('')
  const [mediaAltText, setMediaAltText] = useState('')
  const [mediaUploadedPath, setMediaUploadedPath] = useState('')
  const [academyAssetsOpen, setAcademyAssetsOpen] = useState(false)
  const [academyAssetsQuery, setAcademyAssetsQuery] = useState('')
  const [academyAssetsSearch, setAcademyAssetsSearch] = useState('')
  const [academyAssetsPage, setAcademyAssetsPage] = useState(1)
  const [academyAssetsPagination, setAcademyAssetsPagination] = useState({
    page: 1,
    per_page: 24,
    total: 0,
    last_page: 1,
    has_more: false,
  })
  const [academyAssetsLoading, setAcademyAssetsLoading] = useState(false)
  const [academyAssetsError, setAcademyAssetsError] = useState('')
  const [academyAssets, setAcademyAssets] = useState([])
  const [academyAssetsTarget, setAcademyAssetsTarget] = useState(null)
  const [compareDialogOpen, setCompareDialogOpen] = useState(false)
  const [compareUploading, setCompareUploading] = useState(false)
  const [compareError, setCompareError] = useState('')
  const [compareSubtitle, setCompareSubtitle] = useState('')
  const [compareLeftImage, setCompareLeftImage] = useState({ previewUrl: '', altText: '', uploadedPath: '' })
  const [compareRightImage, setCompareRightImage] = useState({ previewUrl: '', altText: '', uploadedPath: '' })
  const [tableInsertOpen, setTableInsertOpen] = useState(false)
  const [tableRows, setTableRows] = useState(3)
  const [tableCols, setTableCols] = useState(3)
  const [tableHeaderRow, setTableHeaderRow] = useState(true)
  const [tableHeaderColumn, setTableHeaderColumn] = useState(false)
  const [editorViewportHeight, setEditorViewportHeight] = useState(() => {
    if (typeof window === 'undefined') {
      return Math.min(viewportMaxHeight, viewportMinHeight)
    }

    const storedValue = Number(window.localStorage.getItem(viewportStorageKey))

    if (Number.isFinite(storedValue)) {
      return Math.min(viewportMaxHeight, Math.max(viewportMinHeight, storedValue))
    }

    return Math.min(viewportMaxHeight, viewportMinHeight)
  })
  const editorRef = useRef(null)
  const resizeCleanupRef = useRef(null)
  const usesSecondarySourceMode = typeof onSecondarySourceModeValueChange === 'function' && secondarySourceModeValue != null
  const csrfToken = useMemo(() => {
    if (typeof document === 'undefined') {
      return ''
    }

    return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
  }, [])

  const deleteTemporaryMedia = useCallback(async (path) => {
    if (!mediaSupport?.deleteUrl || !path) return

    const response = await fetch(mediaSupport.deleteUrl, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': csrfToken,
        Accept: 'application/json',
      },
      credentials: 'same-origin',
      body: JSON.stringify({ path }),
    })

    const payload = await response.json().catch(() => ({}))
    if (!response.ok) {
      throw new Error(payload?.message || payload?.error || 'Could not remove uploaded media.')
    }
  }, [csrfToken, mediaSupport])

  const resetMediaState = useCallback(() => {
    setMediaUploading(false)
    setMediaError('')
    setMediaUrlValue('')
    setMediaPreviewUrl('')
    setMediaAltText('')
    setMediaUploadedPath('')
  }, [])

  const insertImageIntoEditor = useCallback((src, alt = '') => {
    if (!src || !editorRef.current) return false

    editorRef.current.chain().focus().insertContent({
      type: 'image',
      attrs: {
        src,
        alt: alt || '',
        title: alt || '',
        caption: '',
        width: null,
      },
    }).run()
    return true
  }, [])

  const uploadMediaFile = useCallback(async (file, previousPath = '') => {
    if (!file || !mediaSupport?.uploadUrl) {
      return null
    }

    if (!String(file.type || '').startsWith('image/')) {
      throw new Error('Use an image file to insert media.')
    }

    setMediaUploading(true)
    setMediaError('')

    try {
      if (previousPath) {
        await deleteTemporaryMedia(previousPath)
      }

      const body = new FormData()
      body.append('image', file)
      body.append('slot', mediaSupport.slot || 'body')

      const response = await fetch(mediaSupport.uploadUrl, {
        method: 'POST',
        headers: {
          'X-CSRF-TOKEN': csrfToken,
          Accept: 'application/json',
        },
        credentials: 'same-origin',
        body,
      })

      const payload = await response.json().catch(() => ({}))
      if (!response.ok) {
        throw new Error(payload?.message || payload?.error || 'Image upload failed.')
      }

      return payload
    } finally {
      setMediaUploading(false)
    }
  }, [csrfToken, deleteTemporaryMedia, mediaSupport])

  const loadAcademyAssets = useCallback(async ({ page = academyAssetsPage, query = academyAssetsSearch } = {}) => {
    if (!mediaSupport?.assetsUrl) {
      setAcademyAssets([])
      setAcademyAssetsPagination({ page: 1, per_page: 24, total: 0, last_page: 1, has_more: false })
      return
    }

    setAcademyAssetsLoading(true)
    setAcademyAssetsError('')

    try {
      const params = new URLSearchParams()
      params.set('limit', '24')
      params.set('page', String(Math.max(1, page)))
      if (String(query || '').trim() !== '') {
        params.set('q', String(query).trim())
      }

      const requestUrl = new URL(mediaSupport.assetsUrl, window.location.origin)
      requestUrl.search = params.toString()

      const response = await fetch(requestUrl.toString(), {
        headers: {
          'X-CSRF-TOKEN': csrfToken,
          Accept: 'application/json',
        },
        credentials: 'same-origin',
        cache: 'no-store',
      })

      const payload = await response.json().catch(() => ({}))
      if (!response.ok) {
        throw new Error(payload?.message || 'Could not load academy assets.')
      }

      setAcademyAssets(Array.isArray(payload?.items) ? payload.items : [])
      setAcademyAssetsPagination({
        page: Number(payload?.pagination?.page) || 1,
        per_page: Number(payload?.pagination?.per_page) || 24,
        total: Number(payload?.pagination?.total) || 0,
        last_page: Number(payload?.pagination?.last_page) || 1,
        has_more: Boolean(payload?.pagination?.has_more),
      })
    } catch (loadError) {
      setAcademyAssets([])
      setAcademyAssetsPagination({ page: 1, per_page: 24, total: 0, last_page: 1, has_more: false })
      setAcademyAssetsError(loadError?.message || 'Could not load academy assets.')
    } finally {
      setAcademyAssetsLoading(false)
    }
  }, [academyAssetsPage, academyAssetsSearch, csrfToken, mediaSupport?.assetsUrl])

  const openAcademyAssets = useCallback((target) => {
    setAcademyAssetsTarget(target)
    setAcademyAssetsQuery('')
    setAcademyAssetsSearch('')
    setAcademyAssetsPage(1)
    setAcademyAssetsOpen(true)
  }, [])

  const submitAcademyAssetSearch = useCallback(() => {
    setAcademyAssetsPage(1)
    setAcademyAssetsSearch(academyAssetsQuery.trim())
  }, [academyAssetsQuery])

  const goToNextAcademyAssetsPage = useCallback(() => {
    setAcademyAssetsPage((current) => Math.min(current + 1, academyAssetsPagination.last_page || current + 1))
  }, [academyAssetsPagination.last_page])

  const goToPreviousAcademyAssetsPage = useCallback(() => {
    setAcademyAssetsPage((current) => Math.max(1, current - 1))
  }, [])

  const closeAcademyAssets = useCallback(() => {
    setAcademyAssetsOpen(false)
    setAcademyAssetsTarget(null)
    setAcademyAssetsError('')
    setAcademyAssetsQuery('')
    setAcademyAssetsSearch('')
    setAcademyAssetsPage(1)
  }, [])

  const chooseAcademyAsset = useCallback((asset) => {
    if (!asset?.url) {
      return
    }

    const target = academyAssetsTarget || { type: 'media' }

    if (target.type === 'compare-left') {
      if (compareLeftImage.uploadedPath) {
        void deleteTemporaryMedia(compareLeftImage.uploadedPath).catch(() => {})
      }

      setCompareLeftImage({
        previewUrl: asset.url,
        altText: compareLeftImage.altText || asset.name || '',
        uploadedPath: '',
      })
    } else if (target.type === 'compare-right') {
      if (compareRightImage.uploadedPath) {
        void deleteTemporaryMedia(compareRightImage.uploadedPath).catch(() => {})
      }

      setCompareRightImage({
        previewUrl: asset.url,
        altText: compareRightImage.altText || asset.name || '',
        uploadedPath: '',
      })
    } else {
      if (mediaUploadedPath) {
        void deleteTemporaryMedia(mediaUploadedPath).catch(() => {})
      }

      setMediaUploadedPath('')
      setMediaPreviewUrl(asset.url)
      setMediaUrlValue(asset.url)
      setMediaAltText((current) => current || asset.name || '')
      setMediaError('')
    }

    closeAcademyAssets()
  }, [academyAssetsTarget, closeAcademyAssets, compareLeftImage, compareRightImage, deleteTemporaryMedia, mediaUploadedPath])

  const handleEditorImageFile = useCallback(async (file) => {
    if (!file) return false

    if (!mediaSupport?.uploadUrl) {
      setHelperMessage('Image uploads are not configured for this editor.')
      return false
    }

    try {
      setHelperMessage('Uploading image...')
      const payload = await uploadMediaFile(file, mediaUploadedPath)
      if (!payload?.url) {
        setHelperMessage('Image upload failed.')
        return false
      }

      setMediaUploadedPath(payload?.path || '')
      setMediaPreviewUrl(payload?.url || '')
      setMediaUrlValue(payload?.url || '')

      insertImageIntoEditor(payload.url, file.name?.replace(/\.[^.]+$/, '') || '')
      setHelperMessage('Image uploaded.')
      resetMediaState()
      return true
    } catch (uploadError) {
      setMediaError(uploadError?.message || 'Image upload failed.')
      setHelperMessage('Image upload failed.')
      return false
    }
  }, [insertImageIntoEditor, mediaSupport, mediaUploadedPath, resetMediaState, uploadMediaFile])

  const extensions = useMemo(() => {
    const base = [
      StarterKit.configure({
        link: false,
        underline: false,
        heading: { levels: [2, 3] },
        codeBlock: {
          HTMLAttributes: { class: 'forum-code-block' },
        },
      }),
      Underline,
      Link.configure({
        openOnClick: false,
        HTMLAttributes: {
          class: 'text-sky-300 underline hover:text-sky-200',
          rel: 'noopener noreferrer nofollow',
        },
      }),
      RichImage.configure({
        HTMLAttributes: { class: 'rich-image-node' },
      }),
      RichCompare.configure({
        HTMLAttributes: { class: 'rich-compare-node' },
      }),
      Table.configure({
        resizable: true,
        allowTableNodeSelection: true,
        HTMLAttributes: {
          class: 'rich-table',
        },
      }),
      TableRow,
      TableHeader.configure({
        HTMLAttributes: {
          class: 'rich-table__header',
        },
      }),
      TableCell.configure({
        HTMLAttributes: {
          class: 'rich-table__cell',
        },
      }),
      Placeholder.configure({ placeholder }),
      Mention.configure({
        HTMLAttributes: {
          class: 'mention',
        },
        suggestion: mentionSuggestion,
      }),
    ]

    if (advancedNews) {
      base.push(ArtworkEmbed, VideoEmbed, SocialEmbed)
    }

    return base
  }, [advancedNews, placeholder])

  const editor = useEditor({
    extensions,
    immediatelyRender: false,
    content,
    autofocus,
    editorProps: {
      attributes: {
        class: [
          'prose prose-invert prose-base max-w-none',
          'focus:outline-none',
          'px-4 py-3',
          'prose-headings:text-white prose-headings:font-bold',
          'prose-p:text-zinc-200 prose-p:leading-relaxed prose-p:mb-5',
          'prose-h2:mb-5 prose-h3:mb-4 prose-hr:my-8',
          'prose-a:text-sky-300 prose-a:no-underline hover:prose-a:text-sky-200',
          'prose-blockquote:border-l-sky-500/50 prose-blockquote:text-zinc-400',
          'prose-code:text-amber-300 prose-code:bg-white/[0.06] prose-code:rounded prose-code:px-1.5 prose-code:py-0.5 prose-code:text-xs',
          'prose-pre:bg-white/[0.04] prose-pre:border prose-pre:border-white/[0.06] prose-pre:rounded-xl',
          'prose-img:rounded-xl',
          'prose-hr:border-white/10',
        ].join(' '),
        style: `min-height: ${minHeight}rem`,
      },
      handleDrop: (_view, event) => {
        const file = event.dataTransfer?.files?.[0]
        if (!file || !String(file.type || '').startsWith('image/')) return false

        event.preventDefault()
        void handleEditorImageFile(file)
        return true
      },
      handlePaste: (_view, event) => {
        const file = event.clipboardData?.files?.[0]
        if (!file || !String(file.type || '').startsWith('image/')) return false

        event.preventDefault()
        void handleEditorImageFile(file)
        return true
      },
    },
    onUpdate: ({ editor: currentEditor }) => {
      if (!activeSourceMode) {
        onChange?.(currentEditor.getHTML())
      }
    },
  })

  useEffect(() => {
    editorRef.current = editor
  }, [editor])

  useEffect(() => {
    if (!helperMessage) {
      return undefined
    }

    const timeout = window.setTimeout(() => setHelperMessage(''), 2200)
    return () => window.clearTimeout(timeout)
  }, [helperMessage])

  useEffect(() => {
    if (!academyAssetsOpen) {
      return
    }

    void loadAcademyAssets({ page: academyAssetsPage, query: academyAssetsSearch })
  }, [academyAssetsOpen, academyAssetsPage, academyAssetsSearch, loadAcademyAssets])

  useEffect(() => {
    if (!editor) return
    if (activeSourceMode) return
    if ((content || '') === editor.getHTML()) return

    editor.commands.setContent(content || '', false)

    const normalizedHtml = editor.getHTML()
    if (normalizedHtml !== (content || '')) {
      onChange?.(normalizedHtml)
    }
  }, [activeSourceMode, content, editor, onChange])

  useEffect(() => {
    if (activeSourceMode === 'primary') {
      setSourceValue(String(content || editor?.getHTML() || ''))
    }
  }, [activeSourceMode, content, editor])

  useEffect(() => {
    if (typeof window === 'undefined') {
      return
    }
    window.localStorage.setItem(viewportStorageKey, String(editorViewportHeight))
  }, [editorViewportHeight])

  const stopViewportResize = useCallback(() => {
    if (resizeCleanupRef.current) {
      resizeCleanupRef.current()
      resizeCleanupRef.current = null
    }

    if (typeof document !== 'undefined') {
      document.body.style.userSelect = ''
      document.body.style.cursor = ''
    }
  }, [])

  useEffect(() => stopViewportResize, [stopViewportResize])

  useEffect(() => {
    if (!fullHeightMode || typeof window === 'undefined') {
      return undefined
    }

    const previousOverflow = document.body.style.overflow

    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        setFullHeightMode(false)
      }
    }

    document.body.style.overflow = 'hidden'
    window.addEventListener('keydown', handleKeyDown)

    return () => {
      document.body.style.overflow = previousOverflow
      window.removeEventListener('keydown', handleKeyDown)
    }
  }, [fullHeightMode])

  const decreaseEditorViewportHeight = useCallback(() => {
    setEditorViewportHeight((current) => Math.max(viewportMinHeight, Number((current - viewportStep).toFixed(1))))
  }, [viewportMinHeight, viewportStep])

  const increaseEditorViewportHeight = useCallback(() => {
    setEditorViewportHeight((current) => Math.min(viewportMaxHeight, Number((current + viewportStep).toFixed(1))))
  }, [viewportMaxHeight, viewportStep])

  const toggleFullHeightMode = useCallback(() => {
    setFullHeightMode((current) => !current)
  }, [])

  const startViewportResize = useCallback((event) => {
    if (fullHeightMode || event.button !== 0 || typeof window === 'undefined') {
      return
    }

    event.preventDefault()

    const startY = event.clientY
    const startHeight = editorViewportHeight

    const handlePointerMove = (moveEvent) => {
      const deltaRem = (moveEvent.clientY - startY) / getRootFontSizePx()
      const nextHeight = Number((startHeight + deltaRem).toFixed(1))

      setEditorViewportHeight(Math.min(viewportMaxHeight, Math.max(viewportMinHeight, nextHeight)))
    }

    const handlePointerUp = () => {
      stopViewportResize()
    }

    window.addEventListener('pointermove', handlePointerMove)
    window.addEventListener('pointerup', handlePointerUp)
    window.addEventListener('pointercancel', handlePointerUp)
    resizeCleanupRef.current = () => {
      window.removeEventListener('pointermove', handlePointerMove)
      window.removeEventListener('pointerup', handlePointerUp)
      window.removeEventListener('pointercancel', handlePointerUp)
    }

    document.body.style.userSelect = 'none'
    document.body.style.cursor = 'ns-resize'
  }, [editorViewportHeight, fullHeightMode, stopViewportResize, viewportMaxHeight, viewportMinHeight])

  const pushHelperMessage = useCallback((message) => {
    setHelperMessage(message)
  }, [])

  const commitPrimarySourceToEditor = useCallback(() => {
    if (editor) {
      editor.commands.setContent(sourceValue || '', false)
    }
  }, [editor, sourceValue])

  const handleToggleSourceMode = useCallback(() => {
    if (activeSourceMode === 'primary') {
      setActiveSourceMode(null)
      commitPrimarySourceToEditor()
      pushHelperMessage('Returned to visual editor.')
      return
    }

    if (activeSourceMode === 'secondary') {
      setActiveSourceMode('primary')
      setSourceValue(String(content || editor?.getHTML() || ''))
      return
    }

    setSourceValue(String(content || editor?.getHTML() || ''))
    setActiveSourceMode('primary')
  }, [activeSourceMode, commitPrimarySourceToEditor, content, editor, pushHelperMessage])

  const handleToggleSecondarySourceMode = useCallback(() => {
    if (!usesSecondarySourceMode) {
      return
    }

    if (activeSourceMode === 'secondary') {
      setActiveSourceMode(null)
      pushHelperMessage('Returned to visual editor.')
      return
    }

    if (activeSourceMode === 'primary') {
      commitPrimarySourceToEditor()
    }

    setActiveSourceMode('secondary')
  }, [activeSourceMode, commitPrimarySourceToEditor, pushHelperMessage, usesSecondarySourceMode])

  const handleCloseSourceMode = useCallback(() => {
    if (activeSourceMode === 'primary') {
      commitPrimarySourceToEditor()
    }

    setActiveSourceMode(null)
    pushHelperMessage('Returned to visual editor.')
  }, [activeSourceMode, commitPrimarySourceToEditor, pushHelperMessage])

  const insertArtworkEmbed = useCallback((item) => {
    if (!editor || !item) return

    editor.chain().focus().insertContent({
      type: 'artworkEmbed',
      attrs: {
        title: item.title || 'Embedded artwork',
        url: item.url || '#',
        thumb: item.image || item.avatar || '',
      },
    }).run()
  }, [editor])

  const runArtworkSearch = useCallback(async () => {
    if (typeof searchEntities !== 'function') {
      return
    }

    setArtworkLoading(true)

    try {
      const items = await searchEntities('artwork', artworkQuery)
      setArtworkResults(Array.isArray(items) ? items : [])
    } finally {
      setArtworkLoading(false)
    }
  }, [artworkQuery, searchEntities])

  useEffect(() => {
    if (!artworkPickerOpen || typeof searchEntities !== 'function') {
      return
    }

    runArtworkSearch()
  }, [artworkPickerOpen, runArtworkSearch, searchEntities])

  const handleInsertArtwork = useCallback(() => {
    if (!editor) return

    if (typeof searchEntities === 'function') {
      setArtworkPickerOpen(true)
      return
    }

    const url = normalizeHttpUrl(window.prompt('Artwork URL', 'https://skinbase.org/art/') || '')
    if (!url) {
      pushHelperMessage('Artwork URL is required.')
      return
    }

    const title = window.prompt('Artwork title', 'Embedded artwork')
    if (title === null) return
    const thumb = normalizeHttpUrl(window.prompt('Artwork thumbnail URL (optional)', '') || '') || ''

    insertArtworkEmbed({
      title: title || 'Embedded artwork',
      url,
      image: thumb,
    })
  }, [editor, insertArtworkEmbed, pushHelperMessage, searchEntities])

  const handleInsertImage = useCallback(() => {
    setMediaDialogOpen(true)
    setMediaError('')
  }, [])

  const handleInsertTable = useCallback(() => {
    setTableInsertOpen(true)
  }, [])

  const handleTableInsert = useCallback(() => {
    if (!editor) return

    editor.chain().focus().insertTable({
      rows: Math.max(1, tableRows),
      cols: Math.max(1, tableCols),
      withHeaderRow: tableHeaderRow,
      withHeaderColumn: tableHeaderColumn,
    }).run()

    setTableInsertOpen(false)
  }, [editor, tableCols, tableHeaderColumn, tableHeaderRow, tableRows])

  const handleMediaDialogClose = useCallback(() => {
    const uploadedPath = mediaUploadedPath
    setMediaDialogOpen(false)
    resetMediaState()

    if (uploadedPath) {
      void deleteTemporaryMedia(uploadedPath).catch(() => {})
    }
  }, [deleteTemporaryMedia, mediaUploadedPath, resetMediaState])

  const handleMediaInsert = useCallback(() => {
    const normalizedUrl = normalizeHttpUrl(mediaUrlValue) || mediaUrlValue.trim()
    if (!normalizedUrl) {
      setMediaError('Choose or upload an image first.')
      return
    }

    if (!insertImageIntoEditor(normalizedUrl, mediaAltText.trim())) {
      setMediaError('Editor is not ready yet.')
      return
    }

    setMediaDialogOpen(false)
    resetMediaState()
    pushHelperMessage('Image inserted.')
  }, [insertImageIntoEditor, mediaAltText, mediaUrlValue, pushHelperMessage, resetMediaState])

  const handleMediaPickFile = useCallback(async (file) => {
    if (!file) return

    try {
      const payload = await uploadMediaFile(file, mediaUploadedPath)
      if (!payload?.url) {
        throw new Error('Image upload failed.')
      }

      setMediaUploadedPath(payload?.path || '')
      setMediaPreviewUrl(payload?.url || '')
      setMediaUrlValue(payload?.url || '')
      setMediaError('')
    } catch (uploadError) {
      setMediaError(uploadError?.message || 'Image upload failed.')
    }
  }, [mediaUploadedPath, uploadMediaFile])

  const handleMediaClear = useCallback(() => {
    const uploadedPath = mediaUploadedPath
    resetMediaState()

    if (uploadedPath) {
      void deleteTemporaryMedia(uploadedPath).catch((deleteError) => {
        setMediaError(deleteError?.message || 'Could not remove uploaded media.')
      })
    }
  }, [deleteTemporaryMedia, mediaUploadedPath, resetMediaState])

  const handleInsertComparison = useCallback(() => {
    setCompareDialogOpen(true)
    setCompareError('')
  }, [])

  const resetCompareState = useCallback(() => {
    setCompareUploading(false)
    setCompareError('')
    setCompareSubtitle('')
    setCompareLeftImage({ previewUrl: '', altText: '', uploadedPath: '' })
    setCompareRightImage({ previewUrl: '', altText: '', uploadedPath: '' })
  }, [])

  const handleComparisonDialogClose = useCallback(() => {
    const uploadedPaths = [compareLeftImage.uploadedPath, compareRightImage.uploadedPath].filter(Boolean)
    setCompareDialogOpen(false)
    resetCompareState()

    uploadedPaths.forEach((path) => {
      void deleteTemporaryMedia(path).catch(() => {})
    })
  }, [compareLeftImage.uploadedPath, compareRightImage.uploadedPath, deleteTemporaryMedia, resetCompareState])

  const handleComparisonSidePick = useCallback(async (side, file) => {
    if (!file) return

    if (!mediaSupport?.uploadUrl) {
      setCompareError('Image uploads are not configured for this editor.')
      return
    }

    const currentImage = side === 'left' ? compareLeftImage : compareRightImage

    try {
      setCompareUploading(true)
      setCompareError('')

      const payload = await uploadMediaFile(file, currentImage.uploadedPath)
      if (!payload?.url) {
        throw new Error('Image upload failed.')
      }

      const nextImage = {
        previewUrl: payload.url || '',
        altText: currentImage.altText || file.name?.replace(/\.[^.]+$/, '') || '',
        uploadedPath: payload.path || '',
      }

      if (side === 'left') {
        setCompareLeftImage(nextImage)
      } else {
        setCompareRightImage(nextImage)
      }
    } catch (uploadError) {
      setCompareError(uploadError?.message || 'Image upload failed.')
    } finally {
      setCompareUploading(false)
    }
  }, [compareLeftImage, compareRightImage, mediaSupport, uploadMediaFile])

  const handleComparisonSideClear = useCallback((side) => {
    const currentImage = side === 'left' ? compareLeftImage : compareRightImage
    if (currentImage.uploadedPath) {
      void deleteTemporaryMedia(currentImage.uploadedPath).catch(() => {})
    }

    const nextImage = { previewUrl: '', altText: '', uploadedPath: '' }
    if (side === 'left') {
      setCompareLeftImage(nextImage)
    } else {
      setCompareRightImage(nextImage)
    }
  }, [compareLeftImage, compareRightImage, deleteTemporaryMedia])

  const handleComparisonInsert = useCallback(() => {
    if (!editor) return

    if (!compareLeftImage.previewUrl || !compareRightImage.previewUrl) {
      setCompareError('Upload both images before inserting the comparison.')
      return
    }

    editor.chain().focus().insertContent({
      type: 'imageCompare',
      attrs: {
        leftSrc: compareLeftImage.previewUrl,
        leftAlt: compareLeftImage.altText.trim(),
        rightSrc: compareRightImage.previewUrl,
        rightAlt: compareRightImage.altText.trim(),
        subtitle: compareSubtitle.trim(),
      },
    }).run()

    setCompareDialogOpen(false)
    resetCompareState()
    pushHelperMessage('Comparison inserted.')
  }, [compareLeftImage, compareRightImage, compareSubtitle, editor, pushHelperMessage, resetCompareState])

  const handleInsertSocialEmbed = useCallback(() => {
    if (!editor) return

    const detected = detectSocialPlatform(window.prompt('Social post URL', 'https://') || '')
    if (!detected.url) {
      pushHelperMessage('Social post URL is required.')
      return
    }

    const label = window.prompt('Label (optional)', detected.label)
    if (label === null) return

    editor.chain().focus().insertContent({
      type: 'socialEmbed',
      attrs: {
        url: detected.url,
        platform: detected.platform,
        label: label || detected.label,
      },
    }).run()
  }, [editor, pushHelperMessage])

  const handleInsertVideoEmbed = useCallback(() => {
    if (!editor) return

    const rawUrl = window.prompt('YouTube URL', 'https://www.youtube.com/watch?v=') || ''
    const embedUrl = normalizeVideoEmbedUrl(rawUrl)
    const sourceUrl = normalizeHttpUrl(rawUrl)

    if (!embedUrl || !sourceUrl) {
      pushHelperMessage('A valid YouTube URL is required.')
      return
    }

    const title = window.prompt('Video title (optional)', 'Embedded YouTube video')
    if (title === null) return

    editor.chain().focus().insertContent({
      type: 'videoEmbed',
      attrs: {
        src: embedUrl,
        url: sourceUrl,
        title: title.trim() || 'Embedded video',
      },
    }).run()
  }, [editor, pushHelperMessage])

  const handleInsertHashtag = useCallback(() => {
    if (!editor) return

    const value = String(window.prompt('Hashtag', 'release') || '').trim().replace(/^#+/, '').replace(/\s+/g, '-')
    if (!value) return

    editor.chain().focus().insertContent(`#${value}`).run()
  }, [editor])

  const shellClassName = fullHeightMode
    ? 'fixed inset-0 z-[980] flex min-h-0 w-screen flex-col bg-[#04070df2] p-3 backdrop-blur-sm md:p-4'
    : 'flex w-full min-w-0 flex-col gap-1.5'

  const bodyClassName = fullHeightMode
    ? 'flex min-h-0 w-full flex-1 flex-col gap-1.5'
    : 'flex w-full min-w-0 flex-col gap-1.5'

  const editorCardClassName = [
    'news-rich-text-editor w-full min-w-0 overflow-hidden rounded-xl border bg-white/[0.04] transition-colors',
    fullHeightMode ? 'flex min-h-0 flex-1 flex-col rounded-2xl' : '',
    error
      ? 'border-red-500/60 focus-within:border-red-500/70 focus-within:ring-2 focus-within:ring-red-500/30'
      : 'border-white/12 hover:border-white/20 focus-within:border-sky-500/50 focus-within:ring-2 focus-within:ring-sky-500/20',
  ].filter(Boolean).join(' ')

  const editorViewportStyle = fullHeightMode
    ? { flex: 1 }
    : { height: `${editorViewportHeight}rem` }

  const sourceTextareaStyle = fullHeightMode
    ? { flex: 1 }
    : {
        height: `${Math.max(minHeight, editorViewportHeight)}rem`,
        minHeight: `${Math.max(minHeight, 20)}rem`,
      }

  return (
    <div className={shellClassName}>
      <div className={bodyClassName}>
      <div
        className={editorCardClassName}
      >
        <Toolbar
          editor={editor}
          advancedNews={advancedNews}
          activeSourceMode={activeSourceMode}
          sourceModeLabel={sourceModeLabel}
          sourceModeTitle={sourceModeTitle}
          secondarySourceModeLabel={secondarySourceModeLabel}
          secondarySourceModeTitle={secondarySourceModeTitle}
          showStructureOutlines={showStructureOutlines}
          showComparisonTool={Boolean(mediaSupport?.uploadUrl)}
          fullHeightMode={fullHeightMode}
          onToggleSourceMode={handleToggleSourceMode}
          onToggleSecondarySourceMode={handleToggleSecondarySourceMode}
          onToggleStructureOutlines={() => setShowStructureOutlines((current) => !current)}
          onInsertArtwork={handleInsertArtwork}
          onInsertImage={handleInsertImage}
          onInsertComparison={handleInsertComparison}
          onInsertTable={handleInsertTable}
          onInsertSocialEmbed={handleInsertSocialEmbed}
          onInsertVideoEmbed={handleInsertVideoEmbed}
          onInsertHashtag={handleInsertHashtag}
          editorViewportHeight={editorViewportHeight}
          onIncreaseEditorViewportHeight={increaseEditorViewportHeight}
          onDecreaseEditorViewportHeight={decreaseEditorViewportHeight}
          onToggleFullHeightMode={toggleFullHeightMode}
        />

        {advancedNews && activeSourceMode ? (
          <div className={[
            'border-t border-white/[0.04] bg-black/10 px-4 py-3',
            fullHeightMode ? 'flex min-h-0 flex-1 flex-col' : '',
          ].filter(Boolean).join(' ')}>
            <div className="mb-2 flex items-center justify-between gap-3 text-xs text-slate-400">
              <span>{activeSourceMode === 'secondary' ? secondarySourceModeDescription : sourceModeDescription}</span>
              <button type="button" onClick={handleCloseSourceMode} className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 font-semibold text-white transition hover:bg-white/[0.08]">
                Back to visual
              </button>
            </div>
            <textarea
              value={activeSourceMode === 'secondary' ? String(secondarySourceModeValue || '') : sourceValue}
              onChange={(event) => {
                const nextValue = event.target.value
                if (activeSourceMode === 'secondary') {
                  onSecondarySourceModeValueChange?.(nextValue)
                  return
                }

                setSourceValue(nextValue)
                onChange?.(nextValue)
              }}
              spellCheck={false}
              className="nova-scrollbar w-full rounded-xl border border-white/10 bg-slate-950/85 px-4 py-3 font-mono text-sm leading-6 text-slate-100 outline-none"
              style={sourceTextareaStyle}
            />
          </div>
        ) : (
          <div className={[
            'rich-text-editor-viewport nova-scrollbar w-full min-w-0 border-t border-white/[0.04] bg-black/15',
            fullHeightMode ? 'flex min-h-0 flex-1 flex-col' : '',
            advancedNews && showStructureOutlines ? 'news-editor-outline' : '',
          ].filter(Boolean).join(' ')} style={editorViewportStyle}>
            <EditorContent editor={editor} />
          </div>
        )}

        {!fullHeightMode ? (
          <button
            type="button"
            aria-label="Resize editor height"
            title="Drag to resize editor height"
            onPointerDown={startViewportResize}
            className="group flex h-5 w-full cursor-row-resize items-center justify-center border-t border-white/[0.04] bg-black/10 text-slate-500 transition hover:bg-white/[0.03] hover:text-slate-300"
          >
            <span className="h-1 w-16 rounded-full bg-current opacity-70 transition group-hover:opacity-100" />
          </button>
        ) : null}
      </div>

      {advancedNews && helperMessage ? (
        <p className="text-xs text-sky-300">{helperMessage}</p>
      ) : null}

        {!activeSourceMode ? (
          <RichTableControls editor={editor} />
        ) : null}

      {error ? (
        <p role="alert" className="text-xs text-red-400">{error}</p>
      ) : null}

      <ArtworkPickerDialog
        open={artworkPickerOpen}
        query={artworkQuery}
        items={artworkResults}
        loading={artworkLoading}
        onQueryChange={setArtworkQuery}
        onClose={() => setArtworkPickerOpen(false)}
        onSearch={runArtworkSearch}
        onSelect={(item) => {
          insertArtworkEmbed(item)
          setArtworkPickerOpen(false)
          pushHelperMessage(`Embedded artwork: ${item.title || 'Artwork'}.`)
        }}
      />

      <MediaImageDialog
        open={mediaDialogOpen}
        uploading={mediaUploading}
        error={mediaError}
        previewUrl={mediaPreviewUrl || normalizeHttpUrl(mediaUrlValue) || mediaUrlValue}
        imageUrl={mediaUrlValue}
        altText={mediaAltText}
        uploadLabel="Drop image here or browse"
        helperText="Upload media directly to lesson storage or paste a CDN URL when you already have one."
        allowUpload={Boolean(mediaSupport?.uploadUrl)}
        onClose={handleMediaDialogClose}
        onImageUrlChange={(nextValue) => {
          setMediaUrlValue(nextValue)
          setMediaPreviewUrl(normalizeHttpUrl(nextValue) || nextValue)
          setMediaError('')
        }}
        onAltTextChange={setMediaAltText}
        onPickFile={handleMediaPickFile}
        onBrowseAssets={mediaSupport?.assetsUrl ? () => openAcademyAssets({ type: 'media' }) : null}
        onInsert={handleMediaInsert}
        onClearUploaded={handleMediaClear}
      />

      <CompareImageDialog
        open={compareDialogOpen}
        uploading={compareUploading}
        error={compareError}
        subtitle={compareSubtitle}
        leftImage={compareLeftImage}
        rightImage={compareRightImage}
        allowUpload={Boolean(mediaSupport?.uploadUrl)}
        onClose={handleComparisonDialogClose}
        onSubtitleChange={setCompareSubtitle}
        onLeftAltTextChange={(nextValue) => setCompareLeftImage((current) => ({ ...current, altText: nextValue }))}
        onRightAltTextChange={(nextValue) => setCompareRightImage((current) => ({ ...current, altText: nextValue }))}
        onLeftPickFile={(file) => handleComparisonSidePick('left', file)}
        onRightPickFile={(file) => handleComparisonSidePick('right', file)}
        onLeftBrowseAssets={mediaSupport?.assetsUrl ? () => openAcademyAssets({ type: 'compare-left' }) : null}
        onRightBrowseAssets={mediaSupport?.assetsUrl ? () => openAcademyAssets({ type: 'compare-right' }) : null}
        onLeftClear={() => handleComparisonSideClear('left')}
        onRightClear={() => handleComparisonSideClear('right')}
        onInsert={handleComparisonInsert}
      />

      <AssetPickerDialog
        open={academyAssetsOpen}
        searchQuery={academyAssetsQuery}
        assets={academyAssets}
        loading={academyAssetsLoading}
        error={academyAssetsError}
        pagination={academyAssetsPagination}
        onClose={closeAcademyAssets}
        onRefresh={() => loadAcademyAssets({ page: academyAssetsPage, query: academyAssetsSearch })}
        onSearchQueryChange={setAcademyAssetsQuery}
        onSearch={submitAcademyAssetSearch}
        onPreviousPage={goToPreviousAcademyAssetsPage}
        onNextPage={goToNextAcademyAssetsPage}
        onSelect={chooseAcademyAsset}
      />

      <TableInsertDialog
        open={tableInsertOpen}
        rows={tableRows}
        cols={tableCols}
        withHeaderRow={tableHeaderRow}
        withHeaderColumn={tableHeaderColumn}
        onRowsChange={setTableRows}
        onColsChange={setTableCols}
        onHeaderRowChange={setTableHeaderRow}
        onHeaderColumnChange={setTableHeaderColumn}
        onClose={() => setTableInsertOpen(false)}
        onInsert={handleTableInsert}
      />
      </div>
    </div>
  )
}
