'use client';

/**
 * 议程独立页面视图：Excel 表格风 + in-line cell 编辑
 *
 * - 段标题独占一行（黄底跨列）
 * - 主题项一行（Topic / Code / Description / Time / Presenter / Files）
 * - manager 单击任意单元格 → 进入 in-line 编辑（v1 第一阶段：所有列纯文本）
 *   - Enter / 失焦：保存（PATCH /agenda-sections/{sectionId}/items/{itemId}）
 *   - Escape：取消
 * - 单击 Topic 列前的 chevron 区域 → 展开看附件 / 待办
 */

import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import {
  ChevronDown,
  ChevronRight,
  Columns,
  FileText,
  Plus,
  Trash2,
  Upload,
} from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { useTranslation } from '@/hooks/useTranslation';
import { useAuth } from '@/lib/auth';
import { agendaApi, type AgendaTree, type MeetingAgendaItem, type MeetingAgendaSection } from '@/services/api/agenda';
import { SectionColumnsDialog } from '@/app/(modules)/meetingattendance/[meetingId]/agenda/edit/SectionColumnsDialog';
import {
  attachmentApi,
  type MeetingAttachment,
} from '@/services/api/attachment';
import { UploadDialog } from '@/components/file-upload';
import { ApiClientError } from '@/lib/api-client';

interface Props {
  meetingId: string;
  canManage: boolean;
  /** 顶部 banner 文字（Excel 风深蓝底，如 "05/23/2026 FFAI EC Meeting Agenda"） */
  bannerTitle?: string;
  /** 外部 trigger reload（如父组件上传完成后递增） */
  reloadKey?: number;
}

export function AgendaReadonlyView({ meetingId, canManage, bannerTitle, reloadKey = 0 }: Props) {
  const { t } = useTranslation();
  const tag = t.meetingAttendance.agenda;
  const ta = t.meetingAttendance.attachment;
  const { user } = useAuth();
  const currentUserId = user?.id;

  const [tree, setTree] = useState<AgendaTree | null>(null);
  const [meetingAttachments, setMeetingAttachments] = useState<MeetingAttachment[]>([]);
  const [loading, setLoading] = useState(true);
  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
  const [itemUploadFor, setItemUploadFor] = useState<MeetingAgendaItem | null>(null);
  const [configureSection, setConfigureSection] = useState<MeetingAgendaSection | null>(null);

  const reload = useCallback(async () => {
    try {
      setLoading(true);
      const [agenda, meetingFiles] = await Promise.all([
        agendaApi.getMeetingAgenda(meetingId),
        attachmentApi.listMeetingAttachments(meetingId),
      ]);
      setTree(agenda);
      setMeetingAttachments(meetingFiles.items || []);
    } catch (err) {
      console.error('Failed to load agenda', err);
    } finally {
      setLoading(false);
    }
  }, [meetingId]);

  useEffect(() => {
    reload();
  }, [reload, reloadKey]);

  const toggleItem = useCallback((id: string) => {
    setExpandedItems((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  const handleDownloadItemAttachment = useCallback(
    async (attachmentId: string, filename: string) => {
      try {
        await attachmentApi.downloadAgendaItemAttachment(attachmentId, filename);
      } catch (err) {
        const code = err instanceof ApiClientError ? err.code : undefined;
        toast.error(code === 'ATTACHMENT_NOT_FOUND' ? ta.notFound : ta.netError);
      }
    },
    [ta.notFound, ta.netError],
  );

  const handleDownloadMeetingAttachment = useCallback(
    async (attachmentId: string, filename: string) => {
      try {
        await attachmentApi.downloadMeetingAttachment(attachmentId, filename);
      } catch (err) {
        const code = err instanceof ApiClientError ? err.code : undefined;
        toast.error(code === 'ATTACHMENT_NOT_FOUND' ? ta.notFound : ta.netError);
      }
    },
    [ta.notFound, ta.netError],
  );

  const handleDeleteItemAttachment = useCallback(
    async (itemId: string, attachmentId: string) => {
      if (!window.confirm(ta.deleteConfirm)) return;
      try {
        await attachmentApi.deleteAgendaItemAttachment(itemId, attachmentId);
        toast.success(ta.deleteSuccess);
        await reload();
      } catch (err) {
        const code = err instanceof ApiClientError ? err.code : undefined;
        toast.error(code === 'ATTACHMENT_DELETE_FORBIDDEN' ? ta.deleteForbidden : ta.deleteFailed);
      }
    },
    [reload, ta],
  );

  const handleDeleteMeetingAttachment = useCallback(
    async (attachmentId: string) => {
      if (!window.confirm(ta.deleteConfirm)) return;
      try {
        await attachmentApi.deleteMeetingAttachment(meetingId, attachmentId);
        toast.success(ta.deleteSuccess);
        await reload();
      } catch (err) {
        const code = err instanceof ApiClientError ? err.code : undefined;
        toast.error(code === 'ATTACHMENT_DELETE_FORBIDDEN' ? ta.deleteForbidden : ta.deleteFailed);
      }
    },
    [meetingId, reload, ta],
  );

  const itemUploader = useMemo(() => {
    return (item: MeetingAgendaItem) => async (
      file: File,
      opts: { onProgress: (p: number) => void; signal: AbortSignal },
    ) => {
      await attachmentApi.uploadAgendaItemAttachment(item.id, file, {
        signal: opts.signal,
        onProgress: (e) => opts.onProgress(e.percent),
      });
    };
  }, []);

  // PATCH 单个 item 字段（in-line edit）— 局部替换，不全表 reload
  // resp.item 缺 uploadTasks/attachments 嵌套，用旧 item 的对应字段兜底
  const patchItem = useCallback(
    async (sectionId: string, itemId: string, dto: Record<string, unknown>) => {
      try {
        const resp = await agendaApi.updateAgendaItem(sectionId, itemId, dto);
        setTree((prev) => {
          if (!prev) return prev;
          return {
            ...prev,
            sections: prev.sections.map((sec) =>
              sec.id === sectionId
                ? {
                    ...sec,
                    items: sec.items.map((it) =>
                      it.id === itemId
                        ? {
                            ...resp.item,
                            uploadTasks: it.uploadTasks,
                            attachments: it.attachments,
                            presenter: resp.item.presenter ?? it.presenter,
                          }
                        : it,
                    ),
                  }
                : sec,
            ),
          };
        });
        toast.success(tag.saveSuccess);
      } catch (err) {
        toast.error(tag.saveFailed);
      }
    },
    [tag.saveSuccess, tag.saveFailed],
  );

  /**
   * 加项：默认 append 段末；如传 afterItemId，先创建再 reorder 到该 item 之后
   */
  const addItem = useCallback(
    async (sectionId: string, afterItemId?: string) => {
      try {
        const resp = await agendaApi.createAgendaItem(sectionId, { title: tag.item.newPlaceholder });
        const freshItem: MeetingAgendaItem = {
          ...resp.item,
          uploadTasks: [],
          attachments: [],
          columnDescriptions: resp.item.columnDescriptions ?? [],
        };

        // 计算目标顺序
        let nextItems: MeetingAgendaItem[] = [];
        setTree((prev) => {
          if (!prev) return prev;
          return {
            ...prev,
            sections: prev.sections.map((sec) => {
              if (sec.id !== sectionId) return sec;
              if (afterItemId) {
                const idx = sec.items.findIndex((it) => it.id === afterItemId);
                if (idx === -1) {
                  nextItems = [...sec.items, freshItem];
                } else {
                  nextItems = [
                    ...sec.items.slice(0, idx + 1),
                    freshItem,
                    ...sec.items.slice(idx + 1),
                  ];
                }
              } else {
                nextItems = [...sec.items, freshItem];
              }
              return { ...sec, items: nextItems };
            }),
          };
        });

        // 如果指定了位置（非段末），向 backend 同步顺序
        if (afterItemId && nextItems.length > 0 && nextItems[nextItems.length - 1].id !== freshItem.id) {
          await agendaApi.reorderAgendaItems(
            sectionId,
            nextItems.map((it) => it.id),
          );
        }
        toast.success(tag.saveSuccess);
      } catch {
        toast.error(tag.saveFailed);
      }
    },
    [tag.item.newPlaceholder, tag.saveSuccess, tag.saveFailed],
  );

  const addSection = useCallback(async () => {
    try {
      const resp = await agendaApi.createAgendaSection(meetingId, {
        title: tag.section.newPlaceholder,
      });
      // resp.section 不带 items，本地补空数组确保渲染稳定
      const fresh = { ...resp.section, items: [] as MeetingAgendaItem[] };
      setTree((prev) => (prev ? { ...prev, sections: [...prev.sections, fresh] } : prev));
      toast.success(tag.saveSuccess);
    } catch {
      toast.error(tag.saveFailed);
    }
  }, [meetingId, tag.section.newPlaceholder, tag.saveSuccess, tag.saveFailed]);

  const addColumn = useCallback(
    async (section: MeetingAgendaSection, atIndex: number) => {
      try {
        const next = [...section.columnLabels];
        next.splice(atIndex, 0, '');
        const resp = await agendaApi.updateAgendaSection(meetingId, section.id, {
          columnLabels: next,
        });
        setTree((prev) => {
          if (!prev) return prev;
          return {
            ...prev,
            sections: prev.sections.map((sec) =>
              sec.id === section.id ? { ...sec, columnLabels: resp.section.columnLabels } : sec,
            ),
          };
        });
        toast.success(tag.saveSuccess);
      } catch {
        toast.error(tag.saveFailed);
      }
    },
    [meetingId, tag.saveSuccess, tag.saveFailed],
  );

  const hasAnyAgenda = tree && tree.sections.length > 0;

  return (
    <div className="space-y-6">
      {/* Excel 表格 */}
      {loading ? (
        <div className="text-sm text-gray-500">{t.meetingAttendance.common.loading}</div>
      ) : !hasAnyAgenda ? (
        <div className="rounded border-2 border-dashed border-gray-200 p-12 text-center">
          <FileText className="mx-auto h-12 w-12 text-gray-300" />
          <div className="mt-3 text-sm text-gray-600">{tag.detailEmptyAgenda}</div>
          {canManage && (
            <Link href={`/meetingattendance/${meetingId}/agenda/edit`}>
              <Button variant="outline" size="sm" className="mt-3">
                <Plus className="mr-2 h-4 w-4" />
                {tag.detailEmptyAgendaCta}
              </Button>
            </Link>
          )}
        </div>
      ) : (
        <div className="rounded-md border-2 border-slate-700 shadow-sm">
          <table className="w-full border-collapse text-sm">
            <thead>
              {/* Banner row: 深蓝底白字大标题 */}
              {bannerTitle && (
                <tr className="bg-slate-700">
                  <th
                    colSpan={6}
                    className="border-b border-slate-700 px-3 py-3 text-center text-lg font-bold tracking-wide text-white"
                  >
                    {bannerTitle}
                  </th>
                </tr>
              )}
              {/* 表头行：左 4 列空，只显示 Time / Presenter，最右 Files 空 (Excel 风) */}
              <tr className="border-b-2 border-gray-400 bg-gray-200">
                <th className="w-80 border-r border-gray-400 px-3 py-2" />
                <th className="w-32 border-r border-gray-400 px-3 py-2" />
                <th className="border-r border-gray-400 px-3 py-2" />
                <th className="w-16 border-r border-gray-400 px-2 py-2 text-center font-semibold text-gray-800">
                  {tag.table.time}
                </th>
                <th className="w-32 border-r border-gray-400 px-3 py-2 text-left font-semibold text-gray-800">
                  {tag.table.presenter}
                </th>
                <th className="w-20 px-2 py-2" />
              </tr>
            </thead>
            <tbody>
              {tree!.sections.map((section, sIdx) => (
                <SectionRows
                  key={section.id}
                  section={section}
                  sIdx={sIdx}
                  expandedItems={expandedItems}
                  toggleItem={toggleItem}
                  currentUserId={currentUserId}
                  canManage={canManage}
                  onPatchItem={patchItem}
                  onAddItemAfter={(afterItemId) => addItem(section.id, afterItemId)}
                  onAddSection={addSection}
                  onAddColumnAt={(idx) => addColumn(section, idx)}
                  onConfigureColumns={() => setConfigureSection(section)}
                  onUploadClick={(item) => setItemUploadFor(item)}
                  onDownload={handleDownloadItemAttachment}
                  onDelete={handleDeleteItemAttachment}
                  t={t}
                />
              ))}
              {/* 加段按钮（末尾跨整行 — 同样用间隙形式） */}
              {canManage && (
                <tr className="bg-white">
                  <td colSpan={6} className="border-t border-gray-300 px-3 py-1">
                    <button
                      type="button"
                      onClick={addSection}
                      className="flex items-center gap-1 text-xs text-blue-600 hover:bg-blue-50 hover:text-blue-800 px-2 py-1 rounded"
                    >
                      <Plus className="h-3 w-3" />
                      {tag.section.addSection}
                    </button>
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      )}

      {/* 会议级资料 */}
      <div className="rounded-lg border border-gray-200 bg-white p-4">
        <h3 className="mb-3 text-sm font-semibold text-gray-900">
          {ta.sectionTitle}（{ta.categoryAll}）
        </h3>
        {meetingAttachments.length === 0 ? (
          <div className="text-xs text-gray-500">{ta.listEmpty}</div>
        ) : (
          <ul className="space-y-1">
            {meetingAttachments.map((att) => (
              <li
                key={att.id}
                className="flex items-center gap-2 rounded border border-gray-200 bg-gray-50 px-3 py-2 text-xs"
              >
                <FileText className="h-4 w-4 text-gray-400" />
                <button
                  type="button"
                  className="flex-1 truncate text-left text-blue-600 hover:underline"
                  onClick={() => handleDownloadMeetingAttachment(att.id, att.filename)}
                >
                  {att.filename}
                </button>
                <span className="text-gray-400">{formatSize(att.size)}</span>
                <span className="text-gray-500">{att.uploadedBy?.displayName}</span>
                {(att.uploadedById === currentUserId || canManage) && (
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    onClick={() => handleDeleteMeetingAttachment(att.id)}
                  >
                    <Trash2 className="h-3 w-3 text-red-500" />
                  </Button>
                )}
              </li>
            ))}
          </ul>
        )}
      </div>

      {/* 议程项上传弹窗 */}
      <UploadDialog
        open={!!itemUploadFor}
        onOpenChange={(o) => {
          if (!o) {
            setItemUploadFor(null);
            reload();
          }
        }}
        uploader={
          itemUploadFor
            ? itemUploader(itemUploadFor)
            : async () => {
                /* noop */
              }
        }
        title={itemUploadFor ? `${ta.upload} - ${itemUploadFor.title}` : undefined}
      />

      {/* 段列配置弹窗 — 局部更新，不全表 reload */}
      {configureSection && (
        <SectionColumnsDialog
          meetingId={meetingId}
          section={configureSection}
          open={!!configureSection}
          onOpenChange={(o) => !o && setConfigureSection(null)}
          onSaved={(next) => {
            setTree((prev) => {
              if (!prev) return prev;
              return {
                ...prev,
                sections: prev.sections.map((sec) =>
                  sec.id === next.id ? { ...sec, columnLabels: next.columnLabels } : sec,
                ),
              };
            });
          }}
        />
      )}
    </div>
  );
}

// --------------- 段 + 主题项行子组件 ---------------

/**
 * 行/段左侧的 hover-to-add handle（Notion 风）：
 *  - 不挤压行布局，浮在最左侧 absolute 定位
 *  - hover 父行 700ms 才浮现（避免阅读时干扰）
 *  - 浅灰圆形 + 进入时变蓝
 *  - 点击 → 在当前位置加项 / 加段
 */
function HoverAddHandle({
  onClick,
  title,
}: {
  onClick: () => void;
  title: string;
}) {
  return (
    <button
      type="button"
      onClick={(e) => {
        e.stopPropagation();
        onClick();
      }}
      title={title}
      className="absolute left-[-22px] top-1/2 -translate-y-1/2 flex h-4 w-4 items-center justify-center rounded-full border border-gray-300 bg-white text-gray-400 opacity-0 shadow-sm transition-opacity duration-100 delay-300 group-hover/row:opacity-70 hover:!opacity-100 hover:!border-blue-500 hover:!text-blue-500 hover:!delay-0"
    >
      <Plus className="h-2.5 w-2.5" />
    </button>
  );
}

interface SectionRowsProps {
  section: AgendaTree['sections'][number];
  sIdx: number;
  expandedItems: Set<string>;
  toggleItem: (id: string) => void;
  currentUserId: string | undefined;
  canManage: boolean;
  onPatchItem: (sectionId: string, itemId: string, dto: Record<string, unknown>) => Promise<void>;
  /** 在指定 item 之后插入（不传 = 段末追加） */
  onAddItemAfter: (afterItemId?: string) => void;
  onAddSection: () => void;
  onAddColumnAt: (atIndex: number) => void;
  onConfigureColumns: () => void;
  onUploadClick: (item: MeetingAgendaItem) => void;
  onDownload: (attachmentId: string, filename: string) => void;
  onDelete: (itemId: string, attachmentId: string) => void;
  t: ReturnType<typeof useTranslation>['t'];
}

/**
 * 单元格 in-line 编辑：单击进入编辑，Enter / 失焦保存，Esc 取消
 */
function EditableCell({
  value,
  onSave,
  canEdit,
  multiline = false,
  placeholder = '—',
  className = '',
  emptyClassName = 'text-gray-300',
}: {
  value: string;
  onSave: (next: string) => void | Promise<void>;
  canEdit: boolean;
  multiline?: boolean;
  placeholder?: string;
  className?: string;
  emptyClassName?: string;
}) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    if (!editing) setDraft(value);
  }, [value, editing]);

  const commit = async () => {
    if (busy) return;
    if (draft === value) {
      setEditing(false);
      return;
    }
    setBusy(true);
    try {
      await onSave(draft);
      setEditing(false);
    } finally {
      setBusy(false);
    }
  };

  if (!editing) {
    return (
      <div
        className={`${className} ${canEdit ? 'cursor-text hover:bg-black/5' : ''} whitespace-pre-wrap`}
        title={canEdit ? '点击编辑' : undefined}
        onClick={(e) => {
          if (canEdit) {
            e.stopPropagation();
            setEditing(true);
          }
        }}
      >
        {value || <span className={emptyClassName}>{placeholder}</span>}
      </div>
    );
  }

  const commonProps = {
    autoFocus: true,
    value: draft,
    disabled: busy,
    onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
      setDraft(e.target.value),
    onBlur: commit,
    onClick: (e: React.MouseEvent) => e.stopPropagation(),
    onKeyDown: (e: React.KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.preventDefault();
        setDraft(value);
        setEditing(false);
      } else if (e.key === 'Enter' && (!multiline || e.ctrlKey || e.metaKey)) {
        e.preventDefault();
        void commit();
      }
    },
    className: `${className} w-full border-2 border-blue-500 bg-white outline-none px-2 py-1`,
  };

  return multiline ? (
    <textarea {...(commonProps as any)} rows={3} />
  ) : (
    <input {...(commonProps as any)} type="text" />
  );
}

function SectionRows({
  section,
  sIdx,
  expandedItems,
  toggleItem,
  currentUserId,
  canManage,
  onPatchItem,
  onAddItemAfter,
  onAddSection,
  onAddColumnAt,
  onConfigureColumns,
  onUploadClick,
  onDownload,
  onDelete,
  t,
}: SectionRowsProps) {
  const tag = t.meetingAttendance.agenda;
  const ta = t.meetingAttendance.attachment;

  const isMultiCol = section.columnLabels.length > 0;
  const TOTAL_COLS = 6; // Topic / Code / Description / Time / Presenter / Files

  // 段标题 cell 内容（in-line 可编辑 + 列配置按钮 + 加段 handle）
  const sectionTitleCell = (
    <>
      {canManage && <HoverAddHandle onClick={onAddSection} title={tag.section.addSection} />}
      <div className="flex items-center justify-between gap-2">
        <div className="flex-1 min-w-0">
          <EditableCell
            value={section.title}
            canEdit={canManage}
            className="font-bold inline-block whitespace-nowrap"
            onSave={async (v) => {
              try {
                await agendaApi.updateAgendaSection(section.meetingId, section.id, { title: v });
              } catch {
                /* noop */
              }
            }}
          />
        </div>
        {canManage && (
          <button
            type="button"
            onClick={onConfigureColumns}
            title={tag.section.configureColumns}
            className="shrink-0 flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-gray-700 opacity-0 transition-opacity duration-100 delay-200 group-hover/row:opacity-70 hover:!opacity-100 hover:!bg-black/10 hover:!delay-0"
          >
            <Columns className="h-3 w-3" />
            {tag.section.configureColumns}
          </button>
        )}
      </div>
    </>
  );

  // Excel 风：多栏段（如 5. Routine Strategic Topics）= accent5 浅蓝；其他段白底
  const sectionRowBg = isMultiCol ? 'bg-[#B4C7E7]' : 'bg-white';

  // 单栏段：仅在段下没有 item 时渲染段头独立行（兜底）；有 item 时合并到第一项行
  const renderStandaloneHeader = isMultiCol || section.items.length === 0;

  return (
    <>
      {renderStandaloneHeader && (
        <tr className={`group/row ${sectionRowBg}`}>
          {isMultiCol ? (
            <>
              {/* 段标题占 Topic + Code 两列 */}
              <td
                colSpan={2}
                className="relative border-y border-gray-400 px-3 py-1 text-sm font-bold text-gray-900"
              >
                {sectionTitleCell}
              </td>
              {/* description 列：列名 grid */}
              <td className="border-y border-gray-400 px-0 py-0">
                <div
                  className="grid divide-x divide-gray-400 text-xs font-semibold text-gray-800"
                  style={{ gridTemplateColumns: `repeat(${section.columnLabels.length}, minmax(0, 1fr))` }}
                >
                  {section.columnLabels.map((label, i) => (
                    <div key={i} className="px-3 py-1">
                      {label || `Col ${i + 1}`}
                    </div>
                  ))}
                </div>
              </td>
              <td className="border-y border-gray-400" />
              <td className="border-y border-gray-400" />
              <td className="border-y border-gray-400" />
            </>
          ) : (
            <td
              colSpan={TOTAL_COLS}
              className="relative border-y border-gray-400 px-3 py-1 text-sm font-bold text-gray-900"
            >
              {sectionTitleCell}
            </td>
          )}
        </tr>
      )}

      {/* 主题项行 */}
      {section.items.map((item, iIdx) => {
        const expanded = expandedItems.has(item.id);
        const pendingCount = item.uploadTasks.filter((x) => x.status === 'PENDING').length;
        const uploadedCount = item.uploadTasks.filter((x) => x.status === 'UPLOADED').length;
        const itemAssignedToMe = item.uploadTasks.some(
          (x) => x.assigneeUserId === currentUserId && x.status === 'PENDING',
        );
        const filesCount = item.attachments.length;

        return (
          <Fragment key={item.id}>
            <tr
              id={`agenda-item-${item.id}`}
              className={`group/row border-b border-gray-300 ${
                !isMultiCol && iIdx === 0 ? sectionRowBg : 'bg-white'
              } hover:bg-black/5`}
            >
              {/* Topic — 单栏段第一行 = 段标题（绑 section.title）；其他行 = item.title */}
              <td className="relative border-r border-gray-300 align-top text-sm font-medium text-gray-900">
                {canManage && (
                  <HoverAddHandle
                    onClick={() => onAddItemAfter(item.id)}
                    title={tag.item.addItem}
                  />
                )}
                <div className="flex items-start gap-1 px-3 py-2">
                  <button
                    type="button"
                    onClick={(e) => {
                      e.stopPropagation();
                      toggleItem(item.id);
                    }}
                    className="shrink-0"
                  >
                    {expanded ? (
                      <ChevronDown className="mt-0.5 h-3.5 w-3.5 text-gray-400" />
                    ) : (
                      <ChevronRight className="mt-0.5 h-3.5 w-3.5 text-gray-400" />
                    )}
                  </button>
                  <div className="flex-1">
                    {!isMultiCol && iIdx === 0 ? (
                      <EditableCell
                        value={section.title}
                        canEdit={canManage}
                        className="font-bold"
                        onSave={async (v) => {
                          try {
                            await agendaApi.updateAgendaSection(section.meetingId, section.id, {
                              title: v,
                            });
                          } catch {
                            /* noop */
                          }
                        }}
                      />
                    ) : (
                      <EditableCell
                        value={item.title}
                        canEdit={canManage}
                        onSave={(v) => onPatchItem(section.id, item.id, { title: v })}
                      />
                    )}
                  </div>
                </div>
              </td>
              {/* Code — 可编辑文本 */}
              <td className="border-r border-gray-300 px-3 py-1 align-top text-xs font-mono text-gray-700">
                <EditableCell
                  value={item.code || ''}
                  canEdit={canManage}
                  onSave={(v) => onPatchItem(section.id, item.id, { code: v || null })}
                />
              </td>
              {/* Description (单栏 = 单 EditableCell / 多栏 = 总览 + 分列各自 EditableCell) */}
              <td className="border-r border-gray-300 px-0 py-0 align-top text-sm text-gray-800">
                {isMultiCol ? (
                  <div>
                    <div className="border-b border-gray-300 px-3 py-2">
                      <EditableCell
                        value={item.description || ''}
                        canEdit={canManage}
                        multiline
                        placeholder="(总览描述，可选)"
                        onSave={(v) =>
                          onPatchItem(section.id, item.id, { description: v || null })
                        }
                      />
                    </div>
                    <div
                      className="grid divide-x divide-gray-300"
                      style={{ gridTemplateColumns: `repeat(${section.columnLabels.length}, minmax(0, 1fr))` }}
                    >
                      {section.columnLabels.map((_, i) => (
                        <div key={i} className="px-3 py-2">
                          <EditableCell
                            value={item.columnDescriptions?.[i] ?? ''}
                            canEdit={canManage}
                            multiline
                            onSave={(v) => {
                              const next = [...(item.columnDescriptions ?? [])];
                              while (next.length < section.columnLabels.length) next.push('');
                              next[i] = v;
                              return onPatchItem(section.id, item.id, {
                                columnDescriptions: next,
                              });
                            }}
                          />
                        </div>
                      ))}
                    </div>
                  </div>
                ) : (
                  <div className="px-3 py-2">
                    <EditableCell
                      value={item.description || ''}
                      canEdit={canManage}
                      multiline
                      onSave={(v) => onPatchItem(section.id, item.id, { description: v || null })}
                    />
                  </div>
                )}
              </td>
              {/* Time — 可编辑数字（文本输入，提交时 parseInt） */}
              <td className="border-r border-gray-300 px-2 py-1 text-center align-top text-sm text-gray-800">
                <EditableCell
                  value={
                    typeof item.timeMinutes === 'number' && item.timeMinutes > 0
                      ? String(item.timeMinutes)
                      : ''
                  }
                  canEdit={canManage}
                  className="text-center"
                  onSave={(v) => {
                    const n = v.trim() === '' ? null : Number(v);
                    if (n !== null && (!Number.isFinite(n) || n <= 0)) {
                      toast.error(t.meetingAttendance.agenda.item.saveFailedTimeInvalid);
                      return;
                    }
                    return onPatchItem(section.id, item.id, { timeMinutes: n });
                  }}
                />
              </td>
              {/* Presenter — 显示 displayName（v1 阶段只读，需 user 选择器） */}
              <td className="border-r border-gray-300 px-3 py-1 align-top text-sm text-gray-800">
                {item.presenter?.displayName || <span className="text-gray-300">—</span>}
              </td>
              {/* Files / Tasks */}
              <td className="px-2 py-1 text-center align-top text-xs">
                <div className="flex flex-wrap items-center justify-center gap-1">
                  {filesCount > 0 && (
                    <span className="inline-flex items-center gap-1 rounded bg-gray-100 px-1.5 py-0.5 text-gray-700">
                      <FileText className="h-3 w-3" />
                      {filesCount}
                    </span>
                  )}
                  {pendingCount > 0 && (
                    <span className="rounded bg-amber-100 px-1.5 py-0.5 text-amber-800">
                      {tag.uploadTask.statusBadgePending.replace('{n}', String(pendingCount))}
                    </span>
                  )}
                  {uploadedCount > 0 && (
                    <span className="rounded bg-green-100 px-1.5 py-0.5 text-green-800">
                      {tag.uploadTask.statusBadgeUploaded.replace('{n}', String(uploadedCount))}
                    </span>
                  )}
                  {filesCount === 0 && pendingCount === 0 && uploadedCount === 0 && (
                    <span className="text-gray-400">—</span>
                  )}
                </div>
              </td>
            </tr>

            {/* 展开行：附件 */}
            {expanded && (
              <tr className="border-b border-gray-200 bg-gray-50/50">
                <td colSpan={TOTAL_COLS} className="px-6 py-3">
                  <div className="space-y-3">
                    <div>
                      <div className="flex items-center justify-between">
                        <div className="text-xs font-medium text-gray-700">{ta.sectionTitle}</div>
                        {(canManage || itemAssignedToMe) && (
                          <Button
                            type="button"
                            variant="ghost"
                            size="sm"
                            onClick={(e) => {
                              e.stopPropagation();
                              onUploadClick(item);
                            }}
                          >
                            <Upload className="mr-1 h-3 w-3" />
                            {tag.item.uploadFile}
                          </Button>
                        )}
                      </div>
                      {item.attachments.length === 0 ? (
                        <div className="mt-1 text-xs text-gray-500">{ta.listEmpty}</div>
                      ) : (
                        <ul className="mt-2 space-y-1">
                          {item.attachments.map((att) => (
                            <li
                              key={att.id}
                              className="flex items-center gap-2 rounded border border-gray-200 bg-white px-2 py-1 text-xs"
                              onClick={(e) => e.stopPropagation()}
                            >
                              <FileText className="h-3 w-3 text-gray-400" />
                              <button
                                type="button"
                                className="flex-1 truncate text-left text-blue-600 hover:underline"
                                onClick={() => onDownload(att.id, att.filename)}
                              >
                                {att.filename}
                              </button>
                              <span className="text-gray-400">{formatSize(att.size)}</span>
                              <span className="text-gray-500">{att.uploadedBy?.displayName}</span>
                              {(att.uploadedById === currentUserId || canManage) && (
                                <Button
                                  type="button"
                                  variant="ghost"
                                  size="sm"
                                  onClick={() => onDelete(item.id, att.id)}
                                >
                                  <Trash2 className="h-3 w-3 text-red-500" />
                                </Button>
                              )}
                            </li>
                          ))}
                        </ul>
                      )}
                    </div>
                  </div>
                </td>
              </tr>
            )}
          </Fragment>
        );
      })}
    </>
  );
}

function formatSize(sizeStr: string): string {
  const n = Number(sizeStr);
  if (!Number.isFinite(n)) return sizeStr;
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
}
