import React, { useState, useRef, useEffect } from 'react'; import { SyncStrategy, RegexReplacement, SyncState } from '../types'; import { ArrowRightCircle, ArrowLeftCircle, GitMerge, ChevronDown, Check, HelpCircle, Plus, Trash2, Save, RotateCcw, Loader2, Zap } from 'lucide-react'; interface StrategyOption { value: SyncStrategy; label: string; description: string; icon: React.ElementType; color: string; } const STRATEGIES: StrategyOption[] = [ { value: SyncStrategy.LOCAL_OVERWRITE, label: 'Local Overwrite', description: 'Local playlist completely overwrites Cloud. (No Diff)', icon: ArrowRightCircle, color: 'text-blue-400' }, { value: SyncStrategy.CLOUD_OVERWRITE, label: 'Cloud Overwrite', description: 'Cloud playlist completely overwrites Local. (No Diff)', icon: ArrowLeftCircle, color: 'text-green-400' }, { value: SyncStrategy.MERGE_LOCAL, label: 'Two-way Merge (Local Priority)', description: 'Merge both. Conflicts resolve to Local version.', icon: GitMerge, color: 'text-blue-300' }, { value: SyncStrategy.MERGE_CLOUD, label: 'Two-way Merge (Cloud Priority)', description: 'Merge both. Conflicts resolve to Cloud version.', icon: GitMerge, color: 'text-green-300' } ]; interface StrategySelectorProps { currentStrategy: SyncStrategy; onSelect: (strategy: SyncStrategy, label: string) => void; savedRegexReplacements: RegexReplacement[]; onSaveRegex: (replacements: RegexReplacement[]) => void; syncState: SyncState; onSync: () => void; } const StrategySelector: React.FC = ({ currentStrategy, onSelect, savedRegexReplacements, onSaveRegex, syncState, onSync }) => { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); // Local state for regex editing const [localReplacements, setLocalReplacements] = useState([]); const [isDirty, setIsDirty] = useState(false); const isSyncing = syncState === SyncState.SYNCING; const isLocked = isSyncing || syncState === SyncState.SUCCESS; // Initialize local state when prop updates (only if not dirty, or initially) useEffect(() => { setLocalReplacements(JSON.parse(JSON.stringify(savedRegexReplacements))); setIsDirty(false); }, [savedRegexReplacements]); // Check dirty state whenever local changes useEffect(() => { const isDifferent = JSON.stringify(localReplacements) !== JSON.stringify(savedRegexReplacements); setIsDirty(isDifferent); }, [localReplacements, savedRegexReplacements]); const selectedOption = STRATEGIES.find(s => s.value === currentStrategy) || STRATEGIES[0]; useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const handleSelect = (strategy: StrategyOption) => { if (isLocked) return; onSelect(strategy.value, strategy.label); }; // Regex Handlers const handleAddRegex = () => { const newId = Date.now().toString(); setLocalReplacements(prev => [...prev, { id: newId, pattern: '', replacement: '' }]); }; const handleDeleteRegex = (id: string) => { setLocalReplacements(prev => prev.filter(r => r.id !== id)); }; const handleUpdateRegex = (id: string, field: 'pattern' | 'replacement', value: string) => { setLocalReplacements(prev => prev.map(r => r.id === id ? { ...r, [field]: value } : r )); }; const handleReset = () => { setLocalReplacements(JSON.parse(JSON.stringify(savedRegexReplacements))); }; const handleSave = () => { const validReplacements = localReplacements.filter(r => r.pattern.trim() !== ''); setLocalReplacements(validReplacements); onSaveRegex(validReplacements); }; const handleSyncClick = () => { if (isLocked || isDirty) return; onSync(); }; return (
{/* Trigger Button - Added Ring to create visual 'cutout' over panels */} {/* Dropdown Menu - Persistent Mount for State Preservation */}
{/* Section 1: Sync Strategy */}

Sync Strategy

{STRATEGIES.map((strategy) => (
handleSelect(strategy)} className={`group flex items-center justify-between p-2 rounded-lg cursor-pointer transition-all border ${ currentStrategy === strategy.value ? 'bg-white/10 border-white/10 shadow-sm' : 'hover:bg-white/5 border-transparent' }`} >
{strategy.label}
{strategy.description}
{currentStrategy === strategy.value && ( )}
))}
{/* Section 2: Regex Preprocessing */}

Regex Rules

{localReplacements.length === 0 && ( )}
{localReplacements.length === 0 ? (
No regex replacements configured.
) : ( localReplacements.map((regex) => (
handleUpdateRegex(regex.id, 'pattern', e.target.value)} disabled={isLocked} className={`w-full bg-gray-900/80 border rounded-md px-2.5 py-1.5 text-xs text-gray-200 focus:outline-none focus:ring-1 transition-all placeholder-gray-600 disabled:opacity-60 disabled:cursor-not-allowed ${!regex.pattern && isDirty ? 'border-red-500/30 focus:border-red-500' : 'border-gray-700 focus:border-plex-orange'}`} />
handleUpdateRegex(regex.id, 'replacement', e.target.value)} disabled={isLocked} className="w-full bg-gray-900/80 border border-gray-700 rounded-md px-2.5 py-1.5 text-xs text-gray-200 focus:outline-none focus:border-plex-orange focus:ring-1 focus:ring-plex-orange transition-all placeholder-gray-600 disabled:opacity-60 disabled:cursor-not-allowed" />
)) )}
{/* Actions */}
{localReplacements.length > 0 && (
)}
{/* Section 3: Sync Now Button */}
{isDirty && (

Please save or revert regex rules changes before syncing.

)}
); }; export default StrategySelector;