// @ts-nocheck
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { EditorContent, Extension, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
import Image from '@tiptap/extension-image';
import Link from '@tiptap/extension-link';
import Placeholder from '@tiptap/extension-placeholder';
import Underline from '@tiptap/extension-underline';
import Suggestion from '@tiptap/suggestion';
import { Node, mergeAttributes } from '@tiptap/core';
import { common, createLowlight } from 'lowlight';
import tippy from 'tippy.js';
import { buildBotFingerprint } from '../../lib/security/botFingerprint';
import TurnstileField from '../security/TurnstileField';
import NovaSelect from '../ui/NovaSelect';

type StoryType = {
  slug: string;
  name: string;
};

type Artwork = {
  id: number;
  title: string;
  url: string;
  thumb: string | null;
  thumbs?: {
    xs?: string | null;
    sm?: string | null;
    md?: string | null;
    lg?: string | null;
    xl?: string | null;
  };
};

type StoryPayload = {
  id?: number;
  title: string;
  excerpt: string;
  cover_image: string;
  story_type: string;
  tags_csv: string;
  meta_title: string;
  meta_description: string;
  canonical_url: string;
  og_image: string;
  status: string;
  scheduled_for: string;
  content: Record<string, unknown>;
};

type Endpoints = {
  create: string;
  update: string;
  autosave: string;
  uploadImage: string;
  artworks: string;
  previewBase: string;
  analyticsBase: string;
};

type Props = {
  mode: 'create' | 'edit';
  initialStory: StoryPayload;
  storyTypes: StoryType[];
  endpoints: Endpoints;
  csrfToken: string;
};

const EMPTY_DOC = {
  type: 'doc',
  content: [{ type: 'paragraph' }],
};

const lowlight = createLowlight(common);

const CODE_BLOCK_LANGUAGES = [
  { value: 'bash', label: 'Bash / Shell' },
  { value: 'plaintext', label: 'Plain text' },
  { value: 'php', label: 'PHP' },
  { value: 'javascript', label: 'JavaScript' },
  { value: 'typescript', label: 'TypeScript' },
  { value: 'json', label: 'JSON' },
  { value: 'html', label: 'HTML' },
  { value: 'css', label: 'CSS' },
  { value: 'sql', label: 'SQL' },
  { value: 'xml', label: 'XML / SVG' },
  { value: 'yaml', label: 'YAML' },
  { value: 'markdown', label: 'Markdown' },
];

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

  addAttributes() {
    return {
      artworkId: { default: null },
      title: { default: '' },
      url: { default: '' },
      thumb: { default: '' },
    };
  },

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

  renderHTML({ HTMLAttributes }) {
    return [
      'figure',
      mergeAttributes(HTMLAttributes, {
        'data-artwork-embed': 'true',
        class: 'my-4 overflow-hidden rounded-xl border border-gray-700 bg-gray-800/70',
      }),
      [
        'a',
        {
          href: HTMLAttributes.url || '#',
          class: 'block',
          rel: 'noopener noreferrer nofollow',
          target: '_blank',
        },
        [
          'img',
          {
            src: HTMLAttributes.thumb || '',
            alt: HTMLAttributes.title || 'Artwork',
            class: 'h-48 w-full object-cover',
            loading: 'lazy',
          },
        ],
        [
          'figcaption',
          { class: 'p-3 text-sm text-gray-200' },
          `${HTMLAttributes.title || 'Artwork'} (#${HTMLAttributes.artworkId || 'n/a'})`,
        ],
      ],
    ];
  },
});

const GalleryBlock = Node.create({
  name: 'galleryBlock',
  group: 'block',
  atom: true,

  addAttributes() {
    return {
      images: { default: [] },
    };
  },

  parseHTML() {
    return [{ tag: 'div[data-gallery-block]' }];
  },

  renderHTML({ HTMLAttributes }) {
    const images = Array.isArray(HTMLAttributes.images) ? HTMLAttributes.images : [];
    const children: Array<unknown> = images.slice(0, 6).map((src: string) => [
      'img',
      { src, class: 'h-36 w-full rounded-lg object-cover', loading: 'lazy', alt: 'Gallery image' },
    ]);

    if (children.length === 0) {
      children.push(['div', { class: 'rounded-lg border border-dashed border-gray-600 p-4 text-xs text-gray-400' }, 'Empty gallery block']);
    }

    return [
      'div',
      mergeAttributes(HTMLAttributes, {
        'data-gallery-block': 'true',
        class: 'my-4 grid grid-cols-2 gap-3 rounded-xl border border-gray-700 bg-gray-800/50 p-3',
      }),
      ...children,
    ];
  },
});

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

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

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

  renderHTML({ HTMLAttributes }) {
    return [
      'figure',
      mergeAttributes(HTMLAttributes, {
        'data-video-embed': 'true',
        class: 'my-4 overflow-hidden rounded-xl border border-gray-700 bg-gray-800/60',
      }),
      [
        'iframe',
        {
          src: HTMLAttributes.src || '',
          title: HTMLAttributes.title || 'Embedded video',
          class: 'aspect-video w-full',
          allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share',
          allowfullscreen: 'true',
          frameborder: '0',
          referrerpolicy: 'strict-origin-when-cross-origin',
        },
      ],
    ];
  },
});

const DownloadAssetBlock = Node.create({
  name: 'downloadAsset',
  group: 'block',
  atom: true,

  addAttributes() {
    return {
      url: { default: '' },
      label: { default: 'Download asset' },
    };
  },

  parseHTML() {
    return [{ tag: 'div[data-download-asset]' }];
  },

  renderHTML({ HTMLAttributes }) {
    return [
      'div',
      mergeAttributes(HTMLAttributes, {
        'data-download-asset': 'true',
        class: 'my-4 rounded-xl border border-gray-700 bg-gray-800/60 p-4',
      }),
      [
        'a',
        {
          href: HTMLAttributes.url || '#',
          class: 'inline-flex items-center rounded-lg border border-sky-500/40 bg-sky-500/10 px-3 py-2 text-sm text-sky-200',
          target: '_blank',
          rel: 'noopener noreferrer nofollow',
          download: 'true',
        },
        HTMLAttributes.label || 'Download asset',
      ],
    ];
  },
});

function createSlashCommandExtension(insert: {
  image: () => void;
  uploadImage: () => void;
  artwork: () => void;
  code: () => void;
  quote: () => void;
  divider: () => void;
  gallery: () => void;
  video: () => void;
  download: () => void;
}) {
  return Extension.create({
    name: 'slashCommands',

    addOptions() {
      return {
        suggestion: {
          char: '/',
          startOfLine: true,
          items: ({ query }: { query: string }) => {
            const all = [
              { title: 'Upload Image', key: 'uploadImage' },
              { title: 'Image', key: 'image' },
              { title: 'Artwork', key: 'artwork' },
              { title: 'Code', key: 'code' },
              { title: 'Quote', key: 'quote' },
              { title: 'Divider', key: 'divider' },
              { title: 'Gallery', key: 'gallery' },
              { title: 'Video', key: 'video' },
              { title: 'Download', key: 'download' },
            ];
            return all.filter((item) => item.key.startsWith(query.toLowerCase()));
          },
          command: ({ props }: { editor: any; props: { key: string } }) => {
            if (props.key === 'uploadImage') insert.uploadImage();
            if (props.key === 'image') insert.image();
            if (props.key === 'artwork') insert.artwork();
            if (props.key === 'code') insert.code();
            if (props.key === 'quote') insert.quote();
            if (props.key === 'divider') insert.divider();
            if (props.key === 'gallery') insert.gallery();
            if (props.key === 'video') insert.video();
            if (props.key === 'download') insert.download();
          },
          render: () => {
            let popup: any;
            let root: HTMLDivElement | null = null;
            let selected = 0;
            let items: Array<{ title: string; key: string }> = [];
            let command: ((item: { title: string; key: string }) => void) | null = null;

            const draw = () => {
              if (!root) return;
              root.innerHTML = items
                .map((item, index) => {
                  const active = index === selected ? 'bg-sky-500/20 text-sky-200' : 'text-gray-200';
                  return `<button data-index="${index}" class="block w-full rounded-md px-3 py-2 text-left text-sm ${active}">/${item.key} <span class="text-gray-400">${item.title}</span></button>`;
                })
                .join('');

              root.querySelectorAll('button').forEach((button) => {
                button.addEventListener('mousedown', (event) => {
                  event.preventDefault();
                  const idx = Number((event.currentTarget as HTMLButtonElement).dataset.index || 0);
                  const choice = items[idx];
                  if (choice && command) command(choice);
                });
              });
            };

            return {
              onStart: (props: any) => {
                items = props.items;
                command = props.command;
                selected = 0;
                root = document.createElement('div');
                root.className = 'w-52 rounded-lg border border-gray-700 bg-gray-900 p-1 shadow-xl';
                draw();

                popup = tippy('body', {
                  getReferenceClientRect: props.clientRect,
                  appendTo: () => document.body,
                  content: root,
                  showOnCreate: true,
                  interactive: true,
                  trigger: 'manual',
                  placement: 'bottom-start',
                });
              },
              onUpdate: (props: any) => {
                items = props.items;
                command = props.command;
                if (selected >= items.length) selected = 0;
                draw();
                popup?.[0]?.setProps({ getReferenceClientRect: props.clientRect });
              },
              onKeyDown: (props: any) => {
                if (props.event.key === 'ArrowDown') {
                  selected = (selected + 1) % Math.max(items.length, 1);
                  draw();
                  return true;
                }
                if (props.event.key === 'ArrowUp') {
                  selected = (selected + Math.max(items.length, 1) - 1) % Math.max(items.length, 1);
                  draw();
                  return true;
                }
                if (props.event.key === 'Enter') {
                  const choice = items[selected];
                  if (choice && command) command(choice);
                  return true;
                }
                if (props.event.key === 'Escape') {
                  popup?.[0]?.hide();
                  return true;
                }
                return false;
              },
              onExit: () => {
                popup?.[0]?.destroy();
                popup = null;
                root = null;
              },
            };
          },
        },
      };
    },

    addProseMirrorPlugins() {
      return [
        Suggestion({
          editor: this.editor,
          ...this.options.suggestion,
        }),
      ];
    },
  });
}

async function botHeaders(extra: Record<string, string> = {}, captcha: { token?: string } = {}) {
  const fingerprint = await buildBotFingerprint();

  return {
    ...extra,
    'X-Bot-Fingerprint': fingerprint,
    ...(captcha?.token ? { 'X-Captcha-Token': captcha.token } : {}),
  };
}

async function requestJson<T>(url: string, method: string, body: unknown, csrfToken: string, captcha: { token?: string } = {}): Promise<T> {
  const response = await fetch(url, {
    method,
    headers: await botHeaders({
      'Content-Type': 'application/json',
      'X-CSRF-TOKEN': csrfToken,
      'X-Requested-With': 'XMLHttpRequest',
    }, captcha),
    body: JSON.stringify(body),
  });

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

  if (!response.ok) {
    const error = new Error((payload as any)?.message || `Request failed: ${response.status}`) as Error & { status?: number; payload?: unknown };
    error.status = response.status;
    error.payload = payload;
    throw error;
  }

  return payload as T;
}

export default function StoryEditor({ mode, initialStory, storyTypes, endpoints, csrfToken }: Props) {
  const [storyId, setStoryId] = useState<number | undefined>(initialStory.id);
  const [title, setTitle] = useState(initialStory.title || '');
  const [excerpt, setExcerpt] = useState(initialStory.excerpt || '');
  const [coverImage, setCoverImage] = useState(initialStory.cover_image || '');
  const [storyType, setStoryType] = useState(initialStory.story_type || 'creator_story');
  const [tagsCsv, setTagsCsv] = useState(initialStory.tags_csv || '');
  const [metaTitle, setMetaTitle] = useState(initialStory.meta_title || '');
  const [metaDescription, setMetaDescription] = useState(initialStory.meta_description || '');
  const [canonicalUrl, setCanonicalUrl] = useState(initialStory.canonical_url || '');
  const [ogImage, setOgImage] = useState(initialStory.og_image || '');
  const [status, setStatus] = useState(initialStory.status || 'draft');
  const [scheduledFor, setScheduledFor] = useState(initialStory.scheduled_for || '');
  const [saveStatus, setSaveStatus] = useState('Autosave idle');
  const [artworkModalOpen, setArtworkModalOpen] = useState(false);
  const [artworkResults, setArtworkResults] = useState<Artwork[]>([]);
  const [artworkQuery, setArtworkQuery] = useState('');
  const [inlineToolbar, setInlineToolbar] = useState({ visible: false, top: 0, left: 0 });
  const [fieldErrors, setFieldErrors] = useState<Record<string, string[]>>({});
  const [generalError, setGeneralError] = useState('');
  const [wordCount, setWordCount] = useState(0);
  const [readMinutes, setReadMinutes] = useState(1);
  const [codeBlockLanguage, setCodeBlockLanguage] = useState('bash');
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [plusMenuOpen, setPlusMenuOpen] = useState(false);
  const [plusButtonState, setPlusButtonState] = useState({ visible: false, top: 0, left: 0 });
  const editorContainerRef = useRef<HTMLDivElement | null>(null);
  const [captchaState, setCaptchaState] = useState({
    required: false,
    token: '',
    message: '',
    nonce: 0,
    provider: 'turnstile',
    siteKey: '',
    inputName: 'cf-turnstile-response',
    scriptUrl: '',
  });
  const lastSavedRef = useRef('');
  const editorRef = useRef<any>(null);
  const bodyImageInputRef = useRef<HTMLInputElement | null>(null);
  const coverImageInputRef = useRef<HTMLInputElement | null>(null);

  const emitSaveEvent = useCallback((kind: 'autosave' | 'manual', id?: number) => {
    window.dispatchEvent(new CustomEvent('story-editor:saved', {
      detail: {
        kind,
        storyId: id,
        savedAt: new Date().toISOString(),
      },
    }));
  }, []);

  const resetCaptchaState = useCallback(() => {
    setCaptchaState((prev) => ({
      ...prev,
      required: false,
      token: '',
      message: '',
      nonce: prev.nonce + 1,
    }));
  }, []);

  const captureCaptchaRequirement = useCallback((payload: any = {}) => {
    const requiresCaptcha = !!(payload?.requires_captcha || payload?.requiresCaptcha);
    if (!requiresCaptcha) {
      return false;
    }

    const nextCaptcha = payload?.captcha || {};
    const message = payload?.errors?.captcha?.[0] || payload?.message || 'Complete the captcha challenge to continue.';

    setCaptchaState((prev) => ({
      required: true,
      token: '',
      message,
      nonce: prev.nonce + 1,
      provider: nextCaptcha.provider || payload?.captcha_provider || prev.provider || 'turnstile',
      siteKey: nextCaptcha.siteKey || payload?.captcha_site_key || prev.siteKey || '',
      inputName: nextCaptcha.inputName || payload?.captcha_input || prev.inputName || 'cf-turnstile-response',
      scriptUrl: nextCaptcha.scriptUrl || payload?.captcha_script_url || prev.scriptUrl || '',
    }));

    return true;
  }, []);

  const applyFailure = useCallback((error: any, fallback: string) => {
    const payload = error?.payload || {};
    const nextErrors = payload?.errors && typeof payload.errors === 'object' ? payload.errors : {};
    setFieldErrors(nextErrors);
    const requiresCaptcha = captureCaptchaRequirement(payload);
    const message = nextErrors?.captcha?.[0]
      || nextErrors?.title?.[0]
      || nextErrors?.content?.[0]
      || payload?.message
      || fallback;
    setGeneralError(message);
    setSaveStatus(requiresCaptcha ? 'Captcha required' : message);
  }, [captureCaptchaRequirement]);

  const clearFeedback = useCallback(() => {
    setGeneralError('');
    setFieldErrors({});
  }, []);

  const openLinkPrompt = useCallback((editor: any) => {
    const prev = editor.getAttributes('link').href;
    const url = window.prompt('Link URL', prev || 'https://');
    if (url === null) return;
    if (url.trim() === '') {
      editor.chain().focus().unsetLink().run();
      return;
    }
    editor.chain().focus().setLink({ href: url.trim() }).run();
  }, []);

  const fetchArtworks = useCallback(async (query: string) => {
    const q = encodeURIComponent(query);
    const response = await fetch(`${endpoints.artworks}?q=${q}`, {
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
      },
    });
    if (!response.ok) return;
    const data = await response.json();
    setArtworkResults(Array.isArray(data.artworks) ? data.artworks : []);
  }, [endpoints.artworks]);

  const uploadImageFile = useCallback(async (file: File): Promise<string | null> => {
    const formData = new FormData();
    formData.append('image', file);

    const response = await fetch(endpoints.uploadImage, {
      method: 'POST',
      headers: await botHeaders({
        'X-CSRF-TOKEN': csrfToken,
        'X-Requested-With': 'XMLHttpRequest',
      }, captchaState),
      body: formData,
    });

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

    if (!response.ok) {
      setFieldErrors(payload?.errors && typeof payload.errors === 'object' ? payload.errors : {});
      captureCaptchaRequirement(payload);
      setGeneralError(payload?.errors?.captcha?.[0] || payload?.message || 'Image upload failed');
      return null;
    }

    clearFeedback();
    if (captchaState.required && captchaState.token) {
      resetCaptchaState();
    }

    const data = payload;
    return data.medium_url || data.original_url || data.thumbnail_url || null;
  }, [captchaState, captureCaptchaRequirement, clearFeedback, endpoints.uploadImage, csrfToken, resetCaptchaState]);

  const applyCodeBlockLanguage = useCallback((language: string) => {
    const nextLanguage = (language || 'plaintext').trim() || 'plaintext';
    setCodeBlockLanguage(nextLanguage);

    const currentEditor = editorRef.current;
    if (!currentEditor || !currentEditor.isActive('codeBlock')) {
      return;
    }

    currentEditor.chain().focus().updateAttributes('codeBlock', { language: nextLanguage }).run();
  }, []);

  const toggleCodeBlockWithLanguage = useCallback(() => {
    const currentEditor = editorRef.current;
    if (!currentEditor) return;

    if (currentEditor.isActive('codeBlock')) {
      currentEditor.chain().focus().toggleCodeBlock().run();
      return;
    }

    currentEditor.chain().focus().setCodeBlock({ language: codeBlockLanguage }).run();
  }, [codeBlockLanguage]);

  const insertActions = useMemo(() => ({
    image: () => {
      const currentEditor = editorRef.current;
      const url = window.prompt('Image URL', 'https://');
      if (!url || !currentEditor) return;
      currentEditor.chain().focus().setImage({ src: url }).run();
    },
    uploadImage: () => bodyImageInputRef.current?.click(),
    artwork: () => setArtworkModalOpen(true),
    code: () => {
      toggleCodeBlockWithLanguage();
    },
    quote: () => {
      const currentEditor = editorRef.current;
      if (!currentEditor) return;
      currentEditor.chain().focus().toggleBlockquote().run();
    },
    divider: () => {
      const currentEditor = editorRef.current;
      if (!currentEditor) return;
      currentEditor.chain().focus().setHorizontalRule().run();
    },
    gallery: () => {
      const currentEditor = editorRef.current;
      if (!currentEditor) return;
      const raw = window.prompt('Gallery image URLs (comma separated)', '');
      const images = (raw || '').split(',').map((value) => value.trim()).filter(Boolean);
      currentEditor.chain().focus().insertContent({ type: 'galleryBlock', attrs: { images } }).run();
    },
    video: () => {
      const currentEditor = editorRef.current;
      if (!currentEditor) return;
      const src = window.prompt('Video embed URL (YouTube/Vimeo)', 'https://www.youtube.com/embed/');
      if (!src) return;
      currentEditor.chain().focus().insertContent({ type: 'videoEmbed', attrs: { src, title: 'Embedded video' } }).run();
    },
    download: () => {
      const currentEditor = editorRef.current;
      if (!currentEditor) return;
      const url = window.prompt('Download URL', 'https://');
      if (!url) return;
      const label = window.prompt('Button label', 'Download asset') || 'Download asset';
      currentEditor.chain().focus().insertContent({ type: 'downloadAsset', attrs: { url, label } }).run();
    },
  }), [toggleCodeBlockWithLanguage]);

  const editor = useEditor({
    extensions: [
      StarterKit.configure({
        codeBlock: false,
        link: false,
        underline: false,
        heading: { levels: [1, 2, 3] },
      }),
      CodeBlockLowlight.configure({
        lowlight,
      }),
      Underline,
      Image,
      Link.configure({
        openOnClick: false,
        HTMLAttributes: {
          class: 'text-sky-300 underline',
          rel: 'noopener noreferrer nofollow',
          target: '_blank',
        },
      }),
      Placeholder.configure({
        placeholder: 'Start writing your story...',
      }),
      ArtworkBlock,
      GalleryBlock,
      VideoEmbedBlock,
      DownloadAssetBlock,
      createSlashCommandExtension(insertActions),
    ],
    immediatelyRender: false,
    content: initialStory.content || EMPTY_DOC,
    editorProps: {
      attributes: {
        class: 'tiptap prose prose-lg prose-headings:text-white prose-headings:font-bold prose-headings:tracking-tight prose-p:text-white/90 prose-p:leading-[1.85] prose-strong:text-white prose-a:text-sky-400 prose-a:underline prose-blockquote:border-l-sky-400/60 prose-blockquote:text-white/65 prose-blockquote:italic prose-code:text-sky-300 prose-pre:bg-black/30 prose-pre:text-sky-100 max-w-none min-h-[32rem] bg-transparent px-0 py-0 text-white/90 focus:outline-none',
      },
      handleDrop: (_view, event) => {
        const file = event.dataTransfer?.files?.[0];
        if (!file || !file.type.startsWith('image/')) return false;

        void (async () => {
          setSaveStatus('Uploading image...');
          const uploaded = await uploadImageFile(file);
          if (uploaded && editor) {
            editor.chain().focus().setImage({ src: uploaded }).run();
            setSaveStatus('Image uploaded');
          } else {
            setSaveStatus('Image upload failed');
          }
        })();

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

        void (async () => {
          setSaveStatus('Uploading image...');
          const uploaded = await uploadImageFile(file);
          if (uploaded && editor) {
            editor.chain().focus().setImage({ src: uploaded }).run();
            setSaveStatus('Image uploaded');
          } else {
            setSaveStatus('Image upload failed');
          }
        })();

        return true;
      },
    },
  });

  editorRef.current = editor;

  useEffect(() => {
    if (!editor) return;

    const updatePreview = () => {
      const text = editor.getText().replace(/\s+/g, ' ').trim();
      const words = text === '' ? 0 : text.split(' ').length;
      setWordCount(words);
      setReadMinutes(Math.max(1, Math.ceil(words / 200)));
    };

    updatePreview();
    editor.on('update', updatePreview);

    return () => {
      editor.off('update', updatePreview);
    };
  }, [editor]);

  useEffect(() => {
    if (!editor) return;

    const syncCodeBlockLanguage = () => {
      if (!editor.isActive('codeBlock')) {
        return;
      }

      const nextLanguage = String(editor.getAttributes('codeBlock').language || '').trim();
      if (nextLanguage !== '') {
        setCodeBlockLanguage(nextLanguage);
      }
    };

    syncCodeBlockLanguage();
    editor.on('selectionUpdate', syncCodeBlockLanguage);
    editor.on('update', syncCodeBlockLanguage);

    return () => {
      editor.off('selectionUpdate', syncCodeBlockLanguage);
      editor.off('update', syncCodeBlockLanguage);
    };
  }, [editor]);

  useEffect(() => {
    if (!artworkModalOpen) return;
    void fetchArtworks(artworkQuery);
  }, [artworkModalOpen, artworkQuery, fetchArtworks]);

  useEffect(() => {
    if (!editor) return;

    const updateToolbar = () => {
      const { from, to } = editor.state.selection;
      if (from === to) {
        setInlineToolbar({ visible: false, top: 0, left: 0 });
        return;
      }

      const start = editor.view.coordsAtPos(from);
      const end = editor.view.coordsAtPos(to);
      setInlineToolbar({
        visible: true,
        top: Math.max(10, start.top + window.scrollY - 48),
        left: Math.max(10, (start.left + end.right) / 2 + window.scrollX - 120),
      });
    };

    editor.on('selectionUpdate', updateToolbar);
    editor.on('blur', () => setInlineToolbar({ visible: false, top: 0, left: 0 }));

    return () => {
      editor.off('selectionUpdate', updateToolbar);
    };
  }, [editor]);

  useEffect(() => {
    if (!editor) return;

    const updatePlusButton = () => {
      const { from, to } = editor.state.selection;
      if (from !== to) {
        setPlusButtonState({ visible: false, top: 0, left: 0 });
        setPlusMenuOpen(false);
        return;
      }
      const resolvedPos = editor.state.doc.resolve(from);
      const parentNode = resolvedPos.parent;
      if (parentNode.type.name === 'paragraph' && parentNode.content.size === 0) {
        const coords = editor.view.coordsAtPos(from);
        const containerRect = editorContainerRef.current?.getBoundingClientRect();
        if (!containerRect) {
          setPlusButtonState({ visible: false, top: 0, left: 0 });
          return;
        }
        setPlusButtonState({
          visible: true,
          top: coords.top - 14,
          left: containerRect.left - 48,
        });
      } else {
        setPlusButtonState({ visible: false, top: 0, left: 0 });
        setPlusMenuOpen(false);
      }
    };

    editor.on('selectionUpdate', updatePlusButton);
    editor.on('update', updatePlusButton);

    return () => {
      editor.off('selectionUpdate', updatePlusButton);
      editor.off('update', updatePlusButton);
    };
  }, [editor]);

  const payload = useCallback(() => ({
    story_id: storyId,
    title,
    excerpt,
    cover_image: coverImage,
    story_type: storyType,
    tags_csv: tagsCsv,
    tags: tagsCsv.split(',').map((tag) => tag.trim()).filter(Boolean),
    meta_title: metaTitle || title,
    meta_description: metaDescription || excerpt,
    canonical_url: canonicalUrl,
    og_image: ogImage || coverImage,
    status,
    scheduled_for: scheduledFor || null,
    content: editor?.getJSON() || EMPTY_DOC,
  }), [storyId, title, excerpt, coverImage, storyType, tagsCsv, metaTitle, metaDescription, canonicalUrl, ogImage, status, scheduledFor, editor]);

  useEffect(() => {
    if (!editor) return;

    const timer = window.setInterval(async () => {
      if (isSubmitting) {
        return;
      }

      const body = payload();
      const snapshot = JSON.stringify(body);
      if (snapshot === lastSavedRef.current) {
        return;
      }

      try {
        clearFeedback();
        setSaveStatus('Saving...');
        const data = await requestJson<{ story_id?: number; message?: string; edit_url?: string }>(
          endpoints.autosave,
          'POST',
          captchaState.required && captchaState.inputName ? {
            ...body,
            [captchaState.inputName]: captchaState.token || '',
          } : body,
          csrfToken,
          captchaState,
        );
        if (data.story_id && !storyId) {
          setStoryId(data.story_id);
        }
        if (data.edit_url && window.location.pathname.endsWith('/create')) {
          window.history.replaceState({}, '', data.edit_url);
        }
        lastSavedRef.current = snapshot;
        setSaveStatus(data.message || 'Saved just now');
        if (captchaState.required && captchaState.token) {
          resetCaptchaState();
        }
        emitSaveEvent('autosave', data.story_id || storyId);
      } catch (error) {
        applyFailure(error, 'Autosave failed');
      }
    }, 10000);

    return () => window.clearInterval(timer);
  }, [applyFailure, captchaState, clearFeedback, csrfToken, editor, emitSaveEvent, endpoints.autosave, isSubmitting, payload, resetCaptchaState, storyId]);

  const persistStory = async (submitAction: 'save_draft' | 'submit_review' | 'publish_now' | 'schedule_publish') => {
    const body = {
      ...payload(),
      submit_action: submitAction,
      status: submitAction === 'submit_review' ? 'pending_review' : submitAction === 'publish_now' ? 'published' : submitAction === 'schedule_publish' ? 'scheduled' : status,
      scheduled_for: submitAction === 'schedule_publish' ? scheduledFor : null,
    };

    try {
      clearFeedback();
      setIsSubmitting(true);
      setSaveStatus('Saving...');
      const endpoint = storyId ? endpoints.update : endpoints.create;
      const method = storyId ? 'PUT' : 'POST';
      const data = await requestJson<{ story_id: number; message?: string; status?: string; edit_url?: string; public_url?: string }>(endpoint, method, captchaState.required && captchaState.inputName ? {
        ...body,
        [captchaState.inputName]: captchaState.token || '',
      } : body, csrfToken, captchaState);
      if (data.story_id) {
        setStoryId(data.story_id);
      }
      if (data.edit_url && window.location.pathname.endsWith('/create')) {
        window.history.replaceState({}, '', data.edit_url);
      }
      lastSavedRef.current = JSON.stringify(payload());
      setSaveStatus(data.message || 'Saved just now');
      if (captchaState.required && captchaState.token) {
        resetCaptchaState();
      }
      emitSaveEvent('manual', data.story_id || storyId);
      if (submitAction === 'publish_now' && data.public_url) {
        window.location.assign(data.public_url);
        return;
      }
    } catch (error) {
      applyFailure(error, submitAction === 'publish_now' ? 'Publish failed' : 'Save failed');
    } finally {
      setIsSubmitting(false);
    }
  };

  const handleBodyImagePicked = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    event.target.value = '';
    if (!file) return;

    setSaveStatus('Uploading image...');
    const uploaded = await uploadImageFile(file);
    if (!uploaded || !editor) {
      setSaveStatus('Image upload failed');
      return;
    }

    editor.chain().focus().setImage({ src: uploaded }).run();
    setSaveStatus('Image uploaded');
  };

  const handleCoverImagePicked = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    event.target.value = '';
    if (!file) return;

    setSaveStatus('Uploading cover...');
    const uploaded = await uploadImageFile(file);
    if (!uploaded) {
      setSaveStatus('Cover upload failed');
      return;
    }

    setCoverImage(uploaded);
    setSaveStatus('Cover uploaded');
  };

  const readinessChecks = useMemo(() => ([
    { label: 'Title', ok: title.trim().length > 0, hint: 'Give the story a clear headline.' },
    { label: 'Body', ok: wordCount >= 50, hint: 'Aim for at least 50 words before publishing.' },
    { label: 'Story type', ok: storyType.trim().length > 0, hint: 'Choose the format that fits the post.' },
  ]), [storyType, title, wordCount]);

  const titleError = fieldErrors?.title?.[0] || '';
  const contentError = fieldErrors?.content?.[0] || '';
  const excerptError = fieldErrors?.excerpt?.[0] || '';
  const tagsError = fieldErrors?.tags_csv?.[0] || '';

  const insertArtwork = (item: Artwork) => {
    if (!editor) return;
    editor.chain().focus().insertContent({
      type: 'artworkEmbed',
      attrs: {
        artworkId: item.id,
        title: item.title,
        url: item.url,
        thumb: item.thumbs?.md || item.thumbs?.sm || item.thumb || '',
      },
    }).run();
    setArtworkModalOpen(false);
  };

  return (
    <div className="mx-auto max-w-4xl px-4 py-4 pb-24 md:px-8">
      {/* ── Nova top bar ─────────────────────────────────────────────────── */}
      <div className="sticky top-0 z-30 mb-6 flex h-14 items-center justify-between overflow-hidden rounded-2xl border border-white/10 bg-[linear-gradient(135deg,rgba(12,18,28,0.97),rgba(8,12,20,0.97))] px-5 shadow-[0_8px_32px_rgba(3,7,18,0.32)] backdrop-blur-xl">
        <div className="flex items-center gap-4">
          <a href="/studio/stories" className="flex items-center gap-1.5 text-sm text-white/50 transition-colors hover:text-white/90">
            <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
            Stories
          </a>
          <span className="h-4 w-px bg-white/10" />
          <span className="hidden text-sm text-white/65 sm:inline">{saveStatus}</span>
        </div>
        <div className="flex items-center gap-2">
          <span className="hidden text-xs text-white/55 lg:inline">{wordCount > 0 ? `${wordCount.toLocaleString()} words · ${readMinutes} min` : ''}</span>
          <button
            type="button"
            onClick={() => setSettingsOpen(true)}
            title="Story settings"
            className="rounded-full p-2 text-white/50 transition-colors hover:bg-white/[0.07] hover:text-white"
          >
            <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
          </button>
          <button
            type="button"
            onClick={() => persistStory('save_draft')}
            disabled={isSubmitting}
            className="rounded-full border border-white/10 bg-white/[0.05] px-4 py-1.5 text-sm text-white/80 transition hover:bg-white/[0.10] disabled:opacity-50"
          >
            Save
          </button>
          <button
            type="button"
            onClick={() => persistStory('publish_now')}
            disabled={isSubmitting}
            className="rounded-full bg-sky-500 px-4 py-1.5 text-sm font-medium text-white shadow-[0_2px_12px_rgba(14,165,233,0.45)] transition hover:bg-sky-400 disabled:opacity-50"
          >
            Publish
          </button>
        </div>
      </div>

      {/* ── Writing canvas ───────────────────────────────────────────────── */}
      <div className="mx-auto max-w-[760px] overflow-hidden rounded-[2rem] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.07),_transparent_32%),linear-gradient(180deg,rgba(14,18,27,0.99),rgba(9,12,19,0.98))] shadow-[0_24px_80px_rgba(4,8,20,0.36)]">
        {coverImage ? (
          <div className="group relative overflow-hidden rounded-t-2xl">
            <img src={coverImage} alt="Story cover" className="h-64 w-full object-cover md:h-80" />
            <div className="absolute inset-0 flex items-center justify-center gap-3 bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
              <button
                type="button"
                onClick={() => coverImageInputRef.current?.click()}
                className="flex items-center gap-1.5 rounded-lg bg-white/15 px-4 py-2 text-sm font-medium text-white backdrop-blur-sm transition hover:bg-white/25"
              >
                <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
                Change
              </button>
              <button
                type="button"
                onClick={() => setCoverImage('')}
                className="flex items-center gap-1.5 rounded-lg bg-rose-500/70 px-4 py-2 text-sm font-medium text-white backdrop-blur-sm transition hover:bg-rose-500/90"
              >
                <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
                Remove
              </button>
            </div>
          </div>
        ) : null}

        <div className="px-6 pb-24 pt-10 md:px-14 md:pt-14">
          {/* Error / captcha banner */}
          {(generalError || captchaState.required) && (
            <div className="mb-8 rounded-xl border border-amber-400/20 bg-amber-500/10 p-4">
              <p className="text-sm text-amber-200">{generalError || captchaState.message || 'Complete the captcha to continue.'}</p>
              {captchaState.required && captchaState.siteKey ? (
                <div className="mt-3">
                  <TurnstileField
                    key={`story-editor-captcha-${captchaState.nonce}`}
                    provider={captchaState.provider}
                    siteKey={captchaState.siteKey}
                    scriptUrl={captchaState.scriptUrl}
                    onToken={(token) => setCaptchaState((prev) => ({ ...prev, token }))}
                    className="min-h-16"
                  />
                </div>
              ) : null}
            </div>
          )}

          {/* Cover image upload shortcut */}
          {!coverImage && (
            <button
              type="button"
              onClick={() => coverImageInputRef.current?.click()}
              className="mb-6 flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-white/55 transition hover:bg-white/[0.05] hover:text-white/80"
            >
              <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
              Add a cover image
            </button>
          )}

          {/* Title */}
          <div className="mb-3">
            <textarea
              value={title}
              onChange={(event) => {
                setTitle(event.target.value);
                event.target.style.height = 'auto';
                event.target.style.height = `${event.target.scrollHeight}px`;
              }}
              onFocus={(event) => {
                event.target.style.height = 'auto';
                event.target.style.height = `${event.target.scrollHeight}px`;
              }}
              placeholder="Title"
              rows={1}
              className="w-full resize-none overflow-hidden border-0 bg-transparent p-0 text-[2.4rem] font-bold leading-tight tracking-tight text-white placeholder:text-white/35 focus:outline-none md:text-[2.8rem]"
            />
            {titleError ? <p className="mt-1 text-sm text-rose-300">{titleError}</p> : null}
          </div>

          {/* Excerpt / subtitle */}
          <div className="mb-10 border-b border-white/[0.07] pb-8">
            <textarea
              value={excerpt}
              onChange={(event) => {
                setExcerpt(event.target.value);
                event.target.style.height = 'auto';
                event.target.style.height = `${event.target.scrollHeight}px`;
              }}
              onFocus={(event) => {
                event.target.style.height = 'auto';
                event.target.style.height = `${event.target.scrollHeight}px`;
              }}
              placeholder="Write a short subtitle that sets the scene…"
              rows={1}
              className="w-full resize-none overflow-hidden border-0 bg-transparent p-0 text-xl leading-relaxed text-white/75 placeholder:text-white/35 focus:outline-none"
            />
            {excerptError ? <p className="mt-1 text-sm text-rose-300">{excerptError}</p> : null}
          </div>

          {/* Body editor — the ref is on the wrapper so we can measure its left edge */}
          <div className="relative" ref={editorContainerRef}>
            <EditorContent editor={editor} />
            {contentError ? <p className="mt-4 text-sm text-rose-300">{contentError}</p> : null}
          </div>

          {/* Footer actions */}
          <div className="mt-16 flex flex-wrap items-center gap-3 border-t border-white/[0.07] pt-8 text-sm">
            {storyId && (
              <a href={`${endpoints.previewBase}/${storyId}/preview`} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-white/60 transition hover:bg-white/[0.08] hover:text-white/90">Preview</a>
            )}
            {storyId && (
              <a href={`${endpoints.analyticsBase}/${storyId}/analytics`} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-white/60 transition hover:bg-white/[0.08] hover:text-white/90">Analytics</a>
            )}
            <button
              type="button"
              onClick={() => persistStory('submit_review')}
              disabled={isSubmitting}
              className="rounded-full border border-amber-400/30 bg-amber-400/10 px-4 py-2 text-amber-200 transition hover:bg-amber-400/20 disabled:opacity-50"
            >
              Submit for review
            </button>
            {mode === 'edit' && storyId && (
              <form
                method="POST"
                action={`/creator/stories/${storyId}`}
                onSubmit={(e) => { if (!window.confirm('Delete this story permanently?')) e.preventDefault(); }}
                className="ml-auto"
              >
                <input type="hidden" name="_token" value={csrfToken} />
                <input type="hidden" name="_method" value="DELETE" />
                <button type="submit" className="rounded-full border border-rose-500/30 bg-rose-500/10 px-4 py-2 text-rose-300 transition hover:bg-rose-500/20">Delete story</button>
              </form>
            )}
          </div>
        </div>
      </div>

      {/* ── Floating + block insertion button (fixed, always visible when on empty line) ── */}
      {plusButtonState.visible && (
        <div className="fixed z-40" style={{ top: `${plusButtonState.top}px`, left: `${plusButtonState.left}px` }}>
          <button
            type="button"
            onMouseDown={(e) => { e.preventDefault(); setPlusMenuOpen((v) => !v); }}
            className={`flex h-8 w-8 items-center justify-center rounded-full border transition ${
              plusMenuOpen
                ? 'border-sky-400/60 bg-sky-500/20 text-sky-300 shadow-[0_0_12px_rgba(14,165,233,0.35)]'
                : 'border-white/20 bg-slate-900/90 text-white/60 shadow-[0_4px_16px_rgba(3,7,18,0.4)] hover:border-sky-400/50 hover:text-sky-300'
            }`}
            title="Add a block (or type / for commands)"
          >
            <svg
              className={`h-4 w-4 transition-transform duration-200 ${plusMenuOpen ? 'rotate-45' : ''}`}
              fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}
            >
              <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
            </svg>
          </button>

          {/* Insert block dropdown */}
          {plusMenuOpen && (
            <div className="absolute left-10 top-0 w-52 overflow-hidden rounded-2xl border border-white/10 bg-[linear-gradient(180deg,rgba(18,24,36,0.99),rgba(10,14,22,0.99))] py-1 shadow-[0_16px_48px_rgba(3,7,18,0.5)] backdrop-blur-xl">
              {([
                { label: 'Upload photo', icon: '🖼', key: 'uploadImage' },
                { label: 'Image URL', icon: '🔗', key: 'image' },
                { label: 'Artwork embed', icon: '🎨', key: 'artwork' },
                { label: 'Video (YouTube…)', icon: '▶', key: 'video' },
                { label: 'Gallery', icon: '⊞', key: 'gallery' },
                { label: 'Blockquote', icon: '❝', key: 'quote' },
                { label: 'Code block', icon: '⌨', key: 'code' },
                { label: 'Download link', icon: '↓', key: 'download' },
                { label: 'Divider', icon: '—', key: 'divider' },
              ] as Array<{ label: string; icon: string; key: keyof typeof insertActions }>).map((item) => (
                <button
                  key={item.key}
                  type="button"
                  onMouseDown={(e) => {
                    e.preventDefault();
                    setPlusMenuOpen(false);
                    insertActions[item.key]();
                  }}
                  className="flex w-full items-center gap-3 px-4 py-2.5 text-left text-sm text-white/75 transition-colors hover:bg-white/[0.07] hover:text-white"
                >
                  <span className="w-5 text-center text-base leading-none opacity-70">{item.icon}</span>
                  {item.label}
                </button>
              ))}
            </div>
          )}
        </div>
      )}

      {/* ── Floating inline formatting toolbar ───────────────────────────── */}
      {editor && inlineToolbar.visible && (
        <div
          className="fixed z-50 flex items-center gap-0.5 overflow-hidden rounded-2xl border border-white/10 bg-[linear-gradient(135deg,rgba(12,18,28,0.98),rgba(8,12,20,0.98))] p-1.5 shadow-[0_8px_32px_rgba(3,7,18,0.5)] backdrop-blur-xl"
          style={{ top: `${inlineToolbar.top}px`, left: `${inlineToolbar.left}px` }}
        >
          {([
            { label: 'B', title: 'Bold', action: () => editor.chain().focus().toggleBold().run(), active: editor.isActive('bold'), extra: 'font-bold' },
            { label: 'I', title: 'Italic', action: () => editor.chain().focus().toggleItalic().run(), active: editor.isActive('italic'), extra: 'italic' },
            { label: 'U', title: 'Underline', action: () => editor.chain().focus().toggleUnderline().run(), active: editor.isActive('underline'), extra: 'underline' },
            { label: 'H2', title: 'Heading 2', action: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), active: editor.isActive('heading', { level: 2 }), extra: 'font-semibold text-xs' },
            { label: 'H3', title: 'Heading 3', action: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), active: editor.isActive('heading', { level: 3 }), extra: 'font-semibold text-xs' },
            { label: '❝', title: 'Blockquote', action: () => editor.chain().focus().toggleBlockquote().run(), active: editor.isActive('blockquote'), extra: 'text-base font-serif' },
            { label: '⛓', title: 'Link', action: () => openLinkPrompt(editor), active: editor.isActive('link'), extra: '' },
            { label: '</>', title: 'Inline code', action: () => editor.chain().focus().toggleCode().run(), active: editor.isActive('code'), extra: 'font-mono text-[10px]' },
          ] as Array<{ label: string; title: string; action: () => void; active: boolean; extra: string }>).map((item) => (
            <button
              key={item.title}
              type="button"
              title={item.title}
              onMouseDown={(e) => e.preventDefault()}
              onClick={item.action}
              className={`flex h-8 min-w-[2rem] items-center justify-center rounded-xl px-1.5 text-sm transition ${item.extra} ${item.active ? 'bg-sky-500/25 text-sky-200' : 'text-white/70 hover:bg-white/[0.07] hover:text-white'}`}
            >
              {item.label}
            </button>
          ))}
        </div>
      )}

      {/* ── Settings slide-over panel ─────────────────────────────────────── */}
      {settingsOpen && (
        <>
          <button
            type="button"
            className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm"
            onClick={() => setSettingsOpen(false)}
            aria-label="Close settings"
          />
          <div className="fixed bottom-0 right-0 top-0 z-50 flex w-full max-w-sm flex-col overflow-hidden border-l border-white/10 bg-[linear-gradient(180deg,rgba(14,18,27,0.99),rgba(9,12,19,0.99))] shadow-[−20px_0_60px_rgba(3,7,18,0.5)]">
            <div className="flex shrink-0 items-center justify-between border-b border-white/10 px-5 py-4">
              <h2 className="font-semibold text-white">Story settings</h2>
              <button type="button" onClick={() => setSettingsOpen(false)} className="rounded-full p-1.5 text-white/40 transition-colors hover:bg-white/[0.07] hover:text-white">
                <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
              </button>
            </div>
            <div className="flex-1 overflow-y-auto">
              <div className="space-y-6 p-5">
                {/* Readiness checklist */}
                <div>
                  <p className="mb-3 text-xs font-semibold uppercase tracking-widest text-white/35">Ready to publish?</p>
                  <div className="space-y-2">
                    {readinessChecks.map((check) => (
                      <div key={check.label} className={`flex items-start gap-3 rounded-xl p-3 ${check.ok ? 'bg-emerald-500/10 border border-emerald-500/20' : 'bg-amber-500/10 border border-amber-500/20'}`}>
                        <span className={`mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] font-bold ${check.ok ? 'bg-emerald-500/25 text-emerald-300' : 'bg-amber-500/25 text-amber-300'}`}>
                          {check.ok ? '✓' : '!'}
                        </span>
                        <div>
                          <p className="text-sm font-medium text-white/85">{check.label}</p>
                          <p className="text-xs text-white/40">{check.hint}</p>
                        </div>
                      </div>
                    ))}
                  </div>
                </div>

                {/* Publish actions */}
                <div>
                  <p className="mb-3 text-xs font-semibold uppercase tracking-widest text-white/35">Publish</p>
                  <div className="space-y-2">
                    <button type="button" onClick={() => { void persistStory('publish_now'); setSettingsOpen(false); }} disabled={isSubmitting} className="w-full rounded-xl bg-sky-500 px-4 py-3 text-sm font-medium text-white shadow-[0_2px_12px_rgba(14,165,233,0.35)] transition hover:bg-sky-400 disabled:opacity-50">Publish now</button>
                    <button type="button" onClick={() => { void persistStory('save_draft'); setSettingsOpen(false); }} disabled={isSubmitting} className="w-full rounded-xl border border-white/10 bg-white/[0.05] px-4 py-3 text-sm text-white/75 transition hover:bg-white/[0.09] disabled:opacity-50">Save as draft</button>
                    <button type="button" onClick={() => { void persistStory('submit_review'); setSettingsOpen(false); }} disabled={isSubmitting} className="w-full rounded-xl border border-amber-400/30 bg-amber-400/10 px-4 py-3 text-sm text-amber-200 transition hover:bg-amber-400/20 disabled:opacity-50">Submit for review</button>
                    {scheduledFor && (
                      <button type="button" onClick={() => { void persistStory('schedule_publish'); setSettingsOpen(false); }} disabled={isSubmitting} className="w-full rounded-xl border border-sky-400/30 bg-sky-400/10 px-4 py-3 text-sm text-sky-200 transition hover:bg-sky-400/20 disabled:opacity-50">Schedule publish</button>
                    )}
                  </div>
                </div>

                {/* Cover image */}
                <div>
                  <div className="mb-3 flex items-center justify-between">
                    <p className="text-xs font-semibold uppercase tracking-widest text-white/35">Cover image</p>
                    <div className="flex items-center gap-3">
                      <button type="button" onClick={() => coverImageInputRef.current?.click()} className="text-xs text-sky-400 underline-offset-2 transition-colors hover:underline">Upload file</button>
                      {coverImage && <button type="button" onClick={() => setCoverImage('')} className="text-xs text-rose-400 underline-offset-2 transition-colors hover:underline">Remove</button>}
                    </div>
                  </div>
                  <input value={coverImage} onChange={(e) => setCoverImage(e.target.value)} placeholder="Paste an image URL…" className="w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/25 focus:border-white/20 focus:outline-none" />
                  {coverImage && <img src={coverImage} alt="Cover preview" className="mt-3 h-36 w-full rounded-xl object-cover" />}
                </div>

                {/* Format */}
                <div>
                  <p className="mb-2 text-xs font-semibold uppercase tracking-widest text-white/35">Format</p>
                  <NovaSelect value={storyType} onChange={(val) => setStoryType(val)} options={storyTypes.map((t) => ({ value: t.slug, label: t.name }))} />
                </div>

                {/* Tags */}
                <div>
                  <p className="mb-2 text-xs font-semibold uppercase tracking-widest text-white/35">Tags</p>
                  <input value={tagsCsv} onChange={(e) => setTagsCsv(e.target.value)} placeholder="art direction, tutorial, process" className="w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/25 focus:border-white/20 focus:outline-none" />
                  {tagsError ? <p className="mt-1 text-xs text-rose-400">{tagsError}</p> : <p className="mt-1 text-xs text-white/30">Comma-separated. New tags created automatically.</p>}
                </div>

                {/* Status + schedule */}
                <div>
                  <p className="mb-2 text-xs font-semibold uppercase tracking-widest text-white/35">Workflow</p>
                  <NovaSelect value={status} onChange={(val) => setStatus(val)} searchable={false} options={[{ value: 'draft', label: 'Draft' }, { value: 'pending_review', label: 'Pending Review' }, { value: 'published', label: 'Published' }, { value: 'scheduled', label: 'Scheduled' }, { value: 'archived', label: 'Archived' }]} />
                  <input type="datetime-local" value={scheduledFor} onChange={(e) => setScheduledFor(e.target.value)} className="w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white focus:border-white/20 focus:outline-none" />
                </div>

                {/* SEO */}
                <div>
                  <p className="mb-3 text-xs font-semibold uppercase tracking-widest text-white/35">SEO & social</p>
                  <div className="space-y-2">
                    <input value={metaTitle} onChange={(e) => setMetaTitle(e.target.value)} placeholder="Meta title (defaults to story title)" className="w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/25 focus:border-white/20 focus:outline-none" />
                    <textarea value={metaDescription} onChange={(e) => setMetaDescription(e.target.value)} rows={3} placeholder="Meta description (defaults to excerpt)" className="w-full resize-none rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/25 focus:border-white/20 focus:outline-none" />
                    <input value={canonicalUrl} onChange={(e) => setCanonicalUrl(e.target.value)} placeholder="Canonical URL (optional)" className="w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/25 focus:border-white/20 focus:outline-none" />
                    <input value={ogImage} onChange={(e) => setOgImage(e.target.value)} placeholder="OG image URL (defaults to cover)" className="w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/25 focus:border-white/20 focus:outline-none" />
                  </div>
                </div>

                {/* Quick links */}
                {storyId && (
                  <div className="space-y-2">
                    <a href={`${endpoints.previewBase}/${storyId}/preview`} className="block rounded-xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-center text-sm text-white/65 transition hover:bg-white/[0.08] hover:text-white">Preview story</a>
                    <a href={`${endpoints.analyticsBase}/${storyId}/analytics`} className="block rounded-xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-center text-sm text-white/65 transition hover:bg-white/[0.08] hover:text-white">View analytics</a>
                  </div>
                )}
              </div>
            </div>
          </div>
        </>
      )}

      {/* ── Artwork picker modal ──────────────────────────────────────────── */}
      {artworkModalOpen && (
        <div className="fixed inset-0 z-50 flex items-end justify-center bg-black/60 p-4 backdrop-blur-sm sm:items-center">
          <div className="w-full max-w-2xl overflow-hidden rounded-[1.75rem] border border-white/10 bg-[linear-gradient(180deg,rgba(14,18,27,0.99),rgba(9,12,19,0.99))] shadow-[0_24px_80px_rgba(3,7,18,0.7)]">
            <div className="flex items-center justify-between border-b border-white/10 px-6 py-4">
              <h3 className="font-semibold text-white">Embed an artwork</h3>
              <button type="button" onClick={() => setArtworkModalOpen(false)} className="rounded-full p-1.5 text-white/40 transition-colors hover:bg-white/[0.07] hover:text-white">
                <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
              </button>
            </div>
            <div className="p-6">
              <input
                value={artworkQuery}
                onChange={(e) => setArtworkQuery(e.target.value)}
                className="mb-4 w-full rounded-xl border border-white/10 bg-slate-950/60 px-3 py-2.5 text-sm text-white placeholder:text-white/30 focus:border-white/20 focus:outline-none"
                placeholder="Search your artworks…"
                autoFocus
              />
              <div className="grid max-h-72 gap-3 overflow-y-auto sm:grid-cols-3">
                {artworkResults.map((item) => (
                  <button key={item.id} type="button" onClick={() => insertArtwork(item)} className="overflow-hidden rounded-xl border border-white/10 bg-white/[0.04] text-left transition hover:border-sky-400/40 hover:shadow-lg">
                    {(item.thumbs?.sm || item.thumb) && <img src={item.thumbs?.sm || item.thumb || ''} alt={item.title} className="h-24 w-full object-cover" />}
                    <div className="p-2">
                      <p className="line-clamp-1 text-xs font-medium text-white/80">{item.title}</p>
                    </div>
                  </button>
                ))}
                {artworkResults.length === 0 && artworkQuery.length > 0 && (
                  <p className="col-span-3 py-8 text-center text-sm text-white/35">No artworks found for &ldquo;{artworkQuery}&rdquo;</p>
                )}
              </div>
            </div>
          </div>
        </div>
      )}

      {/* Hidden file inputs */}
      <input ref={bodyImageInputRef} type="file" accept="image/*" className="hidden" onChange={handleBodyImagePicked} />
      <input ref={coverImageInputRef} type="file" accept="image/*" className="hidden" onChange={handleCoverImagePicked} />
    </div>
  );
}
