import { useCallback, useEffect, useRef } from 'react' import type { StructuredGrid } from './types' import type { IpaMode, SyllableMode } from './gridEditorTypes' import { KLAUSUR_API } from './gridEditorTypes' interface ApiDeps { sessionId: string | null ipaMode: IpaMode syllableMode: SyllableMode ocrEnhance: boolean ocrMaxCols: number ocrMinConf: number visionFusion: boolean documentCategory: string setGrid: React.Dispatch> setLoading: React.Dispatch> setSaving: React.Dispatch> setError: React.Dispatch> setDirty: React.Dispatch> grid: StructuredGrid | null undoStack: React.MutableRefObject redoStack: React.MutableRefObject } export function useGridEditorApi(deps: ApiDeps) { const { sessionId, ipaMode, syllableMode, ocrEnhance, ocrMaxCols, ocrMinConf, visionFusion, documentCategory, setGrid, setLoading, setSaving, setError, setDirty, grid, undoStack, redoStack, } = deps // ------------------------------------------------------------------ // Build grid // ------------------------------------------------------------------ const buildGrid = useCallback(async () => { if (!sessionId) return setLoading(true) setError(null) try { const params = new URLSearchParams() params.set('ipa_mode', ipaMode) params.set('syllable_mode', syllableMode) params.set('enhance', String(ocrEnhance)) if (ocrMaxCols > 0) params.set('max_cols', String(ocrMaxCols)) if (ocrMinConf > 0) params.set('min_conf', String(ocrMinConf)) const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`, { method: 'POST' }, ) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } const data: StructuredGrid = await res.json() setGrid(data) setDirty(false) undoStack.current = [] redoStack.current = [] } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setLoading(false) } }, [sessionId, ipaMode, syllableMode, ocrEnhance, ocrMaxCols, ocrMinConf, setGrid, setLoading, setError, setDirty, undoStack, redoStack]) // ------------------------------------------------------------------ // Re-run OCR with current quality settings, then rebuild grid // ------------------------------------------------------------------ const rerunOcr = useCallback(async () => { if (!sessionId) return setLoading(true) setError(null) try { const params = new URLSearchParams() params.set('ipa_mode', ipaMode) params.set('syllable_mode', syllableMode) params.set('enhance', String(ocrEnhance)) if (ocrMaxCols > 0) params.set('max_cols', String(ocrMaxCols)) if (ocrMinConf > 0) params.set('min_conf', String(ocrMinConf)) params.set('vision_fusion', String(visionFusion)) if (documentCategory) params.set('doc_category', documentCategory) const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/rerun-ocr-and-build-grid?${params}`, { method: 'POST' }, ) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } const data: StructuredGrid = await res.json() setGrid(data) setDirty(false) undoStack.current = [] redoStack.current = [] } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setLoading(false) } }, [sessionId, ipaMode, syllableMode, ocrEnhance, ocrMaxCols, ocrMinConf, visionFusion, documentCategory, setGrid, setLoading, setError, setDirty, undoStack, redoStack]) // ------------------------------------------------------------------ // Load grid (with auto-build fallback on 404) // ------------------------------------------------------------------ const loadGrid = useCallback(async () => { if (!sessionId) return setLoading(true) setError(null) try { const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/grid-editor`, ) if (res.status === 404) { // No grid yet — build it with current modes const params = new URLSearchParams() params.set('ipa_mode', ipaMode) params.set('syllable_mode', syllableMode) params.set('enhance', String(ocrEnhance)) if (ocrMaxCols > 0) params.set('max_cols', String(ocrMaxCols)) if (ocrMinConf > 0) params.set('min_conf', String(ocrMinConf)) const buildRes = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`, { method: 'POST' }, ) if (buildRes.ok) { const data: StructuredGrid = await buildRes.json() setGrid(data) setDirty(false) } return } if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } const data: StructuredGrid = await res.json() setGrid(data) setDirty(false) undoStack.current = [] redoStack.current = [] } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setLoading(false) } // Only depends on sessionId — mode changes are handled by the // separate useEffect below, not by re-triggering loadGrid. // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessionId]) // ------------------------------------------------------------------ // Auto-rebuild when IPA or syllable mode changes (skip initial mount) // ------------------------------------------------------------------ const mountedRef = useRef(false) useEffect(() => { if (!mountedRef.current) { // Skip the first trigger (component mount) — don't rebuild yet mountedRef.current = true return } if (!sessionId) return const rebuild = async () => { setLoading(true) setError(null) try { const params = new URLSearchParams() params.set('ipa_mode', ipaMode) params.set('syllable_mode', syllableMode) const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`, { method: 'POST' }, ) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } const data: StructuredGrid = await res.json() setGrid(data) setDirty(false) } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setLoading(false) } } rebuild() // eslint-disable-next-line react-hooks/exhaustive-deps }, [ipaMode, syllableMode]) // ------------------------------------------------------------------ // Save grid // ------------------------------------------------------------------ const saveGrid = useCallback(async () => { if (!sessionId || !grid) return setSaving(true) try { const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/save-grid`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(grid), }, ) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } setDirty(false) } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { setSaving(false) } }, [sessionId, grid, setSaving, setError, setDirty]) return { buildGrid, rerunOcr, loadGrid, saveGrid, } }