import { supabase } from '@/lib/supabaseClient'; import { handleError, handleNonCriticalError } from '@/lib/errorHandler'; import { updateSubmissionItem, type SubmissionItemWithDeps, type DependencyConflict } from './submissionItemsService'; export interface ResolutionResult { success: boolean; updatedSelections?: Set; error?: string; } /** * Main conflict resolution processor */ export async function resolveConflicts( conflicts: DependencyConflict[], resolutions: Record, items: SubmissionItemWithDeps[], userId: string ): Promise { try { const updatedSelections = new Set(); for (const conflict of conflicts) { const resolution = resolutions[conflict.itemId]; if (!resolution) { return { success: false, error: `No resolution selected for ${conflict.itemId}`, }; } const suggestion = conflict.suggestions.find(s => s.action === resolution); if (!suggestion) { return { success: false, error: `Invalid resolution action: ${resolution}`, }; } // Process each resolution action switch (suggestion.action) { case 'link_existing': if (!suggestion.entityId) { return { success: false, error: 'Entity ID required for link_existing action', }; } await linkToExistingEntity(conflict.itemId, suggestion.entityId); updatedSelections.add(conflict.itemId); break; case 'create_parent': const item = items.find(i => i.id === conflict.itemId); if (item?.depends_on) { updatedSelections.add(item.depends_on); updatedSelections.add(conflict.itemId); } break; case 'cascade_reject': await cascadeRejectDependents(conflict.itemId, items); break; case 'escalate': const submissionId = items[0]?.submission_id; if (submissionId) { await escalateForAdminReview(submissionId, `Conflict resolution needed: ${conflict.message}`, userId); } return { success: true, updatedSelections: new Set(), }; default: return { success: false, error: `Unknown action: ${suggestion.action}`, }; } } return { success: true, updatedSelections, }; } catch (error: unknown) { handleError(error, { action: 'Resolve conflicts', metadata: { conflictCount: conflicts.length }, }); return { success: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } /** * Link submission item to existing database entity */ async function linkToExistingEntity(itemId: string, entityId: string): Promise { await updateSubmissionItem(itemId, { approved_entity_id: entityId, status: 'approved' as const, }); } /** * Cascade reject all dependent items */ async function cascadeRejectDependents( itemId: string, items: SubmissionItemWithDeps[] ): Promise { const item = items.find(i => i.id === itemId); if (!item?.dependents) return; const toReject: string[] = []; function collectDependents(current: SubmissionItemWithDeps) { if (current.dependents) { for (const dep of current.dependents) { toReject.push(dep.id); collectDependents(dep); } } } collectDependents(item); // Reject all collected dependents for (const depId of toReject) { await updateSubmissionItem(depId, { status: 'rejected' as const, rejection_reason: 'Parent dependency was rejected', }); } } /** * Escalate submission for admin review */ async function escalateForAdminReview( submissionId: string, reason: string, userId: string ): Promise { const { error } = await supabase .from('content_submissions') .update({ status: 'pending' as const, escalation_reason: reason, escalated_by: userId, updated_at: new Date().toISOString(), }) .eq('id', submissionId); if (error) { throw new Error(`Failed to escalate submission: ${error.message}`); } } /** * Find existing entities that match submission data */ export async function findMatchingEntities( itemType: string, itemData: any ): Promise> { const tableName = getTableNameForItemType(itemType); if (!tableName) return []; try { // Query based on table type if (tableName === 'companies') { const { data, error } = await supabase .from('companies') .select('id, name') .ilike('name', `%${itemData.name}%`) .limit(5); if (error) throw error; return (data || []).map(entity => ({ id: entity.id, name: entity.name, similarity: calculateSimilarity(itemData.name, entity.name), })).sort((a, b) => b.similarity - a.similarity); } else if (tableName === 'parks') { const { data, error } = await supabase .from('parks') .select('id, name') .ilike('name', `%${itemData.name}%`) .limit(5); if (error) throw error; return (data || []).map(entity => ({ id: entity.id, name: entity.name, similarity: calculateSimilarity(itemData.name, entity.name), })).sort((a, b) => b.similarity - a.similarity); } else if (tableName === 'rides') { const { data, error } = await supabase .from('rides') .select('id, name') .ilike('name', `%${itemData.name}%`) .limit(5); if (error) throw error; return (data || []).map(entity => ({ id: entity.id, name: entity.name, similarity: calculateSimilarity(itemData.name, entity.name), })).sort((a, b) => b.similarity - a.similarity); } else if (tableName === 'ride_models') { const { data, error } = await supabase .from('ride_models') .select('id, name') .ilike('name', `%${itemData.name}%`) .limit(5); if (error) throw error; return (data || []).map(entity => ({ id: entity.id, name: entity.name, similarity: calculateSimilarity(itemData.name, entity.name), })).sort((a, b) => b.similarity - a.similarity); } return []; } catch (error: unknown) { handleNonCriticalError(error, { action: 'Find matching entities', metadata: { itemType }, }); return []; } } /** * Calculate string similarity (simple implementation) */ function calculateSimilarity(str1: string, str2: string): number { const s1 = str1.toLowerCase(); const s2 = str2.toLowerCase(); if (s1 === s2) return 1.0; if (s1.includes(s2) || s2.includes(s1)) return 0.8; // Levenshtein distance approximation const maxLen = Math.max(s1.length, s2.length); const distance = levenshteinDistance(s1, s2); return 1 - (distance / maxLen); } function levenshteinDistance(str1: string, str2: string): number { const matrix: number[][] = []; for (let i = 0; i <= str2.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= str1.length; j++) { matrix[0][j] = j; } for (let i = 1; i <= str2.length; i++) { for (let j = 1; j <= str1.length; j++) { if (str2.charAt(i - 1) === str1.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min( matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1 ); } } } return matrix[str2.length][str1.length]; } function getTableNameForItemType(itemType: string): string | null { const typeMap: Record = { park: 'parks', ride: 'rides', manufacturer: 'companies', operator: 'companies', designer: 'companies', property_owner: 'companies', ride_model: 'ride_models', }; return typeMap[itemType] || null; }