import { useCallback, useEffect, useMemo } from "react"; import type { Dispatch, MutableRefObject, SetStateAction } from "react"; import { runAutoReviewScan as runAutoReviewScanAction, runVisibleGridScan as runVisibleGridScanAction, type VisibleGridScanOptions } from "./scanViewScanActions"; import { initializeLearningState, loadReviewQueue as loadReviewQueueFromRepo, persistParsedArtifact as persistParsedArtifactHelper, saveReviewSample as saveReviewSampleHelper, } from "./scanViewReviewHelpers"; import { createReviewContext, createScanActionContext } from "./scanViewControllerService"; import { useScanCommandListener } from "./useScanCommandListener"; import type { ParsedArtifactCandidate } from "../../../lib/artifactOcrParser"; import type { ScannerLearningRules } from "../../../lib/scannerLearning"; import type { BooleanResult, CaptureOptions, CaptureResult, ClickResult, ReviewSampleRecord } from "../../../types/global"; import type { ArtifactRepositoryPort, ReviewSampleRepositoryPort, LearningRepositoryPort, RuntimeRepositoryPort, AutomationRepositoryPort, } from "../../../infrastructure/repositories/rendererBridgeRepositoryTypes"; import type { AutoScanStats, ScanSummary } from "../../../lib/scannerSession"; import type { RuntimeInfo } from "../../../types/global"; type BooleanSetter = Dispatch>; type NumberSetter = Dispatch>; type StringSetter = Dispatch>; type NumberOrNullSetter = Dispatch>; type ScannerRulesSetter = Dispatch>; type ReviewSamplesSetter = Dispatch>; type ScanSummarySetter = Dispatch>; type AutoScanStatsSetter = Dispatch>; interface ScanViewActionInput { autoScanRunning: boolean; setAutoScanRunning: BooleanSetter; isScanning: boolean; stopVisibleScanRef: MutableRefObject; selectedSourceId: string; bridgeReady: boolean; automationRepo?: AutomationRepositoryPort; runtimeInfo?: RuntimeInfo | null; runtimeRepo?: RuntimeRepositoryPort; scanLimit: number; skipRows: number; detectedInventoryCount: number; setScanSummary: ScanSummarySetter; setAutoScanStats: AutoScanStatsSetter; setReviewStatus: StringSetter; appendAutomationLog: (line: string) => void; appendClickDiagnostics: (result: ClickResult, prefix?: string) => void; parseArtifact: (capture: CaptureResult | null) => ParsedArtifactCandidate | null; artifactRepo?: ArtifactRepositoryPort; reviewSamplesRepo?: ReviewSampleRepositoryPort; learningRepo?: LearningRepositoryPort; onStoredArtifactsChanged?: (() => Promise) | (() => void); setReviewSampleTotal: NumberSetter; setReviewSamples: ReviewSamplesSetter; setLearningRulesLoaded: BooleanSetter; setScannerLearningRules: ScannerRulesSetter; setStoredTotal: NumberOrNullSetter; scannerLearningRules: ScannerLearningRules; captureSelectedSource: (delayMs?: number, focusGenshin?: boolean, options?: CaptureOptions) => Promise; latestCapture: CaptureResult | null; parsedArtifact: ParsedArtifactCandidate | null; canCaptureSource: boolean; setReviewQueueOpen: BooleanSetter; } export interface ScanViewActionResult { requestScanStop: (reason?: string) => void; saveReviewSample: ( capture?: CaptureResult | null, parsed?: ParsedArtifactCandidate | null, reason?: string, ) => Promise; loadReviewQueue: () => Promise; openReviewQueue: () => Promise; runAutoReviewScan: () => Promise; runVisibleGridScan: (options?: VisibleGridScanOptions) => Promise; } export function useScanViewActions(input: ScanViewActionInput): ScanViewActionResult { const { autoScanRunning, setAutoScanRunning, isScanning, stopVisibleScanRef, selectedSourceId, bridgeReady, automationRepo, runtimeInfo, runtimeRepo, scanLimit, skipRows, detectedInventoryCount, setScanSummary, setAutoScanStats, setReviewStatus, appendAutomationLog, appendClickDiagnostics, parseArtifact, artifactRepo, reviewSamplesRepo, learningRepo, onStoredArtifactsChanged, setReviewSampleTotal, setReviewSamples, setLearningRulesLoaded, setScannerLearningRules, setStoredTotal, scannerLearningRules, captureSelectedSource, latestCapture, parsedArtifact, canCaptureSource, setReviewQueueOpen, } = input; const requestScanStop = useCallback((reason = "Stop angefordert.") => { stopVisibleScanRef.current = true; appendAutomationLog(`stop requested: ${reason}`); setReviewStatus(`${reason} Der aktuelle Klick/Capture-Schritt wird noch sauber beendet.`); }, [appendAutomationLog, setReviewStatus, stopVisibleScanRef]); const reviewContext = useMemo( () => createReviewContext({ artifactRepo, reviewSamplesRepo, learningRepo, onStoredArtifactsChanged, setReviewSampleTotal, setReviewSamples, setLearningRulesLoaded, setScannerLearningRules, setReviewStatus, setStoredTotal, appendAutomationLog, }), [ artifactRepo, reviewSamplesRepo, learningRepo, onStoredArtifactsChanged, setReviewSampleTotal, setReviewSamples, setLearningRulesLoaded, setScannerLearningRules, setReviewStatus, setStoredTotal, appendAutomationLog, ], ); useEffect(() => { void initializeLearningState(reviewContext); }, [reviewContext]); const focusDashboard = useCallback(async () => { try { await automationRepo?.focusMainWindow?.(); } catch { // Best-effort fallback; state remains in renderer. } }, [automationRepo?.focusMainWindow]); const parseArtifactAndPersist = useCallback( async function parseArtifactAndPersist( capture: CaptureResult | null, parsed: ParsedArtifactCandidate, source: string, needsReview: boolean, ) { return persistParsedArtifactHelper(capture, parsed, source, needsReview, reviewContext); }, [reviewContext], ); const handleSaveReviewSample = useCallback( async function handleSaveReviewSample( capture: CaptureResult | null = latestCapture, parsed: ParsedArtifactCandidate | null = parsedArtifact, reason = "manual", ): Promise { const result = await saveReviewSampleHelper( capture, parsed, reason, scannerLearningRules, reviewContext, ); return result.result ? { ok: result.result.ok } : null; }, [latestCapture, parsedArtifact, reviewContext, scannerLearningRules], ); const scanActionContext = useMemo( () => createScanActionContext({ autoScanRunning, setAutoScanRunning, isScanning, stopVisibleScanRef, selectedSourceId, bridgeReady, automationRepo, runtimeInfo, runtimeRepo, scanLimit, skipRows, detectedInventoryCount, setScanSummary, setAutoScanStats, setReviewStatus, appendAutomationLog, appendClickDiagnostics, parseArtifact, persistParsedArtifact: parseArtifactAndPersist, saveReviewSample: handleSaveReviewSample, focusDashboard, captureSelectedSource, captureFastSelectedSource: (delayMs = 0, focusGenshin = false) => captureSelectedSource(delayMs, focusGenshin, { skipOcr: true }), }), [ autoScanRunning, setAutoScanRunning, isScanning, stopVisibleScanRef, selectedSourceId, bridgeReady, automationRepo, runtimeInfo, runtimeRepo, scanLimit, skipRows, detectedInventoryCount, setScanSummary, setAutoScanStats, setReviewStatus, appendAutomationLog, appendClickDiagnostics, parseArtifact, parseArtifactAndPersist, handleSaveReviewSample, focusDashboard, captureSelectedSource, ], ); const loadReviewQueueAction = useCallback(async () => { await loadReviewQueueFromRepo(reviewContext); }, [reviewContext]); const openReviewQueueModal = useCallback(async () => { await loadReviewQueueAction(); setReviewQueueOpen(true); }, [loadReviewQueueAction, setReviewQueueOpen]); const runAutoReviewScan = useCallback(async () => { if (autoScanRunning || !selectedSourceId || !canCaptureSource) return; await runAutoReviewScanAction(scanActionContext); }, [autoScanRunning, canCaptureSource, selectedSourceId, scanActionContext]); const runVisibleGridScan = useCallback(async (options: VisibleGridScanOptions = {}) => { if (autoScanRunning || !bridgeReady || !selectedSourceId || !automationRepo?.clickScreen || !automationRepo?.scrollScreen) { return; } await runVisibleGridScanAction(scanActionContext, options); }, [ autoScanRunning, bridgeReady, selectedSourceId, automationRepo?.clickScreen, automationRepo?.scrollScreen, scanActionContext, ]); useScanCommandListener({ automationRepo, autoScanRunning, isScanning, selectedSourceId, requestScanStop, runVisibleGridScan, }); return { requestScanStop, saveReviewSample: handleSaveReviewSample, loadReviewQueue: loadReviewQueueAction, openReviewQueue: openReviewQueueModal, runAutoReviewScan, runVisibleGridScan, }; }