import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import ImageExtension from '@tiptap/extension-image';
import LinkExtension from '@tiptap/extension-link';
import PlaceholderExtension from '@tiptap/extension-placeholder';
import TextAlignExtension from '@tiptap/extension-text-align';
import UnderlineExtension from '@tiptap/extension-underline';
import HighlightExtension from '@tiptap/extension-highlight';
import { VideoExtension } from '../lib/tiptap-video';
import { infoPagesApi } from '../api/infoPages';
import { newsApi } from '../api/news';
import { usePrompt } from '../store/promptDialog';
import { AdminBackButton } from '../components/admin';
import { Toggle } from '../components/admin/Toggle';
import { useHapticFeedback } from '../platform/hooks/useHaptic';
import { cn } from '../lib/utils';
import {
  BoldIcon,
  ItalicIcon,
  UnderlineIcon,
  StrikeIcon,
  ListBulletIcon,
  ListOrderedIcon,
  QuoteIcon,
  CodeBlockIcon,
  ImageIcon,
  LinkIcon,
  AlignLeftIcon,
  AlignCenterIcon,
  HighlightIcon,
  ChevronUpIcon,
  ChevronDownIcon,
  TrashSmallIcon,
  PlusSmallIcon,
} from '@/components/icons';
import type { InfoPageType, FaqItem, ReplacesTab } from '../api/infoPages';

const AVAILABLE_LOCALES = ['ru', 'en', 'zh', 'fa'] as const;
type LocaleCode = (typeof AVAILABLE_LOCALES)[number];

// --- Icons ---
const H1Icon = () => <span className="text-xs font-bold">H1</span>;
const H2Icon = () => <span className="text-xs font-bold">H2</span>;
const H3Icon = () => <span className="text-xs font-bold">H3</span>;

// --- Toolbar Button ---
interface ToolbarButtonProps {
  onClick: () => void;
  isActive?: boolean;
  disabled?: boolean;
  title: string;
  children: React.ReactNode;
}

function ToolbarButton({ onClick, isActive, disabled, title, children }: ToolbarButtonProps) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      title={title}
      aria-label={title}
      aria-pressed={isActive}
      className={cn(
        'min-h-[44px] min-w-[44px] rounded p-2.5 transition-colors',
        disabled && 'cursor-not-allowed opacity-50',
        isActive
          ? 'bg-accent-500/20 text-accent-400'
          : 'text-dark-400 hover:bg-dark-700 hover:text-dark-200',
      )}
    >
      {children}
    </button>
  );
}

// --- Security: URL scheme validation ---
function isSafeUrl(url: string | null | undefined): boolean {
  if (!url) return false;
  try {
    const parsed = new URL(url);
    return parsed.protocol === 'https:' || parsed.protocol === 'http:';
  } catch {
    return false;
  }
}

// --- Slug utility ---
const TRANSLIT_MAP: Record<string, string> = {
  а: 'a',
  б: 'b',
  в: 'v',
  г: 'g',
  д: 'd',
  е: 'e',
  ё: 'yo',
  ж: 'zh',
  з: 'z',
  и: 'i',
  й: 'y',
  к: 'k',
  л: 'l',
  м: 'm',
  н: 'n',
  о: 'o',
  п: 'p',
  р: 'r',
  с: 's',
  т: 't',
  у: 'u',
  ф: 'f',
  х: 'kh',
  ц: 'ts',
  ч: 'ch',
  ш: 'sh',
  щ: 'shch',
  ъ: '',
  ы: 'y',
  ь: '',
  э: 'e',
  ю: 'yu',
  я: 'ya',
};

function generateSlug(title: string): string {
  const lower = title.toLowerCase();
  const transliterated = Array.from(lower)
    .map((ch) => TRANSLIT_MAP[ch] ?? ch)
    .join('');
  return transliterated
    .replace(/[^a-z0-9\s-]/g, '')
    .replace(/[\s_]+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-+|-+$/g, '')
    .substring(0, 100);
}

// --- FAQ Answer Rich Editor ---

const FAQ_EDITOR_EXTENSIONS = [
  StarterKit.configure({
    heading: { levels: [2, 3] },
    link: false,
    underline: false,
  }),
  UnderlineExtension,
  LinkExtension.configure({ openOnClick: false, HTMLAttributes: { class: 'link' } }),
  ImageExtension.configure({ HTMLAttributes: { class: 'rounded-xl max-w-full' } }),
  PlaceholderExtension.configure({ placeholder: '' }),
  TextAlignExtension.configure({ types: ['heading', 'paragraph'] }),
  HighlightExtension,
  VideoExtension,
];

function FaqAnswerEditor({ value, onChange }: { value: string; onChange: (html: string) => void }) {
  const { t } = useTranslation();
  const haptic = useHapticFeedback();
  const mediaRef = useRef<HTMLInputElement>(null);
  const [uploadCount, setUploadCount] = useState(0);
  const isUploading = uploadCount > 0;
  const [isDragging, setIsDragging] = useState(false);
  const activeUploadsRef = useRef(new Set<AbortController>());

  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;

  const editor = useEditor({
    extensions: FAQ_EDITOR_EXTENSIONS,
    content: value,
    editorProps: {
      attributes: {
        class: 'prose prose-sm max-w-none min-h-[120px] p-3 focus:outline-none',
      },
    },
    onUpdate: ({ editor: e }) => {
      const html = e.getHTML();
      const isEmpty = html === '<p></p>' || html === '';
      onChangeRef.current(isEmpty ? '' : html);
    },
  });

  // Sync external value changes (e.g., reorder, locale switch)
  useEffect(() => {
    if (!editor || editor.isDestroyed) return;
    const currentHtml = editor.getHTML();
    const normalizedCurrent = currentHtml === '<p></p>' ? '' : currentHtml;
    if (normalizedCurrent !== value) {
      editor.commands.setContent(value || '', { emitUpdate: false });
    }
  }, [value, editor]);

  // Cleanup uploads on unmount
  useEffect(() => {
    const uploads = activeUploadsRef.current;
    return () => {
      for (const c of uploads) c.abort();
      uploads.clear();
    };
  }, []);

  const handleMediaUpload = useCallback(
    async (file: File) => {
      if (!editor) return;
      const isImage = file.type.startsWith('image/');
      const isVideo = file.type.startsWith('video/');
      if (!isImage && !isVideo) return;
      const maxSize = isImage ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
      if (file.size > maxSize) {
        haptic.error();
        return;
      }
      const controller = new AbortController();
      activeUploadsRef.current.add(controller);
      setUploadCount((c) => c + 1);
      try {
        const result = await newsApi.uploadMedia(file, controller.signal);
        if (controller.signal.aborted) return;
        if (!isSafeUrl(result.url)) return;
        if (result.media_type === 'image') {
          editor.chain().focus().setImage({ src: result.url, alt: file.name }).run();
        } else {
          editor
            .chain()
            .focus()
            .insertContent({
              type: 'video',
              attrs: { src: result.url, class: 'w-full rounded-xl max-h-96' },
            })
            .run();
        }
        haptic.success();
      } catch {
        if (!controller.signal.aborted) haptic.error();
      } finally {
        activeUploadsRef.current.delete(controller);
        setUploadCount((c) => c - 1);
      }
    },
    [editor, haptic],
  );

  const handleMediaUploadRef = useRef(handleMediaUpload);
  useEffect(() => {
    handleMediaUploadRef.current = handleMediaUpload;
  }, [handleMediaUpload]);

  // Register paste/drop handlers after editor is created
  useEffect(() => {
    if (!editor) return;
    const handlePaste = (_view: unknown, event: ClipboardEvent) => {
      const items = event.clipboardData?.items;
      if (!items) return false;
      for (const item of Array.from(items)) {
        if (item.type.startsWith('image/') || item.type.startsWith('video/')) {
          const file = item.getAsFile();
          if (file) {
            handleMediaUploadRef.current(file);
            return true;
          }
        }
      }
      return false;
    };
    const handleDrop = (_view: unknown, event: DragEvent) => {
      const file = event.dataTransfer?.files[0];
      if (file && (file.type.startsWith('image/') || file.type.startsWith('video/'))) {
        event.preventDefault();
        handleMediaUploadRef.current(file);
        return true;
      }
      return false;
    };
    editor.setOptions({ editorProps: { ...editor.options.editorProps, handlePaste, handleDrop } });
  }, [editor]);

  const promptDialog = usePrompt();

  const addLink = useCallback(async () => {
    if (!editor) return;
    const url = await promptDialog({
      label: t('news.admin.toolbar.linkUrlPrompt'),
      inputType: 'url',
    });
    if (!url) return;
    try {
      const parsed = new URL(url);
      if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return;
    } catch {
      return;
    }
    editor.chain().focus().setLink({ href: url }).run();
  }, [editor, t, promptDialog]);

  if (!editor) return null;

  return (
    <div className="overflow-hidden rounded-xl border border-dark-700 bg-dark-800/50">
      {/* Upload overlay */}
      {isUploading && (
        <div className="flex items-center justify-center gap-2 bg-dark-900/60 px-3 py-2">
          <div className="h-4 w-4 animate-spin rounded-full border-2 border-accent-400 border-t-transparent" />
          <span className="text-xs text-dark-300">{t('news.admin.uploading')}</span>
        </div>
      )}
      {isDragging && !isUploading && (
        <div className="flex items-center justify-center border-b border-dashed border-accent-400 bg-accent-400/10 px-3 py-2">
          <span className="text-xs font-medium text-accent-400">{t('news.admin.dropMedia')}</span>
        </div>
      )}

      {/* Compact toolbar */}
      <div className="flex flex-wrap items-center gap-0.5 border-b border-dark-700 bg-dark-800 px-1.5 py-1">
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleBold().run()}
          isActive={editor.isActive('bold')}
          title={t('news.admin.toolbar.bold')}
        >
          <BoldIcon className="h-4 w-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleItalic().run()}
          isActive={editor.isActive('italic')}
          title={t('news.admin.toolbar.italic')}
        >
          <ItalicIcon className="h-4 w-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleUnderline().run()}
          isActive={editor.isActive('underline')}
          title={t('news.admin.toolbar.underline')}
        >
          <UnderlineIcon className="h-4 w-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleStrike().run()}
          isActive={editor.isActive('strike')}
          title={t('news.admin.toolbar.strikethrough')}
        >
          <StrikeIcon className="h-4 w-4" />
        </ToolbarButton>
        <div className="mx-0.5 h-4 w-px bg-dark-700" />
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
          isActive={editor.isActive('heading', { level: 2 })}
          title={t('news.admin.toolbar.heading2')}
        >
          <H2Icon />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          isActive={editor.isActive('bulletList')}
          title={t('news.admin.toolbar.bulletList')}
        >
          <ListBulletIcon />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleOrderedList().run()}
          isActive={editor.isActive('orderedList')}
          title={t('news.admin.toolbar.orderedList')}
        >
          <ListOrderedIcon className="h-4 w-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleBlockquote().run()}
          isActive={editor.isActive('blockquote')}
          title={t('news.admin.toolbar.blockquote')}
        >
          <QuoteIcon className="h-4 w-4" />
        </ToolbarButton>
        <div className="mx-0.5 h-4 w-px bg-dark-700" />
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleHighlight().run()}
          isActive={editor.isActive('highlight')}
          title={t('news.admin.toolbar.highlight')}
        >
          <HighlightIcon className="h-4 w-4" />
        </ToolbarButton>
        <ToolbarButton onClick={addLink} title={t('news.admin.toolbar.link')}>
          <LinkIcon className="h-4 w-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => mediaRef.current?.click()}
          disabled={isUploading}
          title={t('news.admin.toolbar.image')}
        >
          {isUploading ? (
            <div className="h-4 w-4 animate-spin rounded-full border-2 border-accent-400 border-t-transparent" />
          ) : (
            <ImageIcon className="h-4 w-4" />
          )}
        </ToolbarButton>
      </div>

      {/* Editor */}
      <div
        onDragOver={(e) => {
          e.preventDefault();
          setIsDragging(true);
        }}
        onDragLeave={(e) => {
          e.preventDefault();
          setIsDragging(false);
        }}
        onDrop={(e) => {
          setIsDragging(false);
          if (e.defaultPrevented) return;
          const file = e.dataTransfer.files[0];
          if (file && (file.type.startsWith('image/') || file.type.startsWith('video/'))) {
            e.preventDefault();
            handleMediaUpload(file);
          }
        }}
      >
        <EditorContent editor={editor} />
      </div>

      {/* Hidden file input */}
      <input
        ref={mediaRef}
        type="file"
        accept="image/jpeg,image/png,image/webp,video/mp4,video/webm"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) handleMediaUpload(file);
          e.target.value = '';
        }}
        className="hidden"
        aria-hidden="true"
      />
    </div>
  );
}

// --- FAQ Q&A Builder ---
interface FaqBuilderProps {
  items: FaqItem[];
  onChange: (items: FaqItem[]) => void;
  locale: string;
  localeLabel: string;
}

function FaqBuilder({ items, onChange, locale, localeLabel }: FaqBuilderProps) {
  const { t } = useTranslation();

  // Stable keys for React — travel with items through reorder/delete
  const keyCounter = useRef(0);
  const [itemKeys, setItemKeys] = useState<number[]>(() => items.map(() => keyCounter.current++));

  // Regenerate all keys on locale switch (items are semantically different)
  useEffect(() => {
    setItemKeys(items.map(() => keyCounter.current++));
  }, [locale]); // eslint-disable-line react-hooks/exhaustive-deps

  // Sync key count when items added/removed within the same locale
  useEffect(() => {
    setItemKeys((prev) => {
      if (prev.length === items.length) return prev;
      if (items.length > prev.length) {
        const newKeys = [...prev];
        for (let i = prev.length; i < items.length; i++) {
          newKeys.push(keyCounter.current++);
        }
        return newKeys;
      }
      return prev.slice(0, items.length);
    });
  }, [items.length]);

  const handleQuestionChange = useCallback(
    (index: number, value: string) => {
      const updated = items.map((item, i) => (i === index ? { ...item, q: value } : item));
      onChange(updated);
    },
    [items, onChange],
  );

  const handleAnswerChange = useCallback(
    (index: number, value: string) => {
      const updated = items.map((item, i) => (i === index ? { ...item, a: value } : item));
      onChange(updated);
    },
    [items, onChange],
  );

  const handleRemove = useCallback(
    (index: number) => {
      setItemKeys((prev) => prev.filter((_, i) => i !== index));
      onChange(items.filter((_, i) => i !== index));
    },
    [items, onChange],
  );

  const handleAdd = useCallback(() => {
    const newKey = keyCounter.current++;
    setItemKeys((prev) => [...prev, newKey]);
    onChange([...items, { q: '', a: '' }]);
  }, [items, onChange]);

  const handleMoveUp = useCallback(
    (index: number) => {
      if (index === 0) return;
      const updated = [...items];
      [updated[index - 1], updated[index]] = [updated[index], updated[index - 1]];
      setItemKeys((prev) => {
        const k = [...prev];
        [k[index - 1], k[index]] = [k[index], k[index - 1]];
        return k;
      });
      onChange(updated);
    },
    [items, onChange],
  );

  const handleMoveDown = useCallback(
    (index: number) => {
      if (index >= items.length - 1) return;
      const updated = [...items];
      [updated[index], updated[index + 1]] = [updated[index + 1], updated[index]];
      setItemKeys((prev) => {
        const k = [...prev];
        [k[index], k[index + 1]] = [k[index + 1], k[index]];
        return k;
      });
      onChange(updated);
    },
    [items, onChange],
  );

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <label className="label">
          {t('admin.infoPages.faq.questions')} ({localeLabel})
        </label>
        <span className="text-xs text-dark-500">
          {items.length} {t('admin.infoPages.faq.questionsCount')}
        </span>
      </div>

      {items.length === 0 && (
        <div className="rounded-xl border border-dashed border-dark-700 bg-dark-800/30 p-6 text-center text-sm text-dark-500">
          {t('admin.infoPages.faq.noQuestions')}
        </div>
      )}

      {items.map((item, index) => (
        <div
          key={itemKeys[index] ?? index}
          className="rounded-xl border border-dark-700 bg-dark-800/50 p-4 transition-all hover:border-dark-600"
        >
          <div className="mb-3 flex items-center justify-between">
            <span className="text-xs font-medium text-dark-400">
              {t('admin.infoPages.faq.questionNumber', { n: index + 1 })}
            </span>
            <div className="flex items-center gap-1">
              <button
                type="button"
                onClick={() => handleMoveUp(index)}
                disabled={index === 0}
                className="min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:cursor-not-allowed disabled:opacity-30"
                title={t('admin.infoPages.faq.moveUp')}
              >
                <ChevronUpIcon className="h-4 w-4" />
              </button>
              <button
                type="button"
                onClick={() => handleMoveDown(index)}
                disabled={index >= items.length - 1}
                className="min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200 disabled:cursor-not-allowed disabled:opacity-30"
                title={t('admin.infoPages.faq.moveDown')}
              >
                <ChevronDownIcon className="h-4 w-4" />
              </button>
              <button
                type="button"
                onClick={() => handleRemove(index)}
                className="min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 transition-colors hover:bg-error-500/10 hover:text-error-400"
                title={t('admin.infoPages.faq.removeQuestion')}
              >
                <TrashSmallIcon />
              </button>
            </div>
          </div>

          <div className="space-y-3">
            <div>
              <label className="mb-1 block text-xs font-medium text-dark-400">
                {t('admin.infoPages.faq.question')}
              </label>
              <input
                type="text"
                value={item.q}
                onChange={(e) => handleQuestionChange(index, e.target.value)}
                className="input text-sm"
                placeholder={t('admin.infoPages.faq.questionPlaceholder')}
              />
            </div>
            <div>
              <label className="mb-1 block text-xs font-medium text-dark-400">
                {t('admin.infoPages.faq.answer')}
              </label>
              <FaqAnswerEditor
                value={item.a}
                onChange={(html) => handleAnswerChange(index, html)}
              />
            </div>
          </div>
        </div>
      ))}

      <button
        type="button"
        onClick={handleAdd}
        className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-xl border border-dashed border-dark-600 bg-dark-800/30 py-3 text-sm font-medium text-dark-300 transition-colors hover:border-dark-500 hover:bg-dark-800/50 hover:text-dark-100"
      >
        <PlusSmallIcon className="h-5 w-5" />
        {t('admin.infoPages.faq.addQuestion')}
      </button>
    </div>
  );
}

export default function AdminInfoPageEditor() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const { id: rawId } = useParams<{ id: string }>();
  const [searchParams] = useSearchParams();
  const queryClient = useQueryClient();
  const haptic = useHapticFeedback();
  const pageId = rawId != null ? Number(rawId) : undefined;
  const isEdit = pageId != null && !Number.isNaN(pageId);
  const initialPageType = (searchParams.get('type') === 'faq' ? 'faq' : 'page') as InfoPageType;

  // Form state
  const [slug, setSlug] = useState('');
  const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);
  const [icon, setIcon] = useState('');
  const [isActive, setIsActive] = useState(true);
  const [sortOrder, setSortOrder] = useState(0);
  const [pageType, setPageType] = useState<InfoPageType>(initialPageType);
  const [replacesTab, setReplacesTab] = useState<ReplacesTab | null>(null);
  const [saveError, setSaveError] = useState<string | null>(null);

  // FAQ Q&A state per locale
  const [faqItems, setFaqItems] = useState<Record<string, FaqItem[]>>({});

  // Multi-locale state
  const [activeLocale, setActiveLocale] = useState<LocaleCode>('ru');
  const [titles, setTitles] = useState<Record<string, string>>({});
  const [contents, setContents] = useState<Record<string, string>>({});

  // Media upload state
  const mediaInputRef = useRef<HTMLInputElement>(null);
  const [uploadCount, setUploadCount] = useState(0);
  const isUploading = uploadCount > 0;
  const [isDragging, setIsDragging] = useState(false);
  const activeUploadsRef = useRef(new Set<AbortController>());

  const handleMediaUploadRef = useRef<(file: File) => void>(() => {});

  // TipTap editor
  const extensions = useMemo(
    () => [
      StarterKit.configure({
        heading: { levels: [1, 2, 3] },
        link: false,
        underline: false,
      }),
      UnderlineExtension,
      LinkExtension.configure({
        openOnClick: false,
        HTMLAttributes: { class: 'link' },
      }),
      ImageExtension.configure({
        HTMLAttributes: { class: 'rounded-xl max-w-full' },
      }),
      PlaceholderExtension.configure({
        placeholder: t('admin.infoPages.fields.content'),
      }),
      TextAlignExtension.configure({
        types: ['heading', 'paragraph'],
      }),
      HighlightExtension,
      VideoExtension,
    ],
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [],
  );

  const editor = useEditor({
    extensions,
    editorProps: {
      attributes: {
        class: 'prose max-w-none min-h-[300px] p-4 focus:outline-none',
      },
      handlePaste: (_view, event) => {
        const items = event.clipboardData?.items;
        if (!items) return false;

        for (const item of Array.from(items)) {
          if (item.type.startsWith('image/') || item.type.startsWith('video/')) {
            const file = item.getAsFile();
            if (file) {
              handleMediaUploadRef.current(file);
              return true;
            }
          }
        }
        return false;
      },
      handleDrop: (_view, event) => {
        const file = event.dataTransfer?.files[0];
        if (file && (file.type.startsWith('image/') || file.type.startsWith('video/'))) {
          event.preventDefault();
          handleMediaUploadRef.current(file);
          return true;
        }
        return false;
      },
    },
  });

  // --- Media upload handlers ---

  const handleMediaUpload = useCallback(
    async (file: File) => {
      if (!editor) return;

      const isImage = file.type.startsWith('image/');
      const isVideo = file.type.startsWith('video/');
      if (!isImage && !isVideo) return;

      const maxSize = isImage ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
      if (file.size > maxSize) {
        haptic.error();
        setSaveError(t('news.admin.fileTooLarge'));
        return;
      }

      const controller = new AbortController();
      activeUploadsRef.current.add(controller);
      setUploadCount((c) => c + 1);

      try {
        const result = await newsApi.uploadMedia(file, controller.signal);
        if (controller.signal.aborted) return;

        if (!isSafeUrl(result.url)) {
          haptic.error();
          setSaveError(t('news.admin.uploadError'));
          return;
        }

        if (result.media_type === 'image') {
          editor.chain().focus().setImage({ src: result.url, alt: file.name }).run();
        } else {
          editor
            .chain()
            .focus()
            .insertContent({
              type: 'video',
              attrs: { src: result.url, class: 'w-full rounded-xl max-h-96' },
            })
            .run();
        }
        haptic.success();
      } catch {
        if (controller.signal.aborted) return;
        haptic.error();
        setSaveError(t('news.admin.uploadError'));
      } finally {
        activeUploadsRef.current.delete(controller);
        setUploadCount((c) => c - 1);
      }
    },
    [editor, haptic, t],
  );

  useEffect(() => {
    handleMediaUploadRef.current = handleMediaUpload;
  }, [handleMediaUpload]);

  useEffect(() => {
    const uploads = activeUploadsRef.current;
    return () => {
      for (const controller of uploads) {
        controller.abort();
      }
      uploads.clear();
    };
  }, []);

  const handleFileInputChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0];
      if (file) handleMediaUpload(file);
      e.target.value = '';
    },
    [handleMediaUpload],
  );

  const handleEditorDragOver = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    setIsDragging(true);
  }, []);

  const handleEditorDragLeave = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    setIsDragging(false);
  }, []);

  const handleEditorDrop = useCallback(
    (e: React.DragEvent) => {
      setIsDragging(false);
      if (e.defaultPrevented) return;
      e.preventDefault();
      const file = e.dataTransfer.files[0];
      if (file) handleMediaUpload(file);
    },
    [handleMediaUpload],
  );

  // Fetch all admin pages to detect tab conflicts
  const { data: allAdminPages } = useQuery({
    queryKey: ['admin', 'info-pages', 'list', 'all'],
    queryFn: () => infoPagesApi.getAdminPages(),
    staleTime: 30_000,
  });

  // Fetch page for editing
  const { data: pageData, isLoading: isLoadingPage } = useQuery({
    queryKey: ['admin', 'info-pages', 'page', pageId],
    queryFn: () => {
      if (pageId == null) throw new Error('Missing page id parameter');
      return infoPagesApi.getAdminPage(pageId);
    },
    enabled: isEdit,
    staleTime: 0,
    gcTime: 0,
    refetchOnMount: true,
    refetchOnWindowFocus: false,
  });

  // Populate form when page data loads (once only -- not on locale switch)
  const editorPopulated = useRef(false);
  const formPopulated = useRef(false);
  useEffect(() => {
    if (!pageData || formPopulated.current) return;
    setSlug(pageData.slug);
    setSlugManuallyEdited(true);
    setIcon(pageData.icon ?? '');
    setIsActive(pageData.is_active);
    setSortOrder(pageData.sort_order);
    setPageType(pageData.page_type ?? 'page');
    setReplacesTab(pageData.replaces_tab ?? null);
    setTitles(pageData.title);

    if (pageData.page_type === 'faq') {
      // For FAQ pages, content stores Q&A arrays per locale
      const parsed: Record<string, FaqItem[]> = {};
      for (const [loc, val] of Object.entries(pageData.content)) {
        try {
          const arr = typeof val === 'string' ? JSON.parse(val) : val;
          parsed[loc] = Array.isArray(arr) ? arr : [];
        } catch {
          parsed[loc] = [];
        }
      }
      setFaqItems(parsed);
      setContents({});
    } else {
      setContents(pageData.content);
    }

    formPopulated.current = true;
  }, [pageData]);

  // Set editor content once when editor is ready
  useEffect(() => {
    if (!pageData || !editor || editorPopulated.current) return;
    const initialContent = pageData.content[activeLocale] ?? pageData.content['ru'] ?? '';
    editor.commands.setContent(initialContent);
    editorPopulated.current = true;
    // activeLocale intentionally omitted — initial content is locked at editor
    // mount; subsequent locale changes are routed through switchLocale().
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pageData, editor]);

  // Auto-generate slug from Russian title
  useEffect(() => {
    if (!slugManuallyEdited && titles['ru']) {
      setSlug(generateSlug(titles['ru']));
    }
  }, [titles, slugManuallyEdited]);

  // --- Locale switching ---
  const switchLocale = useCallback(
    (newLocale: LocaleCode) => {
      if (!editor) return;
      // Save current editor content
      const currentHtml = editor.getHTML();
      const isEmpty = currentHtml === '<p></p>' || currentHtml === '';
      setContents((prev) => ({
        ...prev,
        [activeLocale]: isEmpty ? '' : currentHtml,
      }));
      // Load new locale content
      const newContent = contents[newLocale] ?? '';
      editor.commands.setContent(newContent);
      setActiveLocale(newLocale);
    },
    [editor, activeLocale, contents],
  );

  // Save mutation
  const saveMutation = useMutation({
    mutationFn: (data: {
      slug: string;
      title: Record<string, string>;
      content: Record<string, string>;
      page_type: InfoPageType;
      is_active: boolean;
      sort_order: number;
      icon: string | null;
      replaces_tab: ReplacesTab | null;
    }) => {
      if (isEdit && pageId != null) {
        return infoPagesApi.updatePage(pageId, data);
      }
      return infoPagesApi.createPage(data);
    },
    onSuccess: () => {
      haptic.success();
      queryClient.invalidateQueries({ queryKey: ['admin', 'info-pages'] });
      queryClient.invalidateQueries({ queryKey: ['info-pages'] });
      navigate('/admin/info-pages');
    },
    onError: (error: Error) => {
      haptic.error();
      setSaveError(error.message || t('admin.infoPages.saveError'));
    },
  });

  const handleSave = () => {
    setSaveError(null);
    if (!slug.trim()) return;

    let finalContents: Record<string, string>;

    if (pageType === 'faq') {
      // Serialize FAQ items as JSON strings per locale
      finalContents = {};
      for (const [loc, items] of Object.entries(faqItems)) {
        finalContents[loc] = JSON.stringify(items);
      }
    } else {
      // Capture current editor content for the active locale
      const currentHtml = editor?.getHTML() ?? '';
      const isEmpty = currentHtml === '<p></p>' || currentHtml === '';
      finalContents = {
        ...contents,
        [activeLocale]: isEmpty ? '' : currentHtml,
      };
    }

    const data = {
      slug: slug.trim(),
      title: titles,
      content: finalContents,
      page_type: pageType,
      is_active: isActive,
      sort_order: sortOrder,
      icon: icon.trim() || null,
      replaces_tab: replacesTab,
    };

    haptic.buttonPress();
    saveMutation.mutate(data);
  };

  // Toolbar actions
  const promptDialog = usePrompt();
  const addLink = async () => {
    if (!editor) return;
    const url = await promptDialog({
      label: t('news.admin.toolbar.linkUrlPrompt'),
      inputType: 'url',
    });
    if (!url) return;
    try {
      const parsed = new URL(url);
      if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return;
    } catch {
      return;
    }
    editor.chain().focus().setLink({ href: url }).run();
  };

  if (isEdit && isLoadingPage) {
    return (
      <div className="space-y-6">
        <div className="skeleton h-8 w-48 rounded-lg" />
        <div className="skeleton h-12 w-full rounded-xl" />
        <div className="skeleton h-64 w-full rounded-xl" />
      </div>
    );
  }

  return (
    <div className="space-y-6">
      {/* Header */}
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          <AdminBackButton to="/admin/info-pages" />
          <h1 className="text-xl font-bold text-dark-100">
            {isEdit ? t('admin.infoPages.edit') : t('admin.infoPages.create')}
          </h1>
        </div>
        <button
          onClick={handleSave}
          disabled={saveMutation.isPending || !slug.trim()}
          className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
        >
          {saveMutation.isPending ? t('admin.infoPages.saving') : t('admin.infoPages.save')}
        </button>
      </div>

      {/* Form */}
      <div className="space-y-5">
        {/* Slug */}
        <div>
          <label htmlFor="ip-slug" className="label">
            {t('admin.infoPages.fields.slug')}
          </label>
          <input
            id="ip-slug"
            type="text"
            value={slug}
            onChange={(e) => {
              setSlug(e.target.value);
              setSlugManuallyEdited(true);
            }}
            className="input font-mono text-sm"
            required
          />
        </div>

        {/* Icon + Sort Order row */}
        <div className="grid gap-4 sm:grid-cols-2">
          <div>
            <label htmlFor="ip-icon" className="label">
              {t('admin.infoPages.fields.icon')}
            </label>
            <input
              id="ip-icon"
              type="text"
              value={icon}
              onChange={(e) => setIcon(e.target.value)}
              className="input"
              placeholder="📄"
            />
          </div>
          <div>
            <label htmlFor="ip-sortorder" className="label">
              {t('admin.infoPages.fields.sortOrder')}
            </label>
            <input
              id="ip-sortorder"
              type="number"
              value={sortOrder}
              onChange={(e) => setSortOrder(Number(e.target.value) || 0)}
              min={0}
              className="input max-w-xs"
            />
          </div>
        </div>

        {/* Active toggle */}
        <div className="flex items-center gap-3">
          <Toggle
            checked={isActive}
            onChange={() => setIsActive((v) => !v)}
            aria-label={t('admin.infoPages.fields.isActive')}
          />
          <span className="text-sm text-dark-300">{t('admin.infoPages.fields.isActive')}</span>
        </div>

        {/* Page type selector */}
        <div>
          <label id="ip-pagetype-label" className="label">
            {t('admin.infoPages.fields.pageType')}
          </label>
          <div className="flex gap-1" role="radiogroup" aria-labelledby="ip-pagetype-label">
            {(['page', 'faq'] as const).map((pt) => (
              <button
                key={pt}
                type="button"
                role="radio"
                aria-checked={pageType === pt}
                onClick={() => setPageType(pt)}
                className={cn(
                  'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
                  pageType === pt
                    ? pt === 'faq'
                      ? 'bg-warning-500 text-white'
                      : 'bg-accent-500 text-white'
                    : 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
                )}
              >
                {t(`admin.infoPages.pageTypes.${pt}`)}
              </button>
            ))}
          </div>
        </div>

        {/* Replaces tab selector */}
        <div>
          <label htmlFor="ip-replaces-tab" className="label">
            {t('admin.infoPages.fields.replacesTab')}
          </label>
          <select
            id="ip-replaces-tab"
            value={replacesTab ?? ''}
            onChange={(e) => setReplacesTab((e.target.value || null) as ReplacesTab | null)}
            className="input max-w-xs"
          >
            <option value="">{t('admin.infoPages.replacesTabNone')}</option>
            {(['faq', 'rules', 'privacy', 'offer'] as const).map((tab) => {
              const conflict = allAdminPages?.find(
                (p) => p.replaces_tab === tab && p.id !== pageId,
              );
              return (
                <option key={tab} value={tab}>
                  {t(`admin.infoPages.replacesTabOptions.${tab}`)}
                  {conflict ? ` (${t('admin.infoPages.replacesTabConflict')})` : ''}
                </option>
              );
            })}
          </select>
          {replacesTab &&
            allAdminPages?.some((p) => p.replaces_tab === replacesTab && p.id !== pageId) && (
              <p className="mt-1 text-xs text-warning-400">
                {t('admin.infoPages.replacesTabWarning')}
              </p>
            )}
        </div>

        {/* Locale tabs */}
        <div>
          <label id="ip-locale-label" className="label">
            {t('admin.infoPages.localeLabel')}
          </label>
          <div className="flex flex-wrap gap-1" role="radiogroup" aria-labelledby="ip-locale-label">
            {AVAILABLE_LOCALES.map((loc) => (
              <button
                key={loc}
                type="button"
                role="radio"
                aria-checked={activeLocale === loc}
                onClick={() => switchLocale(loc)}
                className={cn(
                  'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
                  activeLocale === loc
                    ? 'bg-accent-500 text-white'
                    : 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
                )}
              >
                {t(`admin.infoPages.locales.${loc}`)}
              </button>
            ))}
          </div>
        </div>

        {/* Title for current locale */}
        <div>
          <label htmlFor="ip-title" className="label">
            {t('admin.infoPages.fields.title')} ({t(`admin.infoPages.locales.${activeLocale}`)})
          </label>
          <input
            id="ip-title"
            type="text"
            value={titles[activeLocale] ?? ''}
            onChange={(e) => setTitles((prev) => ({ ...prev, [activeLocale]: e.target.value }))}
            className="input"
          />
        </div>

        {/* Content: TipTap editor for pages, FAQ builder for FAQ */}
        {pageType === 'faq' ? (
          <FaqBuilder
            items={faqItems[activeLocale] ?? []}
            onChange={(items) => setFaqItems((prev) => ({ ...prev, [activeLocale]: items }))}
            locale={activeLocale}
            localeLabel={t(`admin.infoPages.locales.${activeLocale}`)}
          />
        ) : (
          <div>
            <label className="label">
              {t('admin.infoPages.fields.content')} ({t(`admin.infoPages.locales.${activeLocale}`)})
            </label>
            <div
              className="relative overflow-hidden rounded-xl border border-dark-700 bg-dark-800/50"
              onDragOver={handleEditorDragOver}
              onDragLeave={handleEditorDragLeave}
              onDrop={handleEditorDrop}
            >
              {/* Upload progress overlay */}
              {isUploading && (
                <div className="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-dark-900/60 backdrop-blur-sm">
                  <div className="flex flex-col items-center gap-3">
                    <div className="h-8 w-8 animate-spin rounded-full border-2 border-accent-400 border-t-transparent" />
                    <span className="text-sm font-medium text-dark-200">
                      {t('news.admin.uploading')}
                    </span>
                  </div>
                </div>
              )}

              {/* Drag overlay */}
              {isDragging && !isUploading && (
                <div className="absolute inset-0 z-10 flex items-center justify-center rounded-xl border-2 border-dashed border-accent-400 bg-accent-400/10">
                  <span className="text-sm font-semibold text-accent-400">
                    {t('news.admin.dropMedia')}
                  </span>
                </div>
              )}

              {/* Toolbar */}
              {editor && (
                <div className="flex flex-wrap items-center gap-0.5 border-b border-dark-700 bg-dark-800 p-2">
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleBold().run()}
                    isActive={editor.isActive('bold')}
                    title={t('news.admin.toolbar.bold')}
                  >
                    <BoldIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleItalic().run()}
                    isActive={editor.isActive('italic')}
                    title={t('news.admin.toolbar.italic')}
                  >
                    <ItalicIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleUnderline().run()}
                    isActive={editor.isActive('underline')}
                    title={t('news.admin.toolbar.underline')}
                  >
                    <UnderlineIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleStrike().run()}
                    isActive={editor.isActive('strike')}
                    title={t('news.admin.toolbar.strikethrough')}
                  >
                    <StrikeIcon className="h-4 w-4" />
                  </ToolbarButton>

                  <div className="mx-1 h-5 w-px bg-dark-700" />

                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
                    isActive={editor.isActive('heading', { level: 1 })}
                    title={t('news.admin.toolbar.heading1')}
                  >
                    <H1Icon />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
                    isActive={editor.isActive('heading', { level: 2 })}
                    title={t('news.admin.toolbar.heading2')}
                  >
                    <H2Icon />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
                    isActive={editor.isActive('heading', { level: 3 })}
                    title={t('news.admin.toolbar.heading3')}
                  >
                    <H3Icon />
                  </ToolbarButton>

                  <div className="mx-1 h-5 w-px bg-dark-700" />

                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleBulletList().run()}
                    isActive={editor.isActive('bulletList')}
                    title={t('news.admin.toolbar.bulletList')}
                  >
                    <ListBulletIcon />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleOrderedList().run()}
                    isActive={editor.isActive('orderedList')}
                    title={t('news.admin.toolbar.orderedList')}
                  >
                    <ListOrderedIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleBlockquote().run()}
                    isActive={editor.isActive('blockquote')}
                    title={t('news.admin.toolbar.blockquote')}
                  >
                    <QuoteIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleCodeBlock().run()}
                    isActive={editor.isActive('codeBlock')}
                    title={t('news.admin.toolbar.codeBlock')}
                  >
                    <CodeBlockIcon className="h-4 w-4" />
                  </ToolbarButton>

                  <div className="mx-1 h-5 w-px bg-dark-700" />

                  <ToolbarButton
                    onClick={() => editor.chain().focus().setTextAlign('left').run()}
                    isActive={editor.isActive({ textAlign: 'left' })}
                    title={t('news.admin.toolbar.alignLeft')}
                  >
                    <AlignLeftIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => editor.chain().focus().setTextAlign('center').run()}
                    isActive={editor.isActive({ textAlign: 'center' })}
                    title={t('news.admin.toolbar.alignCenter')}
                  >
                    <AlignCenterIcon className="h-4 w-4" />
                  </ToolbarButton>

                  <div className="mx-1 h-5 w-px bg-dark-700" />

                  <ToolbarButton
                    onClick={() => editor.chain().focus().toggleHighlight().run()}
                    isActive={editor.isActive('highlight')}
                    title={t('news.admin.toolbar.highlight')}
                  >
                    <HighlightIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton onClick={addLink} title={t('news.admin.toolbar.link')}>
                    <LinkIcon className="h-4 w-4" />
                  </ToolbarButton>
                  <ToolbarButton
                    onClick={() => mediaInputRef.current?.click()}
                    disabled={isUploading}
                    title={t('news.admin.toolbar.image')}
                  >
                    {isUploading ? (
                      <div className="h-4 w-4 animate-spin rounded-full border-2 border-accent-400 border-t-transparent" />
                    ) : (
                      <ImageIcon className="h-4 w-4" />
                    )}
                  </ToolbarButton>
                </div>
              )}

              {/* Editor content */}
              <EditorContent editor={editor} />
            </div>

            {/* Hidden file inputs */}
            <input
              ref={mediaInputRef}
              type="file"
              accept="image/jpeg,image/png,image/webp,video/mp4,video/webm"
              onChange={handleFileInputChange}
              className="hidden"
              aria-hidden="true"
            />
          </div>
        )}

        {/* Error feedback */}
        {saveError && (
          <div className="rounded-lg border border-error-500/30 bg-error-500/10 px-4 py-3 text-sm text-error-400">
            {saveError}
          </div>
        )}

        {/* Bottom save button */}
        <button
          onClick={handleSave}
          disabled={saveMutation.isPending || !slug.trim()}
          className="min-h-[44px] w-full rounded-lg bg-accent-500 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
        >
          {saveMutation.isPending ? t('admin.infoPages.saving') : t('admin.infoPages.save')}
        </button>
      </div>
    </div>
  );
}
