'use client'; import React, { createContext, useContext, useState, useEffect, useRef } from 'react'; import { RecordEvent, RecordingSession } from '@/types/record-mode'; interface RecordModeContextType { isActive: boolean; setIsActive: (active: boolean) => void; events: RecordEvent[]; addEvent: (event: Omit) => void; updateEvent: (id: string, event: Partial) => void; removeEvent: (id: string) => void; clearEvents: () => void; isPlaying: boolean; playEvents: () => void; stopPlayback: () => void; cursorPosition: { x: number; y: number }; zoomLevel: number; isBlurry: boolean; currentSession: RecordingSession | null; saveSession: (name: string) => void; isFeedbackActive: boolean; setIsFeedbackActive: (active: boolean) => void; } const RecordModeContext = createContext(null); export function useRecordMode(): RecordModeContextType { const context = useContext(RecordModeContext); if (!context) { // Return a fail-safe fallback for SSR/Static generation where provider might be missing return { isActive: false, setIsActive: () => { }, events: [], addEvent: () => { }, updateEvent: () => { }, removeEvent: () => { }, clearEvents: () => { }, isPlaying: false, playEvents: () => { }, stopPlayback: () => { }, cursorPosition: { x: 0, y: 0 }, zoomLevel: 1, isBlurry: false, currentSession: null, isFeedbackActive: false, setIsFeedbackActive: () => { }, saveSession: () => { }, }; } return context; } export function RecordModeProvider({ children }: { children: React.ReactNode }) { const [isActive, setIsActiveState] = useState(false); const [events, setEvents] = useState([]); const [isPlaying, setIsPlaying] = useState(false); const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 }); const [zoomLevel, setZoomLevel] = useState(1); const [isBlurry, setIsBlurry] = useState(false); const [isFeedbackActive, setIsFeedbackActiveState] = useState(false); // Synchronous mutually exclusive setters const setIsActive = (active: boolean) => { setIsActiveState(active); if (active) setIsFeedbackActiveState(false); }; const setIsFeedbackActive = (active: boolean) => { setIsFeedbackActiveState(active); if (active) setIsActiveState(false); }; const playbackRequestRef = useRef(0); const isPlayingRef = useRef(false); // Load draft from localStorage on mount useEffect(() => { const savedEvents = localStorage.getItem('klz-record-events'); const savedActive = localStorage.getItem('klz-record-active'); if (savedEvents) setEvents(JSON.parse(savedEvents)); if (savedActive) setIsActive(JSON.parse(savedActive)); }, []); // Sync events to localStorage useEffect(() => { localStorage.setItem('klz-record-events', JSON.stringify(events)); }, [events]); // Sync active state to localStorage useEffect(() => { localStorage.setItem('klz-record-active', JSON.stringify(isActive)); if (isActive && isFeedbackActive) { setIsFeedbackActive(false); } }, [isActive, isFeedbackActive]); useEffect(() => { if (isFeedbackActive && isActive) { setIsActive(false); } }, [isFeedbackActive, isActive]); const addEvent = (event: Omit) => { const newEvent: RecordEvent = { ...event, id: Math.random().toString(36).substr(2, 9), timestamp: Date.now(), }; setEvents((prev) => [...prev, newEvent]); }; const updateEvent = (id: string, updatedFields: Partial) => { setEvents((prev) => prev.map((event) => (event.id === id ? { ...event, ...updatedFields } : event)) ); }; const removeEvent = (id: string) => { setEvents((prev) => prev.filter((event) => event.id !== id)); }; const clearEvents = () => { if (confirm('Clear all recorded events?')) { setEvents([]); } }; const currentSession: RecordingSession | null = events.length > 0 ? { id: 'draft', name: 'Draft Session', events, createdAt: new Date().toISOString(), } : null; const saveSession = (name: string) => { // In a real app, this would be an API call console.log('Saving session:', name, events); }; const playEvents = async () => { if (events.length === 0 || isPlayingRef.current) return; setIsPlaying(true); isPlayingRef.current = true; // Sort events by timestamp just in case const sortedEvents = [...events].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); for (const event of sortedEvents) { if (!isPlayingRef.current) break; // 1. Move Cursor if (event.rect) { setCursorPosition({ x: event.rect.x + event.rect.width / 2, y: event.rect.y + event.rect.height / 2, }); } // 2. Handle Action if (event.selector) { const el = document.querySelector(event.selector) as HTMLElement; if (el) { if (event.type === 'scroll') { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else if (event.type === 'click') { // Precise industrial click sequence: mousedown -> mouseup -> click const eventCoords = { clientX: event.rect ? event.rect.x + event.rect.width / 2 : 0, clientY: event.rect ? event.rect.y + event.rect.height / 2 : 0 }; const dispatchMouse = (type: string) => { el.dispatchEvent(new MouseEvent(type, { view: window, bubbles: true, cancelable: true, ...eventCoords })); }; dispatchMouse('mousedown'); dispatchMouse('mouseup'); dispatchMouse('click'); // Fallback for native elements el.click(); } } } // 3. Zoom/Blur if (event.zoom) setZoomLevel(event.zoom); if (event.motionBlur) setIsBlurry(true); // 4. Wait await new Promise((resolve) => setTimeout(resolve, event.duration || 1000)); setIsBlurry(false); } setIsPlaying(false); isPlayingRef.current = false; setZoomLevel(1); }; const stopPlayback = () => { setIsPlaying(false); isPlayingRef.current = false; setZoomLevel(1); setIsBlurry(false); }; return ( {children} ); }