// Dispute Resolution / 調解中心

const DISPUTE_REASONS = {
  quality: { label: "交付品質不符", desc: "成品與討論內容差異過大" },
  deadline: { label: "延遲交付", desc: "創作者未按期限交付" },
  no_show: { label: "業主失聯", desc: "業主長期未驗收或回應" },
  change_request: { label: "需求變更爭議", desc: "業主臨時更改需求範圍" },
  copyright: { label: "版權疑慮", desc: "疑似抄襲或版權爭議" },
  payment: { label: "款項爭議", desc: "託管金額計算分歧" },
  other: { label: "其他", desc: "未分類爭議" },
};

// ============== Data ==============
function deriveCurrentAdmin(adminEmail) {
  const email = adminEmail || "";
  return {
    id: email || "admin",
    email,
    name: email ? email.split("@")[0] : "客服",
    role: "ops_admin",
  };
}

// 將 API 回傳的 dispute 轉為既有 UI 使用的 shape；某些欄位（severity / assignee / responseDue
// / reason 分類）目前沒有資料來源，先用啟發式預設值
function transformDispute(d) {
  const isCommissionerInitiator = !!d.commissionerReason;
  const initiatorName = isCommissionerInitiator ? d.commissionerName : d.artistName;
  const initiatorId = isCommissionerInitiator ? d.commissionerId : d.artistUserId;
  const description = isCommissionerInitiator ? d.commissionerReason : d.artistResponse;
  const counterpartyResponse = isCommissionerInitiator ? d.artistResponse : d.commissionerReason;
  const initiatorImages = isCommissionerInitiator ? d.commissionerImages : d.artistResponseImages;
  const counterpartyImages = isCommissionerInitiator ? d.artistResponseImages : d.commissionerImages;
  // 嚴重度與分類直接用 DB 真實值，缺值才用 fallback
  const severity = d.disputeSeverity || (d.price >= 5000 ? "high" : d.price >= 1500 ? "medium" : "low");
  const status = d.isActive ? "investigating" : "closed";
  const fmtDate = (s) => s ? s.slice(0, 16).replace("T", " ") : null;

  let outcome = null;
  let outcomeAmount = null;
  if (!d.isActive) {
    if (d.status === "refunded") {
      outcome = d.refundAmount >= d.price ? "full_refund_client" : "partial_refund";
      outcomeAmount = d.refundAmount ?? 0;
    } else if (d.status === "closed") {
      outcome = "pay_artist";
      outcomeAmount = d.price;
    } else {
      outcome = "reopen";
    }
  }

  const fallbackHue = (s) => {
    let h = 0;
    for (let i = 0; i < (s || "").length; i++) h = (h * 31 + s.charCodeAt(i)) % 360;
    return h;
  };

  const clientUser = {
    id: d.commissionerId,
    name: d.commissionerName || "委託人",
    avatarUrl: d.commissionerAvatar || null,
    level: "—",
    disputeCount: d.commissionerDisputeCount ?? 0,
    avatar: { hue: fallbackHue(d.commissionerId), glyph: (d.commissionerName || "委")[0] },
  };
  const artistUser = {
    id: d.artistUserId,
    name: d.artistName || "創作者",
    avatarUrl: d.artistAvatar || null,
    level: "—",
    disputeCount: d.artistDisputeCount ?? 0,
    avatar: { hue: fallbackHue(d.artistUserId), glyph: (d.artistName || "創")[0] },
  };

  return {
    id: "#" + d.id.slice(0, 8).toUpperCase(),
    _rawId: d.id,
    orderId: "#" + d.id.slice(0, 8).toUpperCase(),
    initiator: isCommissionerInitiator ? "client" : "artist",
    initiatorName,
    initiatorId,
    description: description || "（未填寫事由）",
    counterpartyResponse: counterpartyResponse || "",
    initiatorImages: Array.isArray(initiatorImages) ? initiatorImages : [],
    counterpartyImages: Array.isArray(counterpartyImages) ? counterpartyImages : [],
    reason: d.disputeReason || "other",
    severity,
    status,
    openedAt: fmtDate(d.disputeOpenedAt) ?? "",
    closedAt: fmtDate(d.disputeClosedAt),
    responseDue: "—",
    amount: d.price,
    noteCount: d.noteCount ?? 0,
    assignee: d.assigneeEmail ? { id: d.assigneeEmail, email: d.assigneeEmail, name: d.assigneeName || d.assigneeEmail.split("@")[0] } : null,
    outcome,
    outcomeAmount,
    _clientUser: clientUser,
    _artistUser: artistUser,
    order: {
      _rawId: d.id, // 給 ChatTab / FilesTab fetch /api/admin/orders/[id]* 使用
      type: d.type || "委託訂單",
      clientId: d.commissionerId,
      artistId: d.artistUserId,
      artistName: d.artistName,
      amount: d.price,
      platformFee: d.platformFee ?? Math.round(d.price * 0.10),
      artistPayout: d.artistPayout ?? (d.price - Math.round(d.price * 0.10)),
      milestones: d.milestonesCompleted ?? 0,
      totalMilestones: d.milestonesTotal ?? 0,
      milestonesData: Array.isArray(d.milestones) ? d.milestones : [],
      files: Array.isArray(d.files) ? d.files : [],
      progress: d.progress ?? 0,
      createdAt: d.createdAt ? d.createdAt.slice(0, 10) : "",
      deadline: d.deadline || "",
      acceptedAt: fmtDate(d.acceptedAt),
      paidAt: fmtDate(d.paidAt),
      deliveredAt: fmtDate(d.deliveredAt),
      completedAt: fmtDate(d.completedAt),
    },
  };
}


// ============== Main page ==============

const DisputesPage = ({ adminEmail }) => {
  const CURRENT_ADMIN = deriveCurrentAdmin(adminEmail);
  const [tab, setTab] = useState("active");
  const [query, setQuery] = useState("");
  const [severity, setSeverity] = useState("all");
  const [assignee, setAssignee] = useState("all");
  const [detail, setDetail] = useState(null);
  const [disputes, setDisputes] = useState([]);
  const [loading, setLoading] = useState(true);
  const [reports, setReports] = useState([]);
  const [reportsLoading, setReportsLoading] = useState(false);

  function fetchDisputes() {
    setLoading(true);
    fetch("/api/admin/disputes", { credentials: "include" })
      .then(r => r.ok ? r.json() : { disputes: [] })
      .then(data => {
        setDisputes((data.disputes ?? []).map(transformDispute));
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }

  function fetchReports() {
    setReportsLoading(true);
    fetch("/api/admin/reports", { credentials: "include" })
      .then(r => r.ok ? r.json() : { reports: [] })
      .then(data => { setReports(data.reports ?? []); setReportsLoading(false); })
      .catch(() => setReportsLoading(false));
  }

  useEffect(() => { fetchDisputes(); fetchReports(); }, []);

  const today = new Date().toISOString().slice(0, 10);

  const counts = {
    pending: 0, // 目前沒有「待認領」概念
    investigating: disputes.filter(d => d.status === "investigating").length,
    mediating: 0,
    closed: disputes.filter(d => d.status === "closed").length,
    mine: 0,
  };

  const todayNew = {
    pending: 0,
    active: disputes.filter(d => d.status === "investigating" && (d.openedAt ?? "").startsWith(today)).length,
    mine: 0,
    closed: disputes.filter(d => d.status === "closed" && (d.closedAt ?? "").startsWith(today)).length,
  };

  const filtered = disputes.filter(d => {
    if (tab === "active" && d.status !== "investigating") return false;
    if (tab === "closed" && d.status !== "closed") return false;
    // tab === "all" 不過濾
    if (query) {
      const q = query.toLowerCase();
      if (!d.id.toLowerCase().includes(q) && !d.orderId.toLowerCase().includes(q) && !(d.initiatorName || "").includes(query)) return false;
    }
    if (severity !== "all" && d.severity !== severity) return false;
    return true;
  });

  if (detail) return <DisputeDetailPage dispute={detail} currentAdmin={CURRENT_ADMIN} onBack={() => { setDetail(null); fetchDisputes(); }}/>;

  const overdue = 0; // 目前 responseDue 概念尚未實作

  return (
    <div className="fade-in">
      <PageHeader
        title="調解中心"
        subtitle={<>
          {counts.pending + counts.investigating + counts.mediating} 件進行中
          {overdue > 0 && <> · <span style={{ color: "var(--danger)" }}><Icon name="alert" size={11} style={{ verticalAlign: -1 }}/> {overdue} 件已逾期</span></>}
          {" · "}本月結案 {counts.closed} 件 · 平均結案時間 3.8 天
        </>}
        actions={<>
          <Button variant="secondary" icon="refresh" onClick={() => window.location.reload()}>重新整理</Button>
          <Button variant="secondary" icon="download" onClick={() => {
            csvDownload(`disputes_${Date.now()}.csv`,
              ["ID","訂單","申請人","類型","嚴重性","狀態","開案時間"],
              filtered.map(d => [d.id, d.orderId, d.initiatorName, DISPUTE_REASONS[d.reason]?.label ?? d.reason, d.severity, d.status, d.openedAt ?? ""])
            );
          }}>匯出報告</Button>
          <Button variant="secondary" icon="settings">調解準則</Button>
        </>}
      />

      {/* Summary row */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 18 }}>
        <DisputeSummaryCard tone="warn" icon="alert" label="待認領" todayNew={todayNew.pending} total={counts.pending} hint={overdue > 0 ? `${overdue} 件逾期` : "本日新增"}/>
        <DisputeSummaryCard tone="accent" icon="message" label="進行中" todayNew={todayNew.active} total={counts.investigating + counts.mediating} hint="本日新增"/>
        <DisputeSummaryCard tone="ink" icon="user" label="我負責的" todayNew={todayNew.mine} total={counts.mine} hint="本日新增"/>
        <DisputeSummaryCard tone="success" icon="check" label="已結案" todayNew={todayNew.closed} total={counts.closed} hint="今日結案"/>
      </div>

      <div style={{ marginBottom: 14 }}>
        <Tabs active={tab} onChange={setTab} tabs={[
          { value: "active", label: "進行中", count: counts.investigating },
          { value: "closed", label: "已結案", count: counts.closed },
          { value: "all", label: "全部", count: disputes.length },
          { value: "reports", label: "檢舉管理", count: reports.filter(r => r.status === "pending").length || undefined },
        ]}/>
      </div>

      {tab === "reports" ? (
        <ReportsTab reports={reports} loading={reportsLoading} onRefresh={fetchReports}/>
      ) : (
        <>
          <div style={{ display: "flex", gap: 10, alignItems: "center", marginBottom: 14, flexWrap: "wrap" }}>
            <div style={{ flex: 1, maxWidth: 320, minWidth: 200 }}>
              <Input icon="search" placeholder="調解編號、訂單、申訴人…" value={query} onChange={e => setQuery(e.target.value)}/>
            </div>
            <Select value={severity} onChange={setSeverity} icon="flag" options={[
              { value: "all", label: "所有嚴重度" }, { value: "high", label: "高 · 高額 / 版權" },
              { value: "medium", label: "中" }, { value: "low", label: "低" },
            ]}/>
            <Select value={assignee} onChange={setAssignee} icon="users" options={[
              { value: "all", label: "所有承辦人" }, { value: "unassigned", label: "未認領" },
              { value: "mine", label: "我的案件" },
            ]}/>
          </div>

          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {filtered.map(d => <DisputeListCard key={d.id} dispute={d} onClick={() => setDetail(d)}/>)}
            {filtered.length === 0 && (
              <Card padding={50} style={{ textAlign: "center" }}>
                <Icon name="shield" size={30} style={{ color: "var(--success)", marginBottom: 10 }}/>
                <div style={{ fontSize: 13.5, color: "var(--ink-2)", fontWeight: 500 }}>沒有符合條件的調解案</div>
                <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4 }}>更換篩選條件或等待新的申訴</div>
              </Card>
            )}
          </div>
        </>
      )}
    </div>
  );
};

const DisputeSummaryCard = ({ tone, icon, label, todayNew, total, hint }) => (
  <Card padding={16}>
    <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
      <div style={{
        width: 40, height: 40, borderRadius: 10,
        background: tone === "ink" ? "var(--surface-2)" : `var(--${tone}-soft)`,
        color: tone === "ink" ? "var(--ink)" : `var(--${tone})`,
        display: "flex", alignItems: "center", justifyContent: "center",
      }}><Icon name={icon} size={18}/></div>
      <div>
        <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{label}</div>
        <div style={{ display: "flex", alignItems: "baseline", gap: 6, marginTop: 2 }}>
          <div className="serif" style={{ fontSize: 26, lineHeight: 1.1 }}>+{todayNew}</div>
          <div style={{ fontSize: 12, color: "var(--ink-3)" }}>/ {total} 累計</div>
        </div>
      </div>
    </div>
    <div style={{ marginTop: 10, fontSize: 11.5, color: "var(--ink-3)" }}>{hint}</div>
  </Card>
);

const DisputeListCard = ({ dispute: d, onClick }) => {
  const statusMap = {
    pending: { label: "待認領", tone: "warn" },
    investigating: { label: "調查中", tone: "info" },
    mediating: { label: "調解中", tone: "accent" },
    closed: { label: "已結案", tone: "success" },
  };
  const sevColor = d.severity === "high" ? "var(--danger)" : d.severity === "medium" ? "var(--warn)" : "var(--ink-4)";
  const order = d.order;
  const client = d._clientUser ?? MOCK.USERS.find(u => u.id === order.clientId);
  const artist = d._artistUser ?? MOCK.USERS.find(u => u.id === order.artistId);

  return (
    <Card padding={0} style={{ cursor: "pointer", transition: "box-shadow 0.15s, border-color 0.15s" }} onClick={onClick}>
      <div style={{ display: "grid", gridTemplateColumns: "4px 1fr auto", alignItems: "stretch" }}>
        <div style={{ background: sevColor, borderRadius: "10px 0 0 10px" }}/>
        <div style={{ padding: "16px 18px" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8, flexWrap: "wrap" }}>
            <Badge tone={statusMap[d.status].tone} size="sm" dot>{statusMap[d.status].label}</Badge>
            <Badge tone="default" size="sm">{DISPUTE_REASONS[d.reason].label}</Badge>
            <span className="mono" style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{d.id}</span>
            <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>·</span>
            <span className="mono" style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{d.orderId}</span>
            <div style={{ flex: 1 }}/>
            {d.responseDue && d.responseDue !== "—" && (
              <span style={{ fontSize: 11.5, color: d.responseDue === "已逾期" ? "var(--danger)" : "var(--ink-3)", fontWeight: d.responseDue === "已逾期" ? 500 : 400 }}>
                <Icon name="clock" size={11} style={{ verticalAlign: -1, marginRight: 3 }}/>
                {d.responseDue === "已逾期" ? "回應逾期" : `回應 ${d.responseDue}`}
              </span>
            )}
          </div>

          <div style={{ fontSize: 14, color: "var(--ink-2)", lineHeight: 1.55, marginBottom: 10 }}>
            <b style={{ color: d.initiator === "client" ? "var(--info)" : "var(--accent)" }}>
              {d.initiator === "client" ? "委託人" : "創作者"} · {d.initiatorName}
            </b>
            {" 發起:"}
            {d.description}
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: 20, fontSize: 12, color: "var(--ink-3)" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <div style={{ display: "flex" }}>
                {artist && <Avatar hue={artist.avatar.hue} glyph={artist.avatar.glyph} size={20} style={{ border: "2px solid var(--surface)" }}/>}
                {client && <Avatar hue={client.avatar.hue} glyph={client.avatar.glyph} size={20} style={{ border: "2px solid var(--surface)", marginLeft: -6 }}/>}
              </div>
              <span>{d.initiator === "artist" ? `${artist?.name} vs ${client?.name}` : `${client?.name} vs ${artist?.name}`}</span>
            </div>
            <span>·</span>
            <span>爭議金額 <b className="mono" style={{ color: "var(--ink-2)" }}>NT$ {fmtNum(d.amount)}</b></span>
            <span>·</span>
            <span>開立於 {d.openedAt}</span>
            <span>·</span>
            {d.assignee ? (
              <span>承辦 <b style={{ color: "var(--ink-2)" }}>{d.assignee.name}</b></span>
            ) : (
              <span style={{ color: "var(--warn)", fontWeight: 500 }}>未認領</span>
            )}
          </div>
        </div>
        <div style={{ padding: "16px 18px", display: "flex", alignItems: "center" }}>
          <Icon name="arrow-right" size={16} style={{ color: "var(--ink-4)" }}/>
        </div>
      </div>
    </Card>
  );
};

// ============== Detail page ==============

const DisputeDetailPage = ({ dispute, onBack, currentAdmin }) => {
  const CURRENT_ADMIN = currentAdmin || { id: "admin", email: "", name: "客服", role: "ops_admin" };
  const [tab, setTab] = useState("overview");
  const [assignDialog, setAssignDialog] = useState(false);
  const [noteDialog, setNoteDialog] = useState(false);
  const [contactDialog, setContactDialog] = useState(null); // "client" | "artist" | "both"
  const [resolveDialog, setResolveDialog] = useState(null); // kind
  const [state, setState] = useState({
    assignee: dispute.assignee,
    status: dispute.status,
    notes: [],
    resolution: dispute.outcome ? {
      kind: dispute.outcome,
      amount: dispute.outcomeAmount,
      reason: "（系統未保留客服說明文字，請至「調解時間軸」查看裁決時間）",
      decidedBy: dispute.assignee?.name || "客服",
      decidedAt: dispute.closedAt,
    } : null,
  });
  const toast = useToast();

  // 載入內部備註
  useEffect(() => {
    fetch(`/api/admin/commissions/${dispute._rawId}/dispute/notes`, { credentials: "include" })
      .then(r => r.ok ? r.json() : { notes: [] })
      .then(data => {
        const formatted = (data.notes ?? []).map(n => ({
          author: n.authorName,
          role: n.authorRole,
          text: n.content,
          time: n.createdAt ? n.createdAt.slice(0, 16).replace("T", " ") : "",
        }));
        setState(s => ({ ...s, notes: formatted }));
      })
      .catch(() => {});
  }, [dispute._rawId]);

  const order = dispute.order;
  const client = dispute._clientUser ?? MOCK.USERS.find(u => u.id === order.clientId);
  const artist = dispute._artistUser ?? MOCK.USERS.find(u => u.id === order.artistId);
  const isAssignedToMe = state.assignee?.id === CURRENT_ADMIN.id;
  const isClosed = state.status === "closed";

  const handleAssign = async () => {
    const res = await fetch(`/api/admin/commissions/${dispute._rawId}/dispute/assign`, {
      method: "POST",
      credentials: "include",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: CURRENT_ADMIN.name }),
    });
    if (!res.ok) {
      const j = await res.json().catch(() => ({}));
      toast({ title: "認領失敗", message: j.error ?? "請稍後再試", icon: "alert" });
      return;
    }
    const j = await res.json();
    setState(s => ({
      ...s,
      assignee: { id: j.assigneeEmail, email: j.assigneeEmail, name: j.assigneeName },
      status: s.status === "pending" ? "investigating" : s.status,
    }));
    setAssignDialog(false);
    toast({ title: "已認領此案件", message: "已寫入後台紀錄", icon: "check" });
  };
  const handleAddNote = async (text) => {
    const res = await fetch(`/api/admin/commissions/${dispute._rawId}/dispute/notes`, {
      method: "POST",
      credentials: "include",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ content: text, name: CURRENT_ADMIN.name, role: "客服" }),
    });
    if (!res.ok) {
      const j = await res.json().catch(() => ({}));
      toast({ title: "新增備註失敗", message: j.error ?? "請稍後再試", icon: "alert" });
      return;
    }
    const j = await res.json();
    const note = j.note;
    setState(s => ({
      ...s,
      notes: [{
        author: note.authorName,
        role: note.authorRole,
        text: note.content,
        time: note.createdAt ? note.createdAt.slice(0, 16).replace("T", " ") : "剛剛",
      }, ...s.notes],
    }));
    setNoteDialog(false);
    toast({ title: "內部備註已新增", message: "僅管理員可見 · 已關聯至此案件", icon: "check" });
  };
  const handleContact = (target) => {
    setContactDialog(null);
    toast({ title: "訊息已寄出", message: target === "both" ? "委託人 + 創作者" : target === "client" ? client.name : artist.name, icon: "mail" });
  };
  const handleResolve = async (kind, amount, reason) => {
    // 映射 UI kind → 後端 action
    let action = "dismiss";
    let refundRatio;
    if (kind === "full_refund_client") { action = "force_refund"; refundRatio = 100; }
    else if (kind === "partial_refund") {
      action = "force_refund";
      const n = Number(amount);
      refundRatio = dispute.amount > 0 ? Math.round(n / dispute.amount * 100) : 100;
    }
    else if (kind === "pay_artist") { action = "force_close"; }
    else if (kind === "reopen") { action = "dismiss"; }

    const res = await fetch(`/api/admin/commissions/${dispute._rawId}/dispute/resolve`, {
      method: "POST",
      credentials: "include",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action, refundRatio, note: reason ?? "" }),
    });
    if (!res.ok) {
      const j = await res.json().catch(() => ({}));
      toast({ title: "裁定失敗", message: j.error ?? "請稍後再試", icon: "alert" });
      return;
    }
    setState(s => ({
      ...s,
      status: "closed",
      resolution: {
        kind, amount, reason,
        decidedBy: CURRENT_ADMIN.name,
        decidedAt: new Date().toLocaleString("zh-TW"),
      },
    }));
    setResolveDialog(null);
    const labels = { full_refund_client: "全額退款給委託人", pay_artist: "撥款給創作者", partial_refund: "部分退款", reopen: "重啟協商" };
    toast({ title: `調解結果已生效 · ${labels[kind]}`, message: "決議已永久記錄,無法修改", icon: "check" });
  };

  return (
    <div className="fade-in">
      {/* Breadcrumb / back */}
      <div style={{ marginBottom: 14, display: "flex", alignItems: "center", gap: 8 }}>
        <Button variant="ghost" size="sm" onClick={onBack} icon="arrow-left">調解中心</Button>
        <span style={{ color: "var(--ink-4)" }}>/</span>
        <span className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>{dispute.id}</span>
      </div>

      {/* Header banner */}
      <Card padding={0} style={{ marginBottom: 18, overflow: "hidden", borderColor: dispute.severity === "high" ? "var(--danger)" : "var(--border)" }}>
        <div style={{
          padding: "18px 24px",
          background: dispute.severity === "high" ? "linear-gradient(90deg, var(--danger-soft), transparent)" : "var(--surface-2)",
          borderBottom: "1px solid var(--border)",
        }}>
          <div style={{ display: "flex", alignItems: "flex-start", gap: 16 }}>
            <div style={{
              width: 48, height: 48, borderRadius: 12,
              background: dispute.severity === "high" ? "var(--danger)" : "var(--warn)",
              color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
            }}><Icon name="alert" size={22}/></div>
            <div style={{ flex: 1 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4, flexWrap: "wrap" }}>
                <ResolutionStatusBadge status={state.status}/>
                <Badge tone={dispute.severity === "high" ? "danger" : dispute.severity === "medium" ? "warn" : "default"} size="sm" dot>
                  {dispute.severity === "high" ? "高嚴重度" : dispute.severity === "medium" ? "中嚴重度" : "低嚴重度"}
                </Badge>
                <span className="mono" style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{dispute.id}</span>
                <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>·</span>
                <span style={{ fontSize: 11.5, color: "var(--ink-3)" }}>開立於 {dispute.openedAt}</span>
              </div>
              <h1 style={{ fontSize: 22, fontWeight: 600, letterSpacing: -0.2 }}>
                {DISPUTE_REASONS[dispute.reason].label} · 訂單 <span className="mono" style={{ fontSize: 18, color: "var(--accent)" }}>{dispute.orderId}</span>
              </h1>
              <div style={{ marginTop: 6, fontSize: 13, color: "var(--ink-2)", lineHeight: 1.55 }}>
                <b style={{ color: dispute.initiator === "client" ? "var(--info)" : "var(--accent)" }}>
                  {dispute.initiator === "client" ? "委託人" : "創作者"} {dispute.initiatorName}
                </b>{" 發起申訴:"}{dispute.description}
              </div>
            </div>
            <div style={{ textAlign: "right", flexShrink: 0, minWidth: 180 }}>
              <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>爭議金額</div>
              <div className="serif" style={{ fontSize: 28, lineHeight: 1.1, whiteSpace: "nowrap" }}>NT$ {fmtNum(dispute.amount)}</div>
              <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 2 }}>託管中 · 等待裁決</div>
            </div>
          </div>
        </div>

        {/* Parties bar */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 50px 1fr 200px", gap: 16, padding: "14px 24px", alignItems: "center" }}>
          <PartySummary user={client} role="委託人" color="info" initiator={dispute.initiator === "client"}/>
          <div style={{ textAlign: "center", color: "var(--ink-4)" }}>
            <Icon name="alert" size={16}/>
            <div style={{ fontSize: 10, marginTop: 2 }}>vs</div>
          </div>
          <PartySummary user={artist} role="創作者" color="accent" initiator={dispute.initiator === "artist"}/>
          <div style={{ paddingLeft: 16, borderLeft: "1px solid var(--border)" }}>
            <div style={{ fontSize: 11, color: "var(--ink-3)" }}>承辦人</div>
            {state.assignee ? (
              <div style={{ fontSize: 13, fontWeight: 500, marginTop: 2 }}>
                {state.assignee.name}
                {isAssignedToMe && <Badge tone="accent" size="sm" style={{ marginLeft: 6 }}>我</Badge>}
              </div>
            ) : (
              <div style={{ fontSize: 13, color: "var(--warn)", marginTop: 2, fontWeight: 500 }}>尚未認領</div>
            )}
            <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 4 }}>
              {dispute.responseDue && dispute.responseDue !== "—" ? `回應時限 · ${dispute.responseDue}` : "已結案"}
            </div>
          </div>
        </div>
      </Card>

      {/* Final decision banner — immutable */}
      {state.resolution && <FinalDecisionBanner resolution={state.resolution} amount={dispute.amount}/>}

      {/* Admin action toolbar */}
      {!isClosed && (
        <div style={{
          display: "flex", alignItems: "center", gap: 10, padding: "12px 16px",
          background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10,
          marginBottom: 16, position: "sticky", top: 0, zIndex: 5,
        }}>
          {!state.assignee && (
            <Button variant="primary" icon="user-plus" onClick={() => setAssignDialog(true)}>
              認領此案件
            </Button>
          )}
          {state.assignee && !isAssignedToMe && (
            <div style={{ fontSize: 12.5, color: "var(--ink-3)", padding: "6px 12px", background: "var(--surface-2)", borderRadius: 6 }}>
              <Icon name="info" size={12} style={{ verticalAlign: -1, marginRight: 4 }}/>
              此案由 <b style={{ color: "var(--ink-2)" }}>{state.assignee.name}</b> 承辦中
            </div>
          )}
          <Button variant="secondary" icon="edit" onClick={() => setNoteDialog(true)}>加內部備註</Button>
          <Select value="" onChange={v => setContactDialog(v)} placeholder="聯絡雙方" icon="mail" options={[
            { value: "client", label: `僅 ${client?.name} (委託人)` },
            { value: "artist", label: `僅 ${artist?.name} (創作者)` },
            { value: "both", label: "同時聯絡雙方" },
          ]}/>
          <div style={{ flex: 1 }}/>
          {isAssignedToMe && (
            <>
              <span style={{ fontSize: 11.5, color: "var(--ink-3)", marginRight: 4 }}>做出裁決:</span>
              <Button variant="secondary" size="sm" onClick={() => setResolveDialog("reopen")}>重啟協商</Button>
              <Button variant="secondary" size="sm" onClick={() => setResolveDialog("partial_refund")}>部分退款</Button>
              <Button variant="secondary" size="sm" onClick={() => setResolveDialog("pay_artist")} style={{ color: "var(--accent)" }}>撥款給創作者</Button>
              <Button variant="primary" size="sm" onClick={() => setResolveDialog("full_refund_client")} style={{ background: "var(--danger)" }}>全額退款委託人</Button>
            </>
          )}
        </div>
      )}

      {/* Body: tabs + content */}
      <div style={{ marginBottom: 14 }}>
        <Tabs active={tab} onChange={setTab} tabs={[
          { value: "overview", label: "爭議內容" },
          { value: "chat", label: "溝通紀錄" },
          { value: "files", label: "交付檔案", count: dispute.order.files?.length ?? 0 },
          { value: "milestones", label: "訂單階段", count: dispute.order.totalMilestones },
          { value: "notes", label: "內部備註", count: state.notes.length },
          { value: "timeline", label: "調解時間軸" },
        ]}/>
      </div>

      {tab === "overview" && <DisputeOverviewTab dispute={dispute}/>}
      {tab === "chat" && <ChatTab order={order} artist={artist} client={client}/>}
      {tab === "files" && <FilesTab order={order}/>}
      {tab === "milestones" && <MilestonesTab order={order}/>}
      {tab === "notes" && <DisputeNotesTab notes={state.notes} onAdd={() => setNoteDialog(true)} canEdit={!isClosed}/>}
      {tab === "timeline" && <DisputeTimelineTab dispute={dispute} state={state}/>}

      {/* Dialogs */}
      {assignDialog && (
        <ConfirmDialog open onClose={() => setAssignDialog(false)} onConfirm={handleAssign}
          title="認領此調解案" icon="user-plus" variant="info" confirmText="確認認領"
          description={<>
            您將成為此案的主要承辦人 ({CURRENT_ADMIN.name}),系統會自動:
            <ul style={{ paddingLeft: 20, marginTop: 8, fontSize: 12.5, color: "var(--ink-2)" }}>
              <li>通知雙方您的承辦身份</li>
              <li>將狀態變更為<b>調查中</b></li>
              <li>設定 <b>48 小時</b>處理期限</li>
            </ul>
          </>}
          requireReason={false}
        />
      )}

      {noteDialog && (
        <AddNoteDialog onClose={() => setNoteDialog(false)} onSave={handleAddNote}/>
      )}

      {contactDialog && (
        <ConfirmDialog open onClose={() => setContactDialog(null)} onConfirm={() => handleContact(contactDialog)}
          title={`聯絡${contactDialog === "both" ? "雙方" : contactDialog === "client" ? `委託人 · ${client?.name}` : `創作者 · ${artist?.name}`}`}
          icon="mail" variant="info" confirmText="寄出訊息"
          description="訊息將透過站內信 + Email 寄送。內容會以平台官方身份署名,並記入案件溝通紀錄。"
          requireReason reasonLabel="訊息內容" reasonPlaceholder="例:您好,關於此案件我們需要您提供更多資訊,請於 24 小時內回覆..."
        />
      )}

      {resolveDialog && (
        <ResolveDialog
          kind={resolveDialog}
          dispute={dispute}
          onClose={() => setResolveDialog(null)}
          onConfirm={(amount, reason) => handleResolve(resolveDialog, amount, reason)}
        />
      )}
    </div>
  );
};

// ============== Sub-components ==============

const PartySummary = ({ user, role, color, initiator }) => {
  if (!user) return null;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
      <Avatar hue={user.avatar.hue} glyph={user.avatar.glyph} size={40}/>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
          <Badge tone={color} size="sm">{role}</Badge>
          {initiator && <Badge tone="warn" size="sm">發起申訴</Badge>}
        </div>
        <div style={{ fontSize: 14, fontWeight: 500 }}>{user.name}</div>
        <div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{user.id} · {user.level}</div>
      </div>
    </div>
  );
};

const ResolutionStatusBadge = ({ status }) => {
  const map = {
    pending: { label: "待認領", tone: "warn" },
    investigating: { label: "調查中", tone: "info" },
    mediating: { label: "調解中", tone: "accent" },
    closed: { label: "已結案 · 決議生效", tone: "success" },
  };
  return <Badge tone={map[status].tone} size="sm" dot>{map[status].label}</Badge>;
};

const FinalDecisionBanner = ({ resolution, amount }) => {
  const labels = {
    full_refund_client: { title: "全額退款給委託人", icon: "refresh", color: "var(--danger)", bg: "var(--danger-soft)" },
    pay_artist: { title: "撥款給創作者", icon: "check", color: "var(--accent)", bg: "var(--accent-soft)" },
    partial_refund: { title: "部分退款", icon: "refresh", color: "var(--warn)", bg: "var(--warn-soft)" },
    reopen: { title: "重啟協商", icon: "refresh", color: "var(--info)", bg: "var(--info-soft)" },
  };
  const l = labels[resolution.kind];
  return (
    <div style={{
      border: `1px solid ${l.color}`, borderRadius: 12, padding: "16px 20px",
      marginBottom: 18, background: l.bg, position: "relative",
    }}>
      <div style={{
        position: "absolute", top: 10, right: 12,
        fontSize: 10, fontWeight: 600, color: l.color, letterSpacing: 1,
        display: "flex", alignItems: "center", gap: 4,
      }}>
        <Icon name="lock" size={11}/> 永久紀錄 · 不可修改
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
        <div style={{
          width: 42, height: 42, borderRadius: 10,
          background: l.color, color: "#fff",
          display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
        }}><Icon name={l.icon} size={20}/></div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 11, color: l.color, fontWeight: 600, letterSpacing: 0.6, textTransform: "uppercase" }}>最終裁決</div>
          <div style={{ fontSize: 16, fontWeight: 600, marginTop: 2 }}>
            {l.title}
            {resolution.amount && <span className="mono" style={{ marginLeft: 10, fontSize: 14, color: "var(--ink-2)" }}>NT$ {fmtNum(resolution.amount)}</span>}
          </div>
          <div style={{ fontSize: 13, color: "var(--ink-2)", marginTop: 6, lineHeight: 1.5 }}>{resolution.reason}</div>
          <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 8 }}>
            <Icon name="user" size={11} style={{ verticalAlign: -1, marginRight: 3 }}/>
            裁決人 <b style={{ color: "var(--ink-2)" }}>{resolution.decidedBy}</b>
            <span style={{ margin: "0 8px" }}>·</span>
            <Icon name="clock" size={11} style={{ verticalAlign: -1, marginRight: 3 }}/>
            {resolution.decidedAt}
          </div>
        </div>
      </div>
    </div>
  );
};

// ============== Tabs ==============

const DisputeOverviewTab = ({ dispute }) => {
  const client = dispute._clientUser;
  const artist = dispute._artistUser;
  const initiatorRole = dispute.initiator === "client" ? "委託人" : "創作者";
  const responderRole = dispute.initiator === "client" ? "創作者" : "委託人";
  const initiatorImgs = dispute.initiatorImages ?? [];
  const counterpartyImgs = dispute.counterpartyImages ?? [];

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1.3fr 1fr", gap: 16 }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <Card padding={18}>
          <SectionLabel style={{ marginBottom: 10 }}>{initiatorRole}陳述</SectionLabel>
          <div style={{ fontSize: 13.5, lineHeight: 1.7, color: "var(--ink-2)", whiteSpace: "pre-wrap" }}>{dispute.description}</div>
          {DISPUTE_REASONS[dispute.reason] && (
            <div style={{ marginTop: 12, paddingTop: 12, borderTop: "1px solid var(--border)", fontSize: 12, color: "var(--ink-3)" }}>
              申訴理由分類：<Badge tone="default" size="sm" style={{ marginLeft: 6 }}>{DISPUTE_REASONS[dispute.reason].label}</Badge>
              <span style={{ marginLeft: 10 }}>· {DISPUTE_REASONS[dispute.reason].desc}</span>
            </div>
          )}
        </Card>

        <Card padding={18}>
          <SectionLabel style={{ marginBottom: 10 }}>{responderRole}回應</SectionLabel>
          {dispute.counterpartyResponse ? (
            <div style={{ fontSize: 13.5, lineHeight: 1.7, color: "var(--ink-2)", whiteSpace: "pre-wrap" }}>{dispute.counterpartyResponse}</div>
          ) : (
            <div style={{ fontSize: 13, color: "var(--ink-3)", fontStyle: "italic" }}>對方尚未回應</div>
          )}
        </Card>

        <Card padding={18}>
          <SectionLabel style={{ marginBottom: 10 }}>雙方證據</SectionLabel>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
            <div>
              <div style={{ fontSize: 12, fontWeight: 500, color: "var(--ink-2)", marginBottom: 6 }}>{initiatorRole}提供（{initiatorImgs.length}）</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {initiatorImgs.length > 0 ? initiatorImgs.map((url, i) => (
                  <a key={i} href={url} target="_blank" rel="noopener noreferrer"
                     style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 10px", background: "var(--surface-2)", borderRadius: 6, fontSize: 12.5, textDecoration: "none", color: "var(--ink)" }}>
                    <Icon name="file" size={13} style={{ color: "var(--ink-3)" }}/>
                    <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{(url.split("/").pop() || "附件").slice(0, 24)}</span>
                    <span style={{ color: "var(--info)", fontSize: 12 }}>查看</span>
                  </a>
                )) : (
                  <div style={{ fontSize: 12, color: "var(--ink-3)", fontStyle: "italic" }}>無附件</div>
                )}
              </div>
            </div>
            <div>
              <div style={{ fontSize: 12, fontWeight: 500, color: "var(--ink-2)", marginBottom: 6 }}>{responderRole}提供（{counterpartyImgs.length}）</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {counterpartyImgs.length > 0 ? counterpartyImgs.map((url, i) => (
                  <a key={i} href={url} target="_blank" rel="noopener noreferrer"
                     style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 10px", background: "var(--surface-2)", borderRadius: 6, fontSize: 12.5, textDecoration: "none", color: "var(--ink)" }}>
                    <Icon name="file" size={13} style={{ color: "var(--ink-3)" }}/>
                    <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{(url.split("/").pop() || "附件").slice(0, 24)}</span>
                    <span style={{ color: "var(--info)", fontSize: 12 }}>查看</span>
                  </a>
                )) : (
                  <div style={{ fontSize: 12, color: "var(--ink-3)", fontStyle: "italic" }}>無附件</div>
                )}
              </div>
            </div>
          </div>
        </Card>
      </div>

      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <Card padding={18}>
          <SectionLabel style={{ marginBottom: 10 }}>訂單摘要</SectionLabel>
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <InfoLine label="訂單類型" value={dispute.order.type}/>
            <InfoLine label="訂單金額" value={<span className="mono">NT$ {fmtNum(dispute.order.amount)}</span>}/>
            <InfoLine label="平台抽成（10%）" value={<span className="mono">NT$ {fmtNum(dispute.order.platformFee)}</span>}/>
            <InfoLine label="創作者應收" value={<span className="mono">NT$ {fmtNum(dispute.order.artistPayout)}</span>}/>
            <InfoLine label="階段" value={`${dispute.order.milestones} / ${dispute.order.totalMilestones}`}/>
            <InfoLine label="進度" value={<span>{dispute.order.progress}%</span>}/>
            <InfoLine label="建立日期" value={dispute.order.createdAt}/>
            {dispute.order.deadline && <InfoLine label="商品工期" value={dispute.order.deadline}/>}
            {dispute.order.paidAt && <InfoLine label="付款時間" value={dispute.order.paidAt}/>}
            {dispute.order.deliveredAt && <InfoLine label="交付時間" value={dispute.order.deliveredAt}/>}
            {dispute.order.completedAt && <InfoLine label="完成時間" value={dispute.order.completedAt}/>}
          </div>
        </Card>

        <Card padding={18}>
          <SectionLabel style={{ marginBottom: 10 }}>歷史爭議</SectionLabel>
          <div style={{ fontSize: 13, color: "var(--ink-2)" }}>
            <div style={{ padding: "8px 10px", background: "var(--surface-2)", borderRadius: 6, marginBottom: 6 }}>
              <div style={{ fontSize: 12 }}>委託人 · {client?.name}</div>
              <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 2 }}>
                {client?.disputeCount > 0 ? `過往 ${client.disputeCount} 起爭議` : "無歷史爭議紀錄"}
              </div>
            </div>
            <div style={{ padding: "8px 10px", background: "var(--surface-2)", borderRadius: 6 }}>
              <div style={{ fontSize: 12 }}>創作者 · {artist?.name}</div>
              <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 2 }}>
                {artist?.disputeCount > 0 ? `過往 ${artist.disputeCount} 起爭議` : "無歷史爭議紀錄"}
              </div>
            </div>
          </div>
        </Card>

        <Card padding={18} style={{ background: "var(--info-soft)", borderColor: "var(--info)" }}>
          <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
            <Icon name="info" size={16} style={{ color: "var(--info)", marginTop: 2 }}/>
            <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.6 }}>
              <b>平台調解準則</b><br/>
              雙方提供的文字證據、檔案上傳時間戳、階段驗收紀錄皆具同等效力。若無法判定誰對誰錯,建議進入「重啟協商」由雙方自行達成共識。
            </div>
          </div>
        </Card>
      </div>
    </div>
  );
};

const DisputeNotesTab = ({ notes, onAdd, canEdit }) => (
  <div>
    {canEdit && (
      <div style={{ marginBottom: 12 }}>
        <Button variant="secondary" icon="plus" onClick={onAdd}>新增內部備註</Button>
        <span style={{ marginLeft: 10, fontSize: 11.5, color: "var(--ink-3)" }}>
          <Icon name="lock" size={11} style={{ verticalAlign: -1 }}/> 僅管理員可見 · 不會顯示給任何一方
        </span>
      </div>
    )}
    <Card padding={0}>
      {notes.map((n, i) => (
        <div key={i} style={{
          padding: "14px 18px",
          borderBottom: i < notes.length - 1 ? "1px solid var(--border)" : "none",
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6, fontSize: 12 }}>
            <span style={{ fontWeight: 500 }}>{n.author}</span>
            <Badge tone="default" size="sm">{n.role}</Badge>
            <div style={{ flex: 1 }}/>
            <span style={{ color: "var(--ink-3)" }}>{n.time}</span>
          </div>
          <div style={{ fontSize: 13.5, color: "var(--ink-2)", lineHeight: 1.6 }}>{n.text}</div>
        </div>
      ))}
      {notes.length === 0 && (
        <div style={{ padding: 40, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>
          尚未加入任何內部備註
        </div>
      )}
    </Card>
  </div>
);

const DisputeTimelineTab = ({ dispute, state }) => {
  // 依訂單實際時間戳組出時間軸
  const o = dispute.order || {};
  const events = [
    o.createdAt && { time: o.createdAt, label: "訂單成立", icon: "shopping-bag", color: "var(--ink-3)" },
    o.acceptedAt && { time: o.acceptedAt, label: "創作者接受訂單", icon: "check", color: "var(--ink-3)" },
    o.paidAt && { time: o.paidAt, label: "委託人付款完成", icon: "credit-card", color: "var(--info)" },
    o.deliveredAt && { time: o.deliveredAt, label: "創作者交付終稿", icon: "send", color: "var(--accent)" },
    o.completedAt && { time: o.completedAt, label: "委託人驗收完成", icon: "check", color: "var(--success)" },
    dispute.openedAt && { time: dispute.openedAt, label: `${dispute.initiator === "client" ? "委託人" : "創作者"}發起申訴`, icon: "alert", color: "var(--danger)" },
    !!dispute.counterpartyResponse && { time: dispute.openedAt, label: `${dispute.initiator === "client" ? "創作者" : "委託人"}提交回應`, icon: "file", color: "var(--info)" },
    state.assignee && { time: dispute.openedAt, label: `${state.assignee.name} 認領此案`, icon: "user-plus", color: "var(--accent)" },
    state.notes && state.notes.length > 0 && { time: dispute.openedAt, label: `新增內部備註 × ${state.notes.length}`, icon: "edit", color: "var(--ink-3)" },
    dispute.closedAt && { time: dispute.closedAt, label: "客服裁定生效，本案結案", icon: "check", color: "var(--success)", final: true },
    state.resolution && !dispute.closedAt && { time: state.resolution.decidedAt, label: "最終裁決已生效", icon: "check", color: "var(--success)", final: true },
  ].filter(Boolean);
  return (
    <Card padding={20}>
      <div style={{ position: "relative", paddingLeft: 22 }}>
        <div style={{ position: "absolute", left: 7, top: 12, bottom: 12, width: 2, background: "var(--border-strong)" }}/>
        {events.map((e, i) => (
          <div key={i} style={{ display: "flex", gap: 14, padding: "10px 0", position: "relative" }}>
            <div style={{
              position: "absolute", left: -22, top: 10,
              width: 16, height: 16, borderRadius: 999,
              background: e.color, border: "3px solid var(--surface)",
              display: "flex", alignItems: "center", justifyContent: "center",
              boxShadow: "0 0 0 1px " + e.color,
            }}/>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 13.5, color: "var(--ink)", fontWeight: e.final ? 600 : 500 }}>{e.label}</div>
              {e.final && <div style={{ fontSize: 11, color: "var(--success)", marginTop: 2 }}>✓ 已永久寫入審計日誌</div>}
            </div>
            <div className="mono" style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{e.time}</div>
          </div>
        ))}
      </div>
    </Card>
  );
};

// ============== Dialogs ==============

const AddNoteDialog = ({ onClose, onSave }) => {
  const [text, setText] = useState("");
  return (
    <Modal open onClose={onClose} width={480}>
      <div style={{ padding: 20 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
          <div style={{ width: 32, height: 32, borderRadius: 8, background: "var(--surface-2)", display: "flex", alignItems: "center", justifyContent: "center" }}>
            <Icon name="edit" size={16}/>
          </div>
          <div style={{ fontSize: 16, fontWeight: 600 }}>新增內部備註</div>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 14 }}>
          <Icon name="lock" size={12} style={{ verticalAlign: -1, marginRight: 4 }}/>
          僅管理員可見 · 備註會保留在案件紀錄中
        </div>
        <textarea
          autoFocus
          value={text}
          onChange={e => setText(e.target.value)}
          placeholder="例:已致電業主確認經過,建議 24 小時後再安排雙方對話..."
          style={{
            width: "100%", minHeight: 120, padding: 12, borderRadius: 8,
            border: "1px solid var(--border-strong)", resize: "vertical",
            fontSize: 13.5, lineHeight: 1.55, outline: "none",
          }}
        />
        <div style={{ marginTop: 14, display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <Button variant="secondary" onClick={onClose}>取消</Button>
          <Button variant="primary" disabled={!text.trim()} onClick={() => onSave(text.trim())}>儲存備註</Button>
        </div>
      </div>
    </Modal>
  );
};

const ResolveDialog = ({ kind, dispute, onClose, onConfirm }) => {
  const cfg = {
    full_refund_client: {
      title: `全額退款委託人 · ${dispute.orderId}`,
      description: <>
        <b className="mono">NT$ {fmtNum(dispute.amount)}</b> 退回委託人原付款方式。
        創作者已收取的階段款將從下期結算扣除。
        <div style={{ marginTop: 10, padding: "10px 12px", background: "var(--danger-soft)", borderRadius: 6, fontSize: 12 }}>
          ⚠ 此決議生效後<b>永久寫入案件紀錄,不可修改</b>
        </div>
      </>,
      variant: "danger", icon: "refresh", confirmText: "確認全額退款",
      reasonLabel: "裁決理由 (會寄給雙方)",
      reasonPlaceholder: "例:經審查,創作者交付品質明顯與合約描述不符,判定委託人申訴成立。",
      requireOtp: true,
      requireText: dispute.id,
      requireTextHint: `請輸入調解編號確認:${dispute.id}`,
      amount: dispute.amount,
    },
    pay_artist: {
      title: `撥款給創作者 · ${dispute.orderId}`,
      description: <>
        撥付 <b className="mono">NT$ {fmtNum(dispute.order.artistPayout)}</b> 給創作者 {dispute.order.artistName}。
        判定委託人申訴不成立,此案結案。
        <div style={{ marginTop: 10, padding: "10px 12px", background: "var(--accent-soft)", borderRadius: 6, fontSize: 12 }}>
          ⚠ 此決議生效後<b>永久寫入案件紀錄</b>
        </div>
      </>,
      variant: "accent", icon: "check", confirmText: "確認撥款",
      reasonLabel: "裁決理由 (會寄給雙方)",
      reasonPlaceholder: "例:創作者依合約交付,業主在階段驗收時已確認,申訴理由不成立。",
      requireOtp: true,
      amount: dispute.order.artistPayout,
    },
    partial_refund: {
      title: `部分退款 · ${dispute.orderId}`,
      description: <>
        指定退款金額給委託人,剩餘按比例撥付給創作者。
        常用於品質有瑕疵但部分可用、或雙方各有責任的情況。
        <div style={{ marginTop: 10, padding: "10px 12px", background: "var(--warn-soft)", borderRadius: 6, fontSize: 12 }}>
          ⚠ 決議生效後<b>永久寫入紀錄</b> · 需填寫退款金額
        </div>
      </>,
      variant: "warn", icon: "refresh", confirmText: "送出部分退款",
      reasonLabel: "裁決理由",
      reasonPlaceholder: "例:交付 3 張其中 2 張符合需求,雙方均有責任,退款 40% 作為補償。",
      extraField: {
        label: `退款金額給委託人 (0 - ${dispute.amount})`,
        type: "text",
        placeholder: `介於 1 - ${dispute.amount}`,
        defaultValue: String(Math.round(dispute.amount * 0.4)),
      },
      requireOtp: true,
    },
    reopen: {
      title: `重啟協商 · ${dispute.orderId}`,
      description: <>
        暫停平台調解,讓雙方自行協商達成共識。
        系統會開啟 <b>72 小時</b>溝通視窗,期滿若仍未和解會自動回到調解中心。
      </>,
      variant: "info", icon: "refresh", confirmText: "重啟協商",
      reasonLabel: "給雙方的引導訊息",
      reasonPlaceholder: "例:此案責任認定困難,建議雙方直接溝通尋求共識。平台建議協商方案:退款 30% 作為折衷...",
    },
  }[kind];

  return (
    <ConfirmDialog
      open
      onClose={onClose}
      onConfirm={(reason, extra) => {
        const amount = kind === "partial_refund" ? parseInt(extra, 10) : cfg.amount;
        onConfirm(amount, reason);
      }}
      title={cfg.title}
      description={cfg.description}
      variant={cfg.variant}
      icon={cfg.icon}
      confirmText={cfg.confirmText}
      requireReason
      reasonLabel={cfg.reasonLabel}
      reasonPlaceholder={cfg.reasonPlaceholder}
      extraField={cfg.extraField}
      requireOtp={cfg.requireOtp}
      requireText={cfg.requireText}
      requireTextHint={cfg.requireTextHint}
    />
  );
};

// ============== Shared helpers ==============

const InfoLine = ({ label, value }) => (
  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", fontSize: 13, padding: "2px 0" }}>
    <span style={{ color: "var(--ink-3)" }}>{label}</span>
    <span style={{ color: "var(--ink)" }}>{value}</span>
  </div>
);

// ============== 檢舉管理 ==============

const REPORT_STATUS_MAP = {
  pending:   { label: "待處理", tone: "warn" },
  actioned:  { label: "已處置", tone: "success" },
  dismissed: { label: "已駁回", tone: "default" },
};

// 對話檢舉那邊原本就是傳中文（如「騷擾」「色情」），作品檢舉是傳代碼
// 兩種混合，先嘗試從 map 翻譯、找不到就照原樣顯示。
const REASON_LABELS = {
  graphic_content:  "未分類的血腥、暴力、色情等內容",
  off_topic:        "與繪圖、文字創作無關之內容",
  plagiarism:       "剽竊、抄襲他人作品",
  external_contact: "作品中含有外部聯絡資訊",
  other:            "其他",
};
const reasonLabel = (code) => REASON_LABELS[code] ?? code;

const TARGET_TYPE_MAP = {
  conversation: { label: "對話訊息", tone: "default" },
  portfolio:    { label: "作品",    tone: "warn" },
};

const fmtDate = (s) => s ? new Date(s).toLocaleString("zh-TW", {
  month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, timeZone: "Asia/Taipei",
}) : "—";

const ReportDetailModal = ({ report, onClose, onAction }) => {
  const statusCfg = REPORT_STATUS_MAP[report.status] ?? REPORT_STATUS_MAP.pending;
  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.45)", zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center" }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="fade-in" style={{ background: "var(--surface)", borderRadius: 16, width: 520, maxHeight: "85vh", overflow: "auto", boxShadow: "var(--shadow-lg)" }}>
        {/* Header */}
        <div style={{ padding: "18px 22px", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", gap: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 16, fontWeight: 600 }}>檢舉詳情</div>
            <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 2 }}>{fmtDate(report.createdAt)}</div>
          </div>
          <Badge tone={statusCfg.tone}>{statusCfg.label}</Badge>
          <button onClick={onClose} style={{ color: "var(--ink-3)", padding: 4 }}><Icon name="x" size={18}/></button>
        </div>

        <div style={{ padding: "20px 22px", display: "flex", flexDirection: "column", gap: 18 }}>
          {/* Parties */}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 32px 1fr", alignItems: "center", gap: 8 }}>
            <div style={{ padding: 14, background: "var(--surface-2)", borderRadius: 10 }}>
              <div style={{ fontSize: 11, color: "var(--ink-4)", marginBottom: 4 }}>檢舉人</div>
              <div style={{ fontSize: 14, fontWeight: 500 }}>{report.reporter.name}</div>
            </div>
            <Icon name="arrow-right" size={16} style={{ color: "var(--ink-4)", margin: "0 auto" }}/>
            <div style={{ padding: 14, background: "var(--danger-soft)", borderRadius: 10, border: "1px solid var(--danger)" }}>
              <div style={{ fontSize: 11, color: "var(--ink-4)", marginBottom: 4 }}>被檢舉人</div>
              <div style={{ fontSize: 14, fontWeight: 500, color: "var(--danger)" }}>{report.reported.name}</div>
            </div>
          </div>

          {/* Target type + Reason */}
          <div>
            <div style={{ fontSize: 11.5, fontWeight: 600, color: "var(--ink-3)", letterSpacing: 0.8, textTransform: "uppercase", marginBottom: 8 }}>舉報類別</div>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <Badge tone={(TARGET_TYPE_MAP[report.targetType] ?? TARGET_TYPE_MAP.conversation).tone}>
                {(TARGET_TYPE_MAP[report.targetType] ?? TARGET_TYPE_MAP.conversation).label}
              </Badge>
              <div style={{ display: "inline-flex", padding: "6px 14px", background: "var(--warn-soft)", color: "var(--warn)", borderRadius: 8, fontSize: 13.5, fontWeight: 500 }}>
                {reasonLabel(report.reason)}
              </div>
            </div>
          </div>

          {/* Reported portfolio item */}
          {report.targetType === "portfolio" && report.portfolioItemUrl && (
            <div>
              <div style={{ fontSize: 11.5, fontWeight: 600, color: "var(--ink-3)", letterSpacing: 0.8, textTransform: "uppercase", marginBottom: 8 }}>被檢舉作品</div>
              <a href={report.portfolioItemUrl} target="_blank" rel="noopener noreferrer">
                <img src={report.portfolioItemUrl} alt="作品" style={{ maxWidth: "100%", maxHeight: 320, borderRadius: 10, border: "1px solid var(--border)", display: "block" }}
                  onError={e => { e.target.style.display = "none"; }}/>
              </a>
            </div>
          )}

          {/* Description */}
          <div>
            <div style={{ fontSize: 11.5, fontWeight: 600, color: "var(--ink-3)", letterSpacing: 0.8, textTransform: "uppercase", marginBottom: 8 }}>舉報描述</div>
            <div style={{ padding: 14, background: "var(--surface-2)", borderRadius: 10, fontSize: 13.5, lineHeight: 1.7, color: "var(--ink)", whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
              {report.description || <span style={{ color: "var(--ink-4)", fontStyle: "italic" }}>（無描述）</span>}
            </div>
          </div>

          {/* Screenshot */}
          {report.screenshotUrl && (
            <div>
              <div style={{ fontSize: 11.5, fontWeight: 600, color: "var(--ink-3)", letterSpacing: 0.8, textTransform: "uppercase", marginBottom: 8 }}>附上截圖</div>
              <a href={report.screenshotUrl} target="_blank" rel="noopener noreferrer">
                <img src={report.screenshotUrl} alt="截圖" style={{ maxWidth: "100%", borderRadius: 10, border: "1px solid var(--border)", display: "block" }}
                  onError={e => { e.target.style.display = "none"; }}/>
              </a>
            </div>
          )}

          {/* Actions */}
          <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", paddingTop: 4, borderTop: "1px solid var(--border)" }}>
            {report.status === "pending" ? (
              <>
                <Button variant="secondary" onClick={() => onAction(report, "dismissed")}>駁回檢舉</Button>
                <Button variant="primary" onClick={() => onAction(report, "actioned")} style={{ background: "var(--danger)" }}>確認處置</Button>
              </>
            ) : (
              <Button variant="secondary" onClick={() => onAction(report, "pending")}>重開為待處理</Button>
            )}
          </div>
        </div>
      </div>
    </div>
  );
};

const ReportsTab = ({ reports, loading, onRefresh }) => {
  const [filter, setFilter] = useState("pending");
  const [detail, setDetail] = useState(null);
  const toast = useToast();

  const filtered = reports.filter(r => filter === "all" ? true : r.status === filter);

  const handleAction = async (report, action) => {
    const res = await fetch(`/api/admin/reports/${report.id}`, {
      method: "PATCH",
      credentials: "include",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ status: action }),
    });
    if (res.ok) {
      toast({ title: "已更新狀態", message: REPORT_STATUS_MAP[action]?.label ?? action, icon: "check" });
      setDetail(null);
      onRefresh();
    } else {
      toast({ title: "更新失敗", message: "請稍後再試", icon: "alert" });
    }
  };

  return (
    <div>
      {detail && <ReportDetailModal report={detail} onClose={() => setDetail(null)} onAction={handleAction}/>}

      <div style={{ display: "flex", gap: 8, marginBottom: 14, alignItems: "center" }}>
        <div style={{ display: "flex", gap: 6 }}>
          {[
            { value: "pending", label: "待處理" },
            { value: "actioned", label: "已處置" },
            { value: "dismissed", label: "已駁回" },
            { value: "all", label: "全部" },
          ].map(f => (
            <button key={f.value} onClick={() => setFilter(f.value)} style={{
              padding: "5px 12px", borderRadius: 8, fontSize: 12.5, fontWeight: filter === f.value ? 500 : 400,
              background: filter === f.value ? "var(--ink)" : "var(--surface-2)",
              color: filter === f.value ? "#fff" : "var(--ink-2)",
              border: "1px solid " + (filter === f.value ? "var(--ink)" : "var(--border)"),
            }}>
              {f.label}
              {f.value === "pending" && reports.filter(r => r.status === "pending").length > 0
                ? ` (${reports.filter(r => r.status === "pending").length})` : ""}
            </button>
          ))}
        </div>
        <div style={{ flex: 1 }}/>
        <Button variant="secondary" icon="refresh" onClick={onRefresh} size="sm">重新整理</Button>
      </div>

      {loading && (
        <Card padding={50} style={{ textAlign: "center" }}>
          <div style={{ fontSize: 13, color: "var(--ink-3)" }}>載入中⋯</div>
        </Card>
      )}
      {!loading && filtered.length === 0 && (
        <Card padding={50} style={{ textAlign: "center" }}>
          <Icon name="shield" size={30} style={{ color: "var(--success)", marginBottom: 10 }}/>
          <div style={{ fontSize: 13.5, color: "var(--ink-2)", fontWeight: 500 }}>
            {filter === "pending" ? "目前沒有待處理的檢舉" : "沒有符合條件的檢舉"}
          </div>
        </Card>
      )}
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        {filtered.map(r => {
          const statusCfg = REPORT_STATUS_MAP[r.status] ?? REPORT_STATUS_MAP.pending;
          const barColor = statusCfg.tone === "warn" ? "var(--warn)" : statusCfg.tone === "success" ? "var(--success)" : "var(--border-strong)";
          return (
            <Card key={r.id} padding={0} style={{ cursor: "pointer" }} onClick={() => setDetail(r)}>
              <div style={{ display: "grid", gridTemplateColumns: "4px 1fr", alignItems: "stretch" }}>
                <div style={{ background: barColor, borderRadius: "10px 0 0 10px" }}/>
                <div style={{ padding: "14px 18px", display: "flex", alignItems: "center", gap: 14 }}>
                  <div style={{ minWidth: 110 }}>
                    <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginBottom: 2 }}>檢舉人</div>
                    <div style={{ fontSize: 13, fontWeight: 500 }}>{r.reporter.name}</div>
                  </div>
                  <Icon name="arrow-right" size={13} style={{ color: "var(--ink-4)", flexShrink: 0 }}/>
                  <div style={{ minWidth: 110 }}>
                    <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginBottom: 2 }}>被檢舉人</div>
                    <div style={{ fontSize: 13, fontWeight: 500, color: "var(--danger)" }}>{r.reported.name}</div>
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginBottom: 2 }}>類別 · 描述</div>
                    <div style={{ fontSize: 13, display: "flex", gap: 6, alignItems: "center" }}>
                      <span style={{ fontWeight: 500 }}>{reasonLabel(r.reason)}</span>
                      {r.description && (
                        <span style={{ color: "var(--ink-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 200 }}>
                          · {r.description}
                        </span>
                      )}
                    </div>
                  </div>
                  {(TARGET_TYPE_MAP[r.targetType] ?? TARGET_TYPE_MAP.conversation) && (
                    <Badge tone={(TARGET_TYPE_MAP[r.targetType] ?? TARGET_TYPE_MAP.conversation).tone} size="sm">
                      {(TARGET_TYPE_MAP[r.targetType] ?? TARGET_TYPE_MAP.conversation).label}
                    </Badge>
                  )}
                  {r.portfolioItemUrl && (
                    <img src={r.portfolioItemUrl} alt="" style={{ width: 32, height: 32, borderRadius: 6, objectFit: "cover", flexShrink: 0, border: "1px solid var(--border)" }}
                      onError={e => { e.target.style.display = "none"; }}/>
                  )}
                  {r.screenshotUrl && (
                    <Icon name="image" size={14} style={{ color: "var(--ink-3)", flexShrink: 0 }} title="附有截圖"/>
                  )}
                  <div style={{ fontSize: 11.5, color: "var(--ink-3)", flexShrink: 0, minWidth: 80, textAlign: "right" }}>
                    {fmtDate(r.createdAt)}
                  </div>
                  <Badge tone={statusCfg.tone} size="sm">{statusCfg.label}</Badge>
                </div>
              </div>
            </Card>
          );
        })}
      </div>
    </div>
  );
};

window.DisputesPage = DisputesPage;
