import { useCallback, useRef, useState } from 'react' import type { StructuredGrid } from './types' import { MAX_UNDO } from './gridEditorTypes' import type { IpaMode, SyllableMode } from './gridEditorTypes' import { useGridEditorApi } from './useGridEditorApi' import { useGridEditorActions } from './useGridEditorActions' import { useGridEditorLayout } from './useGridEditorLayout' // Re-export types so existing imports keep working export type { GridEditorState, IpaMode, SyllableMode } from './gridEditorTypes' export function useGridEditor(sessionId: string | null) { const [grid, setGrid] = useState(null) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const [dirty, setDirty] = useState(false) const [selectedCell, setSelectedCell] = useState(null) const [selectedZone, setSelectedZone] = useState(null) const [ipaMode, setIpaMode] = useState('auto') const [syllableMode, setSyllableMode] = useState('auto') // OCR Quality Steps (A/B testing toggles — defaults off for now) const [ocrEnhance, setOcrEnhance] = useState(false) const [ocrMaxCols, setOcrMaxCols] = useState(0) const [ocrMinConf, setOcrMinConf] = useState(0) // Vision-LLM Fusion (Step 4) const [visionFusion, setVisionFusion] = useState(false) const [documentCategory, setDocumentCategory] = useState('vokabelseite') // Undo/redo stacks store serialized zone arrays const undoStack = useRef([]) const redoStack = useRef([]) const pushUndo = useCallback((zones: StructuredGrid['zones']) => { undoStack.current.push(JSON.stringify(zones)) if (undoStack.current.length > MAX_UNDO) { undoStack.current.shift() } redoStack.current = [] }, []) // ------------------------------------------------------------------ // API calls (build, load, save, rerunOcr) // ------------------------------------------------------------------ const { buildGrid, rerunOcr, loadGrid, saveGrid } = useGridEditorApi({ sessionId, ipaMode, syllableMode, ocrEnhance, ocrMaxCols, ocrMinConf, visionFusion, documentCategory, setGrid, setLoading, setSaving, setError, setDirty, grid, undoStack, redoStack, }) // ------------------------------------------------------------------ // Cell editing, formatting, multi-select // ------------------------------------------------------------------ const { updateCellText, setCellColor, toggleColumnBold, toggleRowHeader, autoCorrectColumnPatterns, selectedCells, toggleCellSelection, clearCellSelection, toggleSelectedBold, } = useGridEditorActions({ grid, setGrid, setDirty, pushUndo }) // ------------------------------------------------------------------ // Column/row management, layout dividers // ------------------------------------------------------------------ const { deleteColumn, addColumn, deleteRow, addRow, commitUndoPoint, updateColumnDivider, updateLayoutHorizontals, splitColumnAt, } = useGridEditorLayout({ grid, setGrid, setDirty, pushUndo }) // ------------------------------------------------------------------ // Undo / Redo // ------------------------------------------------------------------ const undo = useCallback(() => { if (!grid || undoStack.current.length === 0) return redoStack.current.push(JSON.stringify(grid.zones)) const prev = undoStack.current.pop()! setGrid((g) => (g ? { ...g, zones: JSON.parse(prev) } : g)) setDirty(true) }, [grid]) const redo = useCallback(() => { if (!grid || redoStack.current.length === 0) return undoStack.current.push(JSON.stringify(grid.zones)) const next = redoStack.current.pop()! setGrid((g) => (g ? { ...g, zones: JSON.parse(next) } : g)) setDirty(true) }, [grid]) const canUndo = undoStack.current.length > 0 const canRedo = redoStack.current.length > 0 // ------------------------------------------------------------------ // Navigation helpers // ------------------------------------------------------------------ const getAdjacentCell = useCallback( (cellId: string, direction: 'up' | 'down' | 'left' | 'right'): string | null => { if (!grid) return null for (const zone of grid.zones) { // Find the cell or derive row/col from cellId pattern const cell = zone.cells.find((c) => c.cell_id === cellId) let currentRow: number, currentCol: number if (cell) { currentRow = cell.row_index currentCol = cell.col_index } else { // Try to parse from cellId: Z{zone}_R{row}_C{col} const match = cellId.match(/^Z(\d+)_R(\d+)_C(\d+)$/) if (!match || parseInt(match[1]) !== zone.zone_index) continue currentRow = parseInt(match[2]) currentCol = parseInt(match[3]) } let targetRow = currentRow let targetCol = currentCol if (direction === 'up') targetRow-- if (direction === 'down') targetRow++ if (direction === 'left') targetCol-- if (direction === 'right') targetCol++ // Check bounds const hasRow = zone.rows.some((r) => r.index === targetRow) const hasCol = zone.columns.some((c) => c.index === targetCol) if (!hasRow || !hasCol) return null // Return existing cell ID or construct one const target = zone.cells.find( (c) => c.row_index === targetRow && c.col_index === targetCol, ) return target?.cell_id ?? `Z${zone.zone_index}_R${String(targetRow).padStart(2, '0')}_C${targetCol}` } return null }, [grid], ) return { grid, loading, saving, error, dirty, selectedCell, selectedZone, selectedCells, setSelectedCell, setSelectedZone, buildGrid, loadGrid, saveGrid, updateCellText, toggleColumnBold, toggleRowHeader, undo, redo, canUndo, canRedo, getAdjacentCell, deleteColumn, addColumn, deleteRow, addRow, commitUndoPoint, updateColumnDivider, updateLayoutHorizontals, splitColumnAt, toggleCellSelection, clearCellSelection, toggleSelectedBold, autoCorrectColumnPatterns, setCellColor, ipaMode, setIpaMode, syllableMode, setSyllableMode, ocrEnhance, setOcrEnhance, ocrMaxCols, setOcrMaxCols, ocrMinConf, setOcrMinConf, visionFusion, setVisionFusion, documentCategory, setDocumentCategory, rerunOcr, } }