import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import {
  Badge,
  Button,
  Descriptions,
  Drawer,
  Image,
  Input,
  Modal,
  Space,
  Table,
  Tabs,
  Tag,
  Typography,
  message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type {
  StoreInfoChangeFieldDiff,
  StoreInfoChangeRequestDto,
  StoreInfoChangeStatus,
  StorePackageAuditDetailDto,
  StorePackageAuditSummaryDto,
  StorePackageChangeRequestDto,
  StorePackageChangeStatus,
  StorePackageItemDto,
  StorePackageViewDto,
} from '@dukang/shared-types';
import {
  STORE_INFO_CHANGE_STATUS_LABELS,
  normalizeStorePackageImageUrls,
} from '@dukang/shared-types';
import { request, type Paginated } from '../lib/api';
import { notifyPackageAuditChanged } from '../lib/admin-events';
import { fmtTime } from '../lib/constants';

const HQ_PACKAGE_STATUS_LABELS: Record<StorePackageChangeStatus, string> = {
  PENDING: '待审核',
  APPROVED: '已通过',
  REJECTED: '已驳回',
};

function packageKey(pkg: StorePackageItemDto | StorePackageViewDto, index: number) {
  const name = String(pkg.name ?? '').trim();
  return name ? `name:${name}` : `idx:${index}`;
}

function imageSignature(pkg: StorePackageItemDto | StorePackageViewDto) {
  return normalizeStorePackageImageUrls(pkg).join('|');
}

type FieldChange = { label: string; old: string; now: string; kind: 'text' | 'value' };

/** 文本逐字差异：LCS 比对，产出 equal / delete / insert 段落，用于高亮具体变了哪些字 */
function diffText(a: string, b: string): Array<{ type: 'equal' | 'insert' | 'delete'; text: string }> {
  const m = a.length;
  const n = b.length;
  const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
    }
  }
  const raw: Array<{ type: 'equal' | 'insert' | 'delete'; text: string }> = [];
  let i = 0;
  let j = 0;
  while (i < m && j < n) {
    if (a[i] === b[j]) {
      raw.push({ type: 'equal', text: a[i] });
      i++;
      j++;
    } else if (dp[i + 1][j] >= dp[i][j + 1]) {
      raw.push({ type: 'delete', text: a[i] });
      i++;
    } else {
      raw.push({ type: 'insert', text: b[j] });
      j++;
    }
  }
  while (i < m) raw.push({ type: 'delete', text: a[i++] });
  while (j < n) raw.push({ type: 'insert', text: b[j++] });
  const merged: Array<{ type: 'equal' | 'insert' | 'delete'; text: string }> = [];
  for (const s of raw) {
    const last = merged[merged.length - 1];
    if (last && last.type === s.type) last.text += s.text;
    else merged.push({ ...s });
  }
  return merged;
}

/** 逐字段比较套餐内容，返回发生变化的字段明细（用于“变动的地方详细列出”） */
function fieldChanges(
  live: StorePackageItemDto | StorePackageViewDto,
  proposed: StorePackageItemDto | StorePackageViewDto,
): FieldChange[] {
  const changes: FieldChange[] = [];
  const text = (v: string | number | null | undefined) => (v ?? '').toString().trim();
  const pushText = (label: string, oldV: string, newV: string) => {
    if (oldV !== newV) changes.push({ label, old: oldV, now: newV, kind: 'text' });
  };
  const pushValue = (label: string, oldV: string, newV: string) => {
    if (oldV !== newV) changes.push({ label, old: oldV || '（空）', now: newV || '（空）', kind: 'value' });
  };
  pushValue('价格', `¥${text(live.price)}`, `¥${text(proposed.price)}`);
  pushText('套餐名称', text(live.name), text(proposed.name));
  pushText('菜品内容', text(live.dishes), text(proposed.dishes));
  pushText('可用时间', text(live.usableTime), text(proposed.usableTime));
  pushText('其他说明', text(live.otherNotes), text(proposed.otherNotes));
  const liveImgs = normalizeStorePackageImageUrls(live);
  const proposedImgs = normalizeStorePackageImageUrls(proposed);
  if (imageSignature(live) !== imageSignature(proposed)) {
    changes.push({ label: '图片', old: `${liveImgs.length} 张`, now: `${proposedImgs.length} 张`, kind: 'value' });
  }
  return changes;
}

function diffPackages(live: StorePackageViewDto[], proposed: StorePackageItemDto[]) {
  const liveMap = new Map(live.map((p, i) => [packageKey(p, i), p]));
  const proposedMap = new Map(proposed.map((p, i) => [packageKey(p, i), p]));
  const keys = new Set([...liveMap.keys(), ...proposedMap.keys()]);
  const rows: Array<{
    key: string;
    change: 'added' | 'removed' | 'changed' | 'unchanged';
    live?: StorePackageViewDto;
    proposed?: StorePackageItemDto;
    changes?: FieldChange[];
  }> = [];

  for (const key of keys) {
    const l = liveMap.get(key);
    const p = proposedMap.get(key);
    if (l && !p) {
      rows.push({ key, change: 'removed', live: l });
    } else if (!l && p) {
      rows.push({ key, change: 'added', proposed: p });
    } else if (l && p) {
      const changed =
        l.price !== p.price ||
        l.dishes !== p.dishes ||
        (l.usableTime ?? '') !== (p.usableTime ?? '') ||
        (l.otherNotes ?? '') !== (p.otherNotes ?? '') ||
        imageSignature(l) !== imageSignature(p);
      rows.push({
        key,
        change: changed ? 'changed' : 'unchanged',
        live: l,
        proposed: p,
        changes: changed ? fieldChanges(l, p) : undefined,
      });
    }
  }
  return rows;
}

const CHANGE_LABELS = {
  added: { text: '新增', color: 'green' },
  removed: { text: '删除', color: 'red' },
  changed: { text: '变更', color: 'orange' },
  unchanged: { text: '未变', color: 'default' },
} as const;

/** 文本逐字差异渲染：原行红色删除线标出被删的字，新行绿色标出新增的字 */
function TextDiff({ oldText, newText }: { oldText: string; newText: string }) {
  const segs = diffText(oldText, newText);
  return (
    <div style={{ marginTop: 2 }}>
      <div style={{ lineHeight: 1.6 }}>
        <Typography.Text type="secondary">原：</Typography.Text>
        {segs
          .filter((s) => s.type !== 'insert')
          .map((s, idx) =>
            s.type === 'delete' ? (
              <Typography.Text key={idx} delete style={{ color: '#cf1322' }}>
                {s.text || '（空）'}
              </Typography.Text>
            ) : (
              <Typography.Text key={idx}>{s.text}</Typography.Text>
            ),
          )}
      </div>
      <div style={{ lineHeight: 1.6 }}>
        <Typography.Text type="secondary">新：</Typography.Text>
        {segs
          .filter((s) => s.type !== 'delete')
          .map((s, idx) =>
            s.type === 'insert' ? (
              <Typography.Text key={idx} style={{ color: '#389e0d' }}>
                {s.text || '（空）'}
              </Typography.Text>
            ) : (
              <Typography.Text key={idx}>{s.text}</Typography.Text>
            ),
          )}
      </div>
    </div>
  );
}

function PackageDetailCard({
  title,
  pkg,
  change,
  changes,
}: {
  title?: string;
  pkg: StorePackageItemDto | StorePackageViewDto;
  change?: keyof typeof CHANGE_LABELS;
  changes?: FieldChange[];
}) {
  const images = normalizeStorePackageImageUrls(pkg);
  const meta = change ? CHANGE_LABELS[change] : null;
  return (
    <div
      style={{
        marginBottom: 12,
        padding: 12,
        border: '1px solid #f0f0f0',
        borderRadius: 8,
        background: '#fafafa',
      }}
    >
      <Space style={{ marginBottom: 8 }} wrap>
        {title ? (
          <Typography.Text type="secondary">{title}</Typography.Text>
        ) : null}
        {meta ? <Tag color={meta.color}>{meta.text}</Tag> : null}
      </Space>
      <div style={{ marginBottom: 8 }}>
        <strong>{pkg.name}</strong>
        <span style={{ marginLeft: 8 }}>¥{pkg.price}</span>
      </div>
      <Typography.Paragraph className="admin-package-audit-text" style={{ marginBottom: 8 }}>
        {pkg.dishes || '—'}
      </Typography.Paragraph>
      {pkg.usableTime ? (
        <Typography.Paragraph
          type="secondary"
          className="admin-package-audit-text"
          style={{ marginBottom: 4 }}
        >
          可用时间：{pkg.usableTime}
        </Typography.Paragraph>
      ) : null}
      {pkg.otherNotes ? (
        <Typography.Paragraph
          type="secondary"
          className="admin-package-audit-text"
          style={{ marginBottom: 8 }}
        >
          其他说明：{pkg.otherNotes}
        </Typography.Paragraph>
      ) : null}
      {images.length ? (
        <Image.PreviewGroup>
          <Space wrap size={8}>
            {images.map((url) => (
              <Image
                key={url}
                src={url}
                width={72}
                height={72}
                style={{ objectFit: 'cover', borderRadius: 4 }}
              />
            ))}
          </Space>
        </Image.PreviewGroup>
      ) : (
        <Typography.Text type="secondary">无套餐图片</Typography.Text>
      )}
      {changes && changes.length ? (
        <div
          style={{
            marginTop: 8,
            padding: 8,
            background: '#fff7e6',
            border: '1px solid #ffe7ba',
            borderRadius: 6,
          }}
        >
          <Typography.Text strong style={{ fontSize: 12 }}>
            变更明细
          </Typography.Text>
          <ul style={{ margin: '6px 0 0', paddingLeft: 18 }}>
            {changes.map((c) => (
              <li key={c.label} style={{ marginBottom: 6 }}>
                <Typography.Text type="secondary">{c.label}：</Typography.Text>
                {c.kind === 'text' ? (
                  <TextDiff oldText={c.old} newText={c.now} />
                ) : (
                  <>
                    <Typography.Text delete type="secondary">
                      {c.old}
                    </Typography.Text>
                    <Typography.Text type="secondary"> → </Typography.Text>
                    <Typography.Text strong>{c.now}</Typography.Text>
                  </>
                )}
              </li>
            ))}
          </ul>
        </div>
      ) : null}
    </div>
  );
}

const INFO_CHANGE_FIELD_LABELS: Record<string, string> = {
  name: '门店名称',
  contactPhone: '联系电话',
  address: '详细地址',
  intro: '门店简介',
  benefitUsageRule: '权益券使用规则',
  latitude: '纬度',
  longitude: '经度',
  openTime: '营业开始',
  closeTime: '营业结束',
  openTime2: '第二段开始',
  closeTime2: '第二段结束',
  avgPrice: '人均费用',
};

function fmtFieldValue(field: string, v: unknown): string {
  if (v == null || String(v).trim() === '') return '（空）';
  if (field === 'avgPrice' || field === 'latitude' || field === 'longitude') {
    return String(v);
  }
  return String(v);
}

function InfoChangeAuditPanel({
  initialRequestId,
}: {
  initialRequestId?: string | null;
}) {
  const [loading, setLoading] = useState(false);
  const [items, setItems] = useState<StoreInfoChangeRequestDto[]>([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [status, setStatus] = useState<string>('PENDING');
  const [pendingCount, setPendingCount] = useState(0);
  const [detailOpen, setDetailOpen] = useState(false);
  const [detailLoading, setDetailLoading] = useState(false);
  const [detail, setDetail] = useState<(StoreInfoChangeRequestDto & { diffs?: StoreInfoChangeFieldDiff[] }) | null>(null);
  const [rejectOpen, setRejectOpen] = useState(false);
  const [rejectReason, setRejectReason] = useState('');
  const [activeId, setActiveId] = useState<string | null>(null);

  async function reload(nextPage = page, nextStatus = status) {
    setLoading(true);
    try {
      const qs = new URLSearchParams({ page: String(nextPage), pageSize: '20' });
      if (nextStatus) qs.set('status', nextStatus);
      const [data, summary] = await Promise.all([
        request<{ items: StoreInfoChangeRequestDto[]; total: number; page?: number }>(
          `/admin/store-info-change-requests?${qs}`,
        ),
        request<{ pendingCount: number }>('/admin/store-info-change-requests/summary'),
      ]);
      setItems(data.items);
      setTotal(data.total);
      setPage(data.page ?? nextPage);
      setPendingCount(summary.pendingCount ?? 0);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '加载失败');
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    void reload(1, status);
  }, [status]);

  useEffect(() => {
    if (initialRequestId) void openDetail(initialRequestId);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  async function openDetail(id: string) {
    setDetailOpen(true);
    setDetailLoading(true);
    setDetail(null);
    try {
      const data = await request<StoreInfoChangeRequestDto & { diffs?: StoreInfoChangeFieldDiff[] }>(
        `/admin/store-info-change-requests/${id}`,
      );
      setDetail(data);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '加载详情失败');
      setDetailOpen(false);
    } finally {
      setDetailLoading(false);
    }
  }

  async function audit(id: string, action: 'APPROVE' | 'REJECT', reason?: string) {
    try {
      await request(`/admin/store-info-change-requests/${id}/audit`, {
        method: 'PUT',
        body: JSON.stringify(
          action === 'REJECT' ? { action, rejectReason: reason } : { action },
        ),
      });
      message.success(action === 'APPROVE' ? '已通过' : '已驳回');
      setDetailOpen(false);
      notifyPackageAuditChanged();
      void reload(page, status);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '操作失败');
    }
  }

  const columns: ColumnsType<StoreInfoChangeRequestDto> = [
    { title: '门店', dataIndex: 'storeName', render: (_, row) => row.storeName || row.storeId },
    {
      title: '状态',
      dataIndex: 'status',
      render: (v: StoreInfoChangeStatus) => (
        <Tag>{STORE_INFO_CHANGE_STATUS_LABELS[v] ?? v}</Tag>
      ),
    },
    {
      title: '变更字段',
      render: (_, row) =>
        row.changedFields?.length
          ? row.changedFields.map((f) => (
              <Tag key={f}>{INFO_CHANGE_FIELD_LABELS[f] ?? f}</Tag>
            ))
          : '—',
    },
    {
      title: '提交方',
      render: (_, row) =>
        row.submitterType === 'PARTNER' ? '合伙人' : row.submitterType === 'SHOP' ? '门店' : '总部',
    },
    { title: '提交时间', dataIndex: 'createdAt', render: (v) => fmtTime(String(v)) },
    {
      title: '操作',
      render: (_, row) => (
        <Space>
          <Button type="link" onClick={() => void openDetail(row.id)}>
            查看
          </Button>
          {row.status === 'PENDING' ? (
            <>
              <Button type="link" onClick={() => void audit(row.id, 'APPROVE')}>
                通过
              </Button>
              <Button
                type="link"
                danger
                onClick={() => {
                  setActiveId(row.id);
                  setRejectReason('');
                  setRejectOpen(true);
                }}
              >
                驳回
              </Button>
            </>
          ) : (
            row.rejectReason || null
          )}
        </Space>
      ),
    },
  ];

  return (
    <div>
      <Space style={{ marginBottom: 16 }}>
        {(['PENDING', 'APPROVED', 'REJECTED', ''] as const).map((s) => (
          <Button
            key={s || 'all'}
            type={status === s ? 'primary' : 'default'}
            onClick={() => setStatus(s)}
          >
            {s === 'PENDING' ? (
              <Badge count={pendingCount} size="small" offset={[8, -2]}>
                {STORE_INFO_CHANGE_STATUS_LABELS.PENDING}
              </Badge>
            ) : s ? (
              STORE_INFO_CHANGE_STATUS_LABELS[s]
            ) : (
              '全部'
            )}
          </Button>
        ))}
      </Space>
      <Table
        rowKey="id"
        loading={loading}
        columns={columns}
        dataSource={items}
        pagination={{
          current: page,
          total,
          pageSize: 20,
          onChange: (p) => void reload(p, status),
        }}
      />

      <Drawer
        title={detail ? `${detail.storeName || detail.storeId} · 信息变更` : '信息变更详情'}
        width={680}
        open={detailOpen}
        onClose={() => setDetailOpen(false)}
        extra={
          detail?.status === 'PENDING' ? (
            <Space>
              <Button onClick={() => void audit(detail.id, 'APPROVE')}>通过</Button>
              <Button
                danger
                onClick={() => {
                  setActiveId(detail.id);
                  setRejectReason('');
                  setRejectOpen(true);
                }}
              >
                驳回
              </Button>
            </Space>
          ) : null
        }
      >
        {detailLoading ? (
          <Typography.Text type="secondary">加载中…</Typography.Text>
        ) : detail ? (
          <>
            <Space style={{ marginBottom: 16 }} wrap>
              <Tag>{STORE_INFO_CHANGE_STATUS_LABELS[detail.status]}</Tag>
              <Typography.Text type="secondary">
                提交方：{detail.submitterType === 'PARTNER' ? '合伙人' : detail.submitterType === 'SHOP' ? '门店' : '总部'} · {fmtTime(detail.createdAt)}
              </Typography.Text>
            </Space>
            {detail.rejectReason ? (
              <Typography.Paragraph type="danger">驳回原因：{detail.rejectReason}</Typography.Paragraph>
            ) : null}
            {detail.diffs && detail.diffs.length ? (
              <Descriptions column={1} bordered size="small">
                {detail.diffs.map((d) => (
                  <Descriptions.Item
                    key={d.field}
                    label={INFO_CHANGE_FIELD_LABELS[d.field] ?? d.field}
                  >
                    <span>
                      <Typography.Text delete type="secondary">
                        {fmtFieldValue(d.field, d.live)}
                      </Typography.Text>
                      <Typography.Text type="secondary"> → </Typography.Text>
                      <Typography.Text strong>
                        {fmtFieldValue(d.field, d.proposed)}
                      </Typography.Text>
                    </span>
                  </Descriptions.Item>
                ))}
              </Descriptions>
            ) : (
              <Typography.Text type="secondary">无变更字段明细</Typography.Text>
            )}
          </>
        ) : null}
      </Drawer>

      <Modal
        title="驳回信息变更"
        open={rejectOpen}
        onCancel={() => setRejectOpen(false)}
        onOk={() => {
          if (!activeId) return;
          if (!rejectReason.trim()) {
            message.warning('请填写驳回原因');
            return;
          }
          void audit(activeId, 'REJECT', rejectReason.trim());
          setRejectOpen(false);
        }}
      >
        <Input.TextArea
          rows={3}
          value={rejectReason}
          placeholder="驳回原因"
          onChange={(e) => setRejectReason(e.target.value)}
        />
      </Modal>
    </div>
  );
}

export default function StorePackageAuditsPage() {
  const [loading, setLoading] = useState(false);
  const [items, setItems] = useState<StorePackageChangeRequestDto[]>([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [status, setStatus] = useState<string>('PENDING');
  const [pendingCount, setPendingCount] = useState(0);
  const [rejectOpen, setRejectOpen] = useState(false);
  const [rejectReason, setRejectReason] = useState('');
  const [activeId, setActiveId] = useState<string | null>(null);
  const [detailOpen, setDetailOpen] = useState(false);
  const [detailLoading, setDetailLoading] = useState(false);
  const [detail, setDetail] = useState<StorePackageAuditDetailDto | null>(null);

  async function reload(nextPage = page, nextStatus = status) {
    setLoading(true);
    try {
      const qs = new URLSearchParams({
        page: String(nextPage),
        pageSize: '20',
      });
      if (nextStatus) qs.set('status', nextStatus);
      const [data, summary] = await Promise.all([
        request<Paginated<StorePackageChangeRequestDto>>(`/admin/store-package-audits?${qs}`),
        request<StorePackageAuditSummaryDto>('/admin/store-package-audits/summary'),
      ]);
      setItems(data.items);
      setTotal(data.total);
      setPage(data.page);
      setPendingCount(summary.pendingCount ?? 0);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '加载失败');
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    void reload(1, status);
  }, [status]);

  // 从门店详情 / 门店列表跳转过来时，带 requestId 自动打开审核（对比）抽屉
  const [searchParams] = useSearchParams();
  const initialTab = searchParams.get('tab') === 'info' ? 'info' : 'package';
  const [activeTab, setActiveTab] = useState<string>(initialTab);
  const infoRequestId = searchParams.get('infoRequestId');
  useEffect(() => {
    const rid = searchParams.get('requestId');
    if (rid) void openDetail(rid);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  async function openDetail(id: string) {
    setDetailOpen(true);
    setDetailLoading(true);
    setDetail(null);
    try {
      const data = await request<StorePackageAuditDetailDto>(`/admin/store-package-audits/${id}`);
      setDetail(data);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '加载详情失败');
      setDetailOpen(false);
    } finally {
      setDetailLoading(false);
    }
  }

  async function audit(id: string, action: 'APPROVE' | 'REJECT', reason?: string) {
    try {
      await request(`/admin/store-package-audits/${id}/audit`, {
        method: 'PUT',
        body: JSON.stringify(
          action === 'REJECT' ? { action, rejectReason: reason } : { action },
        ),
      });
      message.success(action === 'APPROVE' ? '已通过' : '已驳回');
      setDetailOpen(false);
      notifyPackageAuditChanged();
      void reload(page, status);
    } catch (e) {
      message.error(e instanceof Error ? e.message : '操作失败');
    }
  }

  const diffRows = detail ? diffPackages(detail.livePackages ?? [], detail.packages ?? []) : [];
  const changeByKey = new Map(diffRows.map((row) => [row.key, row.change]));
  const changesByKey = new Map(diffRows.map((row) => [row.key, row.changes]));
  const addedCount = diffRows.filter((r) => r.change === 'added').length;
  const removedCount = diffRows.filter((r) => r.change === 'removed').length;
  const changedCount = diffRows.filter((r) => r.change === 'changed').length;
  const unchangedCount = diffRows.filter((r) => r.change === 'unchanged').length;

  const columns: ColumnsType<StorePackageChangeRequestDto> = [
    { title: '门店', dataIndex: 'storeName', render: (_, row) => row.storeName || row.storeId },
    {
      title: '状态',
      dataIndex: 'status',
      render: (v: StorePackageChangeRequestDto['status']) => (
        <Tag>{HQ_PACKAGE_STATUS_LABELS[v] ?? v}</Tag>
      ),
    },
    {
      title: '套餐数',
      render: (_, row) => row.packages?.length ?? 0,
    },
    {
      title: '提交方',
      render: (_, row) => (row.submitterType === 'PARTNER' ? '合伙人' : '门店'),
    },
    { title: '提交时间', dataIndex: 'createdAt', render: (v) => fmtTime(String(v)) },
    {
      title: '操作',
      render: (_, row) => (
        <Space>
          <Button type="link" onClick={() => void openDetail(row.id)}>
            查看变更
          </Button>
          {row.status === 'PENDING' ? (
            <>
              <Button type="link" onClick={() => void audit(row.id, 'APPROVE')}>
                通过
              </Button>
              <Button
                type="link"
                danger
                onClick={() => {
                  setActiveId(row.id);
                  setRejectReason('');
                  setRejectOpen(true);
                }}
              >
                驳回
              </Button>
            </>
          ) : (
            row.rejectReason || null
          )}
        </Space>
      ),
    },
  ];

  return (
    <div>
      <Typography.Title level={4}>审核通知</Typography.Title>
      <Tabs
        activeKey={activeTab}
        onChange={setActiveTab}
        items={[
          {
            key: 'package',
            label: '套餐审核',
            children: (
              <>
                <Space style={{ marginBottom: 16 }}>
                  {(['PENDING', 'APPROVED', 'REJECTED', ''] as const).map((s) => (
                    <Button key={s || 'all'} type={status === s ? 'primary' : 'default'} onClick={() => setStatus(s)}>
                      {s === 'PENDING' ? (
                        <Badge count={pendingCount} size="small" offset={[8, -2]}>
                          {HQ_PACKAGE_STATUS_LABELS.PENDING}
                        </Badge>
                      ) : s ? (
                        HQ_PACKAGE_STATUS_LABELS[s]
                      ) : (
                        '全部'
                      )}
                    </Button>
                  ))}
                </Space>
                <Table
                  rowKey="id"
                  loading={loading}
                  columns={columns}
                  dataSource={items}
                  pagination={{
                    current: page,
                    total,
                    pageSize: 20,
                    onChange: (p) => void reload(p, status),
                  }}
                />
              </>
            ),
          },
          {
            key: 'info',
            label: '信息变更',
            children: <InfoChangeAuditPanel initialRequestId={infoRequestId} />,
          },
        ]}
      />

      <Drawer
        title={detail ? `${detail.storeName || detail.storeId} · 套餐变更` : '套餐变更详情'}
        width={880}
        open={detailOpen}
        onClose={() => setDetailOpen(false)}
        extra={
          detail?.status === 'PENDING' ? (
            <Space>
              <Button onClick={() => void audit(detail.id, 'APPROVE')}>通过</Button>
              <Button
                danger
                onClick={() => {
                  setActiveId(detail.id);
                  setRejectReason('');
                  setRejectOpen(true);
                }}
              >
                驳回
              </Button>
            </Space>
          ) : null
        }
      >
        {detailLoading ? (
          <Typography.Text type="secondary">加载中…</Typography.Text>
        ) : detail ? (
          <>
            <Space style={{ marginBottom: 16 }} wrap>
              <Tag>{HQ_PACKAGE_STATUS_LABELS[detail.status]}</Tag>
              <Typography.Text type="secondary">
                提交方：{detail.submitterType === 'PARTNER' ? '合伙人' : '门店'} · {fmtTime(detail.createdAt)}
              </Typography.Text>
            </Space>
            {detail.rejectReason ? (
              <Typography.Paragraph type="danger">驳回原因：{detail.rejectReason}</Typography.Paragraph>
            ) : null}
            <Space direction="vertical" size={4} style={{ marginBottom: 12 }}>
              <Typography.Text type="secondary">
                线上已审核 {detail.livePackages?.length ?? 0} 条 · 待审核 {detail.packages?.length ?? 0} 条
              </Typography.Text>
              <Space wrap>
                <Tag color="green">新增 {addedCount}</Tag>
                <Tag color="red">删除 {removedCount}</Tag>
                <Tag color="orange">变更 {changedCount}</Tag>
                {unchangedCount ? <Tag>未变 {unchangedCount}</Tag> : null}
              </Space>
            </Space>
            <div className="admin-package-audit-cols">
              <div className="admin-package-audit-col">
                <Typography.Title level={5} style={{ marginTop: 0 }}>
                  线上已审核套餐
                </Typography.Title>
                {(detail.livePackages ?? []).length ? (
                  (detail.livePackages ?? []).map((pkg, index) => (
                    <PackageDetailCard
                      key={`live-${packageKey(pkg, index)}`}
                      title={`套餐 ${index + 1}`}
                      pkg={pkg}
                      change={changeByKey.get(packageKey(pkg, index))}
                    />
                  ))
                ) : (
                  <Typography.Text type="secondary">暂无线上套餐</Typography.Text>
                )}
              </div>
              <div className="admin-package-audit-col">
                <Typography.Title level={5} style={{ marginTop: 0 }}>
                  待审核套餐
                </Typography.Title>
                {(detail.packages ?? []).length ? (
                  (detail.packages ?? []).map((pkg, index) => (
                    <PackageDetailCard
                      key={`pending-${packageKey(pkg, index)}`}
                      title={`套餐 ${index + 1}`}
                      pkg={pkg}
                      change={changeByKey.get(packageKey(pkg, index))}
                      changes={changesByKey.get(packageKey(pkg, index))}
                    />
                  ))
                ) : (
                  <Typography.Text type="secondary">暂无待审核套餐</Typography.Text>
                )}
              </div>
            </div>
          </>
        ) : null}
      </Drawer>

      <Modal
        title="驳回套餐变更"
        open={rejectOpen}
        onCancel={() => setRejectOpen(false)}
        onOk={() => {
          if (!activeId) return;
          if (!rejectReason.trim()) {
            message.warning('请填写驳回原因');
            return;
          }
          void audit(activeId, 'REJECT', rejectReason.trim());
          setRejectOpen(false);
        }}
      >
        <Input.TextArea
          rows={3}
          value={rejectReason}
          placeholder="驳回原因"
          onChange={(e) => setRejectReason(e.target.value)}
        />
      </Modal>
    </div>
  );
}
