import { useCallback, useRef, useEffect } from 'react' import { useAppStore } from '@/store' import { convolutionSteps, convolveImage } from '@/lib/convolution/engine' import type { PaddingMode, ConvolutionStep } from '@/types' export function useConvolution() { const { originalImageData, selectedKernel, customKernelMatrix, customKernelNormalize, settings, convolution, startConvolution, pauseConvolution, resumeConvolution, stopConvolution, updateConvolutionStep, updateConvolutionProgress, completeConvolution, setConvolutionError, resetConvolution, } = useAppStore() const animationRef = useRef(null) const generatorRef = useRef | null>(null) const isPausedRef = useRef(false) const stepModeRef = useRef(false) const kernel = selectedKernel?.matrix ?? customKernelMatrix const normalize = selectedKernel?.normalize ?? customKernelNormalize const padding = settings.padding const runConvolution = useCallback(async () => { if (!originalImageData || !kernel) return try { startConvolution() const extendedImage = { data: originalImageData.data, width: originalImageData.width, height: originalImageData.height, channels: 4, } const gen = convolutionSteps(extendedImage, kernel, padding, normalize, (progress) => { const totalPixels = originalImageData.width * originalImageData.height * 4 const processedPixels = Math.round(progress * totalPixels) const elapsedTime = Date.now() - convolution.startTime updateConvolutionProgress(progress, processedPixels, elapsedTime) }) generatorRef.current = gen const processBatch = () => { if (isPausedRef.current || stepModeRef.current) { animationRef.current = requestAnimationFrame(processBatch) return } const batchSize = Math.max(1, Math.floor(1000 * settings.animationSpeed)) let lastStep: ConvolutionStep | null = null for (let i = 0; i < batchSize; i++) { const result = gen.next() if (result.done) { if (lastStep && lastStep.x === -1) { // Final step with output image const outputImageData = new ImageData( new Uint8ClampedArray(originalImageData.data.length), originalImageData.width, originalImageData.height ) // We need to reconstruct from the generator's final output // For now, use the direct convolution const output = convolveImage(originalImageData, kernel, padding, normalize) completeConvolution(output) } return } lastStep = result.value updateConvolutionStep(result.value) } animationRef.current = requestAnimationFrame(processBatch) } animationRef.current = requestAnimationFrame(processBatch) } catch (error) { setConvolutionError(error instanceof Error ? error.message : 'Convolution failed') } }, [ originalImageData, kernel, normalize, padding, settings.animationSpeed, convolution.startTime, startConvolution, updateConvolutionStep, updateConvolutionProgress, completeConvolution, setConvolutionError, ]) const pause = useCallback(() => { isPausedRef.current = true pauseConvolution() }, [pauseConvolution]) const resume = useCallback(() => { isPausedRef.current = false resumeConvolution() }, [resumeConvolution]) const stop = useCallback(() => { if (animationRef.current) { cancelAnimationFrame(animationRef.current) animationRef.current = null } isPausedRef.current = false stepModeRef.current = false generatorRef.current = null stopConvolution() }, [stopConvolution]) const step = useCallback(() => { if (!generatorRef.current) return stepModeRef.current = true const result = generatorRef.current.next() if (!result.done) { updateConvolutionStep(result.value) } else if (result.value && result.value.x === -1) { const output = convolveImage(originalImageData!, kernel, padding, normalize) completeConvolution(output) } }, [originalImageData, kernel, padding, normalize, updateConvolutionStep, completeConvolution]) const reset = useCallback(() => { stop() resetConvolution() }, [stop, resetConvolution]) const toggleStepMode = useCallback((enabled: boolean) => { stepModeRef.current = enabled }, []) useEffect(() => { return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current) } } }, []) return { convolution, runConvolution, pause, resume, stop, step, reset, toggleStepMode, isRunning: convolution.status === 'running', isPaused: convolution.status === 'paused', isCompleted: convolution.status === 'completed', canRun: !!originalImageData && !!kernel && convolution.status !== 'running', } }