import type { AskUserQuestionsAnswer, Approval, CreateIssueTreeHold, DocumentRevision, FeedbackTargetType, FeedbackTrace, FeedbackVote, Issue, IssueAttachment, IssueComment, IssueDocument, IssueLabel, IssueThreadInteraction, IssueTreeControlPreview, IssueTreeHold, IssueWorkProduct, PreviewIssueTreeControl, ReleaseIssueTreeHold, UpsertIssueDocument, } from "@paperclipai/shared"; import { api } from "./client"; export type IssueUpdateResponse = Issue & { comment?: IssueComment | null; }; export const issuesApi = { list: ( companyId: string, filters?: { status?: string; projectId?: string; parentId?: string; assigneeAgentId?: string; participantAgentId?: string; assigneeUserId?: string; touchedByUserId?: string; inboxArchivedByUserId?: string; unreadForUserId?: string; labelId?: string; workspaceId?: string; executionWorkspaceId?: string; originKind?: string; originId?: string; includeRoutineExecutions?: boolean; q?: string; limit?: number; }, ) => { const params = new URLSearchParams(); if (filters?.status) params.set("status", filters.status); if (filters?.projectId) params.set("projectId", filters.projectId); if (filters?.parentId) params.set("parentId", filters.parentId); if (filters?.assigneeAgentId) params.set("assigneeAgentId", filters.assigneeAgentId); if (filters?.participantAgentId) params.set("participantAgentId", filters.participantAgentId); if (filters?.assigneeUserId) params.set("assigneeUserId", filters.assigneeUserId); if (filters?.touchedByUserId) params.set("touchedByUserId", filters.touchedByUserId); if (filters?.inboxArchivedByUserId) params.set("inboxArchivedByUserId", filters.inboxArchivedByUserId); if (filters?.unreadForUserId) params.set("unreadForUserId", filters.unreadForUserId); if (filters?.labelId) params.set("labelId", filters.labelId); if (filters?.workspaceId) params.set("workspaceId", filters.workspaceId); if (filters?.executionWorkspaceId) params.set("executionWorkspaceId", filters.executionWorkspaceId); if (filters?.originKind) params.set("originKind", filters.originKind); if (filters?.originId) params.set("originId", filters.originId); if (filters?.includeRoutineExecutions) params.set("includeRoutineExecutions", "true"); if (filters?.q) params.set("q", filters.q); if (filters?.limit) params.set("limit", String(filters.limit)); const qs = params.toString(); return api.get(`/companies/${companyId}/issues${qs ? `?${qs}` : ""}`); }, listLabels: (companyId: string) => api.get(`/companies/${companyId}/labels`), createLabel: (companyId: string, data: { name: string; color: string }) => api.post(`/companies/${companyId}/labels`, data), deleteLabel: (id: string) => api.delete(`/labels/${id}`), get: (id: string) => api.get(`/issues/${id}`), markRead: (id: string) => api.post<{ id: string; lastReadAt: Date }>(`/issues/${id}/read`, {}), markUnread: (id: string) => api.delete<{ id: string; removed: boolean }>(`/issues/${id}/read`), archiveFromInbox: (id: string) => api.post<{ id: string; archivedAt: Date }>(`/issues/${id}/inbox-archive`, {}), unarchiveFromInbox: (id: string) => api.delete<{ id: string; archivedAt: Date } | { ok: true }>(`/issues/${id}/inbox-archive`), create: (companyId: string, data: Record) => api.post(`/companies/${companyId}/issues`, data), update: (id: string, data: Record) => api.patch(`/issues/${id}`, data), previewTreeControl: (id: string, data: PreviewIssueTreeControl) => api.post(`/issues/${id}/tree-control/preview`, data), createTreeHold: (id: string, data: CreateIssueTreeHold) => api.post<{ hold: IssueTreeHold; preview: IssueTreeControlPreview }>(`/issues/${id}/tree-holds`, data), getTreeHold: (id: string, holdId: string) => api.get(`/issues/${id}/tree-holds/${holdId}`), listTreeHolds: ( id: string, filters?: { status?: "active" | "released"; mode?: "pause" | "resume" | "cancel" | "restore"; includeMembers?: boolean; }, ) => { const params = new URLSearchParams(); if (filters?.status) params.set("status", filters.status); if (filters?.mode) params.set("mode", filters.mode); if (filters?.includeMembers) params.set("includeMembers", "true"); const qs = params.toString(); return api.get(`/issues/${id}/tree-holds${qs ? `?${qs}` : ""}`); }, getTreeControlState: (id: string) => api.get<{ activePauseHold: { holdId: string; rootIssueId: string; issueId: string; isRoot: boolean; mode: "pause"; reason: string | null; releasePolicy: { strategy: "manual" | "after_active_runs_finish"; note?: string | null } | null; } | null; }>(`/issues/${id}/tree-control/state`), releaseTreeHold: (id: string, holdId: string, data: ReleaseIssueTreeHold) => api.post(`/issues/${id}/tree-holds/${holdId}/release`, data), remove: (id: string) => api.delete(`/issues/${id}`), checkout: (id: string, agentId: string) => api.post(`/issues/${id}/checkout`, { agentId, expectedStatuses: ["todo", "backlog", "blocked", "in_review"], }), release: (id: string) => api.post(`/issues/${id}/release`, {}), listComments: ( id: string, filters?: { after?: string; order?: "asc" | "desc"; limit?: number; }, ) => { const params = new URLSearchParams(); if (filters?.after) params.set("after", filters.after); if (filters?.order) params.set("order", filters.order); if (filters?.limit) params.set("limit", String(filters.limit)); const qs = params.toString(); return api.get(`/issues/${id}/comments${qs ? `?${qs}` : ""}`); }, listInteractions: (id: string) => api.get(`/issues/${id}/interactions`), createInteraction: (id: string, data: Record) => api.post(`/issues/${id}/interactions`, data), acceptInteraction: ( id: string, interactionId: string, data?: { selectedClientKeys?: string[] }, ) => api.post(`/issues/${id}/interactions/${interactionId}/accept`, data ?? {}), rejectInteraction: (id: string, interactionId: string, reason?: string) => api.post(`/issues/${id}/interactions/${interactionId}/reject`, reason ? { reason } : {}), respondToInteraction: ( id: string, interactionId: string, data: { answers: AskUserQuestionsAnswer[]; summaryMarkdown?: string | null }, ) => api.post(`/issues/${id}/interactions/${interactionId}/respond`, data), getComment: (id: string, commentId: string) => api.get(`/issues/${id}/comments/${commentId}`), listFeedbackVotes: (id: string) => api.get(`/issues/${id}/feedback-votes`), listFeedbackTraces: (id: string, filters?: Record) => { const params = new URLSearchParams(); for (const [key, value] of Object.entries(filters ?? {})) { if (value === undefined) continue; params.set(key, String(value)); } const qs = params.toString(); return api.get(`/issues/${id}/feedback-traces${qs ? `?${qs}` : ""}`); }, upsertFeedbackVote: ( id: string, data: { targetType: FeedbackTargetType; targetId: string; vote: "up" | "down"; reason?: string; allowSharing?: boolean; }, ) => api.post(`/issues/${id}/feedback-votes`, data), addComment: (id: string, body: string, reopen?: boolean, interrupt?: boolean) => api.post( `/issues/${id}/comments`, { body, ...(reopen === undefined ? {} : { reopen }), ...(interrupt === undefined ? {} : { interrupt }), }, ), cancelComment: (id: string, commentId: string) => api.delete(`/issues/${id}/comments/${commentId}`), listDocuments: (id: string, options?: { includeSystem?: boolean }) => api.get( `/issues/${id}/documents${options?.includeSystem ? "?includeSystem=true" : ""}`, ), getDocument: (id: string, key: string) => api.get(`/issues/${id}/documents/${encodeURIComponent(key)}`), upsertDocument: (id: string, key: string, data: UpsertIssueDocument) => api.put(`/issues/${id}/documents/${encodeURIComponent(key)}`, data), listDocumentRevisions: (id: string, key: string) => api.get(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions`), restoreDocumentRevision: (id: string, key: string, revisionId: string) => api.post(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions/${revisionId}/restore`, {}), deleteDocument: (id: string, key: string) => api.delete<{ ok: true }>(`/issues/${id}/documents/${encodeURIComponent(key)}`), listAttachments: (id: string) => api.get(`/issues/${id}/attachments`), uploadAttachment: ( companyId: string, issueId: string, file: File, issueCommentId?: string | null, ) => { const form = new FormData(); form.append("file", file); if (issueCommentId) { form.append("issueCommentId", issueCommentId); } return api.postForm(`/companies/${companyId}/issues/${issueId}/attachments`, form); }, deleteAttachment: (id: string) => api.delete<{ ok: true }>(`/attachments/${id}`), listApprovals: (id: string) => api.get(`/issues/${id}/approvals`), linkApproval: (id: string, approvalId: string) => api.post(`/issues/${id}/approvals`, { approvalId }), unlinkApproval: (id: string, approvalId: string) => api.delete<{ ok: true }>(`/issues/${id}/approvals/${approvalId}`), listWorkProducts: (id: string) => api.get(`/issues/${id}/work-products`), createWorkProduct: (id: string, data: Record) => api.post(`/issues/${id}/work-products`, data), updateWorkProduct: (id: string, data: Record) => api.patch(`/work-products/${id}`, data), deleteWorkProduct: (id: string) => api.delete(`/work-products/${id}`), };