import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useOutletContext, useLocation } from 'react-router-dom';
import {
  Button,
  Space,
  Spin,
  Grid,
  Result,
  Tooltip,
  theme,
  Tree,
  Input,
  Modal,
  message,
  Dropdown,
  Menu,
  Typography,
  Segmented,
} from 'antd';
import type { TreeDataNode } from 'antd';
import type { MenuProps } from 'antd';
import {
  ReloadOutlined,
  ColumnWidthOutlined,
  CodeOutlined,
  EyeOutlined,
  ExportOutlined,
  SaveOutlined,
  FileAddOutlined,
  FolderAddOutlined,
  FileMarkdownOutlined,
  DeleteOutlined,
  EditOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  SearchOutlined,
  NodeExpandOutlined,
  NodeCollapseOutlined,
  BoldOutlined,
  ItalicOutlined,
  StrikethroughOutlined,
  HighlightOutlined,
  AlertOutlined,
  PlusOutlined,
  DownOutlined,
  LinkOutlined,
  PictureOutlined,
  TableOutlined,
  FontSizeOutlined,
  BuildOutlined,
  HolderOutlined,
  QuestionCircleOutlined,
  UploadOutlined,
  InboxOutlined,
  PlayCircleOutlined,
  HeartOutlined,
  CrownOutlined,
  ShoppingCartOutlined,
  MobileOutlined,
  DesktopOutlined,
  CalendarOutlined,
  FormOutlined,
  ShareAltOutlined,
  LockOutlined,
  HistoryOutlined,
  FolderOpenOutlined,
} from '@ant-design/icons';
import Editor from '@monaco-editor/react';
import type { OnMount } from '@monaco-editor/react';
import type { editor as monacoEditor } from 'monaco-editor';
import { api } from '@/lib/api';
import { buildServiceUrl } from '@/lib/service-url';
import { useMkDocsBuild } from '@/hooks/useMkDocsBuild';
import { useDocsEditor } from '@/hooks/useDocsEditor';
import { MobileDocsEditor } from '@/components/docs/MobileDocsEditor';
import { MoveToModal } from '@/components/docs/MoveToModal';
import type { FileNode, ServicesConfig } from '@/types/api';
import type { AppOutletContext } from '@/components/AppLayout';
import { VideoPickerModal } from '@/components/media/VideoPickerModal';
import type { Video as PickerVideo } from '@/components/media/VideoPickerModal';
import { PhotoPickerModal } from '@/components/media/PhotoPickerModal';
import type { Photo as PickerPhoto } from '@/components/media/PhotoPickerModal';
import { PhotoInsertModal } from '@/components/media/PhotoInsertModal';
import type { PhotoInsertResult } from '@/components/media/PhotoInsertModal';
import { generateVideoCardHtml } from '@/utils/videoCardHtml';
import { generatePhotoCardHtml } from '@/utils/photoCardHtml';
import { DonateInsertModal } from '@/components/payments/DonateInsertModal';
import type { DonateInsertResult } from '@/components/payments/DonateInsertModal';
import { ProductInsertModal } from '@/components/payments/ProductInsertModal';
import type { ProductInsertResult } from '@/components/payments/ProductInsertModal';
import { AdPickerModal } from '@/components/media/AdPickerModal';
import type { AdInsertResult } from '@/components/media/AdPickerModal';
import { PollInsertModal } from '@/components/scheduling/PollInsertModal';
import { useDocsCollaboration } from '@/hooks/useDocsCollaboration';
import { CollaboratorAvatars } from '@/components/docs/CollaboratorAvatars';
import { MonacoBinding } from 'y-monaco';
import type { SiteSettings } from '@/types/api';
import { registerWikiLinkCompletion } from '@/utils/wikiLinkCompletion';
import { WikiLinkPickerModal } from '@/components/docs/WikiLinkPickerModal';
// Blog authoring
import { useBlogFrontmatter } from '@/hooks/useBlogFrontmatter';
import { useBlogAuthors } from '@/hooks/useBlogAuthors';
import { useBlogCategories } from '@/hooks/useBlogCategories';
import { BlogFrontmatterPanel } from '@/components/docs/BlogFrontmatterPanel';
import { NewBlogPostModal } from '@/components/docs/NewBlogPostModal';
import { AuthorsManagementModal } from '@/components/docs/AuthorsManagementModal';
// Access policies & sharing
import { DocAccessPolicyPanel } from '@/components/docs/DocAccessPolicyPanel';
import { DocSharePanel } from '@/components/docs/DocSharePanel';
// Version history
import { DocHistoryDrawer } from '@/components/docs/DocHistoryDrawer';

type LayoutMode = 'split' | 'editor' | 'preview';
type PreviewMode = 'desktop' | 'mobile';

const LAYOUT_STORAGE_KEY = 'docs-editor-layout';
const DIVIDER_STORAGE_KEY = 'docs-editor-split';
const TREE_COLLAPSED_KEY = 'docs-tree-collapsed';
const TREE_WIDTH_KEY = 'docs-tree-width';
const TREE_CACHE_KEY = 'docs-tree-cache';
const TREE_CACHE_TIMESTAMP_KEY = 'docs-tree-cache-timestamp';
const TREE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const DEFAULT_TREE_WIDTH = 200;
const MIN_TREE_WIDTH = 160;
const MAX_TREE_WIDTH = 400;

const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico']);

function isImageFile(filePath: string): boolean {
  const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
  return IMAGE_EXTENSIONS.has(ext);
}

function filePathToMkDocsUrl(filePath: string): string {
  let url = filePath.replace(/\.md$/, '');
  if (url.endsWith('/index') || url === 'index') {
    url = url.replace(/\/?index$/, '');
  }
  return '/mkdocs-proxy/' + url + (url ? '/' : '');
}

// Tree cache helpers
function getCachedTree(): FileNode[] | null {
  try {
    const cached = localStorage.getItem(TREE_CACHE_KEY);
    const timestamp = localStorage.getItem(TREE_CACHE_TIMESTAMP_KEY);
    if (!cached || !timestamp) return null;

    const age = Date.now() - parseInt(timestamp, 10);
    if (age > TREE_CACHE_TTL) {
      // Cache expired
      localStorage.removeItem(TREE_CACHE_KEY);
      localStorage.removeItem(TREE_CACHE_TIMESTAMP_KEY);
      return null;
    }

    return JSON.parse(cached) as FileNode[];
  } catch {
    return null;
  }
}

function setCachedTree(tree: FileNode[]): void {
  try {
    localStorage.setItem(TREE_CACHE_KEY, JSON.stringify(tree));
    localStorage.setItem(TREE_CACHE_TIMESTAMP_KEY, Date.now().toString());
  } catch {
    // Ignore storage errors
  }
}

function invalidateTreeCache(): void {
  try {
    localStorage.removeItem(TREE_CACHE_KEY);
    localStorage.removeItem(TREE_CACHE_TIMESTAMP_KEY);
  } catch {
    // Ignore storage errors
  }
}

// URL Preview Bar Component (shows full clickable URLs above iframe)
const URLPreviewBar = ({
  filePath,
  config,
  previewMode,
  onPreviewModeChange,
}: {
  filePath: string | null;
  config: ServicesConfig | null;
  previewMode: PreviewMode;
  onPreviewModeChange: (mode: PreviewMode) => void;
}) => {
  const { token } = theme.useToken();

  // Only show for markdown files
  if (!filePath || !filePath.endsWith('.md')) return null;

  // Transform file path to URL path
  let urlPath = filePath.replace(/\.md$/, '');
  if (urlPath.endsWith('/index') || urlPath === 'index') {
    urlPath = urlPath.replace(/\/?index$/, '');
  }
  const suffix = urlPath ? `/${urlPath}/` : '/';

  const hostname = window.location.hostname;
  const isRealDomain = hostname.includes('.');

  // Build URLs — production is the static site at root domain, not the dev preview
  const productionUrl = config
    ? `${window.location.protocol}//${config.domain}${suffix}`
    : null;
  const localhostUrl = config
    ? `http://localhost:${config.mkdocsPort}${suffix}`
    : null;

  const openUrl = (url: string) => {
    window.open(url, '_blank', 'noopener,noreferrer');
  };

  const urlLinkStyle: React.CSSProperties = {
    fontFamily: 'monospace',
    fontSize: 11,
    color: token.colorPrimary,
    cursor: 'pointer',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
    maxWidth: 360,
    display: 'inline-flex',
    alignItems: 'center',
    gap: 4,
  };

  return (
    <div
      style={{
        height: 32,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        padding: '0 8px',
        background: token.colorBgElevated,
        borderBottom: `1px solid ${token.colorBorderSecondary}`,
        flexShrink: 0,
        overflow: 'hidden',
      }}
    >
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, overflow: 'hidden', minWidth: 0 }}>
        {productionUrl && (
          <Tooltip title={productionUrl} mouseEnterDelay={0.3}>
            <span style={urlLinkStyle} onClick={() => openUrl(productionUrl)}>
              <ExportOutlined style={{ fontSize: 10, flexShrink: 0 }} />
              {productionUrl}
            </span>
          </Tooltip>
        )}

        {/* Only show localhost URL when on localhost (on real domain they resolve the same) */}
        {!isRealDomain && localhostUrl && (
          <Tooltip title={localhostUrl} mouseEnterDelay={0.3}>
            <span style={urlLinkStyle} onClick={() => openUrl(localhostUrl)}>
              <ExportOutlined style={{ fontSize: 10, flexShrink: 0 }} />
              {localhostUrl}
            </span>
          </Tooltip>
        )}
      </div>

      <Segmented
        size="small"
        value={previewMode}
        onChange={(val) => onPreviewModeChange(val as PreviewMode)}
        options={[
          { value: 'desktop', icon: <DesktopOutlined /> },
          { value: 'mobile', icon: <MobileOutlined /> },
        ]}
        style={{ flexShrink: 0 }}
      />
    </div>
  );
};

function fileNodeToTreeData(nodes: FileNode[]): TreeDataNode[] {
  return nodes.map((node) => {
    const displayName = !node.isDirectory && node.name.endsWith('.md')
      ? node.name.slice(0, -3)
      : node.name;

    const treeNode: TreeDataNode = {
      key: node.path,
      title: displayName,
      isLeaf: !node.isDirectory,
    };
    if (node.isDirectory && node.children) {
      treeNode.children = fileNodeToTreeData(node.children);
    }
    return treeNode;
  });
}

/** Collect all keys (dirs + files) for expand-all on filter */
function collectAllDirKeys(nodes: FileNode[]): string[] {
  const keys: string[] = [];
  for (const node of nodes) {
    if (node.isDirectory) {
      keys.push(node.path);
      if (node.children) keys.push(...collectAllDirKeys(node.children));
    }
  }
  return keys;
}

/** Filter tree to only show nodes matching the query (+ their parent dirs) */
function filterTree(nodes: FileNode[], query: string): FileNode[] {
  const q = query.toLowerCase();
  const filtered: FileNode[] = [];
  for (const node of nodes) {
    if (node.isDirectory) {
      const childMatches = node.children ? filterTree(node.children, query) : [];
      if (childMatches.length > 0 || node.name.toLowerCase().includes(q)) {
        filtered.push({ ...node, children: childMatches.length > 0 ? childMatches : node.children });
      }
    } else {
      if (node.name.toLowerCase().includes(q)) {
        filtered.push(node);
      }
    }
  }
  return filtered;
}

// --- MkDocs Snippet System (shared) ---
import { SNIPPETS, applySnippet as applySnippetShared } from '@/components/docs/mkdocs-snippets';
import type { MkDocsSnippet } from '@/components/docs/mkdocs-snippets';

// --- Inline Donate Block Generator ---
// Produces HTML with data-role attributes for hydration by payment-widgets.js.
// No inline <script> tags — all interactivity provided by the MkDocs payment-widgets.js script.

interface InlineDonateOpts {
  uid: string;          // unique suffix to prevent DOM ID conflicts
  heading: string;
  description: string;
  amounts: number[];    // cents
  preSelectedCents: number | null;
  showAmountPicker: boolean;
}

function generateInlineDonateHtml(o: InlineDonateOpts): string {
  const id = `cm-donate-${o.uid}`;

  const inputStyle = 'width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;';
  const submitBtnStyle = 'width:100%;padding:14px 24px;background:#eb2f96;color:#fff;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:pointer;';

  // Amount buttons HTML (for full card)
  let amountButtonsHtml = '';
  if (o.showAmountPicker) {
    const btns = o.amounts.map((cents) => {
      const dollars = (cents / 100).toFixed(0);
      return `<button type="button" class="cm-amt-btn" data-cents="${cents}">$${dollars}</button>`;
    }).join('\n    ');
    amountButtonsHtml = `
  <div data-role="amounts" style="margin-bottom: 16px;">
    ${btns}
    <button type="button" class="cm-amt-btn" data-cents="custom">Custom</button>
  </div>
  <div data-role="custom-wrap" style="display:none;margin-bottom:12px;">
    <input type="number" data-role="custom-input" min="1" step="1" placeholder="Enter amount ($)" style="${inputStyle}" />
  </div>`;
  }

  const formDisplay = o.showAmountPicker ? 'none' : 'block';
  const amountsAttr = o.amounts.join(',');

  const lines = [
    `<div id="${id}" data-heading="${o.heading}" data-amounts="${amountsAttr}" data-preselected="${o.preSelectedCents || ''}" data-show-picker="${o.showAmountPicker}" style="text-align:center;padding:40px 20px;background:linear-gradient(135deg,#2d1b69,#1a1a2e);border-radius:12px;margin:16px 0;max-width:560px;margin-left:auto;margin-right:auto;">`,
    `  <p style="font-size:48px;margin:0;">&#x2764;&#xFE0F;</p>`,
    `  <h2 style="color:#fff;margin:12px 0;">${o.heading}</h2>`,
    `  <p style="color:rgba(255,255,255,0.8);margin-bottom:24px;">${o.description}</p>`,
    amountButtonsHtml,
    `  <div data-role="form" style="display:${formDisplay};max-width:360px;margin:0 auto;text-align:left;">`,
    `    <input type="email" data-role="email" placeholder="your@email.com *" style="${inputStyle}" required />`,
    `    <input type="text" data-role="name" placeholder="Name (optional)" style="${inputStyle}" />`,
    `    <div style="margin-bottom:12px;color:rgba(255,255,255,0.7);font-size:0.85rem;">`,
    `      <label><input type="checkbox" data-role="anonymous" style="margin-right:6px;" />Make my donation anonymous</label>`,
    `    </div>`,
    `    <div data-role="error" style="color:#ff4d4f;font-size:0.9rem;margin-bottom:8px;display:none;"></div>`,
    `    <button type="button" data-role="submit" style="${submitBtnStyle}">`,
    o.preSelectedCents ? `Donate $${(o.preSelectedCents / 100).toFixed(0)}` : 'Donate',
    `    </button>`,
    `    <p style="margin-top:12px;font-size:0.75rem;color:rgba(255,255,255,0.4);text-align:center;">`,
    `      Secure payment via Stripe.`,
    `    </p>`,
    `  </div>`,
    `  <div class="cm-payment-fallback"><a href="/donate${o.preSelectedCents ? `?amount=${o.preSelectedCents}` : ''}" style="background:#eb2f96;">Donate Now</a></div>`,
    `  <noscript><a href="/donate${o.preSelectedCents ? `?amount=${o.preSelectedCents}` : ''}" style="display:inline-block;padding:14px 36px;background:#eb2f96;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;">Donate Now</a></noscript>`,
    `</div>`,
  ];

  return lines.join('\n');
}

// --- Inline Product Block Generator ---
// Produces HTML with data-role attributes for hydration by payment-widgets.js.

interface InlineProductOpts {
  uid: string;
  productId: string;
  title: string;
  description: string;
  priceCents: number;
  type: string;
  imageUrl: string | null;
  isSoldOut: boolean;
}

function generateInlineProductHtml(o: InlineProductOpts): string {
  const id = `cm-product-${o.uid}`;
  const priceStr = `$${(o.priceCents / 100).toFixed(2)}`;
  const typeColor = o.type === 'EVENT' ? '#52c41a' : o.type === 'DONATION' ? '#722ed1' : '#1890ff';

  const inputStyle = 'width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;';
  const buyBtnStyle = o.isSoldOut
    ? 'width:100%;padding:14px 24px;background:#555;color:#999;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:not-allowed;'
    : 'width:100%;padding:14px 24px;background:#722ed1;color:#fff;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:pointer;';

  const imageHtml = o.imageUrl
    ? `<div style="width:100%;height:180px;border-radius:8px;overflow:hidden;margin-bottom:16px;"><img src="${o.imageUrl}" alt="${o.title}" style="width:100%;height:100%;object-fit:cover;" /></div>`
    : `<div style="width:80px;height:80px;border-radius:12px;background:linear-gradient(135deg,#9d4edd,#722ed1);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;"><span style="font-size:36px;color:#fff;">&#x1F6D2;</span></div>`;

  const lines = [
    `<div id="${id}" data-product-id="${o.productId}" data-price-label="${priceStr}" style="text-align:center;padding:32px 20px;background:linear-gradient(135deg,#1a1a2e,#16213e);border-radius:12px;margin:16px 0;max-width:420px;margin-left:auto;margin-right:auto;">`,
    `  ${imageHtml}`,
    `  <div style="display:inline-block;padding:2px 10px;border-radius:4px;background:${typeColor};color:#fff;font-size:11px;font-weight:600;margin-bottom:8px;">${o.type}</div>`,
    o.isSoldOut ? `  <div style="display:inline-block;padding:2px 10px;border-radius:4px;background:#ff4d4f;color:#fff;font-size:11px;font-weight:600;margin-bottom:8px;margin-left:6px;">SOLD OUT</div>` : '',
    `  <h3 style="color:#fff;margin:8px 0 4px;">${o.title}</h3>`,
    o.description ? `  <p style="color:rgba(255,255,255,0.7);font-size:0.9rem;margin-bottom:12px;">${o.description}</p>` : '',
    `  <p style="color:#fff;font-size:1.4rem;font-weight:700;margin-bottom:20px;">${priceStr}</p>`,
  ];

  if (o.isSoldOut) {
    lines.push(
      `  <button disabled style="${buyBtnStyle}">Sold Out</button>`,
      `</div>`,
    );
    return lines.filter(Boolean).join('\n');
  }

  lines.push(
    `  <div data-role="form" style="max-width:320px;margin:0 auto;text-align:left;">`,
    `    <input type="email" data-role="email" placeholder="your@email.com *" style="${inputStyle}" required />`,
    `    <input type="text" data-role="name" placeholder="Name (optional)" style="${inputStyle}" />`,
    `    <div data-role="error" style="color:#ff4d4f;font-size:0.9rem;margin-bottom:8px;display:none;"></div>`,
    `    <button type="button" data-role="submit" style="${buyBtnStyle}">Buy Now &mdash; ${priceStr}</button>`,
    `    <p style="margin-top:12px;font-size:0.75rem;color:rgba(255,255,255,0.4);text-align:center;">`,
    `      Secure payment via Stripe.`,
    `    </p>`,
    `  </div>`,
    `  <div class="cm-payment-fallback"><a href="/shop" style="background:#722ed1;">View in Shop</a></div>`,
    `  <noscript><a href="/shop" style="display:inline-block;padding:14px 36px;background:#722ed1;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;">View in Shop</a></noscript>`,
    `</div>`,
  );

  return lines.filter(Boolean).join('\n');
}

const applySnippet = applySnippetShared;

/** Wrapper component so useDocsEditor() hook only runs on mobile */
function MobileDocsEditorWrapper() {
  const editor = useDocsEditor();
  const [collabEnabled, setCollabEnabled] = useState(false);
  useEffect(() => {
    api.get<SiteSettings>('/settings')
      .then(({ data }) => setCollabEnabled(!!data.enableDocsCollaboration))
      .catch(() => {});
  }, []);
  return <MobileDocsEditor editor={editor} collabEnabled={collabEnabled} />;
}

export default function DocsPage() {
  const { setPageHeader } = useOutletContext<AppOutletContext>();
  const location = useLocation();
  const screens = Grid.useBreakpoint();
  const isMobile = !screens.md;
  const { token } = theme.useToken();
  const { building, confirmAndBuild, isSuperAdmin } = useMkDocsBuild();

  const [fileTree, setFileTree] = useState<FileNode[]>(() => getCachedTree() || []);
  const [config, setConfig] = useState<ServicesConfig | null>(null);
  const [loading, setLoading] = useState(true);
  const [fetchError, setFetchError] = useState(false);
  const [selectedFile, setSelectedFile] = useState<string | null>(null);
  const [fileContent, setFileContent] = useState<string>('');
  const [originalContent, setOriginalContent] = useState<string>('');
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [fileLoading, setFileLoading] = useState(false);
  const [fileContentCache, setFileContentCache] = useState<Map<string, string>>(new Map());
  const [layout, setLayout] = useState<LayoutMode>(
    () => (localStorage.getItem(LAYOUT_STORAGE_KEY) as LayoutMode) || 'split',
  );
  const [previewMode, setPreviewMode] = useState<PreviewMode>('desktop');
  const [splitPercent, setSplitPercent] = useState<number>(
    () => Number(localStorage.getItem(DIVIDER_STORAGE_KEY)) || 50,
  );
  const [treeCollapsed, setTreeCollapsed] = useState<boolean>(
    () => localStorage.getItem(TREE_COLLAPSED_KEY) === 'true',
  );
  const [treeWidth, setTreeWidth] = useState<number>(
    () => Number(localStorage.getItem(TREE_WIDTH_KEY)) || DEFAULT_TREE_WIDTH,
  );
  const [filterQuery, setFilterQuery] = useState('');
  const [filterVisible, setFilterVisible] = useState(false);

  // Modal state for new file/folder/rename
  const [modalType, setModalType] = useState<'newFile' | 'newFolder' | 'rename' | null>(null);
  const [modalInput, setModalInput] = useState('');
  const [contextPath, setContextPath] = useState<string>('');

  const [moveToModalOpen, setMoveToModalOpen] = useState(false);
  const [moveSourcePath, setMoveSourcePath] = useState('');

  const [videoPickerOpen, setVideoPickerOpen] = useState(false);
  const [photoPickerOpen, setPhotoPickerOpen] = useState(false);
  const [photoInsertOpen, setPhotoInsertOpen] = useState(false);
  const [pendingPhotoVariant, setPendingPhotoVariant] = useState<PhotoInsertResult | null>(null);
  const [donateInsertOpen, setDonateInsertOpen] = useState(false);
  const [productInsertOpen, setProductInsertOpen] = useState(false);
  const [adPickerOpen, setAdPickerOpen] = useState(false);
  const [pollInsertOpen, setPollInsertOpen] = useState(false);
  const [wikiLinkPickerOpen, setWikiLinkPickerOpen] = useState(false);
  // New feature panels
  const [newBlogPostOpen, setNewBlogPostOpen] = useState(false);
  const [authorsModalOpen, setAuthorsModalOpen] = useState(false);
  const [accessPolicyOpen, setAccessPolicyOpen] = useState(false);
  const [sharePanelOpen, setSharePanelOpen] = useState(false);
  const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false);
  const [blogPanelCollapsed, setBlogPanelCollapsed] = useState(
    () => localStorage.getItem('docs-blog-panel-collapsed') === 'true',
  );
  const [dragOver, setDragOver] = useState(false);
  const dragCounter = useRef(0);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const containerRef = useRef<HTMLDivElement>(null);
  const dragging = useRef<'split' | 'tree' | false>(false);
  const editorRef = useRef<HTMLDivElement>(null);
  const previewIframeRef = useRef<HTMLIFrameElement>(null);
  const monacoEditorRef = useRef<monacoEditor.IStandaloneCodeEditor | null>(null);
  const monacoRef = useRef<typeof import('monaco-editor') | null>(null);
  const monacoBindingRef = useRef<MonacoBinding | null>(null);
  const fileTreeRef = useRef<FileNode[]>([]);
  const [editorReady, setEditorReady] = useState(false);

  // --- Collaboration state ---
  const [collabEnabled, setCollabEnabled] = useState(false);
  useEffect(() => {
    api.get<SiteSettings>('/settings')
      .then(({ data }) => setCollabEnabled(!!data.enableDocsCollaboration))
      .catch(() => {});
  }, []);

  const collab = useDocsCollaboration(
    selectedFile?.endsWith('.md') ? selectedFile : null,
    collabEnabled,
  );

  // Blog hooks
  const blogFrontmatter = useBlogFrontmatter(selectedFile, fileContent);
  const blogAuthors = useBlogAuthors();
  const blogCategories = useBlogCategories();

  const [messageApi, contextHolder] = message.useMessage();

  // Keep fileTreeRef in sync for Monaco autocomplete callback
  useEffect(() => { fileTreeRef.current = fileTree; }, [fileTree]);

  // Fetch file tree
  const fetchTree = useCallback(async (showLoading = true, force = false) => {
    try {
      if (showLoading) setLoading(true);
      setFetchError(false);
      const url = force ? '/docs/files?force=true' : '/docs/files';
      const res = await api.get<FileNode[]>(url);
      setFileTree(res.data);
      setCachedTree(res.data);
    } catch {
      setFetchError(true);
    } finally {
      if (showLoading) setLoading(false);
    }
  }, []);

  const fetchConfig = useCallback(async () => {
    try {
      const res = await api.get<ServicesConfig>('/services/config');
      setConfig(res.data);
    } catch {
      // Config fetch failed — leave null
    }
  }, []);

  const fetchData = useCallback(async () => {
    const cached = getCachedTree();
    if (cached) {
      // Show cached tree immediately
      setFileTree(cached);
      setLoading(false);
      // Refresh in background
      fetchTree(false);
    } else {
      // No cache - show loading
      setLoading(true);
      setFetchError(false);
      await fetchTree(true);
    }
    // Fetch config in parallel
    fetchConfig();
  }, [fetchTree, fetchConfig]);

  useEffect(() => { fetchData(); }, [fetchData]);
  useEffect(() => { localStorage.setItem(LAYOUT_STORAGE_KEY, layout); }, [layout]);
  useEffect(() => { localStorage.setItem(DIVIDER_STORAGE_KEY, String(splitPercent)); }, [splitPercent]);
  useEffect(() => { localStorage.setItem(TREE_COLLAPSED_KEY, String(treeCollapsed)); }, [treeCollapsed]);
  useEffect(() => { localStorage.setItem(TREE_WIDTH_KEY, String(treeWidth)); }, [treeWidth]);

  // Load file content when selected
  const loadFile = useCallback(async (filePath: string) => {
    // Check cache first
    const cached = fileContentCache.get(filePath);
    if (cached !== undefined) {
      setFileContent(cached);
      setOriginalContent(cached);
      setSelectedFile(filePath);
      setDirty(false);
      if (previewIframeRef.current && filePath.endsWith('.md')) {
        previewIframeRef.current.src = filePathToMkDocsUrl(filePath);
      }
      return;
    }

    // Cache miss - fetch from API
    setFileLoading(true);
    try {
      const res = await api.get<{ path: string; content: string }>(`/docs/files/${filePath}`);
      const content = res.data.content;

      // Update cache
      setFileContentCache(prev => new Map(prev).set(filePath, content));

      setFileContent(content);
      setOriginalContent(content);
      setSelectedFile(filePath);
      setDirty(false);
      if (previewIframeRef.current && filePath.endsWith('.md')) {
        previewIframeRef.current.src = filePathToMkDocsUrl(filePath);
      }
    } catch {
      messageApi.error('Failed to load file');
    } finally {
      setFileLoading(false);
    }
  }, [fileContentCache, messageApi]);

  // Handle navigation state from command palette or metadata page — auto-select a file
  useEffect(() => {
    const state = location.state as { selectFile?: string; openFile?: string } | null;
    const selectFile = state?.selectFile || state?.openFile;
    if (!selectFile || loading) return;

    // Expand parent directories so the file is visible in the tree
    const parts = selectFile.split('/');
    if (parts.length > 1) {
      const parentKeys: string[] = [];
      for (let i = 1; i < parts.length; i++) {
        parentKeys.push(parts.slice(0, i).join('/'));
      }
      setExpandedKeys(prev => {
        const set = new Set(prev.map(String));
        for (const k of parentKeys) set.add(k);
        return Array.from(set);
      });
    }

    loadFile(selectFile);
    // Clear navigation state so it doesn't re-trigger on revisit
    window.history.replaceState({}, '');
  }, [location.state, loading, loadFile]);

  // Save file
  const saveFile = useCallback(async () => {
    if (!selectedFile || !dirty) return;
    setSaving(true);
    try {
      await api.put(`/docs/files/${selectedFile}`, { content: fileContent });
      setOriginalContent(fileContent);
      setDirty(false);

      // Update cache with new content
      setFileContentCache(prev => new Map(prev).set(selectedFile, fileContent));

      messageApi.success('Saved');
      setTimeout(() => {
        if (previewIframeRef.current) {
          previewIframeRef.current.contentWindow?.location.reload();
        }
      }, 500);
    } catch {
      messageApi.error('Failed to save');
    } finally {
      setSaving(false);
    }
  }, [selectedFile, dirty, fileContent, messageApi]);

  // Ctrl+S keyboard shortcut
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        e.preventDefault();
        if (collab.active) {
          // In collab mode, explicitly save current content + refresh preview
          if (selectedFile && collab.yText) {
            const content = collab.yText.toString();
            api.put(`/docs/files/${selectedFile}`, { content })
              .then(() => messageApi.success('Saved'))
              .catch(() => messageApi.error('Save failed'));
          }
          // Refresh preview with cache-buster
          if (previewIframeRef.current && selectedFile) {
            const url = filePathToMkDocsUrl(selectedFile);
            setTimeout(() => {
              if (previewIframeRef.current) {
                previewIframeRef.current.src = url + '?_t=' + Date.now();
              }
            }, 1500);
          }
        } else {
          saveFile();
        }
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [saveFile, collab.active]);

  const onEditorChange = useCallback((value: string | undefined) => {
    const v = value ?? '';
    setFileContent(v);
    setDirty(v !== originalContent);
  }, [originalContent]);

  const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null);

  const handleEditorMount: OnMount = useCallback((ed, monaco) => {
    monacoEditorRef.current = ed;
    monacoRef.current = monaco;
    setEditorReady(true);

    // Register keyboard shortcuts only (no context menu items — we use a custom menu)
    SNIPPETS.filter(s => s.keybinding).forEach(snippet => {
      const kb = snippet.keybinding === 'ctrl+b'
        ? monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyB
        : monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyI;
      ed.addAction({
        id: `mkdocs.${snippet.id}`,
        label: snippet.label,
        keybindings: [kb],
        run: (editor) => applySnippet(editor as monacoEditor.IStandaloneCodeEditor, snippet, monaco),
      });
    });

    // Register wiki-link [[ autocomplete
    registerWikiLinkCompletion(monaco, () => fileTreeRef.current);

    // Custom right-click context menu (replaces Monaco's flat list)
    const domNode = ed.getDomNode();
    if (domNode) {
      domNode.addEventListener('contextmenu', (e) => {
        e.preventDefault();
        e.stopPropagation();
        setCtxMenu({ x: e.clientX, y: e.clientY });
      });
    }
  }, []);

  // --- MonacoBinding effect: binds Y.Text to Monaco editor when both are ready ---
  useEffect(() => {
    // Clean up any existing binding
    if (monacoBindingRef.current) {
      monacoBindingRef.current.destroy();
      monacoBindingRef.current = null;
    }

    const ed = monacoEditorRef.current;
    if (!collab.active || !collab.yText || !ed || !collab.provider) return;

    const model = ed.getModel();
    if (!model) return;

    // Create the MonacoBinding — this syncs Y.Text ↔ Monaco model
    // Remote cursors/selections are rendered automatically
    const binding = new MonacoBinding(
      collab.yText,
      model,
      new Set([ed]),
      collab.provider.awareness,
    );
    monacoBindingRef.current = binding;

    return () => {
      binding.destroy();
      monacoBindingRef.current = null;
    };
  }, [collab.active, collab.yText, collab.provider, selectedFile, editorReady]);

  // Auto-refresh preview when remote changes arrive in collab mode
  useEffect(() => {
    if (!collab.active || !collab.yText || !selectedFile) return;
    let refreshTimer: ReturnType<typeof setTimeout>;
    const observer = () => {
      clearTimeout(refreshTimer);
      refreshTimer = setTimeout(() => {
        // Re-set src to force reload (avoids cross-origin issues with contentWindow.reload)
        if (previewIframeRef.current && selectedFile) {
          const url = filePathToMkDocsUrl(selectedFile);
          // Append cache-buster to force fresh load
          const buster = `_t=${Date.now()}`;
          const sep = url.includes('?') ? '&' : '?';
          previewIframeRef.current.src = url + sep + buster;
        }
      }, 2500);
    };
    collab.yText.observe(observer);
    return () => {
      collab.yText?.unobserve(observer);
      clearTimeout(refreshTimer);
    };
  }, [collab.active, collab.yText, selectedFile]);

  const handleToolbarSnippet = useCallback((snippetId: string) => {
    if (snippetId === 'video-card') {
      setVideoPickerOpen(true);
      return;
    }

    if (snippetId === 'photo-insert') {
      setPhotoInsertOpen(true);
      return;
    }

    // Donate button — opens variant picker modal
    if (snippetId === 'donate-button') {
      setDonateInsertOpen(true);
      return;
    }

    // Product card — opens product picker modal
    if (snippetId === 'product-card') {
      setProductInsertOpen(true);
      return;
    }

    // Ad — opens ad picker modal
    if (snippetId === 'ad-insert') {
      setAdPickerOpen(true);
      return;
    }

    // Scheduling poll — opens picker modal
    if (snippetId === 'scheduling-poll') {
      setPollInsertOpen(true);
      return;
    }

    // Wiki link — opens file picker or inserts [[ for autocomplete
    if (snippetId === 'wiki-link') {
      setWikiLinkPickerOpen(true);
      return;
    }

    // Pricing table — static CTA (plans are dynamic, so link out)
    if (snippetId === 'pricing-table') {
      const appUrl = config
        ? `${window.location.protocol}//${config.domain.replace(/^([^.]+)/, 'app')}`
        : window.location.origin;
      const html = `<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; margin: 16px 0;">\n  <h2 style="color: #fff; margin: 12px 0;">Choose Your Plan</h2>\n  <p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Get access to exclusive content and features.</p>\n  <a href="${appUrl}/pricing" style="display: inline-block; padding: 14px 36px; background: #722ed1; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">View Plans</a>\n</div>`;
      const ed = monacoEditorRef.current;
      if (ed) {
        const sel = ed.getSelection();
        if (sel) {
          ed.executeEdits('payment-block-insert', [{ range: sel, text: '\n' + html + '\n' }]);
        }
      }
      return;
    }

    const snippet = SNIPPETS.find(s => s.id === snippetId);
    if (!snippet || !monacoEditorRef.current || !monacoRef.current) return;
    applySnippet(monacoEditorRef.current, snippet, monacoRef.current);
  }, [config]);

  const handleVideoCardInsert = useCallback((video: PickerVideo) => {
    const adminUrl = config
      ? `${window.location.protocol}//${config.domain.replace(/^([^.]+)/, 'app')}`
      : window.location.origin;

    // Use a placeholder thumbnail — video-player.js will hydrate with
    // the correct MEDIA_API_URL at runtime (avoids Docker hostname issues)
    const placeholderThumb = 'data:image/svg+xml,' + encodeURIComponent(
      '<svg xmlns="http://www.w3.org/2000/svg" width="480" height="270" viewBox="0 0 480 270">' +
      '<rect fill="#0d1b2a" width="480" height="270"/>' +
      '<circle cx="240" cy="135" r="32" fill="rgba(157,78,221,0.6)"/>' +
      '<polygon points="230,118 258,135 230,152" fill="#fff"/>' +
      '</svg>'
    );

    const html = generateVideoCardHtml({
      id: video.id,
      title: video.title,
      durationSeconds: video.durationSeconds || 0,
      quality: '',
      viewCount: 0,
      thumbnailUrl: placeholderThumb,
    }, { baseUrl: adminUrl });

    const ed = monacoEditorRef.current;
    if (ed) {
      const sel = ed.getSelection();
      if (sel) {
        ed.executeEdits('video-card-insert', [{ range: sel, text: '\n' + html + '\n' }]);
      }
    }
    setVideoPickerOpen(false);
  }, [config]);

  const handlePhotoInsert = useCallback((result: PhotoInsertResult) => {
    if (result.variant === 'single-photo' || result.variant === 'photo-card') {
      // Photo variants: save result then open photo picker
      setPendingPhotoVariant(result);
      setPhotoPickerOpen(true);
      return;
    }

    // Album variants: generate HTML directly
    const album = result.album;
    if (!album) return;

    let html = '';
    if (result.variant === 'album-grid') {
      const cols = result.options.columns || 3;
      const max = result.options.maxPhotos || 12;
      const title = result.options.showTitle !== false ? 'true' : 'false';
      html = `<div class="photo-album-block" data-album-id="${album.id}" data-columns="${cols}" data-max-photos="${max}" data-show-title="${title}">Loading album...</div>`;
    } else if (result.variant === 'album-carousel') {
      const max = result.options.maxPhotos || 20;
      const title = result.options.showTitle !== false ? 'true' : 'false';
      const auto = result.options.autoPlay ? 'true' : 'false';
      html = `<div class="photo-album-carousel" data-album-id="${album.id}" data-max-photos="${max}" data-show-title="${title}" data-auto-play="${auto}">Loading carousel...</div>`;
    }

    const ed = monacoEditorRef.current;
    if (ed && html) {
      const sel = ed.getSelection();
      if (sel) {
        ed.executeEdits('photo-album-insert', [{ range: sel, text: '\n' + html + '\n' }]);
      }
    }
  }, []);

  const handlePhotoSelected = useCallback((photo: PickerPhoto) => {
    const variant = pendingPhotoVariant?.variant || 'photo-card';

    if (variant === 'single-photo') {
      // Generate a photo-block div for hydration
      const opts = pendingPhotoVariant?.options || {};
      const sizeAttr = opts.size || 'large';
      const alignAttr = opts.alignment || 'center';
      const linkAttr = opts.linkToGallery !== false ? 'true' : 'false';
      const html = `<div class="photo-block" data-photo-id="${photo.id}" data-size="${sizeAttr}" data-caption="" data-link-to-gallery="${linkAttr}" data-alignment="${alignAttr}">Loading...</div>`;

      const ed = monacoEditorRef.current;
      if (ed) {
        const sel = ed.getSelection();
        if (sel) {
          ed.executeEdits('photo-block-insert', [{ range: sel, text: '\n' + html + '\n' }]);
        }
      }
    } else {
      // photo-card variant — use existing generatePhotoCardHtml
      const adminUrl = config
        ? `${window.location.protocol}//${config.domain.replace(/^([^.]+)/, 'app')}`
        : window.location.origin;

      const placeholderThumb = 'data:image/svg+xml,' + encodeURIComponent(
        '<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320" viewBox="0 0 480 320">' +
        '<rect fill="#0d1b2a" width="480" height="320"/>' +
        '<circle cx="240" cy="160" r="32" fill="rgba(46,125,50,0.6)"/>' +
        '<rect x="224" y="144" width="32" height="32" rx="4" fill="none" stroke="#fff" stroke-width="2"/>' +
        '<circle cx="234" cy="154" r="3" fill="#fff"/>' +
        '<path d="M256 176l-10-10L224 176" fill="none" stroke="#fff" stroke-width="2"/>' +
        '</svg>'
      );

      const html = generatePhotoCardHtml({
        id: photo.id,
        title: photo.title || photo.originalFilename || 'Untitled Photo',
        description: photo.description || undefined,
        showMetadata: true,
        format: photo.format || undefined,
        width: photo.width || undefined,
        height: photo.height || undefined,
        viewCount: photo.viewCount || 0,
        thumbnailUrl: placeholderThumb,
      }, { baseUrl: adminUrl });

      const ed = monacoEditorRef.current;
      if (ed) {
        const sel = ed.getSelection();
        if (sel) {
          ed.executeEdits('photo-card-insert', [{ range: sel, text: '\n' + html + '\n' }]);
        }
      }
    }

    setPhotoPickerOpen(false);
    setPendingPhotoVariant(null);
  }, [config, pendingPhotoVariant]);

  const handleDonateInsert = useCallback((result: DonateInsertResult) => {
    const uid = Date.now().toString(36);

    let html = '';

    if (result.variant === 'simple') {
      html = [
        '<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #2d1b69, #1a1a2e); border-radius: 12px; margin: 16px 0;">',
        '  <p style="font-size: 48px; margin: 0;">&#x2764;&#xFE0F;</p>',
        '  <h2 style="color: #fff; margin: 12px 0;">Support Our Cause</h2>',
        '  <p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Your contribution helps us create lasting change in our community.</p>',
        '  <a href="/donate" style="display: inline-block; padding: 14px 36px; background: #eb2f96; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">Donate Now</a>',
        '</div>',
      ].join('\n');
    } else if (result.variant === 'set-amount') {
      const cents = result.amount || 2500;
      const dollars = (cents / 100).toFixed(0);
      html = generateInlineDonateHtml({
        uid,
        heading: `Donate $${dollars}`,
        description: 'Make a meaningful impact with your generous contribution.',
        amounts: [cents],
        preSelectedCents: cents,
        showAmountPicker: false,
      });
    } else {
      const cfg = result.config;
      const title = cfg?.donationPageTitle || 'Support Our Cause';
      const desc = cfg?.donationPageDescription || 'Every contribution makes a difference. Choose an amount below.';
      const amounts = cfg?.donationSuggestedAmounts?.length
        ? cfg.donationSuggestedAmounts
        : [1000, 2500, 5000, 10000];
      html = generateInlineDonateHtml({
        uid,
        heading: title,
        description: desc,
        amounts,
        preSelectedCents: null,
        showAmountPicker: true,
      });
    }

    const ed = monacoEditorRef.current;
    if (ed) {
      const sel = ed.getSelection();
      if (sel) {
        ed.executeEdits('donate-block-insert', [{ range: sel, text: '\n' + html + '\n' }]);
      }
    }
  }, []);

  const handleProductInsert = useCallback((result: ProductInsertResult) => {
    const uid = Date.now().toString(36);
    const p = result.product;
    const isSoldOut = p.maxPurchases !== null && p.purchaseCount >= (p.maxPurchases ?? 0);

    const html = generateInlineProductHtml({
      uid,
      productId: p.id,
      title: p.title,
      description: p.description || '',
      priceCents: p.priceCAD,
      type: p.type,
      imageUrl: p.imageUrl,
      isSoldOut,
    });

    const ed = monacoEditorRef.current;
    if (ed) {
      const sel = ed.getSelection();
      if (sel) {
        ed.executeEdits('product-block-insert', [{ range: sel, text: '\n' + html + '\n' }]);
      }
    }
  }, []);

  const handleAdInsert = useCallback((result: AdInsertResult) => {
    let html = '';
    if (result.type === 'specific') {
      html = `<div class="ad-specific-block" data-ad-id="${result.adId}" style="max-width:400px; margin:16px auto;">\n  Loading ad...\n</div>`;
    } else {
      html = `<div class="ad-slot-block" data-placement="docs" data-variant="${result.variant || 'standard'}" style="max-width:400px; margin:16px auto;">\n  Loading ad...\n</div>`;
    }

    const ed = monacoEditorRef.current;
    if (ed) {
      const sel = ed.getSelection();
      if (sel) {
        ed.executeEdits('ad-block-insert', [{ range: sel, text: '\n' + html + '\n' }]);
      }
    }
    setAdPickerOpen(false);
  }, []);

  const handlePollInsert = useCallback((slug: string) => {
    const html = `<div class="scheduling-poll-block" data-poll-slug="${slug}" data-show-comments="true" data-title="Vote on a Meeting Time">\n  Loading poll...\n</div>`;

    const ed = monacoEditorRef.current;
    if (ed) {
      const sel = ed.getSelection();
      if (sel) {
        ed.executeEdits('poll-block-insert', [{ range: sel, text: '\n' + html + '\n' }]);
      }
    }
    setPollInsertOpen(false);
  }, []);

  const handleCtxMenuClick = useCallback((snippetId: string) => {
    setCtxMenu(null);
    handleToolbarSnippet(snippetId);
  }, [handleToolbarSnippet]);

  // Build context menu items with submenus
  const ctxMenuItems: MenuProps['items'] = useMemo(() => {
    const groupToSubmenu = (
      group: MkDocsSnippet['group'],
      label: string,
      icon: React.ReactNode,
    ): NonNullable<MenuProps['items']>[number] => ({
      key: group,
      label,
      icon,
      children: SNIPPETS.filter(s => s.group === group).map(s => ({
        key: s.id,
        label: s.keybinding === 'ctrl+b' ? `${s.label}    Ctrl+B`
             : s.keybinding === 'ctrl+i' ? `${s.label}    Ctrl+I`
             : s.label,
        onClick: () => handleCtxMenuClick(s.id),
      })),
    });
    return [
      groupToSubmenu('formatting', 'Formatting', <BoldOutlined />),
      groupToSubmenu('heading', 'Headings', <FontSizeOutlined />),
      groupToSubmenu('admonition', 'Admonitions', <AlertOutlined />),
      groupToSubmenu('code', 'Code', <CodeOutlined />),
      groupToSubmenu('insert', 'Insert', <PlusOutlined />),
    ];
  }, [handleCtxMenuClick]);

  // Close custom context menu on click outside or Escape
  useEffect(() => {
    if (!ctxMenu) return;
    const close = () => setCtxMenu(null);
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close(); };
    // Delay to avoid immediate close from the same click
    const timer = setTimeout(() => {
      window.addEventListener('click', close);
      window.addEventListener('contextmenu', close);
      window.addEventListener('keydown', onKey);
    }, 0);
    return () => {
      clearTimeout(timer);
      window.removeEventListener('click', close);
      window.removeEventListener('contextmenu', close);
      window.removeEventListener('keydown', onKey);
    };
  }, [ctxMenu]);

  // Select an image file (no text loading, just set selectedFile)
  const selectImageFile = useCallback((filePath: string) => {
    setSelectedFile(filePath);
    setFileContent('');
    setOriginalContent('');
    setDirty(false);
    if (previewIframeRef.current) {
      // Navigate preview to the image's parent page (if any)
      previewIframeRef.current.src = '/mkdocs-proxy/';
    }
  }, []);

  const onTreeSelect = useCallback(async (keys: React.Key[]) => {
    if (keys.length === 0) return;
    const path = keys[0] as string;

    // Image files — just select, no text loading
    if (isImageFile(path)) {
      if (dirty) {
        Modal.confirm({
          title: 'Unsaved Changes',
          content: `Save changes to ${selectedFile} before switching?`,
          okText: 'Save',
          cancelText: 'Discard',
          onOk: async () => { await saveFile(); selectImageFile(path); },
          onCancel: () => { setDirty(false); selectImageFile(path); },
        });
        return;
      }
      selectImageFile(path);
      return;
    }

    if (dirty) {
      Modal.confirm({
        title: 'Unsaved Changes',
        content: `Save changes to ${selectedFile} before switching?`,
        okText: 'Save',
        cancelText: 'Discard',
        onOk: async () => { await saveFile(); await loadFile(path); },
        onCancel: () => { setDirty(false); loadFile(path); },
      });
      return;
    }
    await loadFile(path);
  }, [dirty, selectedFile, saveFile, loadFile]);

  // --- Drag logic for both tree resize and editor/preview split ---
  const onSplitDividerDown = useCallback(() => {
    dragging.current = 'split';
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
    if (editorRef.current) editorRef.current.style.pointerEvents = 'none';
    if (previewIframeRef.current) previewIframeRef.current.style.pointerEvents = 'none';
  }, []);

  const onTreeDividerDown = useCallback(() => {
    dragging.current = 'tree';
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
    if (editorRef.current) editorRef.current.style.pointerEvents = 'none';
    if (previewIframeRef.current) previewIframeRef.current.style.pointerEvents = 'none';
  }, []);

  useEffect(() => {
    const onMouseMove = (e: MouseEvent) => {
      if (!dragging.current || !containerRef.current) return;
      const rect = containerRef.current.getBoundingClientRect();

      if (dragging.current === 'tree') {
        const w = e.clientX - rect.left;
        setTreeWidth(Math.min(MAX_TREE_WIDTH, Math.max(MIN_TREE_WIDTH, w)));
      } else if (dragging.current === 'split') {
        const currentTW = treeCollapsed ? 0 : treeWidth;
        const editorAreaLeft = rect.left + currentTW + 4; // +4 for tree divider
        const editorAreaWidth = rect.width - currentTW - 4;
        const pct = ((e.clientX - editorAreaLeft) / editorAreaWidth) * 100;
        setSplitPercent(Math.min(85, Math.max(15, pct)));
      }
    };
    const onMouseUp = () => {
      if (dragging.current) {
        dragging.current = false;
        document.body.style.cursor = '';
        document.body.style.userSelect = '';
        if (editorRef.current) editorRef.current.style.pointerEvents = '';
        if (previewIframeRef.current) previewIframeRef.current.style.pointerEvents = '';
      }
    };
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseup', onMouseUp);
    return () => {
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseup', onMouseUp);
    };
  }, [treeCollapsed, treeWidth]);

  const refreshPreview = useCallback(() => {
    if (previewIframeRef.current) {
      previewIframeRef.current.contentWindow?.location.reload();
    }
  }, []);

  const refreshTree = useCallback(() => {
    invalidateTreeCache();
    fetchTree(false, true);
  }, [fetchTree]);

  const handleUploadButtonClick = useCallback(() => {
    fileInputRef.current?.click();
  }, []);

  // File tree context menu
  const getContextMenuItems = useCallback((nodePath: string, isDirectory: boolean): MenuProps['items'] => {
    const items: MenuProps['items'] = [];
    if (isDirectory) {
      items.push(
        { key: 'newFile', icon: <FileAddOutlined />, label: 'New File', onClick: () => { setContextPath(nodePath); setModalInput(''); setModalType('newFile'); } },
        { key: 'newFolder', icon: <FolderAddOutlined />, label: 'New Folder', onClick: () => { setContextPath(nodePath); setModalInput(''); setModalType('newFolder'); } },
        { key: 'upload', icon: <UploadOutlined />, label: 'Upload File', onClick: () => { setSelectedFile(nodePath); handleUploadButtonClick(); } },
        { type: 'divider' },
      );
    }
    items.push(
      { key: 'rename', icon: <EditOutlined />, label: 'Rename', onClick: () => { setContextPath(nodePath); setModalInput(nodePath.split('/').pop() || ''); setModalType('rename'); } },
      { key: 'moveTo', icon: <FolderOpenOutlined />, label: 'Move to...', onClick: () => { setMoveSourcePath(nodePath); setMoveToModalOpen(true); } },
      { key: 'delete', icon: <DeleteOutlined />, label: 'Delete', danger: true, onClick: () => handleDelete(nodePath) },
    );
    return items;
  }, [handleUploadButtonClick]);

  const handleDelete = useCallback(async (filePath: string) => {
    Modal.confirm({
      title: 'Delete',
      content: `Are you sure you want to delete "${filePath}"?`,
      okText: 'Delete',
      okButtonProps: { danger: true },
      onOk: async () => {
        try {
          await api.delete(`/docs/files/${filePath}`);
          messageApi.success('Deleted');

          // Invalidate caches
          invalidateTreeCache();
          setFileContentCache(prev => {
            const next = new Map(prev);
            next.delete(filePath);
            return next;
          });

          if (selectedFile === filePath) {
            setSelectedFile(null);
            setFileContent('');
            setOriginalContent('');
            setDirty(false);
          }
          fetchTree();
        } catch {
          messageApi.error('Failed to delete');
        }
      },
    });
  }, [selectedFile, messageApi, fetchTree]);

  const handleModalOk = useCallback(async () => {
    if (!modalInput.trim()) return;
    try {
      if (modalType === 'newFile') {
        const name = modalInput.endsWith('.md') ? modalInput : `${modalInput}.md`;
        const path = contextPath ? `${contextPath}/${name}` : name;
        await api.post(`/docs/files/${path}`, { content: `# ${modalInput.replace(/\.md$/, '')}\n` });
        messageApi.success('File created');
        // Invalidate tree cache (structure changed)
        invalidateTreeCache();
        // Parallel loading for faster workflow
        await Promise.all([fetchTree(), loadFile(path)]);
      } else if (modalType === 'newFolder') {
        const path = contextPath ? `${contextPath}/${modalInput}` : modalInput;
        await api.post(`/docs/files/${path}`, { isDirectory: true });
        messageApi.success('Folder created');
        // Invalidate tree cache (structure changed)
        invalidateTreeCache();
        fetchTree();
      } else if (modalType === 'rename') {
        const parentDir = contextPath.includes('/') ? contextPath.substring(0, contextPath.lastIndexOf('/')) : '';
        const newPath = parentDir ? `${parentDir}/${modalInput}` : modalInput;
        await api.post('/docs/files/rename', { from: contextPath, to: newPath });
        messageApi.success('Renamed');

        // Invalidate tree cache (structure changed)
        invalidateTreeCache();

        // Update file content cache: move cached content from old path to new path
        setFileContentCache(prev => {
          const next = new Map(prev);
          const cached = next.get(contextPath);
          next.delete(contextPath);
          if (cached) next.set(newPath, cached);
          return next;
        });

        if (selectedFile === contextPath) setSelectedFile(newPath);
        fetchTree();
      }
    } catch {
      messageApi.error('Operation failed');
    }
    setModalType(null);
  }, [modalType, modalInput, contextPath, messageApi, fetchTree, loadFile, selectedFile]);

  const handleMoveFile = useCallback(async (sourcePath: string, targetDir: string) => {
    const fileName = sourcePath.split('/').pop() || '';
    const newPath = targetDir ? `${targetDir}/${fileName}` : fileName;
    if (newPath === sourcePath) return;
    if (sourcePath === targetDir || targetDir.startsWith(sourcePath + '/')) {
      messageApi.warning('Cannot move a folder into itself');
      return;
    }
    try {
      await api.post('/docs/files/rename', { from: sourcePath, to: newPath });
      messageApi.success(`Moved to ${targetDir || '/'}`);
      invalidateTreeCache();
      setFileContentCache(prev => {
        const next = new Map(prev);
        const cached = next.get(sourcePath);
        next.delete(sourcePath);
        if (cached) next.set(newPath, cached);
        const prefix = sourcePath + '/';
        for (const [key, val] of Array.from(next.entries())) {
          if (key.startsWith(prefix)) {
            next.delete(key);
            next.set(newPath + key.substring(sourcePath.length), val);
          }
        }
        return next;
      });
      if (selectedFile === sourcePath) {
        setSelectedFile(newPath);
      } else if (selectedFile && selectedFile.startsWith(sourcePath + '/')) {
        setSelectedFile(newPath + selectedFile.substring(sourcePath.length));
      }
      fetchTree();
    } catch {
      messageApi.error('Failed to move');
    }
  }, [selectedFile, messageApi, fetchTree]);

  const handleTreeNodeDrop = useCallback((info: {
    node: TreeDataNode;
    dragNode: TreeDataNode;
    dropToGap: boolean;
  }) => {
    const dragPath = info.dragNode.key as string;
    let targetDir: string;

    if (info.dropToGap) {
      const targetPath = info.node.key as string;
      const lastSlash = targetPath.lastIndexOf('/');
      targetDir = lastSlash >= 0 ? targetPath.substring(0, lastSlash) : '';
    } else {
      const targetPath = info.node.key as string;
      if (info.node.isLeaf) {
        const lastSlash = targetPath.lastIndexOf('/');
        targetDir = lastSlash >= 0 ? targetPath.substring(0, lastSlash) : '';
      } else {
        targetDir = targetPath;
      }
    }

    handleMoveFile(dragPath, targetDir);
  }, [handleMoveFile]);

  const handleNewFileRoot = useCallback(() => { setContextPath(''); setModalInput(''); setModalType('newFile'); }, []);
  const handleNewFolderRoot = useCallback(() => { setContextPath(''); setModalInput(''); setModalType('newFolder'); }, []);

  const mkdocsDirectUrl = config
    ? buildServiceUrl(config.mkdocsSubdomain, config.domain, config.mkdocsPort)
    : null;
  const toggleTree = useCallback(() => setTreeCollapsed(c => !c), []);
  const toggleFilter = useCallback(() => {
    setFilterVisible(v => {
      if (v) setFilterQuery(''); // clear filter when hiding
      return !v;
    });
  }, []);

  const toggleExpand = useCallback((key: string) => {
    setExpandedKeys(prev =>
      prev.includes(key)
        ? prev.filter(k => k !== key)
        : [...prev, key]
    );
  }, []);

  // Resize indicator component for dividers
  const ResizeIndicator = () => (
    <Tooltip title="Drag to resize" mouseEnterDelay={0.5}>
      <div
        style={{
          position: 'absolute',
          bottom: 8,
          left: '50%',
          transform: 'translateX(-50%)',
          width: 24,
          height: 24,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          borderRadius: 4,
          background: token.colorBgElevated,
          border: `1px solid ${token.colorBorderSecondary}`,
          color: token.colorTextTertiary,
          fontSize: 12,
          pointerEvents: 'none',
          opacity: 0.8,
          transition: 'opacity 0.2s',
        }}
      >
        <HolderOutlined />
      </div>
    </Tooltip>
  );

  // Header actions
  const headerActions = useMemo(() => (
    <Space size={8}>
      <Tooltip title="MkDocs Reference">
        <Button type="text" icon={<QuestionCircleOutlined />} onClick={() => window.open('https://squidfunk.github.io/mkdocs-material/reference/', '_blank', 'noopener,noreferrer')} size="middle" />
      </Tooltip>
      <div style={{ width: 1, height: 24, background: token.colorBorderSecondary, margin: '0 4px' }} />
      <Tooltip title="Editor + Preview">
        <Button type={layout === 'split' ? 'primary' : 'text'} icon={<ColumnWidthOutlined />} onClick={() => setLayout('split')} size="middle" />
      </Tooltip>
      <Tooltip title="Editor Only">
        <Button type={layout === 'editor' ? 'primary' : 'text'} icon={<CodeOutlined />} onClick={() => setLayout('editor')} size="middle" />
      </Tooltip>
      <Tooltip title="Preview Only">
        <Button type={layout === 'preview' ? 'primary' : 'text'} icon={<EyeOutlined />} onClick={() => setLayout('preview')} size="middle" />
      </Tooltip>
      <div style={{ width: 1, height: 24, background: token.colorBorderSecondary, margin: '0 4px' }} />
      {dirty && (
        <Tooltip title="Save (Ctrl+S)">
          <Button type="primary" icon={<SaveOutlined />} onClick={saveFile} loading={saving} size="middle">Save</Button>
        </Tooltip>
      )}
      <Tooltip title="Refresh Preview">
        <Button type="text" icon={<ReloadOutlined />} onClick={refreshPreview} size="middle" />
      </Tooltip>
      {mkdocsDirectUrl && (
        <Tooltip title="Open MkDocs in new tab">
          <Button type="text" icon={<ExportOutlined />} onClick={() => window.open(mkdocsDirectUrl, '_blank')} size="middle" />
        </Tooltip>
      )}
      {isSuperAdmin && (
        <>
          <div style={{ width: 1, height: 24, background: token.colorBorderSecondary, margin: '0 4px' }} />
          <Button type="primary" icon={<BuildOutlined />} onClick={confirmAndBuild} loading={building} size="middle">
            Build
          </Button>
        </>
      )}
    </Space>
  ), [layout, dirty, saving, saveFile, refreshPreview, mkdocsDirectUrl, token.colorBorderSecondary, isSuperAdmin, building, confirmAndBuild]);

  // Inject header
  useEffect(() => {
    if (!loading && !fetchError) {
      if (isMobile) {
        setPageHeader({ fullBleed: true });
      } else {
        setPageHeader({ title: 'Documentation', actions: headerActions, fullBleed: true });
      }
    } else {
      setPageHeader(null);
    }
  }, [setPageHeader, headerActions, isMobile, loading, fetchError]);

  useEffect(() => { return () => setPageHeader(null); }, [setPageHeader]);

  // Tree data — apply filter
  const filteredTree = useMemo(() => {
    if (!filterQuery.trim()) return fileTree;
    return filterTree(fileTree, filterQuery.trim());
  }, [fileTree, filterQuery]);

  const treeData = useMemo(() => fileNodeToTreeData(filteredTree), [filteredTree]);

  // When filtering, expand all; otherwise collapsed
  const expandedKeysForFilter = useMemo(() => {
    if (filterQuery.trim()) return collectAllDirKeys(filteredTree);
    return [];
  }, [filterQuery, filteredTree, fileTree]);

  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
  // Sync expanded keys when filter or tree changes
  useEffect(() => {
    setExpandedKeys(expandedKeysForFilter);
  }, [expandedKeysForFilter]);

  // Lookup whether a node path is a directory
  const isDirectoryPath = useCallback((path: string): boolean => {
    function find(nodes: FileNode[]): boolean {
      for (const node of nodes) {
        if (node.path === path) return node.isDirectory;
        if (node.isDirectory && node.children && find(node.children)) return true;
      }
      return false;
    }
    return find(fileTree);
  }, [fileTree]);

  const allExpanded = expandedKeys.length > 0;

  const toggleExpandAll = useCallback(() => {
    if (allExpanded) {
      setExpandedKeys([]);
    } else {
      setExpandedKeys(collectAllDirKeys(fileTree));
    }
  }, [allExpanded, fileTree]);

  // Upload files (images, pdfs, etc.) to the docs directory
  const handleUploadFiles = useCallback(async (files: FileList | File[]) => {
    const fileArray = Array.from(files);
    if (fileArray.length === 0) return;

    // Determine target directory: selected folder, or parent of selected file, or root
    let targetDir = '';
    if (selectedFile) {
      if (isDirectoryPath(selectedFile)) {
        targetDir = selectedFile;
      } else if (selectedFile.includes('/')) {
        targetDir = selectedFile.substring(0, selectedFile.lastIndexOf('/'));
      }
    }

    const hideLoading = messageApi.loading(`Uploading ${fileArray.length} file${fileArray.length > 1 ? 's' : ''}...`, 0);
    let successCount = 0;
    let lastMdPath: string | null = null;

    for (const file of fileArray) {
      try {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('path', targetDir);
        const res = await api.post<{ success: boolean; path: string }>('/docs/upload', formData);
        successCount++;
        if (file.name.endsWith('.md')) {
          lastMdPath = res.data.path;
        }
      } catch (err: unknown) {
        const msg = (err as { response?: { data?: { error?: { message?: string } } } })?.response?.data?.error?.message || 'Upload failed';
        messageApi.error(`Failed to upload ${file.name}: ${msg}`);
      }
    }

    hideLoading();

    if (successCount > 0) {
      messageApi.success(`Uploaded ${successCount} file${successCount > 1 ? 's' : ''}`);
      invalidateTreeCache();
      await fetchTree(false, true);
      if (lastMdPath) {
        await loadFile(lastMdPath);
      }
    }
  }, [selectedFile, isDirectoryPath, messageApi, fetchTree, loadFile]);

  // Drag-and-drop handlers for the file tree panel
  const handleTreeDragEnter = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    dragCounter.current++;
    if (e.dataTransfer.types.includes('Files')) {
      setDragOver(true);
    }
  }, []);

  const handleTreeDragOver = useCallback((e: React.DragEvent) => {
    if (!e.dataTransfer.types.includes('Files')) return;
    e.preventDefault();
    e.stopPropagation();
  }, []);

  const handleTreeDragLeave = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    dragCounter.current--;
    if (dragCounter.current <= 0) {
      dragCounter.current = 0;
      setDragOver(false);
    }
  }, []);

  const handleTreeDrop = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    dragCounter.current = 0;
    setDragOver(false);
    if (e.dataTransfer.files.length > 0) {
      handleUploadFiles(e.dataTransfer.files);
    }
  }, [handleUploadFiles]);

  const handleFileInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files && e.target.files.length > 0) {
      handleUploadFiles(e.target.files);
      e.target.value = ''; // Reset so same file can be re-selected
    }
  }, [handleUploadFiles]);

  if (isMobile) {
    return <MobileDocsEditorWrapper />;
  }

  if (loading) {
    return <div style={{ textAlign: 'center', padding: 80 }}><Spin size="large" /></div>;
  }

  if (fetchError) {
    return (
      <Result
        status="error"
        title="Cannot Load Editor"
        subTitle="Failed to connect to the documentation services. Please check that the API is running."
        extra={<Button type="primary" onClick={fetchData}>Retry</Button>}
      />
    );
  }

  const showEditor = layout === 'split' || layout === 'editor';
  const showPreview = layout === 'split' || layout === 'preview';

  return (
    <>
      {contextHolder}
      {/* Obsidian-style tight tree overrides */}
      <style>{`
        .docs-tree .ant-tree-treenode {
          padding: 0 !important;
          margin: 0 !important;
          min-height: 28px !important;
          line-height: 28px !important;
          border-radius: 0 !important;
          width: 100% !important;
          transition: background 0.05s !important;
        }
        .docs-tree .ant-tree-treenode:hover {
          background: rgba(255,255,255,0.06) !important;
        }
        .docs-tree .ant-tree-treenode-selected,
        .docs-tree .ant-tree-treenode-selected:hover {
          background: rgba(255,255,255,0.10) !important;
        }
        .docs-tree .ant-tree-node-content-wrapper {
          padding: 0 4px !important;
          min-height: 28px !important;
          line-height: 28px !important;
          border-radius: 0 !important;
          background: transparent !important;
        }
        .docs-tree .ant-tree-node-content-wrapper:hover {
          background: transparent !important;
        }
        .docs-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
          background: transparent !important;
        }
        .docs-tree .ant-tree-switcher {
          width: 18px !important;
          height: 28px !important;
          line-height: 28px !important;
        }
        .docs-tree .ant-tree-indent-unit {
          width: 12px !important;
        }
        .docs-tree .ant-tree-list-holder-inner {
          padding: 2px 0 !important;
        }
        .docs-tree .ant-tree-treenode.drag-over > .ant-tree-node-content-wrapper,
        .docs-tree .ant-tree-treenode.drop-target:not(.drag-over-gap-top):not(.drag-over-gap-bottom) > .ant-tree-node-content-wrapper {
          background: rgba(22, 119, 255, 0.15) !important;
          outline: 1px solid rgba(22, 119, 255, 0.4);
          border-radius: 3px !important;
        }
        .docs-tree .ant-tree-treenode.dragging {
          opacity: 0.4;
        }
        .docs-filter .ant-input-affix-wrapper {
          border-radius: 0 !important;
          border: none !important;
          border-bottom: 1px solid ${token.colorBorderSecondary} !important;
          box-shadow: none !important;
          background: transparent !important;
        }
        .docs-filter .ant-input {
          font-size: 11px !important;
          background: transparent !important;
        }
      `}</style>

      <div
        ref={containerRef}
        style={{
          display: 'flex',
          height: 'calc(100vh - 64px)',
          overflow: 'hidden',
        }}
      >
        {/* File Tree Panel */}
        {!treeCollapsed && (
          <>
            <div
              style={{
                width: treeWidth,
                minWidth: MIN_TREE_WIDTH,
                maxWidth: MAX_TREE_WIDTH,
                height: '100%',
                display: 'flex',
                flexDirection: 'column',
                background: token.colorBgContainer,
                flexShrink: 0,
                position: 'relative',
              }}
              onDragEnter={handleTreeDragEnter}
              onDragOver={handleTreeDragOver}
              onDragLeave={handleTreeDragLeave}
              onDrop={handleTreeDrop}
            >
              {/* Drag-and-drop overlay */}
              {dragOver && (
                <div
                  style={{
                    position: 'absolute',
                    inset: 0,
                    zIndex: 10,
                    background: `${token.colorPrimary}22`,
                    border: `2px dashed ${token.colorPrimary}`,
                    borderRadius: 4,
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    flexDirection: 'column',
                    gap: 8,
                    pointerEvents: 'none',
                  }}
                >
                  <InboxOutlined style={{ fontSize: 32, color: token.colorPrimary }} />
                  <Typography.Text style={{ color: token.colorPrimary, fontWeight: 600, fontSize: 13 }}>
                    Drop files here
                  </Typography.Text>
                </div>
              )}

              {/* Hidden file input for upload button fallback */}
              <input
                ref={fileInputRef}
                type="file"
                multiple
                accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico,.pdf,.zip"
                style={{ display: 'none' }}
                onChange={handleFileInputChange}
              />

              {/* Toolbar */}
              <div
                style={{
                  height: 36,
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'flex-end',
                  padding: '0 4px',
                  borderBottom: `1px solid ${token.colorBorderSecondary}`,
                  flexShrink: 0,
                  overflow: 'hidden',
                  flexWrap: 'nowrap',
                  gap: 1,
                }}
              >
                <Tooltip title="Filter" mouseEnterDelay={0.4}>
                  <Button
                    type="text"
                    size="small"
                    icon={<SearchOutlined />}
                    onClick={toggleFilter}
                    aria-label="Filter files"
                    style={{ width: 26, height: 26, minWidth: 26, color: filterVisible ? token.colorPrimary : token.colorTextSecondary }}
                  />
                </Tooltip>
                <Tooltip title="Refresh" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<ReloadOutlined />} onClick={refreshTree} aria-label="Refresh file tree" style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
                <Tooltip title={allExpanded ? 'Collapse All' : 'Expand All'} mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={allExpanded ? <NodeCollapseOutlined /> : <NodeExpandOutlined />} onClick={toggleExpandAll} aria-label={allExpanded ? 'Collapse all folders' : 'Expand all folders'} style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
                <Tooltip title="New File" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<FileAddOutlined />} onClick={handleNewFileRoot} aria-label="Create new file" style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
                <Tooltip title="New Folder" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<FolderAddOutlined />} onClick={handleNewFolderRoot} aria-label="Create new folder" style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
                <Tooltip title="New Blog Post" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<FormOutlined />} onClick={() => setNewBlogPostOpen(true)} aria-label="New blog post" style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
                <Tooltip title="Upload File" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<UploadOutlined />} onClick={handleUploadButtonClick} aria-label="Upload file" style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
                <Tooltip title="Hide Panel" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<MenuFoldOutlined />} onClick={toggleTree} aria-label="Hide file tree panel" style={{ width: 26, height: 26, minWidth: 26, color: token.colorTextSecondary }} />
                </Tooltip>
              </div>

              {/* Filter input (toggled) */}
              {filterVisible && (
                <div className="docs-filter">
                  <Input
                    prefix={<SearchOutlined style={{ color: token.colorTextQuaternary, fontSize: 11 }} />}
                    placeholder="Filter..."
                    allowClear
                    size="small"
                    value={filterQuery}
                    onChange={(e) => setFilterQuery(e.target.value)}
                    style={{ height: 26 }}
                    autoFocus
                  />
                </div>
              )}

              {/* Tree */}
              <Dropdown
                menu={{
                  items: [
                    { key: 'newFile', icon: <FileAddOutlined />, label: 'New File', onClick: handleNewFileRoot },
                    { key: 'newFolder', icon: <FolderAddOutlined />, label: 'New Folder', onClick: handleNewFolderRoot },
                    { type: 'divider' },
                    { key: 'upload', icon: <UploadOutlined />, label: 'Upload File', onClick: handleUploadButtonClick },
                    { key: 'refresh', icon: <ReloadOutlined />, label: 'Refresh', onClick: refreshTree },
                  ],
                }}
                trigger={['contextMenu']}
              >
              <div style={{ flex: 1, overflow: 'auto' }} className="docs-tree">
                <Tree
                  treeData={treeData}
                  showIcon={false}
                  showLine={false}
                  motion={false}
                  draggable={!filterQuery.trim() ? { icon: false, nodeDraggable: () => true } : false}
                  allowDrop={({ dragNode, dropNode }) => {
                    const dragPath = dragNode.key as string;
                    const dropPath = dropNode.key as string;
                    if (dragPath === dropPath) return false;
                    if (dropPath.startsWith(dragPath + '/')) return false;
                    return true;
                  }}
                  onDrop={handleTreeNodeDrop}
                  selectedKeys={selectedFile ? [selectedFile] : []}
                  expandedKeys={expandedKeys}
                  onExpand={(keys) => setExpandedKeys(keys)}
                  onSelect={(keys) => {
                    if (keys.length === 0) return;
                    const path = keys[0] as string;
                    if (isDirectoryPath(path)) return;
                    onTreeSelect(keys);
                  }}
                  blockNode
                  titleRender={(nodeData) => {
                    const nodePath = nodeData.key as string;
                    const isDir = isDirectoryPath(nodePath);
                    return (
                      <Dropdown
                        menu={{ items: getContextMenuItems(nodePath, isDir) }}
                        trigger={['contextMenu']}
                      >
                        <span
                          onClick={(e) => {
                            if (isDir) {
                              e.stopPropagation();
                              toggleExpand(nodePath);
                            }
                          }}
                          onContextMenu={(e) => e.stopPropagation()}
                          style={{
                            display: 'block',
                            overflow: 'hidden',
                            textOverflow: 'ellipsis',
                            whiteSpace: 'nowrap',
                            fontSize: 13,
                            lineHeight: '24px',
                            color: isDir ? token.colorTextSecondary : token.colorText,
                            cursor: isDir ? 'pointer' : 'default',
                          }}
                        >
                          {nodeData.title as string}
                        </span>
                      </Dropdown>
                    );
                  }}
                  style={{ fontSize: 13 }}
                />
              </div>
              </Dropdown>
            </div>

            {/* Tree resize handle */}
            <div
              onMouseDown={onTreeDividerDown}
              style={{
                width: 3,
                cursor: 'col-resize',
                background: 'transparent',
                flexShrink: 0,
                position: 'relative',
              }}
            >
              <div
                style={{
                  position: 'absolute',
                  inset: 0,
                  width: 1,
                  margin: '0 auto',
                  background: token.colorBorderSecondary,
                  transition: 'background 0.15s, width 0.15s',
                }}
                onMouseEnter={(e) => { (e.currentTarget.style.background = token.colorPrimary); (e.currentTarget.style.width = '3px'); }}
                onMouseLeave={(e) => { (e.currentTarget.style.background = token.colorBorderSecondary); (e.currentTarget.style.width = '1px'); }}
              />
              <ResizeIndicator />
            </div>
          </>
        )}

        {/* Collapsed: thin strip to re-open */}
        {treeCollapsed && (
          <Tooltip title="Show Files" placement="right">
            <div
              onClick={toggleTree}
              style={{
                width: 24,
                height: '100%',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                cursor: 'pointer',
                borderRight: `1px solid ${token.colorBorderSecondary}`,
                flexShrink: 0,
              }}
            >
              <MenuUnfoldOutlined style={{ fontSize: 14, color: token.colorTextSecondary }} />
            </div>
          </Tooltip>
        )}

        {/* Editor Panel */}
        {showEditor && (
          <div
            ref={editorRef}
            style={{
              width: layout === 'split' ? `${splitPercent}%` : undefined,
              flex: layout === 'editor' ? 1 : undefined,
              height: '100%',
              display: 'flex',
              flexDirection: 'column',
              minWidth: 0,
            }}
          >
            {/* Status bar */}
            <div
              style={{
                height: 32,
                display: 'flex',
                alignItems: 'center',
                padding: '0 12px',
                background: token.colorBgElevated,
                borderBottom: `1px solid ${token.colorBorderSecondary}`,
                fontSize: 12,
                color: token.colorTextSecondary,
                gap: 8,
                flexShrink: 0,
              }}
            >
              {selectedFile ? (
                <>
                  <span style={{ fontFamily: 'monospace' }}>{selectedFile}</span>
                  {collab.active && (
                    <CollaboratorAvatars collaborators={collab.collaborators} connected={collab.connected} />
                  )}
                  {dirty && !collab.active && <span style={{ color: token.colorWarning, fontWeight: 600 }}>Modified</span>}
                  {collab.active && <span style={{ color: token.colorSuccess, fontSize: 11 }}>Auto-saving</span>}
                  <span style={{ flex: 1 }} />
                  <Tooltip title="Version History" mouseEnterDelay={0.4}>
                    <Button type="text" size="small" icon={<HistoryOutlined />} onClick={() => setHistoryDrawerOpen(true)} style={{ width: 24, height: 22, color: token.colorTextSecondary }} />
                  </Tooltip>
                  <Tooltip title="Share" mouseEnterDelay={0.4}>
                    <Button type="text" size="small" icon={<ShareAltOutlined />} onClick={() => setSharePanelOpen(true)} style={{ width: 24, height: 22, color: token.colorTextSecondary }} />
                  </Tooltip>
                  <Tooltip title="Access Policy" mouseEnterDelay={0.4}>
                    <Button type="text" size="small" icon={<LockOutlined />} onClick={() => setAccessPolicyOpen(true)} style={{ width: 24, height: 22, color: token.colorTextSecondary }} />
                  </Tooltip>
                </>
              ) : (
                <span>Select a file from the tree</span>
              )}
            </div>

            {/* Formatting Toolbar — only for .md files */}
            {selectedFile?.endsWith('.md') && !fileLoading && (
              <div
                style={{
                  height: 28,
                  display: 'flex',
                  alignItems: 'center',
                  padding: '0 8px',
                  background: token.colorBgElevated,
                  borderBottom: `1px solid ${token.colorBorderSecondary}`,
                  gap: 2,
                  flexShrink: 0,
                  overflow: 'hidden',
                }}
              >
                {/* Formatting buttons */}
                <Tooltip title="Bold (Ctrl+B)" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<BoldOutlined />} onClick={() => handleToolbarSnippet('bold')} style={{ width: 26, height: 24 }} />
                </Tooltip>
                <Tooltip title="Italic (Ctrl+I)" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<ItalicOutlined />} onClick={() => handleToolbarSnippet('italic')} style={{ width: 26, height: 24 }} />
                </Tooltip>
                <Tooltip title="Strikethrough" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<StrikethroughOutlined />} onClick={() => handleToolbarSnippet('strikethrough')} style={{ width: 26, height: 24 }} />
                </Tooltip>
                <Tooltip title="Highlight" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<HighlightOutlined />} onClick={() => handleToolbarSnippet('highlight')} style={{ width: 26, height: 24 }} />
                </Tooltip>
                <Tooltip title="Inline Code" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" icon={<CodeOutlined />} onClick={() => handleToolbarSnippet('inline-code')} style={{ width: 26, height: 24 }} />
                </Tooltip>
                <Tooltip title="Keyboard Key" mouseEnterDelay={0.4}>
                  <Button type="text" size="small" style={{ width: 26, height: 24, fontSize: 11, fontWeight: 700 }} onClick={() => handleToolbarSnippet('kbd')}>K</Button>
                </Tooltip>

                <div style={{ width: 1, height: 16, background: token.colorBorderSecondary, margin: '0 4px' }} />

                {/* Headings dropdown */}
                <Dropdown menu={{ items: SNIPPETS.filter(s => s.group === 'heading').map(s => ({ key: s.id, label: s.label, icon: <FontSizeOutlined />, onClick: () => handleToolbarSnippet(s.id) })) }} trigger={['click']}>
                  <Button type="text" size="small" style={{ height: 24, fontSize: 12 }}>
                    <FontSizeOutlined /> H <DownOutlined style={{ fontSize: 8 }} />
                  </Button>
                </Dropdown>

                <div style={{ width: 1, height: 16, background: token.colorBorderSecondary, margin: '0 4px' }} />

                {/* Admonitions dropdown */}
                <Dropdown menu={{ items: SNIPPETS.filter(s => s.group === 'admonition').map(s => ({ key: s.id, label: s.label, icon: <AlertOutlined />, onClick: () => handleToolbarSnippet(s.id) })) }} trigger={['click']}>
                  <Button type="text" size="small" style={{ height: 24, fontSize: 12 }}>
                    <AlertOutlined /> Admonitions <DownOutlined style={{ fontSize: 8 }} />
                  </Button>
                </Dropdown>

                {/* Code dropdown */}
                <Dropdown menu={{ items: SNIPPETS.filter(s => s.group === 'code').map(s => ({ key: s.id, label: s.label, icon: <CodeOutlined />, onClick: () => handleToolbarSnippet(s.id) })) }} trigger={['click']}>
                  <Button type="text" size="small" style={{ height: 24, fontSize: 12 }}>
                    <CodeOutlined /> Code <DownOutlined style={{ fontSize: 8 }} />
                  </Button>
                </Dropdown>

                {/* Insert dropdown */}
                <Dropdown menu={{ items: SNIPPETS.filter(s => s.group === 'insert').map(s => ({
                  key: s.id,
                  label: s.label,
                  icon: s.id === 'wiki-link' ? <NodeExpandOutlined /> : s.id === 'video-card' ? <PlayCircleOutlined /> : s.id === 'photo-insert' ? <PictureOutlined /> : s.id === 'donate-button' ? <HeartOutlined /> : s.id === 'pricing-table' ? <CrownOutlined /> : s.id === 'product-card' ? <ShoppingCartOutlined /> : s.id === 'ad-insert' ? <BuildOutlined /> : s.id === 'scheduling-poll' ? <CalendarOutlined /> : s.id === 'link' ? <LinkOutlined /> : s.id === 'image' ? <FileMarkdownOutlined /> : s.id === 'table' ? <TableOutlined /> : <PlusOutlined />,
                  onClick: () => handleToolbarSnippet(s.id),
                })) }} trigger={['click']}>
                  <Button type="text" size="small" style={{ height: 24, fontSize: 12 }}>
                    <PlusOutlined /> Insert <DownOutlined style={{ fontSize: 8 }} />
                  </Button>
                </Dropdown>
              </div>
            )}

            {/* Editor / Image Viewer + Blog Panel */}
            <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'row' }}>
              {/* Blog Frontmatter Panel (right side when blog post selected) */}
              {blogFrontmatter.isBlogPost && !isMobile && (
                <BlogFrontmatterPanel
                  frontmatter={blogFrontmatter.frontmatter}
                  onUpdate={(field, value) => {
                    const newContent = blogFrontmatter.updateFrontmatter(field, value);
                    if (newContent !== null) {
                      setFileContent(newContent);
                      setDirty(true);
                    }
                  }}
                  authors={blogAuthors.authors}
                  categories={blogCategories.categories}
                  loading={blogAuthors.loading || blogCategories.loading}
                  collapsed={blogPanelCollapsed}
                  onCollapsedChange={(c) => {
                    setBlogPanelCollapsed(c);
                    localStorage.setItem('docs-blog-panel-collapsed', String(c));
                  }}
                  onManageAuthors={() => setAuthorsModalOpen(true)}
                />
              )}
              <div style={{ flex: 1, minHeight: 0 }}>
              {fileLoading ? (
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
                  <Spin />
                </div>
              ) : selectedFile && isImageFile(selectedFile) ? (
                /* Image preview */
                <div
                  style={{
                    height: '100%',
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'center',
                    justifyContent: 'center',
                    background: token.colorBgLayout,
                    overflow: 'auto',
                    padding: 24,
                  }}
                >
                  <img
                    src={`/mkdocs-proxy/${selectedFile}`}
                    alt={selectedFile}
                    style={{
                      maxWidth: '100%',
                      maxHeight: 'calc(100% - 60px)',
                      objectFit: 'contain',
                      borderRadius: 4,
                      background: `repeating-conic-gradient(${token.colorBorderSecondary} 0% 25%, transparent 0% 50%) 50% / 16px 16px`,
                    }}
                  />
                  <div style={{ marginTop: 12, textAlign: 'center' }}>
                    <Typography.Text type="secondary" style={{ fontSize: 12, fontFamily: 'monospace' }}>
                      {selectedFile}
                    </Typography.Text>
                    <div style={{ marginTop: 4 }}>
                      <Typography.Text type="secondary" style={{ fontSize: 11 }}>
                        MkDocs reference: <Typography.Text code copyable style={{ fontSize: 11 }}>{`![](${selectedFile.split('/').pop()})`}</Typography.Text>
                      </Typography.Text>
                    </div>
                  </div>
                </div>
              ) : selectedFile ? (
                <Editor
                  language={selectedFile.endsWith('.md') ? 'markdown' : selectedFile.endsWith('.yml') || selectedFile.endsWith('.yaml') ? 'yaml' : selectedFile.endsWith('.json') ? 'json' : selectedFile.endsWith('.css') ? 'css' : selectedFile.endsWith('.html') ? 'html' : selectedFile.endsWith('.js') ? 'javascript' : 'plaintext'}
                  theme="vs-dark"
                  {...(collab.active ? {} : { value: fileContent, onChange: onEditorChange })}
                  onMount={handleEditorMount}
                  options={{
                    minimap: { enabled: false },
                    contextmenu: false,
                    wordWrap: 'on',
                    lineNumbers: 'on',
                    fontSize: 14,
                    scrollBeyondLastLine: false,
                    automaticLayout: true,
                    tabSize: 2,
                  }}
                />
              ) : (
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: token.colorTextTertiary }}>
                  <div style={{ textAlign: 'center' }}>
                    <FileMarkdownOutlined style={{ fontSize: 48, marginBottom: 16 }} />
                    <div>Select a file to edit</div>
                  </div>
                </div>
              )}
            </div>
            </div>
          </div>
        )}

        {/* Draggable divider between editor and preview */}
        {layout === 'split' && (
          <div
            onMouseDown={onSplitDividerDown}
            style={{
              width: 4,
              cursor: 'col-resize',
              background: token.colorBorderSecondary,
              flexShrink: 0,
              transition: 'background 0.2s',
              position: 'relative',
            }}
            onMouseEnter={(e) => { (e.target as HTMLElement).style.background = token.colorPrimary; }}
            onMouseLeave={(e) => { if (!dragging.current) (e.target as HTMLElement).style.background = token.colorBorderSecondary; }}
          >
            <ResizeIndicator />
          </div>
        )}

        {/* MkDocs Preview Panel */}
        {showPreview && (
          <div
            style={{
              flex: layout === 'preview' ? 1 : undefined,
              width: layout === 'split' ? `${100 - splitPercent}%` : undefined,
              height: '100%',
              display: 'flex',
              flexDirection: 'column',
            }}
          >
            {/* URL Preview Bar */}
            <URLPreviewBar
              filePath={selectedFile}
              config={config}
              previewMode={previewMode}
              onPreviewModeChange={setPreviewMode}
            />

            {/* Preview iframe */}
            <div style={{ flex: 1, overflow: 'hidden', display: 'flex', justifyContent: 'center', alignItems: 'stretch' }}>
              <div
                style={previewMode === 'mobile' ? {
                  width: 375,
                  margin: '12px auto',
                  borderRadius: 16,
                  boxShadow: `0 0 0 2px ${token.colorBorderSecondary}, 0 4px 24px rgba(0,0,0,0.15)`,
                  overflow: 'hidden',
                  height: 'calc(100% - 24px)',
                } : {
                  width: '100%',
                  height: '100%',
                }}
              >
                <iframe
                  ref={previewIframeRef}
                  src="/mkdocs-proxy/"
                  style={{ width: '100%', height: '100%', border: 'none' }}
                  title="MkDocs Preview"
                />
              </div>
            </div>
          </div>
        )}
      </div>

      {/* New File / New Folder / Rename Modal */}
      <Modal
        title={modalType === 'newFile' ? 'New File' : modalType === 'newFolder' ? 'New Folder' : 'Rename'}
        open={modalType !== null}
        onOk={handleModalOk}
        onCancel={() => setModalType(null)}
        okText={modalType === 'rename' ? 'Rename' : 'Create'}
        destroyOnHidden
      >
        <Input
          placeholder={modalType === 'newFolder' ? 'Folder name' : 'File name (e.g. my-page.md)'}
          value={modalInput}
          onChange={(e) => setModalInput(e.target.value)}
          onPressEnter={handleModalOk}
          autoFocus
        />
        {contextPath && (
          <Typography.Text type="secondary" style={{ display: 'block', marginTop: 8, fontSize: 12 }}>
            {modalType === 'rename' ? `Renaming: ${contextPath}` : `Inside: ${contextPath}/`}
          </Typography.Text>
        )}
      </Modal>

      {/* Video Card Picker Modal */}
      <VideoPickerModal
        open={videoPickerOpen}
        onClose={() => setVideoPickerOpen(false)}
        onSelect={handleVideoCardInsert}
        title="Insert Video Card"
      />

      {/* Photo Insert Variant Picker Modal */}
      <PhotoInsertModal
        open={photoInsertOpen}
        onClose={() => setPhotoInsertOpen(false)}
        onInsert={handlePhotoInsert}
      />

      {/* Photo Picker Modal (opened by photo insert for single-photo / photo-card variants) */}
      <PhotoPickerModal
        open={photoPickerOpen}
        onClose={() => { setPhotoPickerOpen(false); setPendingPhotoVariant(null); }}
        onSelect={handlePhotoSelected}
        title={pendingPhotoVariant?.variant === 'single-photo' ? 'Select Photo' : 'Select Photo for Card'}
      />

      {/* Donate Block Picker Modal */}
      <DonateInsertModal
        open={donateInsertOpen}
        onClose={() => setDonateInsertOpen(false)}
        onInsert={handleDonateInsert}
      />

      {/* Product Card Picker Modal */}
      <ProductInsertModal
        open={productInsertOpen}
        onClose={() => setProductInsertOpen(false)}
        onInsert={handleProductInsert}
      />

      {/* Ad Picker Modal */}
      <AdPickerModal
        open={adPickerOpen}
        onCancel={() => setAdPickerOpen(false)}
        onInsert={handleAdInsert}
      />

      {/* Scheduling Poll Insert Modal */}
      <PollInsertModal
        open={pollInsertOpen}
        onCancel={() => setPollInsertOpen(false)}
        onInsert={handlePollInsert}
      />

      <WikiLinkPickerModal
        open={wikiLinkPickerOpen}
        fileTree={fileTree}
        onClose={() => setWikiLinkPickerOpen(false)}
        onSelect={(wikiLink) => {
          const ed = monacoEditorRef.current;
          if (ed) {
            const sel = ed.getSelection();
            if (sel) {
              ed.executeEdits('wiki-link-insert', [{ range: sel, text: wikiLink }]);
              ed.focus();
            }
          }
          setWikiLinkPickerOpen(false);
        }}
      />

      <MoveToModal
        open={moveToModalOpen}
        fileTree={fileTree}
        sourcePath={moveSourcePath}
        onMove={(targetDir) => { setMoveToModalOpen(false); handleMoveFile(moveSourcePath, targetDir); }}
        onClose={() => setMoveToModalOpen(false)}
      />

      {/* Custom right-click context menu with submenus */}
      {ctxMenu && (
        <div
          style={{
            position: 'fixed',
            left: ctxMenu.x,
            top: ctxMenu.y,
            zIndex: 1050,
            boxShadow: '0 6px 16px 0 rgba(0,0,0,0.32)',
            borderRadius: 8,
            overflow: 'hidden',
          }}
        >
          <Menu
            items={ctxMenuItems}
            mode="vertical"
            style={{ borderInlineEnd: 'none', minWidth: 180 }}
          />
        </div>
      )}

      {/* Blog Authoring Modals */}
      <NewBlogPostModal
        open={newBlogPostOpen}
        onClose={() => setNewBlogPostOpen(false)}
        onCreated={(path) => {
          setNewBlogPostOpen(false);
          fetchTree(false, true);
          loadFile(path);
        }}
        authors={blogAuthors.authors}
        categories={blogCategories.categories}
      />
      <AuthorsManagementModal
        open={authorsModalOpen}
        onClose={() => setAuthorsModalOpen(false)}
        authors={blogAuthors.authors}
        onSaved={() => blogAuthors.refetch()}
      />

      {/* Access Policy & Sharing Drawers */}
      <DocAccessPolicyPanel
        open={accessPolicyOpen}
        onClose={() => setAccessPolicyOpen(false)}
        documentPath={selectedFile}
      />
      <DocSharePanel
        open={sharePanelOpen}
        onClose={() => setSharePanelOpen(false)}
        documentPath={selectedFile}
      />

      {/* Version History Drawer */}
      <DocHistoryDrawer
        open={historyDrawerOpen}
        onClose={() => setHistoryDrawerOpen(false)}
        documentPath={selectedFile}
        currentContent={fileContent}
        onRestore={(content) => {
          setFileContent(content);
          setOriginalContent(content);
          setDirty(false);
          setHistoryDrawerOpen(false);
          message.success('File restored to previous version');
        }}
      />
    </>
  );
}
