import { t, type MessageKey } from '@/shared/i18n' import type { ChangedFile, MergeMethod, PullState, ReviewState, ReviewerState, UserDto } from '@/entities/pull-request' import type { DiffFile, DiffFileStatus } from '@/entities/repo' /** UI-only filter: the backend understands open/closed/merged and no filter. */ export type PullFilter = PullState | 'all' | 'draft' const PULL_STATE_KEYS: Record = { open: 'pull.state.open', closed: 'pull.state.closed', merged: 'pull.state.merged', } /** Lazy so badges follow the active locale. */ export const PULL_STATE_LABEL = new Proxy({} as Record, { get: (_target, key: PullState) => t(PULL_STATE_KEYS[key]), }) /** tailwind-ish tone classes shared across badges (mirrors repo-history colors). */ export const TONE = { green: 'text-[#1a7f37] bg-[rgba(35,165,90,0.12)]', red: 'text-[#b42318] bg-[rgba(242,63,67,0.10)]', amber: 'text-[#8a6100] bg-[rgba(240,178,50,0.16)]', violet: 'text-[#6937c3] bg-[rgba(120,82,196,0.12)]', gray: 'text-shell-ink-muted bg-shell-active', } as const const REVIEW_STATE_KEYS: Record = { approved: 'pull.reviewState.approved', changes_requested: 'pull.reviewState.changesRequested', commented: 'pull.reviewState.commented', pending: 'pull.reviewState.pending', dismissed: 'pull.reviewState.dismissed', } export const REVIEW_STATE_LABEL = new Proxy({} as Record, { get: (_target, key: ReviewState) => t(REVIEW_STATE_KEYS[key]), }) export const REVIEW_STATE_TONE: Record = { approved: 'green', changes_requested: 'red', commented: 'gray', pending: 'amber', dismissed: 'gray', } const REVIEWER_STATE_KEYS: Record = { requested: 'pull.reviewerState.requested', approved: 'pull.reviewerState.approved', changes_requested: 'pull.reviewerState.changesRequested', commented: 'pull.reviewerState.commented', dismissed: 'pull.reviewerState.dismissed', } export const REVIEWER_STATE_LABEL = new Proxy({} as Record, { get: (_target, key: ReviewerState) => t(REVIEWER_STATE_KEYS[key]), }) const MERGE_METHOD_KEYS: Record = { merge: 'pull.mergeMethod.merge', squash: 'pull.mergeMethod.squash', rebase: 'pull.mergeMethod.rebase', } export const MERGE_METHOD_LABEL = new Proxy({} as Record, { get: (_target, key: MergeMethod) => t(MERGE_METHOD_KEYS[key]), }) /** The backend formats status with `{:?}` (Debug names) — normalize loosely. */ export function normalizeFileStatus(raw: string): DiffFileStatus { const s = raw.toLowerCase() if (s.startsWith('ad') || s.startsWith('new') || s.startsWith('creat') || s === 'a') return 'added' if (s.startsWith('del') || s.startsWith('rem') || s === 'd') return 'deleted' if (s.startsWith('ren') || s.startsWith('mov') || s === 'r') return 'renamed' if (s.startsWith('cop') || s === 'c') return 'copied' if (s.startsWith('bin')) return 'binary' return 'modified' } const EVENT_KEYS: Record = { created: 'pull.event.created', updated: 'pull.event.updated', title_changed: 'pull.event.titleChanged', body_edited: 'pull.event.bodyEdited', base_changed: 'pull.event.baseChanged', marked_ready: 'pull.event.markedReady', marked_draft: 'pull.event.markedDraft', closed: 'pull.event.closed', reopened: 'pull.event.reopened', merged: 'pull.event.merged', review_requested: 'pull.event.reviewRequested', review_request_removed: 'pull.event.reviewRequestRemoved', review_submitted: 'pull.event.reviewSubmitted', approved: 'pull.event.approved', changes_requested: 'pull.event.changesRequested', comment: 'pull.event.comment', labeled: 'pull.event.labeled', unlabeled: 'pull.event.unlabeled', assigned: 'pull.event.assigned', unassigned: 'pull.event.unassigned', locked: 'pull.event.locked', unlocked: 'pull.event.unlocked', force_push: 'pull.event.forcePush', branch_updated: 'pull.event.branchUpdated', head_ref_updated: 'pull.event.headRefUpdated', auto_merge_enabled: 'pull.event.autoMergeEnabled', auto_merge_disabled: 'pull.event.autoMergeDisabled', } /** Unknown backend event types keep rendering as their raw snake_case name. */ export const eventLabel = (eventType: string): string => EVENT_KEYS[eventType] ? t(EVENT_KEYS[eventType]) : eventType.replaceAll('_', ' ') export const userName = (user?: UserDto | null): string => user?.display_name || user?.username || t('common.state.unknownUser') export interface InlineAnchor { path: string line: number side: 'left' | 'right' } export const anchorKey = (path: string, line: number, side: 'left' | 'right') => `${path}#${side}${line}` /** A pinned inline comment as rendered under its diff line. */ export interface AnchoredComment { id: string body: string } export function changedFileToDiff(file: ChangedFile): DiffFile { const status = file.binary ? 'binary' : normalizeFileStatus(file.path.status) return { path: file.path.new_path, oldPath: file.path.old_path && file.path.old_path !== file.path.new_path ? file.path.old_path : null, status, raw: file.patch ?? '', additions: file.lines_added, deletions: file.lines_removed, } }