import { reactive } from 'vue' import { useI18n } from 'vue-i18n' import { apiCall } from '@/api' import { invalidatePeople, ensurePeople } from '@/store' interface HistoryCounts { patchUndo: number patchRedo: number bindUndo: number bindRedo: number } interface MergeHistoryItem { mergeId: string sourceUuid: string targetUuid: string sourceName: string targetName: string undone: boolean createdAt: string } interface ActionHistoryItem { type: 'patch' | 'bind' | 'merge' | 'delete' label: string uuid: string timestamp: string canUndo: boolean canRedo: boolean } export function useUndoRedo() { const { t } = useI18n() const counts = reactive>({}) const mergeHistory = reactive([]) const recentActions = reactive([]) async function refreshCounts(uuid: string) { try { const [patchHist, bindHist] = await Promise.all([ apiCall('identity_history', { uuid, page: 1, pageSize: 1 }), apiCall('identity_bind_history', { uuid, page: 1, pageSize: 1 }), ]) if (!counts[uuid]) counts[uuid] = { patchUndo: 0, patchRedo: 0, bindUndo: 0, bindRedo: 0 } counts[uuid].patchUndo = patchHist?.undo_stack_count ?? patchHist?.undoStackCount ?? 0 counts[uuid].patchRedo = patchHist?.redo_stack_count ?? patchHist?.redoStackCount ?? 0 counts[uuid].bindUndo = bindHist?.undo_stack_count ?? bindHist?.undoStackCount ?? 0 counts[uuid].bindRedo = bindHist?.redo_stack_count ?? bindHist?.redoStackCount ?? 0 } catch (e) { console.error('Failed to refresh undo counts:', e) } } async function refreshMergeHistory(uuid?: string) { try { const params: Record = { pageSize: 10 } if (uuid) params.sourceUuid = uuid const data = await apiCall('merge_history', params) const items = data?.history || data?.merges || data || [] mergeHistory.splice(0, mergeHistory.length) for (const m of Array.isArray(items) ? items : []) { mergeHistory.push({ mergeId: m.merge_id ?? m.mergeId ?? '', sourceUuid: m.source_uuid ?? m.sourceUuid ?? '', targetUuid: m.target_uuid ?? m.targetUuid ?? '', sourceName: m.source_name ?? m.sourceName ?? '', targetName: m.target_name ?? m.targetName ?? '', undone: m.is_undone ?? m.isUndone ?? false, createdAt: m.created_at ?? m.createdAt ?? '', }) } } catch (e) { console.error('Failed to refresh merge history:', e) } } async function refreshActionHistory(uuid: string) { try { const [patchHist, bindHist] = await Promise.all([ apiCall('identity_history', { uuid, page: 1, pageSize: 10 }), apiCall('identity_bind_history', { uuid, page: 1, pageSize: 10 }), ]) const actions: ActionHistoryItem[] = [] const patchItems = patchHist?.history || patchHist?.records || [] for (const r of Array.isArray(patchItems) ? patchItems : []) { const op = r.operation ?? r.op ?? '' const label = op === 'delete' ? t('people.action.delete_person') : op === 'update' ? t('people.action.update_person') : op actions.push({ type: op === 'delete' ? 'delete' : 'patch', label, uuid, timestamp: r.created_at ?? r.createdAt ?? r.timestamp ?? '', canUndo: !r.is_undone && !r.isUndone, canRedo: !!(r.is_undone || r.isUndone), }) } const bindItems = bindHist?.history || bindHist?.records || [] for (const r of Array.isArray(bindItems) ? bindItems : []) { const op = r.operation ?? r.op ?? '' const label = op === 'bind' ? t('people.action.bind_face') : op === 'unbind' ? t('people.action.unbind_face') : op === 'bind_trace' ? t('people.action.bind_trace') : op actions.push({ type: 'bind', label, uuid, timestamp: r.created_at ?? r.createdAt ?? r.timestamp ?? '', canUndo: !(r.is_undone ?? r.isUndone), canRedo: !!(r.is_undone ?? r.isUndone), }) } actions.sort((a, b) => b.timestamp.localeCompare(a.timestamp)) recentActions.splice(0, recentActions.length, ...actions.slice(0, 20)) } catch (e) { console.error('Failed to refresh action history:', e) } } async function undo(uuid: string, steps = 1) { try { await apiCall('identity_undo', { uuid, steps }) await refreshCounts(uuid) await refreshActionHistory(uuid) invalidatePeople() await ensurePeople() } catch (e) { console.error('Undo failed:', e) throw e } } async function redo(uuid: string, steps = 1) { try { await apiCall('identity_redo', { uuid, steps }) await refreshCounts(uuid) await refreshActionHistory(uuid) invalidatePeople() await ensurePeople() } catch (e) { console.error('Redo failed:', e) throw e } } async function bindUndo(uuid: string, steps = 1) { try { await apiCall('identity_bind_undo', { uuid, steps }) await refreshCounts(uuid) await refreshActionHistory(uuid) invalidatePeople() await ensurePeople() } catch (e) { console.error('Bind undo failed:', e) throw e } } async function bindRedo(uuid: string, steps = 1) { try { await apiCall('identity_bind_redo', { uuid, steps }) await refreshCounts(uuid) await refreshActionHistory(uuid) invalidatePeople() await ensurePeople() } catch (e) { console.error('Bind redo failed:', e) throw e } } async function mergeUndo(mergeId: string) { try { await apiCall('merge_undo', { mergeId }) invalidatePeople() await ensurePeople() } catch (e) { console.error('Merge undo failed:', e) throw e } } async function mergeRedo(mergeId: string) { try { await apiCall('merge_redo', { mergeId }) invalidatePeople() await ensurePeople() } catch (e) { console.error('Merge redo failed:', e) throw e } } function canUndo(uuid: string): boolean { const c = counts[uuid] if (!c) return false return c.patchUndo > 0 || c.bindUndo > 0 } function canRedo(uuid: string): boolean { const c = counts[uuid] if (!c) return false return c.patchRedo > 0 || c.bindRedo > 0 } async function undoAction(uuid: string, type: 'patch' | 'bind') { try { if (type === 'bind') { await apiCall('identity_bind_undo', { uuid, steps: 1 }) } else { await apiCall('identity_undo', { uuid, steps: 1 }) } await refreshCounts(uuid) await refreshActionHistory(uuid) invalidatePeople() await ensurePeople() } catch (e) { console.error('Undo action failed:', e) throw e } } async function redoAction(uuid: string, type: 'patch' | 'bind') { try { if (type === 'bind') { await apiCall('identity_bind_redo', { uuid, steps: 1 }) } else { await apiCall('identity_redo', { uuid, steps: 1 }) } await refreshCounts(uuid) await refreshActionHistory(uuid) invalidatePeople() await ensurePeople() } catch (e) { console.error('Redo action failed:', e) throw e } } return { counts, mergeHistory, recentActions, refreshCounts, refreshMergeHistory, refreshActionHistory, undo, redo, bindUndo, bindRedo, mergeUndo, mergeRedo, canUndo, canRedo, undoAction, redoAction, } }