import { useCallback, useMemo, useRef, useState } from "react"; import { requiresAdminForAutomation } from "../../../lib/automationPlanner"; import { countScannerLearningRules, type ScannerLearningRules, } from "../../../lib/scannerLearning"; import { type ParsedArtifactCandidate } from "../../../lib/artifactOcrParser"; import { analyzeReviewSamples } from "../../../lib/reviewSampleAnalysis"; import { parseLearnedArtifact as parseLearnedArtifactHelper, } from "./scanViewReviewHelpers"; import { useScanRuntimeInfo } from "./useScanRuntimeInfo"; import { useScanGoodInterop } from "./useScanGoodInterop"; import { useScanSnapshotPublisher } from "./useScanSnapshotPublisher"; import { useScanViewActions } from "./useScanViewActions"; import { useScanViewStateSync } from "./useScanViewStateSync"; import { emptyAutoScanStats, resolveScanTargetCount, type AutoScanStats, type ScanSummary } from "../../../lib/scannerSession"; import { createScanDiagnosticEvent, summarizeClickResult, type ScanDiagnosticEvent } from "../../../lib/scanDiagnosticsLog"; import type { ScanViewProps, ScanViewControllerResult } from "../types"; import type { CaptureResult, ClickResult, ReviewSampleRecord } from "../../../types/global"; import { createRendererRepositories } from "../../../infrastructure/repositories/rendererBridgeRepositories"; export function useScanViewController({ snapshot, isScanning, captureSources, selectedSourceId, latestCapture, captureStatus, captureSelectedSource, bridgeReady, onStoredArtifactsChanged, }: ScanViewProps): ScanViewControllerResult { const repositories = useMemo(() => createRendererRepositories(), []); const artifactRepo = repositories?.artifacts; const runtimeRepo = repositories?.runtime; const reviewSamplesRepo = repositories?.reviewSamples; const learningRepo = repositories?.learning; const snapshotRepo = repositories?.snapshot; const automationRepo = repositories?.automation; const captureRepo = repositories?.capture; const exportRepo = repositories?.export; const [detailsOpen, setDetailsOpen] = useState(false); const [diagnosticsOpen, setDiagnosticsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const [reviewQueueOpen, setReviewQueueOpen] = useState(false); const [reviewSamples, setReviewSamples] = useState([]); const [reviewSampleTotal, setReviewSampleTotal] = useState(0); const [reviewStatus, setReviewStatus] = useState(""); const [autoScanRunning, setAutoScanRunning] = useState(false); const stopVisibleScanRef = useRef(false); const [autoScanStats, setAutoScanStats] = useState(emptyAutoScanStats); const [scanSummary, setScanSummary] = useState(null); const [scanLimit, setScanLimit] = useState(16); const [scanLimitTouched, setScanLimitTouched] = useState(false); const [skipRows, setSkipRows] = useState(0); const [automationLog, setAutomationLog] = useState([]); const [diagnosticEvents, setDiagnosticEvents] = useState([]); const [storedTotal, setStoredTotal] = useState(null); const [devMode, setDevMode] = useState(() => localStorage.getItem("gaa-dev-mode") === "1"); const [scannerLearningRules, setScannerLearningRules] = useState({ textReplacements: {} }); const [learningRulesLoaded, setLearningRulesLoaded] = useState(false); const runtimeInfo = useScanRuntimeInfo(runtimeRepo); const parsedArtifact = useMemo( () => parseLearnedArtifactHelper(latestCapture, scannerLearningRules), [latestCapture, scannerLearningRules], ); const selectedSource = captureSources.find((source) => source.id === selectedSourceId); const genshinSource = captureSources.find((source) => source.isGenshinCandidate); const requiresAdminForAutoScan = requiresAdminForAutomation(runtimeInfo); const hasSourceSelected = selectedSourceId.length > 0; const canCaptureSource = bridgeReady && Boolean(captureRepo?.captureSource); const canReadReviewQueue = bridgeReady && Boolean(reviewSamplesRepo?.loadSamples); const canSaveReviewSample = bridgeReady && Boolean(reviewSamplesRepo?.saveSample); const canAutoScan = bridgeReady && Boolean(automationRepo?.clickScreen) && Boolean(automationRepo?.scrollScreen); const reviewAnalysis = useMemo(() => analyzeReviewSamples(reviewSamples), [reviewSamples]); const learningRuleCount = countScannerLearningRules(scannerLearningRules); const detectedInventoryCount = latestCapture?.inventoryCount?.total ?? latestCapture?.inventoryCount?.current ?? 0; const activeTargetCount = autoScanRunning ? resolveScanTargetCount(scanLimit, detectedInventoryCount) : scanSummary?.targetCount ?? resolveScanTargetCount(scanLimit, detectedInventoryCount); const scanProgressBase = Math.max(autoScanStats.attempted, autoScanStats.parsed, autoScanStats.verified); const scanProgressPercent = Math.min(100, Math.round((scanProgressBase / Math.max(1, activeTargetCount)) * 100)); const scannerModeLabel = autoScanRunning ? "Scan laeuft" : runtimeInfo?.isElevated ? "Admin bereit" : "Bereit"; const sourceLabel = selectedSource?.name ?? "Keine Quelle"; const gridLabel = latestCapture?.inventoryGrid ? `${latestCapture.inventoryGrid.cols} x ${latestCapture.inventoryGrid.rows}` : "warte auf Capture"; const inventoryLabel = latestCapture?.inventoryCount?.current ? `${latestCapture.inventoryCount.current}/${latestCapture.inventoryCount.total || "?"}` : "nicht erkannt"; const appendAutomationLog = useCallback((line: string) => { setAutomationLog((previous) => [...previous.slice(-11), `${new Date().toLocaleTimeString()} ${line}`]); }, []); const appendDiagnosticEvent = useCallback((event: Omit[0], "includeFullScreenshot">) => { setDiagnosticEvents((previous) => [ ...previous.slice(-23), createScanDiagnosticEvent({ ...event, includeFullScreenshot: false, }), ]); }, []); const appendClickDiagnostics = useCallback((result: ClickResult, prefix = "input") => { const cursor = `${result.cursorX ?? "?"},${result.cursorY ?? "?"}`; const focus = result.focused ? `fg:${result.foregroundProcess || "Genshin"}` : `fg-miss:${result.foregroundProcess || "?"}`; const blocked = result.inputBlocked ? " input:blocked" : ""; appendAutomationLog(`${prefix}: ${focus}${blocked} cursor ${cursor} moved:${result.moved ? "yes" : "no"} clicked:${result.clicked ? "yes" : "no"}`); appendDiagnosticEvent({ phase: "input", severity: result.inputBlocked || result.clicked === false || result.moved === false ? "warn" : "ok", message: `${prefix}: click ${result.clicked ? "sent" : "not sent"}`, details: summarizeClickResult(result), }); }, [appendAutomationLog, appendDiagnosticEvent]); const toggleDevMode = useCallback(() => { setDevMode((previous) => { const next = !previous; localStorage.setItem("gaa-dev-mode", next ? "1" : "0"); return next; }); }, []); const parseArtifact = useCallback( (capture: CaptureResult | null) => parseLearnedArtifactHelper(capture, scannerLearningRules), [scannerLearningRules], ); const { requestScanStop, saveReviewSample: handleSaveReviewSample, loadReviewQueue: loadReviewQueueAction, openReviewQueue: openReviewQueueModal, runAutoReviewScan, runGuidedAutoScan, runVisibleGridScan, } = useScanViewActions({ autoScanRunning, setAutoScanRunning, isScanning, stopVisibleScanRef, selectedSourceId, bridgeReady, automationRepo, runtimeInfo, runtimeRepo, scanLimit, skipRows, detectedInventoryCount, setScanSummary, setAutoScanStats, setReviewStatus, appendAutomationLog, appendClickDiagnostics, appendDiagnosticEvent, parseArtifact, artifactRepo, reviewSamplesRepo, learningRepo, onStoredArtifactsChanged, setReviewSampleTotal, setReviewSamples, setLearningRulesLoaded, setScannerLearningRules, setStoredTotal, scannerLearningRules, captureSelectedSource, latestCapture, parsedArtifact, canCaptureSource, setReviewQueueOpen, }); const { canGoodInterop, exportGoodFromStore, importGoodFromFile, importGoodArtifacts, } = useScanGoodInterop({ artifactRepo, exportRepo, onStoredArtifactsChanged, setStoredTotal, bridgeReady, }); useScanViewStateSync({ artifactRepo, latestCapture, scanLimitTouched, setScanLimit, setStoredTotal, }); useScanSnapshotPublisher({ autoScanRunning, reviewStatus, captureStatus, selectedSourceName: selectedSource?.name ?? null, autoScanStats, scanSummary, snapshot, latestInventoryGrid: latestCapture?.inventoryGrid ?? null, automationLog, diagnosticEvents, runtimeInfo, storedTotal, learningRuleCount, snapshotRepo, }); return { detailsOpen, diagnosticsOpen, settingsOpen, reviewQueueOpen, reviewSamples, reviewSampleTotal, reviewStatus, autoScanRunning, autoScanStats, scanSummary, scanLimit, scanLimitTouched, skipRows, automationLog, diagnosticEvents, storedTotal, devMode, scannerLearningRules, learningRulesLoaded, runtimeInfo, parsedArtifact, reviewAnalysis, learningRuleCount, detectedInventoryCount, scanProgressPercent, activeTargetCount, requiresAdminForAutoScan, hasSourceSelected, canCaptureSource, canReadReviewQueue, canSaveReviewSample, canAutoScan, scannerModeLabel, sourceLabel, gridLabel, inventoryLabel, selectedSource, genshinSource, setDetailsOpen, setDiagnosticsOpen, setSettingsOpen, setReviewQueueOpen, setScanSummary, setScanLimit, setScanLimitTouched, setSkipRows, toggleDevMode, requestScanStop, saveReviewSample: handleSaveReviewSample, loadReviewQueue: loadReviewQueueAction, openReviewQueue: openReviewQueueModal, runAutoReviewScan, runGuidedAutoScan, runVisibleGridScan, canGoodInterop, exportGoodFromStore, importGoodFromFile, importGoodArtifacts, }; }