'use client' import { useState, useCallback, useRef, useEffect } from 'react' import { useAppStore } from '@/store' import { PREDEFINED_KERNELS, KERNEL_CATEGORIES, getKernelsByCategory, normalizeKernel } from '@/lib/kernels' import type { KernelCategory } from '@/lib/kernels' import { Button, Card, CardContent, Badge, Input } from '@/components/ui' import { cn } from '@/utils/helpers' import { Search, SlidersHorizontal, Plus, Copy, Check, RotateCcw, Sparkles } from 'lucide-react' // Preset matrices for quick start const CUSTOM_PRESETS = { identity: { name: 'Identity', matrix: [[0, 0, 0], [0, 1, 0], [0, 0, 0]], size: 3, }, edgeDetect: { name: 'Edge Detect', matrix: [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], size: 3, }, blur: { name: 'Box Blur', matrix: [[1, 1, 1], [1, 1, 1], [1, 1, 1]], size: 3, }, sharpen: { name: 'Sharpen', matrix: [[0, -1, 0], [-1, 5, -1], [0, -1, 0]], size: 3, }, emboss: { name: 'Emboss', matrix: [[-2, -1, 0], [-1, 1, 1], [0, 1, 2]], size: 3, }, outline: { name: 'Outline', matrix: [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], size: 3, }, } export function KernelSelector() { const { selectedKernel, setSelectedKernel, customKernelMatrix, customKernelSize, customKernelNormalize, setCustomKernel, setCustomKernelSize, setCustomKernelNormalize, updateCustomKernelCell } = useAppStore() const [activeCategory, setActiveCategory] = useState('blur') const [searchQuery, setSearchQuery] = useState('') const [showCustom, setShowCustom] = useState(false) const [copiedPreset, setCopiedPreset] = useState(null) // Handle keyboard navigation in matrix inputs const matrixInputRefs = useRef([]) useEffect(() => { matrixInputRefs.current = Array.from({ length: customKernelSize }, () => Array(customKernelSize).fill(null)) }, [customKernelSize]) const handleKeyDown = useCallback( (e: React.KeyboardEvent, row: number, col: number) => { const refs = matrixInputRefs.current let newRow = row let newCol = col switch (e.key) { case 'ArrowUp': e.preventDefault() newRow = Math.max(0, row - 1) break case 'ArrowDown': e.preventDefault() newRow = Math.min(customKernelSize - 1, row + 1) break case 'ArrowLeft': e.preventDefault() newCol = Math.max(0, col - 1) break case 'ArrowRight': e.preventDefault() newCol = Math.min(customKernelSize - 1, col + 1) break case 'Tab': e.preventDefault() if (e.shiftKey) { newCol = col === 0 ? (row === 0 ? customKernelSize - 1 : customKernelSize - 1) : col - 1 newRow = col === 0 ? Math.max(0, row - 1) : row } else { newCol = col === customKernelSize - 1 ? 0 : col + 1 newRow = col === customKernelSize - 1 ? Math.min(customKernelSize - 1, row + 1) : row } break case 'Enter': e.preventDefault() newCol = col === customKernelSize - 1 ? 0 : col + 1 newRow = col === customKernelSize - 1 ? Math.min(customKernelSize - 1, row + 1) : row break } if (refs[newRow]?.[newCol]) { refs[newRow][newCol]?.focus() } }, [customKernelSize] ) const handleKernelSelect = (kernel: typeof PREDEFINED_KERNELS[0]) => { setSelectedKernel(kernel) setShowCustom(false) } const handleCustomKernelSubmit = () => { const kernel = { id: `custom-${Date.now()}`, name: `Custom ${customKernelSize}×${customKernelSize}`, category: 'custom' as KernelCategory, matrix: customKernelMatrix, size: customKernelSize, normalize: customKernelNormalize, description: 'User-defined custom kernel', } setSelectedKernel(kernel) } const applyPreset = useCallback((presetKey: keyof typeof CUSTOM_PRESETS) => { const preset = CUSTOM_PRESETS[presetKey] if (!preset) return // Resize matrix if needed if (preset.size !== customKernelSize) { setCustomKernelSize(preset.size) // Wait for state update then apply matrix setTimeout(() => { const matrix = preset.matrix.map(row => [...row]) setCustomKernel(matrix, preset.size, customKernelNormalize) }, 0) } else { const matrix = preset.matrix.map(row => [...row]) setCustomKernel(matrix, preset.size, customKernelNormalize) } setCopiedPreset(presetKey) setTimeout(() => setCopiedPreset(null), 2000) }, [customKernelSize, customKernelNormalize, setCustomKernel, setCustomKernelSize]) const copyMatrix = useCallback(() => { const matrixStr = customKernelMatrix.map(row => row.join(', ')).join('\n') navigator.clipboard.writeText(matrixStr) // Show toast or visual feedback }, [customKernelMatrix]) const pasteMatrix = useCallback(async () => { try { const text = await navigator.clipboard.readText() const rows = text.trim().split('\n').map(line => line.trim().split(/[,\s]+/).map(Number)) if (rows.length > 0 && rows.every(row => row.length === rows[0].length)) { const size = rows.length if (size >= 3 && size <= 9 && size % 2 === 1) { setCustomKernelSize(size) setTimeout(() => setCustomKernel(rows, size, customKernelNormalize), 0) } } } catch { // Clipboard access denied or invalid format } }, [customKernelNormalize, setCustomKernel, setCustomKernelSize]) const resetMatrix = useCallback(() => { const matrix = Array(customKernelSize).fill(0).map(() => Array(customKernelSize).fill(0)) const center = Math.floor(customKernelSize / 2) matrix[center][center] = 1 setCustomKernel(matrix, customKernelSize, customKernelNormalize) }, [customKernelSize, customKernelNormalize, setCustomKernel]) const validateMatrix = useCallback(() => { const sum = customKernelMatrix.flat().reduce((a, b) => a + b, 0) const hasNonZero = customKernelMatrix.flat().some(v => v !== 0) return { sum, hasNonZero, isNormalized: customKernelNormalize } }, [customKernelMatrix, customKernelNormalize]) const filteredKernels = PREDEFINED_KERNELS.filter( (k) => k.name.toLowerCase().includes(searchQuery.toLowerCase()) || k.category.toLowerCase().includes(searchQuery.toLowerCase()) || k.description.toLowerCase().includes(searchQuery.toLowerCase()) ) const kernelsByCategory = KERNEL_CATEGORIES.map((cat) => ({ ...cat, kernels: getKernelsByCategory(cat.value).filter((k) => k.name.toLowerCase().includes(searchQuery.toLowerCase()) ), })) return (

Kernel Selector

{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-800 dark:text-white" />
{/* Category Tabs */}
{KERNEL_CATEGORIES.map((cat) => ( ))}
{/* Custom Kernel Editor */} {showCustom && (

Custom Kernel Editor

setCustomKernelSize(Math.max(3, Math.min(9, parseInt(e.target.value) || 3)))} min={3} max={9} step={2} className="w-24" />
{validateMatrix().hasNonZero ? ( Valid ) : ( All zeros )} {customKernelNormalize && ( Σ={validateMatrix().sum.toFixed(2)} )}
{/* Preset Buttons */}
Quick Presets:
{Object.entries(CUSTOM_PRESETS).map(([key, preset]) => ( ))}
{/* Matrix Input Grid */}
{Array.from({ length: customKernelSize }, (_, row) => Array.from({ length: customKernelSize }, (_, col) => ( { if (el && matrixInputRefs.current[row]) matrixInputRefs.current[row][col] = el }} type="number" step="0.1" value={customKernelMatrix[row]?.[col] ?? 0} onChange={(e) => updateCustomKernelCell(row, col, parseFloat(e.target.value) || 0)} onKeyDown={(e) => handleKeyDown(e, row, col)} className="w-full h-8 text-center text-sm border border-gray-300 rounded dark:border-gray-600 dark:bg-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent" title={`Row ${row + 1}, Col ${col + 1}`} /> )) )}
)} {/* Kernel List */}
{kernelsByCategory .filter((cat) => cat.kernels.length > 0) .map((cat) => (

{cat.label}

{cat.kernels.map((kernel) => ( ))}
))} {filteredKernels.length === 0 && (
No kernels found matching “{searchQuery}”
)}
) }