import { allow, deny } from "@/lib/decision" import { isApiError, toApiError, type ApiDecision } from "@/lib/errors" import type { Dispute } from "@/lib/types" import { httpJson } from "./http" export type DisputeTimelineItem = { id: string content: string createdAt: string } export type DisputeRecord = Dispute & { respondentReason?: string respondentEvidence: string[] appealReason?: string appealedAt?: string timeline: DisputeTimelineItem[] } export type ListDisputesOptions = { offset?: number limit?: number } type Paginated = { items: T[] meta?: { total: number offset: number limit: number } } function withOffsetLimit(path: string, options?: ListDisputesOptions): string { const offset = options?.offset ?? 0 const limit = options?.limit ?? 1000 const searchParams = new URLSearchParams({ offset: String(offset), limit: String(limit), }) return `${path}?${searchParams.toString()}` } function unwrapItems(value: unknown): T[] { if (Array.isArray(value)) return value as T[] if (typeof value !== "object" || value === null) throw new Error("Invalid response") if ("items" in value) { const envelope = value as { items?: unknown } if (Array.isArray(envelope.items)) return envelope.items as T[] } throw new Error("Invalid response") } function unwrapDispute(value: unknown): unknown { if (typeof value !== "object" || value === null) return value if ("dispute" in value) { const envelope = value as { dispute?: unknown } return envelope.dispute } return value } function deriveMinimalTimeline(dispute: { id: string status: string createdAt: string timeline?: DisputeTimelineItem[] }): DisputeTimelineItem[] { const existing = dispute.timeline if (existing?.length) return existing const steps = [ { status: "open", content: "争议已提交" }, { status: "reviewing", content: "平台审核中" }, { status: "resolved", content: "争议已解决" }, { status: "appealed", content: "已发起申诉" }, ] const currentIndex = steps.findIndex((step) => step.status === dispute.status) const lastIndex = currentIndex >= 0 ? currentIndex : 0 return steps.slice(0, lastIndex + 1).map((step) => ({ id: `${dispute.id}-${step.status}`, content: step.content, createdAt: dispute.createdAt, })) } function normalizeDisputeRecord(value: unknown): DisputeRecord { const dispute = unwrapDispute(value) as DisputeRecord const respondentEvidence = Array.isArray(dispute.respondentEvidence) ? dispute.respondentEvidence : [] const evidence = Array.isArray(dispute.evidence) ? dispute.evidence : [] const timeline = deriveMinimalTimeline({ id: dispute.id, status: dispute.status, createdAt: dispute.createdAt, timeline: dispute.timeline, }) return { ...dispute, evidence, respondentEvidence, timeline, } } function denyFromError(error: unknown): ApiDecision { if (error instanceof Error && error.message === "UNAUTHORIZED") { return deny(401, "请先登录") } const apiError = toApiError(error) return deny(apiError.code, apiError.msg) } export async function listDisputes(options?: ListDisputesOptions): Promise { const res = await httpJson | DisputeRecord[]>( withOffsetLimit("/api/v1/disputes", options), { cache: "no-store" }, ) return unwrapItems(res).map((item) => normalizeDisputeRecord(item)) } export async function getDisputeByOrderId(orderId: string): Promise { try { const res = await httpJson(`/api/v1/orders/${encodeURIComponent(orderId)}/dispute`, { cache: "no-store", }) return normalizeDisputeRecord(res) } catch (error) { const apiError = isApiError(error) ? error : toApiError(error) if (apiError.code === 404) return undefined throw error } } export async function submitDispute(input: { orderId: string reason: string evidence: string[] }): Promise<{ decision: ApiDecision }> { try { await httpJson(`/api/v1/orders/${encodeURIComponent(input.orderId)}/dispute`, { method: "POST", cache: "no-store", json: { reason: input.reason, evidence: input.evidence }, }) return { decision: allow() } } catch (error) { return { decision: denyFromError(error) } } } export async function submitDisputeResponse(input: { disputeId: string reason: string evidence: string[] }): Promise { try { await httpJson(`/api/v1/disputes/${encodeURIComponent(input.disputeId)}/response`, { method: "POST", cache: "no-store", json: { reason: input.reason, evidence: input.evidence }, }) return allow() } catch (error) { return denyFromError(error) } } export async function submitDisputeAppeal(input: { disputeId: string reason: string }): Promise { try { await httpJson(`/api/v1/disputes/${encodeURIComponent(input.disputeId)}/appeal`, { method: "POST", cache: "no-store", json: { reason: input.reason }, }) return allow() } catch (error) { return denyFromError(error) } }