import { useEffect, useMemo, useRef, useState, type ChangeEvent, type MouseEvent } from 'react';
import { useLocation } from 'react-router-dom';
import { useChat } from '../context/ChatProvider';
import {
  cancelMindSpaceAgentJob,
  createMindSpaceAgentJob,
  createMindSpacePage,
  createMindSpacePageFromAsset,
  deleteMindSpaceAsset,
  deleteMindSpacePage,
  bindMindSpacePageLiveEdit,
  getMindSpaceAgentJob,
  getMindSpace,
  getMindSpacePage,
  getMindSpacePageDeletePreview,
  listMindSpaceAgentJobs,
  listMindSpaceAssets,
  listMindSpaceCleanupItems,
  listMindSpacePages,
  retryMindSpaceAgentJob,
  runMindSpaceAgentJob,
  runMindSpaceCleanup,
  purchaseSpaceQuota,
  uploadMindSpaceAsset,
  ApiError,
} from '../api/client';
import { compressImageForUpload } from '../utils/imageUpload';
import type {
  MindSpace,
  MindSpaceAsset,
  MindSpaceAgentJob,
  MindSpaceCategory,
  MindSpaceCleanupItem,
  MindSpacePage,
  MindSpacePageDeletePreview,
  MindSpaceQuota,
  MindSpaceSaveCategory,
  PortalUser,
} from '../types';
import { buildMindSpaceChatContext } from '../utils/mindspaceChatContext';
import { resolveH5ApiBase } from '../utils/h5ApiBase';
import { MindSpaceSpaceChat } from './MindSpaceSpaceChat';
import {
  assetIcon,
  assetSourceLabel,
  buildAssetImageUrl,
  buildAssetPreviewFrameConfig,
  isImageAsset,
  isPreviewableAsset,
  isWorkAsset,
} from '../utils/mindspaceCards';
import { PREVIEW_ASSETS, PREVIEW_PAGES, PREVIEW_SPACE } from '../dev/mindspacePreviewData';
import { MindSpaceFeedCard } from './MindSpaceFeedCard';
import { MindSpacePageDetail } from './MindSpacePageDetail';
import { MindSpaceModal } from './MindSpaceModal';
import { ShareSheet } from './ShareSheet';
import { WorkCover } from './WorkCover';
import { buildAssetSharePayload, type SharePayload } from '../utils/shareChannels';
import { resolvePublicPageUrl, resolvePlazaHomeUrl } from '../utils/publicUrl';
import { PagePreviewFrame } from './PagePreviewFrame';
import { BalanceRing } from './BalanceRing';
import { NotificationCenter } from './NotificationCenter';

const CATEGORY_DESCRIPTIONS: Record<MindSpaceCategory['code'], string> = {
  oa: '上传文档、表格和资料，让 AI 生成报告与页面',
  private: '保存敏感资料，发布前必须完成风险检查与脱敏',
  public: '管理可公开使用的页面、图片和静态资源',
  draft: '继续编辑尚未发布或等待安全检查的页面',
  archive: '保存不再活跃但仍需要保留的内容',
};

const CATEGORY_ACTIONS: Partial<Record<MindSpaceCategory['code'], string>> = {
  oa: '进入工作区',
  private: '进入私人区',
  public: '查看发布',
  draft: '继续创作',
};

const AGENT_JOBS_PAGE_SIZE = 10;
const RECENT_PAGES_PAGE_SIZE = 6;
const IMAGE_PAGE_SIZE = 10;
const DEFAULT_MAX_UPLOAD_FILE_BYTES = 5 * 1024 * 1024;
const UPLOAD_FILE_EXTENSIONS = [
  '.doc',
  '.docx',
  '.xls',
  '.xlsx',
  '.pdf',
  '.ppt',
  '.pptx',
  '.png',
  '.jpg',
  '.jpeg',
  '.webp',
  '.md',
  '.csv',
  '.txt',
  '.html',
  '.htm',
] as const;
const UPLOAD_ACCEPT = UPLOAD_FILE_EXTENSIONS.join(',');
const UPLOAD_FILE_TYPE_LABEL = 'Word、Excel、PDF、PPT、图片、Markdown、CSV、TXT 和 HTML';

const JOB_STATUS_LABELS: Record<MindSpaceAgentJob['status'], string> = {
  queued: '排队中',
  running: '执行中',
  completed: '已完成',
  failed: '失败',
  cancelled: '已取消',
  timed_out: '超时',
};

const PREVIEW_SCHEDULE_ITEMS = [
  {
    id: 'schedule-preview-1',
    time: '09:30',
    kind: '待办',
    title: '跟进客户报价单',
    meta: 'OA 工作区资料',
    tone: 'task',
  },
  {
    id: 'schedule-preview-2',
    time: '14:00',
    kind: '日程',
    title: '产品评审会',
    meta: '提前 30 分钟提醒',
    tone: 'event',
  },
  {
    id: 'schedule-preview-3',
    time: '明天',
    kind: '提醒',
    title: '带项目材料去会议',
    meta: '服务号推送',
    tone: 'reminder',
  },
] as const;

function formatScheduleClock(value: number | null | undefined, timezone?: string | null) {
  if (typeof value !== 'number' || Number.isNaN(value)) return '—';
  try {
    return new Intl.DateTimeFormat('zh-CN', {
      hour: '2-digit',
      minute: '2-digit',
      hour12: false,
      timeZone: timezone || undefined,
    }).format(new Date(value));
  } catch {
    return new Date(value).toLocaleTimeString('zh-CN', {
      hour: '2-digit',
      minute: '2-digit',
      hour12: false,
    });
  }
}

function formatDigestSchedule(digest: {
  hour: number;
  minute: number;
  timezone?: string | null;
  nextRunAt?: number | null;
}) {
  const time = `${String(digest.hour).padStart(2, '0')}:${String(digest.minute).padStart(2, '0')}`;
  const nextRun = formatScheduleClock(digest.nextRunAt, digest.timezone);
  return nextRun === '—' ? time : `${time} · 下次 ${nextRun}`;
}

const PREVIEW_PAGE_READERS: Record<string, { kicker: string; body: string[] }> = {
  'page-1': {
    kicker: '市场趋势',
    body: ['消费者对 AI 辅助决策的接受度持续提升，企业侧开始从试点转向流程内嵌。', '下一阶段重点是把洞察沉淀为可复用页面，而不是停留在一次性聊天结论。'],
  },
  'page-2': {
    kicker: '旅行攻略',
    body: ['吉隆坡和槟城适合组合成 5 天轻行程，城市交通、夜市和海岛体验都比较均衡。', '预算建议优先保证住宿位置，减少跨城交通带来的时间损耗。'],
  },
  'page-3': {
    kicker: '客户成功',
    body: ['本月高价值客户续费意向稳定，但两个账号存在使用频次下降信号。', '建议在下周安排专项回访，并用案例页同步给销售团队。'],
  },
  'page-4': {
    kicker: '经营摘要',
    body: ['收入增长主要来自老客户扩容，新线索转化仍需提高。', '成本侧波动集中在交付人力，建议把高频交付问题整理为自动化模板。'],
  },
  'page-5': {
    kicker: '私人清单',
    body: ['出行前 48 小时确认证件、充电器、常用药和当地交通 App。', '个人资料区默认不公开，适合保存只给自己看的备忘。'],
  },
  'page-6': {
    kicker: '选型笔记',
    body: ['模型选择需要同时看质量、延迟、成本和可观测性。', '对高频生产任务，优先选择稳定性更强、可回放的流程。'],
  },
  'page-7': {
    kicker: '发布指南',
    body: ['公开发布前先做风险检查，再确认标题、摘要和封面是否准确。', '适合进入 Plaza 的内容应该有明确主题和可快速理解的首屏。'],
  },
  'page-8': {
    kicker: '活动复盘',
    body: ['旧活动的关键价值在于保留假设、执行过程和结论，而不是只保存结果截图。', '归档内容保持可搜索即可，不需要占据首页主要空间。'],
  },
};

type Html2CanvasRenderer = (
  element: HTMLElement,
  options?: Record<string, unknown>,
) => Promise<HTMLCanvasElement>;

type Html2CanvasWindow = Window & { html2canvas?: Html2CanvasRenderer };

const HTML2CANVAS_CDN = 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js';
const LONG_IMAGE_VIEWPORT_WIDTH = 1280;
const LONG_IMAGE_VIEWPORT_HEIGHT = 720;
const LONG_IMAGE_RENDER_SCALE = 2;
const LONG_IMAGE_MAX_PIXELS = 64_000_000;
const SPACE_PURCHASE_PRESETS_MB = [5, 10, 20];
let html2CanvasLoader: Promise<Html2CanvasRenderer> | null = null;

function formatYuan(cents: number) {
  return `¥${(cents / 100).toFixed(2)}`;
}

async function loadHtml2CanvasRenderer(): Promise<Html2CanvasRenderer> {
  const existing = (window as Html2CanvasWindow).html2canvas;
  if (existing) return existing;
  if (html2CanvasLoader) return html2CanvasLoader;

  html2CanvasLoader = new Promise<Html2CanvasRenderer>((resolve, reject) => {
    const script = document.createElement('script');
    script.src = HTML2CANVAS_CDN;
    script.async = true;
    script.onload = () => {
      const loaded = (window as Html2CanvasWindow).html2canvas;
      if (!loaded) {
        reject(new Error('无法加载页面截图能力'));
        return;
      }
      resolve(loaded);
    };
    script.onerror = () => {
      reject(new Error('无法加载页面截图能力'));
    };
    document.body.appendChild(script);
  }).catch((error) => {
    html2CanvasLoader = null;
    throw error;
  });
  return html2CanvasLoader;
}

function blobToDataUrl(blob: Blob): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result ?? ''));
    reader.onerror = () => reject(new Error('无法读取图片数据'));
    reader.readAsDataURL(blob);
  });
}

function downloadBlobAsFile(dataUrl: string, filename: string) {
  const link = document.createElement('a');
  link.href = dataUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
}

function buildExportFilename(pageId: string, title: string) {
  const safeTitle = String(title ?? '')
    .trim()
    .replace(/[\\/*?:"<>|]/g, '-')
    .replace(/\s+/g, '-')
    .slice(0, 24);
  const suffix = safeTitle ? `-${safeTitle}` : '';
  const timestamp = new Date().toISOString().replace(/[T:]/g, '-').replace(/\..+/, '');
  return `mindspace-page-${pageId}${suffix}-${timestamp}.png`;
}

function fileExtension(filename: string) {
  const dotIndex = filename.lastIndexOf('.');
  return dotIndex >= 0 ? filename.slice(dotIndex).toLowerCase() : '';
}

function validateSelectedUploadFile(file: File, maxBytes: number) {
  if (file.size <= 0) return '文件不能为空，请重新选择。';
  if (file.size > maxBytes) {
    return `文件不能超过 ${formatBytes(maxBytes)}，当前为 ${formatBytes(file.size)}。`;
  }
  const extension = fileExtension(file.name);
  if (!UPLOAD_FILE_EXTENSIONS.includes(extension as (typeof UPLOAD_FILE_EXTENSIONS)[number])) {
    return `暂不支持 ${extension || '无扩展名'} 文件，请选择支持的资料格式。`;
  }
  return null;
}

function ensureBlobFromCanvas(canvas: HTMLCanvasElement): Promise<Blob> {
  return new Promise((resolve, reject) => {
    canvas.toBlob((blob) => {
      if (!blob) {
        reject(new Error('图片生成失败'));
        return;
      }
      resolve(blob);
    }, 'image/png');
  });
}

async function exportIframeToPng(iframe: HTMLIFrameElement): Promise<string> {
  const renderer = await loadHtml2CanvasRenderer();
  const doc = iframe.contentDocument;
  if (!doc || !doc.body) {
    throw new Error('预览内容无法访问');
  }
  const width = Math.max(
    doc.documentElement?.scrollWidth ?? 0,
    doc.body.scrollWidth,
    LONG_IMAGE_VIEWPORT_WIDTH,
  );
  const height = Math.max(
    doc.documentElement?.scrollHeight ?? 0,
    doc.body.scrollHeight,
    LONG_IMAGE_VIEWPORT_HEIGHT,
  );
  const scale = Math.min(
    LONG_IMAGE_RENDER_SCALE,
    Math.max(1, Math.sqrt(LONG_IMAGE_MAX_PIXELS / Math.max(width * height, 1))),
  );
  const canvas = await renderer(doc.body, {
    useCORS: true,
    scale,
    backgroundColor: '#ffffff',
    width,
    height,
    windowWidth: width,
    windowHeight: height,
    x: 0,
    y: 0,
    scrollX: 0,
    scrollY: 0,
  });
  const blob = await ensureBlobFromCanvas(canvas);
  return await blobToDataUrl(blob);
}

async function waitForIframeAssets(iframe: HTMLIFrameElement): Promise<void> {
  const doc = iframe.contentDocument;
  if (!doc) return;
  await doc.fonts?.ready.catch(() => undefined);
  await Promise.all(
    Array.from(doc.images)
      .filter((img) => !img.complete)
      .map(
        (img) =>
          new Promise<void>((resolve) => {
            img.addEventListener('load', () => resolve(), { once: true });
            img.addEventListener('error', () => resolve(), { once: true });
          }),
      ),
  );
  await new Promise((resolve) => window.setTimeout(resolve, 450));
}

async function exportPagePreviewToPng(page: MindSpacePage): Promise<string> {
  const iframe = document.createElement('iframe');
  iframe.src = `/api/mindspace/v1/pages/${encodeURIComponent(page.id)}/preview?v=${page.versionNo ?? page.updatedAt}`;
  iframe.setAttribute('aria-hidden', 'true');
  iframe.tabIndex = -1;
  iframe.style.position = 'fixed';
  iframe.style.left = '-99999px';
  iframe.style.top = '0';
  iframe.style.width = `${LONG_IMAGE_VIEWPORT_WIDTH}px`;
  iframe.style.height = `${LONG_IMAGE_VIEWPORT_HEIGHT}px`;
  iframe.style.border = '0';
  iframe.style.opacity = '0';
  iframe.style.pointerEvents = 'none';
  document.body.appendChild(iframe);
  try {
    await new Promise<void>((resolve, reject) => {
      const timer = window.setTimeout(() => reject(new Error('页面预览加载超时')), 12000);
      iframe.onload = () => {
        window.clearTimeout(timer);
        resolve();
      };
      iframe.onerror = () => {
        window.clearTimeout(timer);
        reject(new Error('页面预览加载失败'));
      };
    });
    await waitForIframeAssets(iframe);
    return await exportIframeToPng(iframe);
  } finally {
    iframe.remove();
  }
}

function formatBytes(bytes: number) {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}

type AssetFilter = 'all' | 'images' | 'files' | 'pages';

function canGenerateWithAgent(asset: MindSpaceAsset) {
  const textLikeMimeTypes = new Set([
    'text/plain',
    'text/markdown',
    'text/csv',
    'text/html',
    'application/json',
  ]);
  return (
    asset.status === 'ready' &&
    asset.scanStatus !== 'blocked' &&
    textLikeMimeTypes.has(asset.mimeType)
  );
}

type MindSpaceRouteSync = {
  pushHome: () => void;
  pushCategory: (code: MindSpaceSaveCategory | MindSpaceCategory['code']) => void;
  pushPage: (pageId: string) => void;
};

function MindSpaceBalanceRing() {
  const { balanceCents, totalCreditCents, tokensUsed, subscription, openRecharge, openSubscribe } = useChat();
  if (typeof balanceCents !== 'number') return null;
  return (
    <BalanceRing
      balanceCents={balanceCents}
      totalCreditCents={totalCreditCents}
      tokensUsed={tokensUsed}
      subscription={subscription}
      onRecharge={openRecharge}
      onSubscribe={openSubscribe}
    />
  );
}

function MindSpaceNotificationCenter() {
  const { openRecharge } = useChat();
  return <NotificationCenter onOpenRecharge={() => openRecharge(false)} />;
}

export function MindSpaceView({
  user,
  previewMode = false,
  initialPageId,
  initialCategoryCode,
  onBack,
  onLogout,
  routeSync,
}: {
  user: PortalUser;
  previewMode?: boolean;
  initialPageId?: string | null;
  initialCategoryCode?: MindSpaceSaveCategory | null;
  onBack: () => void;
  onLogout: () => void;
  routeSync?: MindSpaceRouteSync;
}) {
  const [space, setSpace] = useState<MindSpace | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  const [selectedCategory, setSelectedCategory] = useState<MindSpaceCategory | null>(null);
  const [assets, setAssets] = useState<MindSpaceAsset[]>([]);
  const [pages, setPages] = useState<MindSpacePage[]>([]);
  const [selectedPageId, setSelectedPageId] = useState<string | null>(initialPageId ?? null);
  const [selectedPageAutoPlaza, setSelectedPageAutoPlaza] = useState(false);
  const [assetsLoading, setAssetsLoading] = useState(false);
  const [uploadOpen, setUploadOpen] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [uploadError, setUploadError] = useState<string | null>(null);
  const [pendingDeleteId, setPendingDeleteId] = useState<string | null>(null);
  const [selectedAssetIds, setSelectedAssetIds] = useState<string[]>([]);
  const [bulkDeletingAssets, setBulkDeletingAssets] = useState(false);
  const [newPageOpen, setNewPageOpen] = useState(false);
  const [newPageTitle, setNewPageTitle] = useState('');
  const [newPageContent, setNewPageContent] = useState('');
  const [newPageTemplate, setNewPageTemplate] = useState('editorial');
  const [creatingPage, setCreatingPage] = useState(false);
  const [assetFilter, setAssetFilter] = useState<AssetFilter>('all');
  const [imagePage, setImagePage] = useState(0);
  const [previewAssetId, setPreviewAssetId] = useState<string | null>(null);
  const [editingAssetId, setEditingAssetId] = useState<string | null>(null);
  const [cleanupOpen, setCleanupOpen] = useState(false);
  const [cleanupItems, setCleanupItems] = useState<MindSpaceCleanupItem[]>([]);
  const [cleanupLoading, setCleanupLoading] = useState(false);
  const [cleanupRunning, setCleanupRunning] = useState(false);
  const [selectedCleanupIds, setSelectedCleanupIds] = useState<string[]>([]);
  const [agentAsset, setAgentAsset] = useState<MindSpaceAsset | null>(null);
  const [agentInstruction, setAgentInstruction] = useState('');
  const [agentJob, setAgentJob] = useState<MindSpaceAgentJob | null>(null);
  const [agentSubmitting, setAgentSubmitting] = useState(false);
  const [agentJobsPanelOpen, setAgentJobsPanelOpen] = useState(false);
  const [agentJobsList, setAgentJobsList] = useState<MindSpaceAgentJob[]>([]);
  const [agentJobsTotal, setAgentJobsTotal] = useState(0);
  const [agentJobsPage, setAgentJobsPage] = useState(0);
  const [agentJobsLoading, setAgentJobsLoading] = useState(false);
  const [allPagesOpen, setAllPagesOpen] = useState(false);
  const [recentPagesPage, setRecentPagesPage] = useState(0);
  const [exportingPageId, setExportingPageId] = useState<string | null>(null);
  const [sharePayload, setSharePayload] = useState<SharePayload | null>(null);
  const [previewPageTarget, setPreviewPageTarget] = useState<MindSpacePage | null>(null);
  const [pageDeleteTarget, setPageDeleteTarget] = useState<MindSpacePage | null>(null);
  const [pageDeletePreview, setPageDeletePreview] = useState<MindSpacePageDeletePreview | null>(
    null,
  );
  const [pageDeleteLoading, setPageDeleteLoading] = useState(false);
  const [deletingPageId, setDeletingPageId] = useState<string | null>(null);
  const [assetDeleteDialog, setAssetDeleteDialog] = useState<{
    message: string;
    references: Array<{ id: string; title: string; publishedOnline?: boolean }>;
  } | null>(null);
  const [pageLiveContext, setPageLiveContext] = useState<{
    page: MindSpacePage;
    title: string;
    summary: string;
    content: string;
  } | null>(null);
  const [pageRefreshTrigger, setPageRefreshTrigger] = useState(0);
  const [pageFullscreenPreviewOpen, setPageFullscreenPreviewOpen] = useState(false);
  const [spacePurchaseOpen, setSpacePurchaseOpen] = useState(false);
  const [spacePurchaseMb, setSpacePurchaseMb] = useState('5');
  const [spacePurchasePending, setSpacePurchasePending] = useState(false);
  const [spacePurchaseMessage, setSpacePurchaseMessage] = useState<string | null>(null);
  const { chatState, messages, session, balanceCents, completeRecharge, openRecharge } = useChat();
  const prevChatStateRef = useRef(chatState);
  const h5ApiBase = useMemo(() => resolveH5ApiBase(), []);
  const location = useLocation();
  const fromPlaza = useMemo(() => {
    const params = new URLSearchParams(location.search);
    return params.get('utm_source') === 'plaza';
  }, [location.search]);
  const plazaReturnUrl = useMemo(() => {
    const params = new URLSearchParams(location.search);
    return params.get('return_to') || resolvePlazaHomeUrl();
  }, [location.search]);
  const maxUploadFileBytes = space?.quota.maxFileBytes ?? DEFAULT_MAX_UPLOAD_FILE_BYTES;

  useEffect(() => {
    if (!selectedPageId) {
      setPageLiveContext(null);
      setPageFullscreenPreviewOpen(false);
    }
  }, [selectedPageId]);

  useEffect(() => {
    if (!allPagesOpen && !selectedPageId) return;
    window.requestAnimationFrame(() => {
      window.scrollTo({ top: 0, left: 0 });
    });
  }, [allPagesOpen, selectedPageId]);

  useEffect(() => {
    if (!selectedPageId || !session?.id || previewMode || pageFullscreenPreviewOpen) return;
    void bindMindSpacePageLiveEdit(selectedPageId, session.id).catch(() => {});
  }, [selectedPageId, session?.id, previewMode, pageFullscreenPreviewOpen]);

  const mindspaceChatContext = useMemo(() => {
    if (!space) return null;
    return buildMindSpaceChatContext({
      space,
      ownerUsername: user.username,
      selectedCategory,
      selectedPageId,
      pages,
      pageLive: pageLiveContext,
      assets,
      focusedAsset: agentAsset,
      route: `${location.pathname}${location.search}`,
      agentSessionId: session?.id ?? null,
      h5ApiBase,
    });
  }, [
    space,
    user.username,
    selectedCategory,
    selectedPageId,
    pages,
    pageLiveContext,
    assets,
    agentAsset,
    location.pathname,
    location.search,
    session?.id,
    h5ApiBase,
  ]);

  const truncateLabel = (text: string, max = 48) => {
    const value = String(text ?? '').replace(/\s+/g, ' ').trim();
    if (value.length <= max) return value;
    return `${value.slice(0, max - 1)}…`;
  };

  const showPage = (pageId: string, autoPlaza = false) => {
    setSelectedPageId(pageId);
    setSelectedPageAutoPlaza(autoPlaza);
    routeSync?.pushPage(pageId);
  };

  const openPagePreview = (page: MindSpacePage) => {
    setPreviewPageTarget(page);
  };

  const openPageDeleteDialog = async (page: MindSpacePage) => {
    if (previewMode) {
      setError('预览模式下无法删除页面');
      return;
    }
    setPageDeleteTarget(page);
    setPageDeletePreview(null);
    setPageDeleteLoading(true);
    setError(null);
    try {
      setPageDeletePreview(await getMindSpacePageDeletePreview(page.id));
    } catch (err) {
      setPageDeleteTarget(null);
      setError(err instanceof Error ? err.message : '无法加载删除预览');
    } finally {
      setPageDeleteLoading(false);
    }
  };

  const closePageDeleteDialog = () => {
    if (deletingPageId) return;
    setPageDeleteTarget(null);
    setPageDeletePreview(null);
  };

  const confirmDeletePage = async () => {
    if (!pageDeleteTarget) return;
    setDeletingPageId(pageDeleteTarget.id);
    setError(null);
    try {
      const result = await deleteMindSpacePage(pageDeleteTarget.id);
      if (selectedPageId === pageDeleteTarget.id) {
        closePage();
      }
      setPageDeleteTarget(null);
      setPageDeletePreview(null);
      await refreshMindSpaceSnapshot({ quota: result.quota, freedBytes: result.freedBytes });
    } catch (err) {
      setError(err instanceof Error ? err.message : '删除页面失败');
    } finally {
      setDeletingPageId(null);
    }
  };

  const buildPageCardActions = (page: MindSpacePage) => [
    {
      label: '预览',
      onClick: (event: MouseEvent) => {
        event.stopPropagation();
        openPagePreview(page);
      },
    },
    {
      label: '编辑',
      onClick: (event: MouseEvent) => {
        event.stopPropagation();
        showPage(page.id);
      },
    },
    {
      label: '公开链接',
      onClick: (event: MouseEvent) => {
        event.stopPropagation();
        void openPagePublicShare(page);
      },
    },
    {
      label: exportingPageId === page.id ? '保存中…' : '保存长图',
      onClick: (event: MouseEvent) => {
        event.stopPropagation();
        void exportPageLongImage(page);
      },
    },
    {
      label: '删除',
      onClick: (event: MouseEvent) => {
        event.stopPropagation();
        void openPageDeleteDialog(page);
      },
    },
  ];

  const closePage = () => {
    setSelectedPageId(null);
    if (selectedCategory) {
      routeSync?.pushCategory(selectedCategory.code);
    } else {
      routeSync?.pushHome();
    }
  };

  const openPagePublicShare = async (page: MindSpacePage) => {
    if (previewMode) {
      setError('预览模式下暂不支持跳转分享页');
      return;
    }
    if (page.publicationUrl) {
      window.open(resolvePublicPageUrl(page.publicationUrl), '_blank', 'noopener,noreferrer');
      return;
    }
    try {
      const pageDetail = await getMindSpacePage(page.id);
      if (!pageDetail.publication?.publicUrl) {
        setError('该页面未发布，先发布后再分享');
        return;
      }
      window.open(resolvePublicPageUrl(pageDetail.publication.publicUrl), '_blank', 'noopener,noreferrer');
    } catch (err) {
      setError(err instanceof Error ? err.message : '获取分享链接失败');
    }
  };

  const exportPageLongImage = async (page: MindSpacePage) => {
    if (previewMode) {
      setError('预览模式下暂不支持保存长图');
      return;
    }
    setExportingPageId(page.id);
    setError(null);
    try {
      const dataUrl = await exportPagePreviewToPng(page);
      downloadBlobAsFile(dataUrl, buildExportFilename(page.id, page.title || 'page'));
    } catch (err) {
      setError(err instanceof Error ? err.message : '保存长图失败');
    } finally {
      setExportingPageId((current) => (current === page.id ? null : current));
    }
  };

  const closeCategory = () => {
    setSelectedCategory(null);
    setUploadOpen(false);
    setPendingDeleteId(null);
    setSelectedAssetIds([]);
    setImagePage(0);
    routeSync?.pushHome();
  };

  const loadCleanupItems = async () => {
    if (previewMode) {
      setCleanupItems([]);
      return;
    }
    setCleanupLoading(true);
    try {
      const result = await listMindSpaceCleanupItems();
      setCleanupItems(result.items);
      setSelectedCleanupIds(result.items.map((item) => item.id));
    } catch (err) {
      setError(err instanceof Error ? err.message : '临时文件扫描失败');
    } finally {
      setCleanupLoading(false);
    }
  };

  const runCleanup = async () => {
    if (!selectedCleanupIds.length) return;
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    setCleanupRunning(true);
    setError(null);
    try {
      const result = await runMindSpaceCleanup(selectedCleanupIds);
      if (result.quota) {
        setSpace((prev) => (prev ? { ...prev, quota: result.quota! } : prev));
      } else {
        await refreshSpaceQuietly();
      }
      setCleanupOpen(false);
      setCleanupItems([]);
      setSelectedCleanupIds([]);
    } catch (err) {
      setError(err instanceof Error ? err.message : '清理失败');
    } finally {
      setCleanupRunning(false);
    }
  };

  const refreshAgentJobsSummary = async () => {
    if (previewMode) return;
    const { page } = await listMindSpaceAgentJobs({ limit: 1, offset: 0 });
    setAgentJobsTotal(page.total ?? 0);
  };

  const loadAgentJobsPage = async (pageIndex: number) => {
    if (previewMode) {
      setAgentJobsList([]);
      setAgentJobsTotal(0);
      return;
    }
    setAgentJobsLoading(true);
    setError(null);
    try {
      const { items, page } = await listMindSpaceAgentJobs({
        limit: AGENT_JOBS_PAGE_SIZE,
        offset: pageIndex * AGENT_JOBS_PAGE_SIZE,
      });
      setAgentJobsList(items);
      setAgentJobsPage(pageIndex);
      setAgentJobsTotal(page.total ?? 0);
    } catch (err) {
      setError(err instanceof Error ? err.message : '任务记录加载失败');
    } finally {
      setAgentJobsLoading(false);
    }
  };

  const openAgentJobsPanel = () => {
    setSelectedCategory(null);
    setSelectedPageId(null);
    setNewPageOpen(false);
    setAllPagesOpen(false);
    setAgentJobsPanelOpen(true);
    routeSync?.pushHome();
    void loadAgentJobsPage(0);
  };

  const closeAgentJobsPanel = () => {
    setAgentJobsPanelOpen(false);
  };

  const refreshAgentJobsPanel = async () => {
    await refreshAgentJobsSummary();
    if (agentJobsPanelOpen) {
      await loadAgentJobsPage(agentJobsPage);
    }
  };

  const load = async () => {
    setLoading(true);
    setError(null);
    try {
      if (previewMode) {
        setSpace(PREVIEW_SPACE);
        setPages(PREVIEW_PAGES);
        return;
      }
      const [nextSpace, nextPages] = await Promise.all([
        getMindSpace(),
        listMindSpacePages(),
      ]);
      setSpace(nextSpace);
      setPages(nextPages);
      await refreshAgentJobsSummary();
    } catch (err) {
      setError(err instanceof Error ? err.message : '空间加载失败');
    } finally {
      setLoading(false);
    }
  };

  const refreshSpaceQuietly = async () => {
    if (previewMode) return null;
    try {
      const nextSpace = await getMindSpace();
      setSpace(nextSpace);
      return nextSpace;
    } catch {
      return null;
    }
  };

  const refreshPagesSilently = async () => {
    if (previewMode) return;
    try {
      const nextPages = await listMindSpacePages();
      setPages(nextPages);
    } catch {
      // ignore background list refresh errors
    }
  };

  const refreshMindSpaceSnapshot = async (options?: {
    quota?: MindSpaceQuota;
    freedBytes?: number;
  }) => {
    if (options?.quota) {
      setSpace((prev) => (prev ? { ...prev, quota: options.quota! } : prev));
    } else if (typeof options?.freedBytes === 'number' && options.freedBytes > 0) {
      setSpace((prev) => {
        if (!prev) return prev;
        const usedBytes = Math.max(0, prev.quota.usedBytes - options.freedBytes!);
        return {
          ...prev,
          quota: {
            ...prev.quota,
            usedBytes,
            availableBytes: Math.max(
              0,
              prev.quota.quotaBytes - usedBytes - prev.quota.reservedBytes,
            ),
          },
        };
      });
    } else {
      await refreshSpaceQuietly();
    }
    await refreshPagesSilently();
  };

  useEffect(() => {
    const previous = prevChatStateRef.current;
    prevChatStateRef.current = chatState;
    const wasBusy = previous === 'streaming' || previous === 'waiting';
    if (!wasBusy || chatState !== 'idle' || !selectedPageId || previewMode || pageFullscreenPreviewOpen) return;
    setPageRefreshTrigger((value) => value + 1);
    void refreshPagesSilently();
  }, [chatState, pageFullscreenPreviewOpen, previewMode, selectedPageId]);

  useEffect(() => {
    void load();
  }, []);

  useEffect(() => {
    if (initialPageId) setSelectedPageId(initialPageId);
  }, [initialPageId]);

  useEffect(() => {
    if (!space || !initialCategoryCode || initialCategoryCode === 'draft') return;
    const category = space.categories.find((item) => item.code === initialCategoryCode);
    if (category) void openCategory(category);
  }, [space, initialCategoryCode]);

  useEffect(() => {
    if (previewMode || !agentJob || !['queued', 'running'].includes(agentJob.status)) return;
    const timer = window.setInterval(() => {
      void getMindSpaceAgentJob(agentJob.id)
        .then(async (nextJob) => {
          setAgentJob(nextJob);
          await refreshAgentJobsPanel();
          if (nextJob.status === 'completed' && nextJob.resultPageId) {
            await load();
            setAgentAsset(null);
            setAgentInstruction('');
            setSelectedPageId(nextJob.resultPageId);
            routeSync?.pushPage(nextJob.resultPageId);
          }
        })
        .catch((err) => {
          setError(err instanceof Error ? err.message : '任务状态刷新失败');
        });
    }, 1200);
    return () => window.clearInterval(timer);
  }, [agentJob, previewMode]);

  const openCategory = async (category: MindSpaceCategory) => {
    setAgentJobsPanelOpen(false);
    setAllPagesOpen(false);
    setSelectedCategory(category);
    setSelectedPageId(null);
    setNewPageOpen(false);
    setUploadOpen(false);
    setPendingDeleteId(null);
    setImagePage(0);
    setAssetsLoading(true);
    setError(null);
    try {
      if (previewMode) {
        setAssets(PREVIEW_ASSETS[category.code] ?? []);
        if (category.code === 'draft') {
          setPages(PREVIEW_PAGES);
        }
        return;
      }
      setAssets(await listMindSpaceAssets(category.code));
    } catch (err) {
      setError(err instanceof Error ? err.message : '资产加载失败');
    } finally {
      setAssetsLoading(false);
    }
    routeSync?.pushCategory(category.code);
  };

  const openUpload = (category?: MindSpaceCategory) => {
    const target =
      category ??
      selectedCategory ??
      space?.categories.find((item) => item.code === 'oa') ??
      null;
    if (target) setSelectedCategory(target);
    setSelectedFile(null);
    setUploadError(null);
    setUploadOpen(true);
  };

  const closeUpload = () => {
    if (uploading) return;
    setUploadOpen(false);
    setSelectedFile(null);
    setUploadError(null);
  };

  const uploadDialogTitle = (category: MindSpaceCategory) =>
    category.code === 'oa' ? '上传资料' : `上传到 ${category.name}`;

  const submitUpload = async () => {
    if (!selectedCategory || !selectedFile) return;
    const validationError = validateSelectedUploadFile(selectedFile, maxUploadFileBytes);
    if (validationError) {
      setUploadError(validationError);
      return;
    }
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    setUploading(true);
    setError(null);
    setUploadError(null);
    try {
      const fileToUpload = selectedFile.type.startsWith('image/')
        ? await compressImageForUpload(selectedFile, {
            maxInputBytes: maxUploadFileBytes,
            maxOutputBytes: maxUploadFileBytes,
            maxDimension: 1600,
          })
        : selectedFile;
      await uploadMindSpaceAsset(selectedCategory.id, fileToUpload);
      setUploadOpen(false);
      setSelectedFile(null);
      await load();
      await openCategory(selectedCategory);
    } catch (err) {
      setError(err instanceof Error ? err.message : '上传失败');
    } finally {
      setUploading(false);
    }
  };

  const submitSpacePurchase = async (requestedMb?: number) => {
    const sizeMb = Math.floor(Number(requestedMb ?? spacePurchaseMb));
    if (!Number.isFinite(sizeMb) || sizeMb <= 0) {
      setSpacePurchaseMessage('请输入大于 0 的空间大小（MB）');
      return;
    }
    setSpacePurchasePending(true);
    setSpacePurchaseMessage(null);
    setError(null);
    try {
      const result = await purchaseSpaceQuota(sizeMb);
      completeRecharge(result.balanceCents);
      await refreshMindSpaceSnapshot({ quota: result.quota });
      setSpacePurchaseMb(String(sizeMb));
      setSpacePurchaseMessage(
        `已扩容 ${sizeMb} MB，支付 ¥${(result.costCents / 100).toFixed(2)}。`,
      );
    } catch (err) {
      if (err instanceof ApiError && err.code === 'INSUFFICIENT_BALANCE') {
        setSpacePurchaseMessage('余额不足，请先到余额里充值，再回来购买空间。');
      } else {
        setSpacePurchaseMessage(err instanceof Error ? err.message : '购买空间失败');
      }
    } finally {
      setSpacePurchasePending(false);
    }
  };

  const handleUploadFileChange = (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0] ?? null;
    if (!file) {
      setSelectedFile(null);
      setUploadError(null);
      return;
    }
    const validationError = validateSelectedUploadFile(file, maxUploadFileBytes);
    if (validationError) {
      setSelectedFile(null);
      setUploadError(validationError);
      event.currentTarget.value = '';
      return;
    }
    setSelectedFile(file);
    setUploadError(null);
  };

  const removeAsset = async (assetId: string) => {
    if (!selectedCategory) return;
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    setError(null);
    setAssetDeleteDialog(null);
    try {
      await deleteMindSpaceAsset(assetId);
      setPendingDeleteId(null);
      setSelectedAssetIds((ids) => ids.filter((id) => id !== assetId));
      await load();
      await openCategory(selectedCategory);
    } catch (err) {
      if (err instanceof ApiError && err.code === 'asset_in_use') {
        const details = err.details as
          | {
              hint?: string;
              references?: Array<{
                id: string;
                title: string;
                publishedOnline?: boolean;
              }>;
            }
          | undefined;
        const references = details?.references ?? [];
        setAssetDeleteDialog({
          message:
            details?.hint ??
            (references.length
              ? `无法删除：页面「${truncateLabel(references[0]?.title ?? '未命名')}」正在使用该资产。`
              : '资产正在被页面使用，不能删除'),
          references,
        });
        return;
      }
      setAssetDeleteDialog({
        message: err instanceof Error ? err.message : '删除失败',
        references: [],
      });
    }
  };

  const occupiedBytes = space ? space.quota.usedBytes + space.quota.reservedBytes : 0;
  const usedPercent = space
    ? Math.min(100, (occupiedBytes / space.quota.quotaBytes) * 100)
    : 0;
  const currentBalanceCents = typeof balanceCents === 'number' ? balanceCents : user.balanceCents;
  const purchaseMbValue = Math.max(0, Math.floor(Number(spacePurchaseMb) || 0));
  const purchaseCostCents = purchaseMbValue * 200;
  const canPurchaseSpace = purchaseMbValue > 0 && currentBalanceCents >= purchaseCostCents;

  const openAssetEditor = async (asset: MindSpaceAsset) => {
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    const linkedPage = pages.find(
      (page) => page.sourceAssetId === asset.id && page.status !== 'deleted',
    );
    if (linkedPage) {
      setPreviewAssetId(null);
      showPage(linkedPage.id);
      return;
    }
    setEditingAssetId(asset.id);
    setError(null);
    try {
      const page = await createMindSpacePageFromAsset({
        assetId: asset.id,
        title: asset.displayName,
      });
      setPreviewAssetId(null);
      showPage(page.id);
      void load();
    } catch (err) {
      setError(err instanceof Error ? err.message : '打开编辑失败');
    } finally {
      setEditingAssetId(null);
    }
  };

  const openAgentGenerator = (asset: MindSpaceAsset) => {
    setAgentAsset(asset);
    setAgentJob(null);
    setAgentInstruction(`请根据《${asset.displayName}》生成一个中文页面草稿，提炼核心结论、重点信息和可执行建议。`);
  };

  const closeAgentGenerator = () => {
    if (agentSubmitting) return;
    setAgentAsset(null);
    setAgentInstruction('');
    setAgentJob(null);
  };

  const submitAgentGenerator = async () => {
    if (!agentAsset || !agentInstruction.trim()) return;
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    setAgentSubmitting(true);
    setError(null);
    try {
      const created = await createMindSpaceAgentJob({
        jobType: 'generate_page',
        instruction: agentInstruction.trim(),
        allowedAssetIds: [agentAsset.id],
        outputType: 'page_draft',
      });
      setAgentJob(created);
      await refreshAgentJobsSummary();
      await runMindSpaceAgentJob(created.id);
      const running = await getMindSpaceAgentJob(created.id);
      setAgentJob(running);
      if (agentJobsPanelOpen) await loadAgentJobsPage(0);
    } catch (err) {
      setError(err instanceof Error ? err.message : '启动 AI 生成失败');
    } finally {
      setAgentSubmitting(false);
    }
  };

  const cancelAgentJob = async (jobId: string) => {
    try {
      const cancelled = await cancelMindSpaceAgentJob(jobId);
      setAgentJob((current) => (current?.id === jobId ? cancelled : current));
      await refreshAgentJobsPanel();
    } catch (err) {
      setError(err instanceof Error ? err.message : '取消任务失败');
    }
  };

  const retryAgentJob = async (jobId: string) => {
    try {
      const retried = await retryMindSpaceAgentJob(jobId);
      setAgentJob((current) => (current?.id === jobId ? retried : current));
      await refreshAgentJobsPanel();
      await runMindSpaceAgentJob(jobId);
      const running = await getMindSpaceAgentJob(jobId);
      setAgentJob((current) => (current?.id === jobId ? running : current));
      await refreshAgentJobsPanel();
    } catch (err) {
      setError(err instanceof Error ? err.message : '重试任务失败');
    }
  };

  const imageAssets = useMemo(() => assets.filter(isImageAsset), [assets]);
  const fileAssets = useMemo(
    () => assets.filter((asset) => !isWorkAsset(asset) && !isImageAsset(asset)),
    [assets],
  );
  const workAssets = useMemo(() => assets.filter(isWorkAsset), [assets]);

  const visibleImageAssets =
    assetFilter === 'all' || assetFilter === 'images' ? imageAssets : [];
  const imagePaginationEnabled = visibleImageAssets.length > IMAGE_PAGE_SIZE;
  const imagePageCount = Math.max(
    1,
    Math.ceil(visibleImageAssets.length / IMAGE_PAGE_SIZE),
  );
  const safeImagePage = Math.min(imagePage, imagePageCount - 1);
  const pagedImageAssets = imagePaginationEnabled
    ? visibleImageAssets.slice(
        safeImagePage * IMAGE_PAGE_SIZE,
        safeImagePage * IMAGE_PAGE_SIZE + IMAGE_PAGE_SIZE,
      )
    : visibleImageAssets;
  const recentPagesTotal = Math.max(1, Math.ceil(pages.length / RECENT_PAGES_PAGE_SIZE));
  const safeRecentPagesPage = Math.min(recentPagesPage, recentPagesTotal - 1);
  const pagedRecentPages = pages.slice(
    safeRecentPagesPage * RECENT_PAGES_PAGE_SIZE,
    safeRecentPagesPage * RECENT_PAGES_PAGE_SIZE + RECENT_PAGES_PAGE_SIZE,
  );
  useEffect(() => {
    const lastRecentPage = Math.max(0, recentPagesTotal - 1);
    setRecentPagesPage((current) => Math.min(current, lastRecentPage));
  }, [recentPagesTotal, pages.length]);

  const visibleCardAssets =
    assetFilter === 'all'
      ? [...fileAssets, ...workAssets]
      : assetFilter === 'files'
        ? fileAssets
        : assetFilter === 'pages'
          ? workAssets
          : [];
  const visibleSelectableAssets = [...pagedImageAssets, ...visibleCardAssets];
  const selectedAssetSet = useMemo(() => new Set(selectedAssetIds), [selectedAssetIds]);
  const allVisibleAssetsSelected =
    visibleSelectableAssets.length > 0 &&
    visibleSelectableAssets.every((asset) => selectedAssetSet.has(asset.id));

  useEffect(() => {
    const availableIds = new Set(assets.map((asset) => asset.id));
    setSelectedAssetIds((ids) => ids.filter((id) => availableIds.has(id)));
  }, [assets]);

  const toggleAssetSelected = (assetId: string) => {
    setSelectedAssetIds((ids) =>
      ids.includes(assetId) ? ids.filter((id) => id !== assetId) : [...ids, assetId],
    );
  };

  const toggleVisibleAssetsSelected = () => {
    const visibleIds = visibleSelectableAssets.map((asset) => asset.id);
    if (allVisibleAssetsSelected) {
      setSelectedAssetIds((ids) => ids.filter((id) => !visibleIds.includes(id)));
      return;
    }
    setSelectedAssetIds((ids) => Array.from(new Set([...ids, ...visibleIds])));
  };

  const removeSelectedAssets = async () => {
    if (!selectedCategory || selectedAssetIds.length === 0) return;
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    setBulkDeletingAssets(true);
    setError(null);
    setAssetDeleteDialog(null);
    const deletingIds = [...selectedAssetIds];
    const failedIds: string[] = [];
    const failedNames: string[] = [];
    try {
      for (const assetId of deletingIds) {
        try {
          await deleteMindSpaceAsset(assetId);
        } catch (err) {
          const assetName = assets.find((asset) => asset.id === assetId)?.displayName ?? assetId;
          failedIds.push(assetId);
          failedNames.push(assetName);
        }
      }
      setSelectedAssetIds((ids) => ids.filter((id) => failedIds.includes(id)));
      await load();
      await openCategory(selectedCategory);
      if (failedNames.length > 0) {
        setAssetDeleteDialog({
          message: `有 ${failedNames.length} 个资产未删除，可能正在被页面使用。`,
          references: failedNames.slice(0, 6).map((title, index) => ({
            id: `${index}`,
            title,
          })),
        });
      }
    } finally {
      setBulkDeletingAssets(false);
    }
  };

  const previewAsset = useMemo(
    () => assets.find((asset) => asset.id === previewAssetId) ?? null,
    [assets, previewAssetId],
  );
  const previewAssetFrame = previewAsset ? buildAssetPreviewFrameConfig(previewAsset) : null;

  const renderAssetActions = (asset: MindSpaceAsset) => (
    <div className="mindspace-item-actions">
      {isPreviewableAsset(asset) && (
        <button type="button" onClick={() => setPreviewAssetId(asset.id)}>
          预览
        </button>
      )}
      {isWorkAsset(asset) && (
        <button
          type="button"
          onClick={() => void openAssetEditor(asset)}
          disabled={editingAssetId === asset.id}
        >
          {editingAssetId === asset.id ? '打开中…' : '编辑'}
        </button>
      )}
      {asset.sourcePageId && (
        <button type="button" onClick={() => showPage(asset.sourcePageId!, true)}>
          发布到广场
        </button>
      )}
      {canGenerateWithAgent(asset) && (
        <button type="button" onClick={() => openAgentGenerator(asset)}>
          AI 生成页面
        </button>
      )}
      {asset.publicUrl && (
        <a
          className="mindspace-asset-share"
          href={asset.publicUrl}
          target="_blank"
          rel="noreferrer"
        >
          公网图
        </a>
      )}
      {asset.status === 'ready' && asset.scanStatus !== 'blocked' ? (
        <button
          type="button"
          className="mindspace-asset-share"
          onClick={() => setSharePayload(buildAssetSharePayload(asset, selectedCategory?.code))}
        >
          分享
        </button>
      ) : (
        <span className="mindspace-asset-blocked">暂不可用</span>
      )}
      {pendingDeleteId === asset.id ? (
        <>
          <button
            type="button"
            className="mindspace-delete-confirm"
            onClick={() => void removeAsset(asset.id)}
          >
            确认删除
          </button>
          <button type="button" onClick={() => setPendingDeleteId(null)}>
            取消
          </button>
        </>
      ) : (
        <button type="button" onClick={() => setPendingDeleteId(asset.id)}>
          删除
        </button>
      )}
    </div>
  );

  const createPage = async () => {
    if (!newPageTitle.trim() || !newPageContent.trim()) return;
    if (previewMode) {
      setError(previewBlocked().message);
      return;
    }
    setCreatingPage(true);
    setError(null);
    try {
      const page = await createMindSpacePage({
        title: newPageTitle.trim(),
        content: newPageContent.trim(),
        templateId: newPageTemplate,
      });
      setNewPageOpen(false);
      setNewPageTitle('');
      setNewPageContent('');
      await load();
      showPage(page.id);
    } catch (err) {
      setError(err instanceof Error ? err.message : '页面创建失败');
    } finally {
      setCreatingPage(false);
    }
  };

  const agentJobsPageCount = Math.max(1, Math.ceil(agentJobsTotal / AGENT_JOBS_PAGE_SIZE));

  const renderAgentJobCard = (job: MindSpaceAgentJob) => (
    <article className={`mindspace-agent-history-card is-${job.status}`} key={job.id}>
      <div className="mindspace-agent-history-top">
        <div>
          <strong>{job.assets[0]?.displayName ?? '未命名资料'}</strong>
          <p>{job.instruction}</p>
        </div>
        <span>{JOB_STATUS_LABELS[job.status]}</span>
      </div>
      <div className="mindspace-agent-history-meta">
        <span>阶段：{job.progress.stage}</span>
        <span>{new Date(job.queuedAt).toLocaleString()}</span>
      </div>
      {job.errorMessage && <div className="mindspace-agent-history-error">{job.errorMessage}</div>}
      <div className="mindspace-agent-history-actions">
        {job.resultPageId && (
          <button type="button" onClick={() => showPage(job.resultPageId!)}>
            打开草稿
          </button>
        )}
        {['queued', 'running'].includes(job.status) && (
          <button type="button" onClick={() => void cancelAgentJob(job.id)}>
            取消任务
          </button>
        )}
        {job.retryable && ['failed', 'cancelled', 'timed_out'].includes(job.status) && (
          <button type="button" onClick={() => void retryAgentJob(job.id)}>
            重新生成
          </button>
        )}
      </div>
    </article>
  );

  const showHomeModules =
    space && !selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen;
  const scheduleItems = previewMode
    ? PREVIEW_SCHEDULE_ITEMS
    : (space?.schedule?.todayTodoItems ?? []).map((item) => ({
        id: item.id,
        time: item.allDay ? '全天' : formatScheduleClock(item.dueAt ?? item.startAt, item.timezone),
        kind: item.kind === 'event' ? '日程' : '待办',
        title: item.title,
        meta:
          item.description?.trim() ||
          (item.sourceChannel === 'wechat' ? '来自服务号' : '来自空间同步'),
        tone: item.kind === 'event' ? 'event' : 'task',
      }));
  const scheduleDigests = previewMode ? [] : space?.schedule?.digestSubscriptions ?? [];
  const selectedPreviewPage = selectedPageId
    ? pages.find((page) => page.id === selectedPageId) ?? null
    : null;
  const selectedPreviewReader = selectedPreviewPage
    ? PREVIEW_PAGE_READERS[selectedPreviewPage.id] ?? {
        kicker: selectedPreviewPage.templateId,
        body: [
          selectedPreviewPage.summary || '这是该页面的站内预览内容。',
          '正式接入真实数据后，这里会直接呈现用户生成页面的完整内容。',
        ],
      }
    : null;

  return (
    <div className={`mindspace-page${previewMode ? ' mindspace-page-preview' : ''}`}>
      {previewMode && (
        <div className="mindspace-preview-banner">
          <span>UI 预览模式 · 示例数据，无需登录</span>
          <a href="/">退出预览</a>
        </div>
      )}
      <header className="mindspace-header">
        <div className="mindspace-header-nav">
          {fromPlaza && (
            <a href={plazaReturnUrl} className="mindspace-back mindspace-back-plaza">
              返回广场
            </a>
          )}
          <button type="button" className="mindspace-back" onClick={onBack}>
            返回聊天
          </button>
        </div>
        <div className="mindspace-header-actions">
          {!fromPlaza && (
            <a
              href={resolvePlazaHomeUrl()}
              className="ghost-btn mindspace-plaza-link"
              target="_blank"
              rel="noopener noreferrer"
            >
              发现广场
            </a>
          )}
          {previewMode ? (
            <BalanceRing
              balanceCents={user.balanceCents}
              totalCreditCents={user.totalCreditCents}
              tokensUsed={user.tokensUsed}
              onRecharge={() => {}}
            />
          ) : (
            <MindSpaceBalanceRing />
          )}
          {previewMode ? (
            <NotificationCenter onOpenRecharge={() => {}} />
          ) : (
            <MindSpaceNotificationCenter />
          )}
          <span>{user.displayName}</span>
          <button type="button" className="ghost-btn logout-btn" onClick={onLogout}>
            登出
          </button>
        </div>
      </header>

      <main className="mindspace-content">
        {!allPagesOpen && (
        <section className="mindspace-hero">
          <div className="mindspace-hero-left">
            <p className="mindspace-eyebrow">MINDSPACE</p>
            <h1>{space?.name ?? '我的空间'}</h1>
            <p>让每一次 AI 对话、分析和创作，都成为可以继续使用的作品。</p>
            <div className="mindspace-actions">
              <button
                type="button"
                className="mindspace-primary"
                onClick={() => openUpload()}
                disabled={!space}
              >
                上传文件
              </button>
              <button
                type="button"
                className="mindspace-secondary"
                onClick={() => {
                  setSelectedCategory(null);
                  setSelectedPageId(null);
                  setAllPagesOpen(false);
                  setNewPageOpen(true);
                }}
              >
                新建页面
              </button>
              <a
                href={resolvePlazaHomeUrl()}
                className="mindspace-secondary mindspace-plaza-link"
                target="_blank"
                rel="noopener noreferrer"
              >
                逛 Plaza
              </a>
            </div>
          </div>
          {space && (
            <aside className="mindspace-hero-quota">
              <div className="mindspace-hero-quota-top">
                <span className="mindspace-hero-quota-label">
                  <span>空间容量</span>
                  <button
                    type="button"
                    className="mindspace-hero-quota-buy-trigger"
                    aria-label="购买空间扩容"
                    onClick={() => {
                      setSpacePurchaseMessage(null);
                      setSpacePurchaseOpen(true);
                    }}
                  >
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                      <path
                        d="M12 5v14M5 12h14"
                        stroke="currentColor"
                        strokeWidth="1.8"
                        strokeLinecap="round"
                      />
                    </svg>
                    <span>扩容</span>
                  </button>
                </span>
                <strong>
                  {formatBytes(occupiedBytes)} / {formatBytes(space.quota.quotaBytes)}
                </strong>
              </div>
              <div
                className="mindspace-hero-quota-track"
                role="progressbar"
                aria-label="空间使用量"
                aria-valuemin={0}
                aria-valuemax={100}
                aria-valuenow={Math.round(usedPercent)}
              >
                <span style={{ width: `${usedPercent}%` }} />
              </div>
              <p className="mindspace-hero-quota-meta">
                {space.quota.reservedBytes > 0 && (
                  <>
                    含上传预留 {formatBytes(space.quota.reservedBytes)} ·{' '}
                  </>
                )}
                剩余 {formatBytes(space.quota.availableBytes)} ·{' '}
                单文件上限 {formatBytes(space.quota.maxFileBytes)} · 可发布页面{' '}
                {space.quota.publicPageUsed}/{space.quota.publicPageLimit} · 今日 AI{' '}
                {space.quota.aiDailyUsed}/{space.quota.aiDailyLimit}
              </p>
              <button
                type="button"
                className="mindspace-hero-quota-cleanup"
                onClick={() => {
                  setCleanupOpen((value) => !value);
                  if (!cleanupOpen) void loadCleanupItems();
                }}
              >
                {cleanupOpen ? '收起清理' : '清理空间'}
              </button>
            </aside>
          )}
          {cleanupOpen && (
            <div className="mindspace-hero-cleanup">
              {cleanupLoading && <p>正在扫描可清理项…</p>}
              {!cleanupLoading && cleanupItems.length === 0 && (
                <p>
                  {space && occupiedBytes >= space.quota.quotaBytes * 0.85
                    ? `空间已用 ${formatBytes(occupiedBytes)} / ${formatBytes(space.quota.quotaBytes)}，暂无可自动清理的临时文件或历史版本。请到下方分类中手动删除不需要的页面或资产。`
                    : '当前没有可清理的临时文件或历史页面版本。'}
                </p>
              )}
              {!cleanupLoading && cleanupItems.length > 0 && (
                <>
                  <p>
                    可释放约{' '}
                    {formatBytes(
                      cleanupItems
                        .filter((item) => selectedCleanupIds.includes(item.id))
                        .reduce((sum, item) => sum + item.sizeBytes, 0),
                    )}
                  </p>
                  <ul className="mindspace-cleanup-list">
                    {cleanupItems.map((item) => (
                      <li key={item.id}>
                        <label>
                          <input
                            type="checkbox"
                            checked={selectedCleanupIds.includes(item.id)}
                            onChange={(event) => {
                              setSelectedCleanupIds((current) =>
                                event.target.checked
                                  ? [...current, item.id]
                                  : current.filter((id) => id !== item.id),
                              );
                            }}
                          />
                          <span>
                            <strong>{item.label}</strong>
                            <small>
                              {item.detail} · {formatBytes(item.sizeBytes)} · {item.path}
                            </small>
                          </span>
                        </label>
                      </li>
                    ))}
                  </ul>
                  <div className="mindspace-cleanup-actions">
                    <button
                      type="button"
                      onClick={() =>
                        setSelectedCleanupIds(
                          selectedCleanupIds.length === cleanupItems.length
                            ? []
                            : cleanupItems.map((item) => item.id),
                        )
                      }
                    >
                      {selectedCleanupIds.length === cleanupItems.length ? '取消全选' : '全选'}
                    </button>
                    <button
                      type="button"
                      className="mindspace-primary"
                      disabled={cleanupRunning || selectedCleanupIds.length === 0}
                      onClick={() => void runCleanup()}
                    >
                      {cleanupRunning ? '清理中…' : '清除所选'}
                    </button>
                  </div>
                </>
              )}
            </div>
          )}
        </section>
        )}

        {showHomeModules && (
          <section className="mindspace-schedule-module" aria-labelledby="mindspace-schedule-title">
            <div className="mindspace-schedule-head">
              <div>
                <p className="mindspace-eyebrow">TODAY</p>
                <h2 id="mindspace-schedule-title">待办行事历</h2>
              </div>
              <button type="button" className="mindspace-schedule-link">
                查看日历
              </button>
            </div>
            <div className="mindspace-schedule-summary">
              <strong>今天 {scheduleItems.length || 0} 项</strong>
              <span>
                {scheduleItems.length > 0
                  ? '已有待办记录'
                  : scheduleDigests.length > 0
                    ? `${scheduleDigests.length} 个服务号待办推送`
                    : '还没有安排'}
              </span>
            </div>
            {scheduleDigests.length > 0 && (
              <div className="mindspace-schedule-list">
                {scheduleDigests.map((digest) => (
                  <article className="mindspace-schedule-item" key={digest.id}>
                    <time>{String(digest.hour).padStart(2, '0')}:{String(digest.minute).padStart(2, '0')}</time>
                    <span className={`mindspace-schedule-kind is-${digest.status === 'active' ? 'event' : 'reminder'}`}>
                      订阅
                    </span>
                    <div>
                      <strong>每天待办摘要</strong>
                      <small>{formatDigestSchedule(digest)}</small>
                    </div>
                  </article>
                ))}
              </div>
            )}
            {scheduleItems.length > 0 ? (
              <div className="mindspace-schedule-list">
                {scheduleItems.map((item) => (
                  <article className="mindspace-schedule-item" key={item.id}>
                    <time>{item.time}</time>
                    <span className={`mindspace-schedule-kind is-${item.tone}`}>{item.kind}</span>
                    <div>
                      <strong>{item.title}</strong>
                      <small>{item.meta}</small>
                    </div>
                  </article>
                ))}
              </div>
            ) : (
              <p className="mindspace-schedule-empty">
                今天还没有待办，可以在聊天里说“明天上午提醒我带材料”。
              </p>
            )}
          </section>
        )}

        {loading && <div className="mindspace-state">正在加载个人空间…</div>}

        {error && !assetDeleteDialog && (
          <div className="mindspace-state mindspace-error">
            <span>{error}</span>
            <button type="button" onClick={() => void load()}>
              重新加载
            </button>
          </div>
        )}

        {space && (
          <>
            {selectedPageId ? (
              previewMode ? (
                <section className="mindspace-page-reader">
                  <header className="mindspace-page-reader-bar">
                    <button type="button" className="mindspace-inline-back" onClick={closePage}>
                      {allPagesOpen ? '返回全部页面' : '返回空间首页'}
                    </button>
                    <span>站内浏览</span>
                  </header>
                  {selectedPreviewPage && selectedPreviewReader ? (
                    <article className="mindspace-page-reader-sheet">
                      <p className="mindspace-eyebrow">{selectedPreviewReader.kicker}</p>
                      <h2>{selectedPreviewPage.title}</h2>
                      <p className="mindspace-page-reader-summary">
                        {selectedPreviewPage.summary}
                      </p>
                      <div className="mindspace-page-reader-body">
                        {selectedPreviewReader.body.map((paragraph) => (
                          <p key={paragraph}>{paragraph}</p>
                        ))}
                      </div>
                    </article>
                  ) : (
                    <div className="mindspace-state">页面不存在或已被删除</div>
                  )}
                </section>
              ) : (
              <MindSpacePageDetail
                pageId={selectedPageId}
                onBack={closePage}
                onSaved={refreshMindSpaceSnapshot}
                onContextUpdate={setPageLiveContext}
                autoOpenPlaza={selectedPageAutoPlaza}
                refreshTrigger={pageRefreshTrigger}
                onFullscreenPreviewChange={setPageFullscreenPreviewOpen}
              />
              )
            ) : newPageOpen ? (
              <section className="mindspace-section mindspace-new-page">
                <button
                  type="button"
                  className="mindspace-inline-back"
                  onClick={() => setNewPageOpen(false)}
                >
                  返回空间首页
                </button>
                <p className="mindspace-eyebrow">CREATE PAGE</p>
                <h2>创建页面草稿</h2>
                <label>
                  标题
                  <input
                    value={newPageTitle}
                    maxLength={255}
                    onChange={(event) => setNewPageTitle(event.target.value)}
                  />
                </label>
                <label>
                  模板
                  <select
                    value={newPageTemplate}
                    onChange={(event) => setNewPageTemplate(event.target.value)}
                  >
                    <option value="editorial">编辑长文</option>
                    <option value="report">分析报告</option>
                    <option value="knowledge-card">知识卡片</option>
                    <option value="profile">个人介绍</option>
                  </select>
                </label>
                <label>
                  内容
                  <textarea
                    value={newPageContent}
                    rows={14}
                    onChange={(event) => setNewPageContent(event.target.value)}
                  />
                </label>
                <div className="mindspace-upload-actions">
                  <button
                    type="button"
                    className="mindspace-primary"
                    disabled={creatingPage || !newPageTitle.trim() || !newPageContent.trim()}
                    onClick={() => void createPage()}
                  >
                    {creatingPage ? '创建中…' : '创建并预览'}
                  </button>
                </div>
              </section>
            ) : allPagesOpen ? (
              <section className="mindspace-independent-page mindspace-all-pages">
                <div className="mindspace-section-heading">
                  <div>
                    <button
                      type="button"
                      className="mindspace-inline-back"
                      onClick={() => setAllPagesOpen(false)}
                    >
                      返回空间首页
                    </button>
                    <p className="mindspace-eyebrow">ALL PAGES</p>
                    <h2>全部页面</h2>
                  </div>
                  <span>{pages.length} 个页面</span>
                </div>
                {pages.length > 0 ? (
                  <div className="mindspace-feed-grid mindspace-recent-work-grid is-all-pages">
                    {pages.map((page) => (
                      <MindSpaceFeedCard
                        key={page.id}
                        page={page}
                        previewMode={previewMode}
                        onClick={() => showPage(page.id)}
                      />
                    ))}
                  </div>
                ) : (
                  <div className="mindspace-recent-work-empty">
                    <p>上传资料或从聊天保存页面后，所有页面会集中显示在这里。</p>
                  </div>
                )}
              </section>
            ) : agentJobsPanelOpen ? (
              <section className="mindspace-section">
                <div className="mindspace-section-heading">
                  <div>
                    <button
                      type="button"
                      className="mindspace-inline-back"
                      onClick={closeAgentJobsPanel}
                    >
                      返回空间首页
                    </button>
                    <p className="mindspace-eyebrow">AGENT JOBS</p>
                    <h2>AI 生成任务</h2>
                  </div>
                  <span>共 {agentJobsTotal} 条</span>
                </div>
                {agentJobsLoading && <div className="mindspace-state">正在加载任务记录…</div>}
                {!agentJobsLoading && agentJobsList.length === 0 && (
                  <div className="mindspace-empty">
                    <h2>还没有 AI 生成任务</h2>
                    <p>在 OA 工作区选择资料并点击「AI 生成页面」后，任务会出现在这里。</p>
                  </div>
                )}
                {!agentJobsLoading && agentJobsList.length > 0 && (
                  <>
                    <div className="mindspace-agent-history">{agentJobsList.map(renderAgentJobCard)}</div>
                    {agentJobsTotal > AGENT_JOBS_PAGE_SIZE && (
                      <div className="mindspace-pagination">
                        <button
                          type="button"
                          disabled={agentJobsPage <= 0 || agentJobsLoading}
                          onClick={() => void loadAgentJobsPage(agentJobsPage - 1)}
                        >
                          上一页
                        </button>
                        <span>
                          第 {agentJobsPage + 1} / {agentJobsPageCount} 页
                        </span>
                        <button
                          type="button"
                          disabled={
                            agentJobsLoading ||
                            (agentJobsPage + 1) * AGENT_JOBS_PAGE_SIZE >= agentJobsTotal
                          }
                          onClick={() => void loadAgentJobsPage(agentJobsPage + 1)}
                        >
                          下一页
                        </button>
                      </div>
                    )}
                  </>
                )}
              </section>
            ) : selectedCategory ? (
              <section className="mindspace-section mindspace-assets-section">
                <div className="mindspace-section-heading">
                  <div>
                    <button
                      type="button"
                      className="mindspace-inline-back"
                      onClick={closeCategory}
                    >
                      返回空间首页
                    </button>
                    <p className="mindspace-eyebrow">
                      {selectedCategory.code.toUpperCase()} WORKSPACE
                    </p>
                    <h2>{selectedCategory.name}</h2>
                  </div>
                  {['oa', 'private', 'public'].includes(selectedCategory.code) && (
                    <button
                      type="button"
                      className="mindspace-primary"
                      onClick={() => openUpload(selectedCategory)}
                    >
                      上传资料
                    </button>
                  )}
                </div>

                {selectedCategory.code === 'private' && (
                  <div className="mindspace-security-note">
                    私人区默认不可公开。后续生成公开内容时，系统会要求风险扫描和脱敏副本。
                  </div>
                )}

                {selectedCategory.code === 'draft' ? (
                  pages.length > 0 ? (
                    <div className="mindspace-feed-grid">
                      {pages.map((page) => (
                        <MindSpaceFeedCard
                          key={page.id}
                          page={page}
                          previewMode={previewMode}
                          onClick={() => showPage(page.id)}
                        />
                      ))}
                    </div>
                  ) : (
                    <div className="mindspace-empty">
                      <h2>还没有页面草稿</h2>
                      <p>从聊天保存 AI 回答，或创建第一张页面。</p>
                    </div>
                  )
                ) : (
                  <>
                {(() => {
                  const categoryPages = pages.filter(
                    (p) => p.categoryCode === selectedCategory.code,
                  );
                  return categoryPages.length > 0 ? (
                    <div className="mindspace-feed-grid mindspace-category-pages">
                      {categoryPages.map((page) => (
                        <MindSpaceFeedCard
                          key={page.id}
                          page={page}
                          previewMode={previewMode}
                          onClick={() => showPage(page.id)}
                          actions={
                            page.status === 'published'
                              ? [{ label: '发布到广场', onClick: (e) => { e.stopPropagation(); showPage(page.id, true); } }]
                              : undefined
                          }
                        />
                      ))}
                    </div>
                  ) : null;
                })()}
                {assetsLoading && <div className="mindspace-state">正在加载资产…</div>}
                {!assetsLoading && assets.length === 0 && (
                  <div className="mindspace-empty">
                    <h2>这里还没有内容</h2>
                    <p>上传第一份资料后，它会成为 AI 可以安全处理的资产。</p>
                  </div>
                )}
                {!assetsLoading && assets.length > 0 && (
                  <>
                    <div className="mindspace-asset-filters">
                      {(
                        [
                          ['all', '全部'],
                          ['images', '图片'],
                          ['files', '资料'],
                          ['pages', '页面'],
                        ] as const
                      ).map(([value, label]) => (
                        <button
                          type="button"
                          key={value}
                          className={assetFilter === value ? 'is-active' : undefined}
                          onClick={() => {
                            setAssetFilter(value);
                            setImagePage(0);
                          }}
                        >
                          {label}
                          {value === 'images' && imageAssets.length > 0
                            ? ` (${imageAssets.length})`
                            : ''}
                        </button>
                      ))}
                    </div>
                    <div className="mindspace-asset-bulkbar">
                      <button
                        type="button"
                        onClick={toggleVisibleAssetsSelected}
                        disabled={visibleSelectableAssets.length === 0 || bulkDeletingAssets}
                      >
                        {allVisibleAssetsSelected ? '取消当前列表' : '选择当前列表'}
                      </button>
                      <span>已选 {selectedAssetIds.length} 项</span>
                      {selectedAssetIds.length > 0 && (
                        <button
                          type="button"
                          onClick={() => setSelectedAssetIds([])}
                          disabled={bulkDeletingAssets}
                        >
                          清空
                        </button>
                      )}
                      <button
                        type="button"
                        className="mindspace-asset-bulk-delete"
                        onClick={() => void removeSelectedAssets()}
                        disabled={selectedAssetIds.length === 0 || bulkDeletingAssets}
                      >
                        {bulkDeletingAssets ? '删除中…' : '删除选中'}
                      </button>
                    </div>
                    {visibleImageAssets.length > 0 && (
                      <>
                        {assetFilter === 'all' && (
                          <div className="mindspace-section-subheading">
                            <h3>图片</h3>
                            <span>{visibleImageAssets.length} 张</span>
                          </div>
                        )}
                        <div
                          className={`mindspace-image-grid${imagePaginationEnabled ? ' is-compact' : ''}`}
                        >
                          {pagedImageAssets.map((asset) => (
                            <article
                              className={`mindspace-image-card${imagePaginationEnabled ? ' is-compact' : ''}`}
                              key={asset.id}
                            >
                              <label className="mindspace-asset-select">
                                <input
                                  type="checkbox"
                                  checked={selectedAssetSet.has(asset.id)}
                                  onChange={() => toggleAssetSelected(asset.id)}
                                />
                                <span>选择</span>
                              </label>
                              <button
                                type="button"
                                className="mindspace-image-thumb"
                                onClick={() => setPreviewAssetId(asset.id)}
                                title={asset.displayName}
                              >
                                <img
                                  src={buildAssetImageUrl(asset)}
                                  alt={asset.displayName}
                                  loading="lazy"
                                />
                              </button>
                              {!imagePaginationEnabled && (
                                <>
                                  <div className="mindspace-image-meta">
                                    <span className="mindspace-image-name">{asset.displayName}</span>
                                    <span className="mindspace-image-size">
                                      {formatBytes(asset.sizeBytes)}
                                    </span>
                                  </div>
                                  {renderAssetActions(asset)}
                                </>
                              )}
                            </article>
                          ))}
                        </div>
                        {imagePaginationEnabled && (
                          <div className="mindspace-pagination mindspace-image-pagination">
                            <button
                              type="button"
                              disabled={safeImagePage <= 0}
                              onClick={() => setImagePage((page) => Math.max(0, page - 1))}
                            >
                              上一页
                            </button>
                            <span>
                              第 {safeImagePage + 1} / {imagePageCount} 页 · 共{' '}
                              {visibleImageAssets.length} 张
                            </span>
                            <button
                              type="button"
                              disabled={safeImagePage >= imagePageCount - 1}
                              onClick={() =>
                                setImagePage((page) => Math.min(imagePageCount - 1, page + 1))
                              }
                            >
                              下一页
                            </button>
                          </div>
                        )}
                      </>
                    )}
                    {visibleCardAssets.length > 0 && (
                      <>
                        {assetFilter === 'all' && visibleImageAssets.length > 0 && (
                          <div className="mindspace-section-subheading">
                            <h3>资料与页面</h3>
                            <span>{visibleCardAssets.length} 项</span>
                          </div>
                        )}
                        <div className="mindspace-item-grid">
                          {visibleCardAssets.map((asset) => (
                            <article
                              className={`mindspace-item-card${isWorkAsset(asset) ? ' is-work' : ' is-file'}`}
                              key={asset.id}
                            >
                              <label className="mindspace-asset-select is-card">
                                <input
                                  type="checkbox"
                                  checked={selectedAssetSet.has(asset.id)}
                                  onChange={() => toggleAssetSelected(asset.id)}
                                />
                                <span>选择</span>
                              </label>
                              <button
                                type="button"
                                className="mindspace-item-card-main"
                                onClick={() => {
                                  if (isWorkAsset(asset)) {
                                    void openAssetEditor(asset);
                                    return;
                                  }
                                  if (isPreviewableAsset(asset)) setPreviewAssetId(asset.id);
                                }}
                              >
                                {isWorkAsset(asset) ? (
                                  <div className="mindspace-item-cover">
                                    {isPreviewableAsset(asset) ? (
                                      <WorkCover
                                        thumbnailUrl={`/api/mindspace/v1/assets/${asset.id}/thumbnail?v=${asset.updatedAt}`}
                                        previewAsset={asset}
                                        fallbackLabel={assetIcon(asset)}
                                        title={asset.displayName}
                                      />
                                    ) : (
                                      <span>{assetIcon(asset)}</span>
                                    )}
                                  </div>
                                ) : (
                                  <div className="mindspace-item-icon">{assetIcon(asset)}</div>
                                )}
                                <div className="mindspace-item-copy">
                                  <span className="mindspace-item-badge">
                                    {assetSourceLabel(asset)}
                                  </span>
                                  <h3>{asset.displayName}</h3>
                                  <p>
                                    {formatBytes(asset.sizeBytes)} ·{' '}
                                    {new Date(asset.updatedAt).toLocaleDateString()}
                                  </p>
                                </div>
                              </button>
                              {renderAssetActions(asset)}
                            </article>
                          ))}
                        </div>
                      </>
                    )}
                    {visibleImageAssets.length === 0 && visibleCardAssets.length === 0 && (
                      <div className="mindspace-empty">
                        <h2>该分类下暂无内容</h2>
                        <p>试试切换其他筛选，或上传新资料。</p>
                      </div>
                    )}
                  </>
                )}

                {previewAsset && previewAssetFrame && (
                  <div className="mindspace-asset-preview-backdrop" role="presentation">
                    <section className="mindspace-asset-preview-panel" role="dialog" aria-modal="true">
                      <div className="mindspace-asset-preview-bar">
                        <strong>文件预览</strong>
                        <button type="button" onClick={() => setPreviewAssetId(null)}>
                          关闭
                        </button>
                      </div>
                      <iframe
                        title="资产预览"
                        src={previewAssetFrame.src}
                        {...(previewAssetFrame.sandbox ? { sandbox: previewAssetFrame.sandbox } : {})}
                      />
                    </section>
                  </div>
                )}
                  </>
                )}
              </section>
            ) : (
              <section className="mindspace-section">
                <div className="mindspace-section-heading">
                  <div>
                    <p className="mindspace-eyebrow">QUICK ACCESS</p>
                    <h2>空间分类</h2>
                  </div>
                  <span>先从成果开始，目录稍后再整理</span>
                </div>
                <div className="mindspace-grid">
                  {space.categories
                    .filter((category) => ['oa', 'private', 'public'].includes(category.code))
                    .map((category) => (
                      <article
                        className={`mindspace-card mindspace-card-${category.code}`}
                        key={category.id}
                      >
                        <div className="mindspace-card-top">
                          <span className="mindspace-card-code">{category.code.toUpperCase()}</span>
                          <span>{category.itemCount} 项</span>
                        </div>
                        <h3>{category.name}</h3>
                        <p>{CATEGORY_DESCRIPTIONS[category.code]}</p>
                        {CATEGORY_ACTIONS[category.code] && (
                          <button type="button" onClick={() => void openCategory(category)}>
                            {CATEGORY_ACTIONS[category.code]}
                          </button>
                        )}
                      </article>
                    ))}
                  <div className="mindspace-grid-secondary">
                    {space.categories
                      .filter((category) => category.code === 'draft')
                      .map((category) => (
                        <article
                          className={`mindspace-card mindspace-card-${category.code}`}
                          key={category.id}
                        >
                          <div className="mindspace-card-top">
                            <span className="mindspace-card-code">{category.code.toUpperCase()}</span>
                            <span>{category.itemCount} 项</span>
                          </div>
                          <h3>{category.name}</h3>
                          <p>{CATEGORY_DESCRIPTIONS[category.code]}</p>
                          {CATEGORY_ACTIONS[category.code] && (
                            <button type="button" onClick={() => void openCategory(category)}>
                              {CATEGORY_ACTIONS[category.code]}
                            </button>
                          )}
                        </article>
                      ))}
                    {space.categories
                      .filter((category) => category.code === 'archive')
                      .map((category) => (
                        <article
                          className={`mindspace-card mindspace-card-${category.code}`}
                          key={category.id}
                        >
                          <div className="mindspace-card-top">
                            <span className="mindspace-card-code">{category.code.toUpperCase()}</span>
                            <span>{category.itemCount} 项</span>
                          </div>
                          <h3>{category.name}</h3>
                          <p>{CATEGORY_DESCRIPTIONS[category.code]}</p>
                        </article>
                      ))}
                  </div>
                </div>
              </section>
            )}

            {!selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen && (
              <section className="mindspace-section mindspace-recent-work">
                <div className="mindspace-section-heading">
                  <div>
                    <p className="mindspace-eyebrow">RECENT WORK</p>
                    <h2>{pages.length > 0 ? '最近页面' : '这里会出现你的 AI 成果'}</h2>
                  </div>
                  {pages.length > 0 && (
                    <button
                      type="button"
                      className="mindspace-section-link"
                      onClick={() => setAllPagesOpen(true)}
                    >
                      查看全部
                    </button>
                  )}
                </div>
                {pages.length > 0 ? (
                  <>
                    <div className="mindspace-feed-grid mindspace-recent-work-grid">
                      {pagedRecentPages.map((page) => (
                        <MindSpaceFeedCard
                          key={page.id}
                          page={page}
                          previewMode={previewMode}
                          onClick={() => showPage(page.id)}
                        />
                      ))}
                    </div>
                    {pages.length > RECENT_PAGES_PAGE_SIZE ? (
                      <div className="mindspace-pagination">
                        <button
                          type="button"
                          disabled={safeRecentPagesPage <= 0}
                          onClick={() =>
                            setRecentPagesPage((value) => Math.max(0, value - 1))
                          }
                        >
                          上一页
                        </button>
                        <span>
                          第 {safeRecentPagesPage + 1} / {recentPagesTotal} 页
                        </span>
                        <button
                          type="button"
                          disabled={safeRecentPagesPage >= recentPagesTotal - 1}
                          onClick={() =>
                            setRecentPagesPage((value) =>
                              Math.min(recentPagesTotal - 1, value + 1),
                            )
                          }
                        >
                          下一页
                        </button>
                      </div>
                    ) : null}
                  </>
                ) : (
                  <div className="mindspace-recent-work-empty">
                    <p>上传资料或从聊天保存页面后，报告、图片和作品会以卡片形式出现在这里。</p>
                  </div>
                )}
              </section>
            )}

          </>
        )}
      </main>

      {uploadOpen && selectedCategory && (
        <div
          className="mindspace-upload-backdrop"
          role="presentation"
          onClick={(event) => {
            if (event.target === event.currentTarget) closeUpload();
          }}
        >
          <section
            className="mindspace-upload-dialog"
            role="dialog"
            aria-modal="true"
            aria-labelledby="mindspace-upload-title"
          >
            <div className="mindspace-upload-dialog-bar">
              <div>
                <p className="mindspace-eyebrow">UPLOAD</p>
                <h3 id="mindspace-upload-title">{uploadDialogTitle(selectedCategory)}</h3>
              </div>
              <button
                type="button"
                className="mindspace-upload-dialog-close"
                onClick={closeUpload}
                disabled={uploading}
                aria-label="关闭"
              >
                关闭
              </button>
            </div>
            <p className="mindspace-upload-dialog-desc">
              支持 {UPLOAD_FILE_TYPE_LABEL}，单文件最大 {formatBytes(maxUploadFileBytes)}。
            </p>
            <input
              type="file"
              accept={UPLOAD_ACCEPT}
              onChange={handleUploadFileChange}
            />
            {uploadError && <p className="mindspace-upload-error">{uploadError}</p>}
            {selectedFile && (
              <div className="mindspace-upload-file">
                <span>{selectedFile.name}</span>
                <strong>{formatBytes(selectedFile.size)}</strong>
              </div>
            )}
            <div className="mindspace-upload-actions">
              <button
                type="button"
                className="mindspace-secondary"
                onClick={closeUpload}
                disabled={uploading}
              >
                取消
              </button>
              <button
                type="button"
                className="mindspace-primary"
                onClick={() => void submitUpload()}
                disabled={!selectedFile || uploading}
              >
                {uploading ? '上传中…' : '开始上传'}
              </button>
            </div>
          </section>
        </div>
      )}

      {agentAsset && (
        <div
          className="mindspace-upload-backdrop"
          role="presentation"
          onClick={(event) => {
            if (event.target === event.currentTarget) closeAgentGenerator();
          }}
        >
          <section
            className="mindspace-upload-dialog mindspace-agent-dialog"
            role="dialog"
            aria-modal="true"
            aria-labelledby="mindspace-agent-title"
          >
            <div className="mindspace-upload-dialog-bar">
              <div>
                <p className="mindspace-eyebrow">AGENT JOB</p>
                <h3 id="mindspace-agent-title">基于资料生成页面</h3>
              </div>
              <button
                type="button"
                className="mindspace-upload-dialog-close"
                onClick={closeAgentGenerator}
                disabled={agentSubmitting}
                aria-label="关闭"
              >
                关闭
              </button>
            </div>
            <div className="mindspace-agent-asset">
              <strong>{agentAsset.displayName}</strong>
              <span>{formatBytes(agentAsset.sizeBytes)} · {agentAsset.mimeType}</span>
            </div>
            <p className="mindspace-upload-dialog-desc">
              当前会读取这份资料，为你生成一个新的草稿页。适合摘要、周报、说明页和知识整理。
            </p>
            <label className="mindspace-agent-field">
              <span>生成要求</span>
              <textarea
                value={agentInstruction}
                onChange={(event) => setAgentInstruction(event.target.value)}
                placeholder="描述你希望页面如何组织、强调什么、输出成什么风格"
                rows={7}
                disabled={agentSubmitting || Boolean(agentJob && ['running', 'completed'].includes(agentJob.status))}
              />
            </label>
            {agentJob && (
              <div className={`mindspace-agent-status is-${agentJob.status}`}>
                <strong>
                  {agentJob.status === 'completed'
                    ? '任务已完成'
                    : agentJob.status === 'failed'
                      ? '任务失败'
                      : agentJob.status === 'running'
                        ? '正在生成'
                        : '任务已排队'}
                </strong>
                <span>
                  {agentJob.progress.message ??
                    agentJob.errorMessage ??
                    `当前阶段：${agentJob.progress.stage}`}
                </span>
              </div>
            )}
            <div className="mindspace-upload-actions">
              <button
                type="button"
                className="mindspace-secondary"
                onClick={closeAgentGenerator}
                disabled={agentSubmitting}
              >
                取消
              </button>
              <button
                type="button"
                className="mindspace-primary"
                onClick={() => void submitAgentGenerator()}
                disabled={
                  !agentInstruction.trim() ||
                  agentSubmitting ||
                  Boolean(agentJob && ['queued', 'running', 'completed'].includes(agentJob.status))
                }
              >
                {agentSubmitting
                  ? '提交中…'
                  : agentJob?.status === 'running'
                    ? '生成中…'
                    : agentJob?.status === 'completed'
                      ? '已生成'
                      : '开始生成'}
              </button>
            </div>
          </section>
        </div>
      )}

      {sharePayload && (
        <ShareSheet payload={sharePayload} onClose={() => setSharePayload(null)} />
      )}

      {previewPageTarget ? (
        <div className="mindspace-asset-preview-backdrop" role="presentation">
          <section className="mindspace-asset-preview-panel" role="dialog" aria-modal="true">
            <div className="mindspace-asset-preview-bar">
              <strong>{previewPageTarget.title}</strong>
              <button type="button" onClick={() => setPreviewPageTarget(null)}>
                关闭
              </button>
            </div>
            <PagePreviewFrame
              title={previewPageTarget.title}
              src={`/api/mindspace/v1/pages/${previewPageTarget.id}/preview?v=${previewPageTarget.versionNo ?? previewPageTarget.updatedAt}`}
            />
          </section>
        </div>
      ) : null}

      <MindSpaceModal
        open={spacePurchaseOpen && Boolean(space)}
        onClose={() => setSpacePurchaseOpen(false)}
        title="购买空间"
        eyebrow="SPACE UPGRADE"
        className="mindspace-space-purchase-dialog"
        disableClose={spacePurchasePending}
      >
        {space && (
          <>
            <p className="mindspace-upload-dialog-desc">
              仅支持使用余额支付。购买成功后会立即同步后台空间配额，并在当前页面实时生效。
            </p>
            <div className="mindspace-space-purchase-summary">
              <div>
                <span>当前余额</span>
                <strong>{formatYuan(currentBalanceCents)}</strong>
              </div>
              <div>
                <span>当前剩余空间</span>
                <strong>{formatBytes(space.quota.availableBytes)}</strong>
              </div>
              <div>
                <span>购买价格</span>
                <strong>{purchaseMbValue > 0 ? `${formatYuan(purchaseCostCents)} / ${purchaseMbValue} MB` : '请选择容量'}</strong>
              </div>
            </div>
            <div className="mindspace-hero-purchase">
              <div className="mindspace-hero-purchase-row">
                {SPACE_PURCHASE_PRESETS_MB.map((sizeMb) => (
                  <button
                    key={sizeMb}
                    type="button"
                    className={`mindspace-hero-purchase-chip${purchaseMbValue === sizeMb ? ' is-active' : ''}`}
                    disabled={spacePurchasePending}
                    onClick={() => {
                      setSpacePurchaseMb(String(sizeMb));
                      setSpacePurchaseMessage(null);
                    }}
                  >
                    {sizeMb} MB
                  </button>
                ))}
              </div>
              <div className="mindspace-hero-purchase-row">
                <input
                  className="mindspace-hero-purchase-input"
                  type="number"
                  min="1"
                  step="1"
                  value={spacePurchaseMb}
                  onChange={(event) => {
                    setSpacePurchaseMb(event.target.value);
                    setSpacePurchaseMessage(null);
                  }}
                  placeholder="自定义 MB"
                />
              </div>
              <p className="mindspace-hero-purchase-meta">
                价格为每 1 MB = ¥2，购买后直接增加总容量，不展示在首页主区。
              </p>
              {spacePurchaseMessage && (
                <p className="mindspace-hero-purchase-message">{spacePurchaseMessage}</p>
              )}
            </div>
            {!canPurchaseSpace && purchaseMbValue > 0 && (
              <div className="mindspace-space-purchase-recharge">
                <span>当前余额不足以支付这次扩容。</span>
                <button
                  type="button"
                  className="mindspace-secondary"
                  onClick={() => {
                    setSpacePurchaseOpen(false);
                    openRecharge(false);
                  }}
                >
                  去余额充值
                </button>
              </div>
            )}
            <div className="mindspace-page-editor-actions">
              <button
                type="button"
                onClick={() => setSpacePurchaseOpen(false)}
                disabled={spacePurchasePending}
              >
                取消
              </button>
              <button
                type="button"
                className="mindspace-primary"
                onClick={() => void submitSpacePurchase()}
                disabled={spacePurchasePending || purchaseMbValue <= 0 || !canPurchaseSpace}
              >
                {spacePurchasePending ? '支付中…' : '确认购买'}
              </button>
            </div>
          </>
        )}
      </MindSpaceModal>

      <MindSpaceModal
        open={Boolean(pageDeleteTarget)}
        onClose={closePageDeleteDialog}
        title="确认删除页面"
        eyebrow="DELETE PAGE"
        className="mindspace-delete-dialog"
        disableClose={pageDeleteLoading || Boolean(deletingPageId)}
      >
        {pageDeleteLoading && <div className="mindspace-state">正在分析连带内容…</div>}
        {pageDeletePreview && (
          <>
            <p className="mindspace-upload-dialog-desc">
              删除「{truncateLabel(pageDeleteTarget?.title ?? '未命名页面', 48)}」后无法恢复。以下相关内容将一并处理：
            </p>
            <ul className="mindspace-delete-summary">
              {pageDeletePreview.summaryLines.map((line) => (
                <li key={line}>{line}</li>
              ))}
            </ul>
            {pageDeletePreview.onlinePublication && (
              <a
                className="mindspace-delete-public-url"
                href={resolvePublicPageUrl(pageDeletePreview.onlinePublication.publicUrl)}
                target="_blank"
                rel="noreferrer"
              >
                {resolvePublicPageUrl(pageDeletePreview.onlinePublication.publicUrl)}
              </a>
            )}
            <div className="mindspace-page-editor-actions">
              <button type="button" onClick={closePageDeleteDialog} disabled={Boolean(deletingPageId)}>
                取消
              </button>
              <button
                type="button"
                className="mindspace-primary"
                onClick={() => void confirmDeletePage()}
                disabled={Boolean(deletingPageId)}
              >
                {deletingPageId ? '删除中…' : '确认删除'}
              </button>
            </div>
          </>
        )}
      </MindSpaceModal>

      <MindSpaceModal
        open={Boolean(assetDeleteDialog)}
        onClose={() => setAssetDeleteDialog(null)}
        title={assetDeleteDialog?.references.length ? '无法删除资产' : '删除失败'}
        eyebrow={assetDeleteDialog?.references.length ? 'ASSET IN USE' : 'DELETE FAILED'}
        className="mindspace-delete-dialog"
      >
        {assetDeleteDialog && (
          <>
            <p className="mindspace-upload-dialog-desc">{assetDeleteDialog.message}</p>
            {assetDeleteDialog.references.length > 0 && (
              <div className="mindspace-asset-delete-blockers">
                {assetDeleteDialog.references.map((reference) => (
                  <button
                    type="button"
                    key={reference.id}
                    onClick={() => {
                      setAssetDeleteDialog(null);
                      showPage(reference.id);
                    }}
                  >
                    打开页面「{truncateLabel(reference.title)}」
                    {reference.publishedOnline ? '（删除时会自动下线）' : ''}
                  </button>
                ))}
              </div>
            )}
            <div className="mindspace-page-editor-actions">
              <button type="button" onClick={() => setAssetDeleteDialog(null)}>
                知道了
              </button>
            </div>
          </>
        )}
      </MindSpaceModal>

      {!previewMode && mindspaceChatContext && !pageFullscreenPreviewOpen && (
        <MindSpaceSpaceChat
          context={mindspaceChatContext}
          user={user}
          onOpenFullChat={onBack}
          onPageSaved={(result) => {
            if (result.kind === 'page' && result.pageId) {
              void refreshPagesSilently();
              if (result.pageId === selectedPageId) {
                setPageRefreshTrigger((value) => value + 1);
              } else {
                showPage(result.pageId);
              }
            }
          }}
        />
      )}
    </div>
  );
}
