import { useState } from 'react' import { analysisApi } from '../services/api' import type { ChangeDetectionSummary, DatasetCreateResponse, VectorSelectionBBox } from '../types' import { formatError } from '../lib/formatError' interface ChangeDetectionWorkflowOptions { selectedProjectId: string | null availableVectorDatasets: DatasetCreateResponse[] loadDatasetJobs: (projectId: string, datasetId: string) => Promise /** * Reads the active map selection when the comparison is started. Without a * selection the comparison covers both datasets entire, which is rarely the * question being asked and returns a payload no map can draw. * * A getter rather than a value because this hook runs before the selection * state is declared in the component; it is only ever called from a user * event, long after that binding is initialised. */ getSelection?: () => { bbox: VectorSelectionBBox | null; areaId: string | null } } export function useChangeDetectionWorkflow({ selectedProjectId, availableVectorDatasets, loadDatasetJobs, getSelection, }: ChangeDetectionWorkflowOptions) { const [changeSourceDatasetId, setChangeSourceDatasetId] = useState('') const [changeTargetDatasetId, setChangeTargetDatasetId] = useState('') const [changeIouThreshold, setChangeIouThreshold] = useState(0.8) const [changeIncludeUnchanged, setChangeIncludeUnchanged] = useState(true) const [runningChangeDetection, setRunningChangeDetection] = useState(false) const [changeDetectionResult, setChangeDetectionResult] = useState(null) const [changeDetectionError, setChangeDetectionError] = useState(null) const runChangeDetection = async () => { const sourceDatasetId = changeSourceDatasetId || availableVectorDatasets[0]?.id const targetDatasetId = changeTargetDatasetId || availableVectorDatasets.find((dataset) => dataset.id !== sourceDatasetId)?.id if (!sourceDatasetId || !targetDatasetId) { setChangeDetectionError('Kies twee vectorbronnen') return } if (sourceDatasetId === targetDatasetId) { setChangeDetectionError('Source and target datasets must differ') return } if (changeIouThreshold < 0 || changeIouThreshold > 1) { setChangeDetectionError('De IoU-drempel moet tussen 0 en 1 liggen.') return } setChangeDetectionError(null) setChangeDetectionResult(null) setRunningChangeDetection(true) const selection = getSelection?.() ?? { bbox: null, areaId: null } try { const job = await analysisApi.runChangeDetection({ source_dataset_id: sourceDatasetId, target_dataset_id: targetDatasetId, iou_threshold: changeIouThreshold, include_unchanged: changeIncludeUnchanged, ...(selection.bbox ? { bbox: selection.bbox } : {}), ...(selection.areaId ? { area_id: selection.areaId } : {}), }) if (job.status !== 'success') { throw new Error(job.error_message || 'De wijzigingsanalyse is mislukt.') } if (!job.result_json) { throw new Error('De wijzigingsanalyse is afgerond zonder resultaat.') } setChangeSourceDatasetId(sourceDatasetId) setChangeTargetDatasetId(targetDatasetId) setChangeDetectionResult(job.result_json) if (selectedProjectId) { await loadDatasetJobs(selectedProjectId, sourceDatasetId) } } catch (error) { setChangeDetectionError(formatError(error, 'Change detection failed')) } finally { setRunningChangeDetection(false) } } return { changeSourceDatasetId, changeTargetDatasetId, changeIouThreshold, changeIncludeUnchanged, runningChangeDetection, changeDetectionResult, changeDetectionError, runChangeDetection, setChangeSourceDatasetId, setChangeTargetDatasetId, setChangeIouThreshold, setChangeIncludeUnchanged, } }