import type { BooleanResult, CaptureResult, ClickResult, AutomationGuard, ScrollResult } from "../types/global";
import type { ParsedArtifactCandidate } from "./artifactOcrParser";
import { sessionSignature } from "./artifactStore";
import { buildGridModel, buildInventoryPagePlan, type GridTarget } from "./automationPlanner";
import { classifyAutoScanCapture, shouldAbortAfterConsecutiveMisses } from "./autoScanController";
import { captureRejectionReason, captureSourceRejectionReason, shouldPersistParsedArtifact } from "./scannerCaptureQuality";
import type { AutoScanStats, ScanSummary } from "./scannerSession";
import { clampSkipRows, emptyAutoScanStats, resolveScanTargetCount } from "./scannerSession";

// Simplified to match Inventory Kamera's proven approach (see docs/DECISIONS.md
// ADR-007): one click per tile, a fixed settle delay, one retry if the detail
// view did not change, then move on. No click-profile matrix, no offset
// retries, no double-read conflict resolution - those never fixed a single
// click and only made failures harder to diagnose.

type AutoScanApi = {
  clickScreen: (x: number, y: number) => Promise<ClickResult>;
  scrollScreen: (notches: number, anchorX?: number, anchorY?: number) => Promise<ScrollResult>;
  getAutomationGuard?: () => Promise<AutomationGuard>;
};

export type AutoScanLoopDependencies = {
  api: AutoScanApi;
  captureSelectedSource: (delayMs?: number, focusGenshin?: boolean) => Promise<CaptureResult | null>;
  captureFastSelectedSource: (delayMs?: number, focusGenshin?: boolean) => Promise<CaptureResult | null>;
  parseArtifact: (capture: CaptureResult | null) => ParsedArtifactCandidate | null;
  persistParsedArtifact: (
    capture: CaptureResult | null,
    parsed: ParsedArtifactCandidate,
    source: string,
    needsReview: boolean,
  ) => Promise<boolean>;
  saveReviewSample: (
    capture: CaptureResult | null,
    parsed: ParsedArtifactCandidate | null,
    reason: string,
  ) => Promise<BooleanResult | null>;
  getAutoReviewReason: (capture: CaptureResult, parsed: ParsedArtifactCandidate) => string;
  shouldFlagArtifactForReview: (parsed: ParsedArtifactCandidate | null) => boolean;
  appendAutomationLog: (line: string) => void;
  appendClickDiagnostics: (result: ClickResult, prefix?: string) => void;
  setReviewStatus: (value: string) => void;
  setAutoScanStats: (stats: AutoScanStats) => void;
  shouldStop: () => boolean;
};

export type AutoScanLoopOptions = {
  scanLimit: number;
  skipRows: number;
  detectedInventoryCount?: number | null;
};

export type AutoScanLoopResult = {
  status: ScanSummary["status"];
  stats: AutoScanStats;
  blockedReason: string;
  pageCount: number;
  gridLabel: string;
  targetCount: number;
};

const CLICK_SETTLE_MS = 280;
const MISS_ABORT_THRESHOLD = 3;
const UNREADABLE_ABORT_THRESHOLD = 5;

export async function runAutoScanLoop(
  deps: AutoScanLoopDependencies,
  options: AutoScanLoopOptions,
): Promise<AutoScanLoopResult> {
  const {
    api,
    captureSelectedSource,
    captureFastSelectedSource,
    parseArtifact,
    persistParsedArtifact,
    saveReviewSample,
    getAutoReviewReason,
    shouldFlagArtifactForReview,
    appendAutomationLog,
    appendClickDiagnostics,
    setReviewStatus,
    setAutoScanStats,
    shouldStop,
  } = deps;

  const stats: AutoScanStats = { ...emptyAutoScanStats };
  const maxTargets = resolveScanTargetCount(options.scanLimit, options.detectedInventoryCount);
  const rowsToSkip = clampSkipRows(options.skipRows);
  const seen = new Set<string>();
  const seenPageFingerprints = new Set<string>();
  let page = 0;
  let blockedReason = "";
  let aborted = false;
  let consecutiveMisses = 0;
  let rowsQueued = 0;

  function updateStats() {
    setAutoScanStats({ ...stats });
  }

  async function saveAutomaticReviewSample(capture: CaptureResult | null, parsed: ParsedArtifactCandidate | null, reason: string) {
    const saved = await saveReviewSample(capture, parsed, reason);
    if (saved?.ok) {
      stats.review++;
      updateStats();
    }
  }

  async function checkGuard() {
    if (shouldStop()) return "Stop-Button gedrueckt.";
    if (!api.getAutomationGuard) return "";
    try {
      const guard = await api.getAutomationGuard();
      if (guard.escapePressed) return "ESC wird gehalten - Scan sofort gestoppt.";
      if (guard.enterPressed) return "ENTER wird gehalten - Scan sofort gestoppt.";
      if (guard.f9Pressed) return "F9 wird gehalten - Scan sofort gestoppt.";
      return "";
    } catch {
      return "";
    }
  }

  function inputStopReason(result: ClickResult) {
    if (result.escapePressed) return "ESC wird gehalten - Scan sofort gestoppt.";
    if (result.enterPressed) return "ENTER wird gehalten - Scan sofort gestoppt.";
    if (result.f9Pressed) return "F9 wird gehalten - Scan sofort gestoppt.";
    return "";
  }

  async function waitDuringScan(ms: number) {
    const startedAt = Date.now();
    while (Date.now() - startedAt < ms) {
      const guardReason = await checkGuard();
      if (guardReason) return guardReason;
      await wait(Math.min(120, ms - (Date.now() - startedAt)));
    }
    return "";
  }

  async function clickTarget(target: GridTarget, label: string) {
    appendAutomationLog(`${label} r${target.row} c${target.col} -> ${target.x},${target.y}`);
    const clickResult = await api.clickScreen(target.x, target.y);
    appendClickDiagnostics(clickResult, `${label} r${target.row} c${target.col}`);
    stats.clicked++;
    stats.attempted = stats.clicked;
    updateStats();
    return clickResult;
  }

  let currentCapture = await captureSelectedSource(0, true);
  const initialCaptureRejection = captureSourceRejectionReason(currentCapture);
  let gridModel = buildGridModel(currentCapture?.inventoryGrid);

  if (initialCaptureRejection || !gridModel || gridModel.targets.length === 0) {
    const reason = initialCaptureRejection || "Kein verlaessliches Kachel-Grid erkannt. Artifact-Inventar sichtbar lassen und Smart Capture einmal ausfuehren.";
    setReviewStatus(reason);
    return { status: "blocked", stats, blockedReason: reason, pageCount: 0, gridLabel: reason, targetCount: maxTargets };
  }

  let lastDetailSignature = "";
  const initialParsed = parseArtifact(currentCapture);
  if (initialParsed) lastDetailSignature = sessionSignature(initialParsed);
  let lastDetailViewFingerprint = detailFingerprint(currentCapture);

  try {
    while (!blockedReason && !shouldStop() && stats.clicked < maxTargets) {
      page++;
      stats.pages = page;
      updateStats();

      const currentPageFingerprint = screenFingerprint(currentCapture);
      if (isRepeatedProcessedPageFingerprint(currentPageFingerprint, seenPageFingerprints, page)) {
        blockedReason = `Inventarseite ${page} wurde bereits zuvor gesehen. Scrollen hat wahrscheinlich keine neue Seite geliefert; Scan gestoppt, um keine Duplikat-Schleife zu erzeugen.`;
        break;
      }
      if (currentPageFingerprint) seenPageFingerprints.add(currentPageFingerprint);

      const pageSkipRows = page === 1 ? Math.min(rowsToSkip, Math.max(0, gridModel.rows - 1)) : 0;
      const baseTargets = gridModel.targets.filter((target) => target.row >= pageSkipRows);
      const pagePlan = buildInventoryPagePlan({
        targets: baseTargets,
        cols: gridModel.cols,
        rows: Math.max(1, gridModel.rows - pageSkipRows),
        totalTargetCount: maxTargets,
        processedTargets: stats.clicked,
        rowsQueued,
      });
      const targets = pagePlan.pageTargets;

      if (targets.length === 0) {
        blockedReason = `Keine Klick-Ziele nach dem Skippen von ${pageSkipRows} Zeile(n) auf Seite ${page}.`;
        break;
      }

      setReviewStatus(`Automatischer Scan Seite ${page}: ${gridModel.cols} x ${gridModel.rows} Raster (${gridModel.source}, ${gridModel.confidence}%), ${targets.length} Klick-Ziele, ${stats.clicked}/${maxTargets} geklickt.`);
      let newArtifactsOnPage = 0;

      for (const target of targets) {
        if (shouldStop() || stats.clicked >= maxTargets) break;

        const guardReason = await checkGuard();
        if (guardReason) {
          blockedReason = guardReason;
          aborted = true;
          break;
        }

        let clickResult = await clickTarget(target, "click");
        let stopReason = inputStopReason(clickResult);
        if (stopReason) {
          blockedReason = stopReason;
          aborted = true;
          break;
        }

        if (clickResult.moved === false || clickResult.clicked === false) {
          // A structural failure (cursor could not be placed, or SendInput
          // was rejected outright) means clicks are not reaching Genshin at
          // all - almost always an elevation mismatch. Abort immediately
          // instead of clicking blindly through the rest of the inventory.
          blockedReason = clickResult.inputBlocked
            ? "Windows blockiert die Eingabe (UIPI). Starte die App als Administrator (Scanner Diagnose > 'App als Administrator neu starten')."
            : `Klick kam nicht an (Ziel ${target.x},${target.y}). Starte die App als Administrator und versuche es erneut.`;
          break;
        }

        let waitStop = await waitDuringScan(CLICK_SETTLE_MS);
        if (waitStop) {
          blockedReason = waitStop;
          aborted = true;
          break;
        }

        let previewCapture = await captureFastSelectedSource(0, true);
        let previewFingerprint = detailFingerprint(previewCapture);
        let changedDetail = Boolean(previewFingerprint && previewFingerprint !== lastDetailViewFingerprint);

        if (!changedDetail) {
          appendAutomationLog(`retry r${target.row} c${target.col}: Detailansicht unveraendert`);
          clickResult = await clickTarget(target, "retry");
          stopReason = inputStopReason(clickResult);
          if (stopReason) {
            blockedReason = stopReason;
            aborted = true;
            break;
          }
          waitStop = await waitDuringScan(CLICK_SETTLE_MS);
          if (waitStop) {
            blockedReason = waitStop;
            aborted = true;
            break;
          }
          previewCapture = await captureFastSelectedSource(0, true);
          previewFingerprint = detailFingerprint(previewCapture);
          changedDetail = Boolean(previewFingerprint && previewFingerprint !== lastDetailViewFingerprint);
        }

        if (!changedDetail) {
          stats.misses++;
          consecutiveMisses++;
          updateStats();
          appendAutomationLog(`miss r${target.row} c${target.col}: Detailansicht unveraendert`);
          if (shouldAbortAfterConsecutiveMisses(consecutiveMisses, MISS_ABORT_THRESHOLD)) {
            blockedReason = "Mehrere Klicks hintereinander haben die Detailansicht nicht veraendert. Auto-Scan gestoppt: Klicks landen wahrscheinlich nicht auf neuen Artifacts.";
            break;
          }
          continue;
        }

        stats.verified++;

        const capture = await captureSelectedSource(0, true);
        if (capture?.ocrTimedOut) {
          stats.misses++;
          consecutiveMisses++;
          lastDetailViewFingerprint = detailFingerprint(capture);
          updateStats();
          appendAutomationLog(`miss r${target.row} c${target.col}: OCR timeout`);
          if (shouldAbortAfterConsecutiveMisses(consecutiveMisses, UNREADABLE_ABORT_THRESHOLD)) {
            blockedReason = "Mehrere OCR-Timeouts hintereinander. Auto-Scan gestoppt, damit die Session nicht haengen bleibt.";
            break;
          }
          continue;
        }

        const parsed = parseArtifact(capture);
        const rejection = captureRejectionReason(capture, parsed);

        if (rejection) {
          await saveAutomaticReviewSample(capture, parsed, `automatic:capture-rejected:p${page}:r${target.row}c${target.col}`);
          if (parsed && shouldPersistParsedArtifact(parsed, true)) {
            consecutiveMisses = 0;
            const signature = sessionSignature(parsed);
            stats.parsed++;
            lastDetailSignature = signature;
            lastDetailViewFingerprint = detailFingerprint(capture);
            seen.add(signature);
            newArtifactsOnPage++;
            if (await persistParsedArtifact(capture, parsed, "auto-scan-review", true)) stats.stored++;
            updateStats();
            continue;
          }
          stats.misses++;
          consecutiveMisses++;
          updateStats();
          appendAutomationLog(`miss r${target.row} c${target.col}: ${rejection}`);
          if (shouldAbortAfterConsecutiveMisses(consecutiveMisses, UNREADABLE_ABORT_THRESHOLD)) {
            blockedReason = "Mehrere unlesbare Artifact-Captures hintereinander. Auto-Scan gestoppt, damit nicht blind weitergeklickt wird.";
            break;
          }
          continue;
        }

        const signature = parsed ? sessionSignature(parsed) : "";
        const decision = classifyAutoScanCapture({ signature, lastDetailSignature, seen });

        if (!capture || !parsed || decision.kind === "unreadable") {
          stats.misses++;
          consecutiveMisses++;
          updateStats();
          appendAutomationLog(`miss r${target.row} c${target.col}: kein Artifact lesbar`);
          if (shouldAbortAfterConsecutiveMisses(consecutiveMisses, UNREADABLE_ABORT_THRESHOLD)) {
            blockedReason = "Mehrere unlesbare Artifacts hintereinander. Auto-Scan gestoppt: Klicks treffen wahrscheinlich nicht das Artifact-Raster.";
            break;
          }
          continue;
        }

        if (decision.kind === "stuck") {
          stats.misses++;
          consecutiveMisses++;
          updateStats();
          appendAutomationLog(`miss r${target.row} c${target.col}: Detail zeigt weiterhin "${parsed.name}"`);
          if (shouldAbortAfterConsecutiveMisses(consecutiveMisses, MISS_ABORT_THRESHOLD)) {
            blockedReason = `Mehrere Klicks blieben auf "${parsed.name}". Auto-Scan gestoppt.`;
            break;
          }
          continue;
        }

        consecutiveMisses = 0;
        stats.parsed++;
        lastDetailSignature = signature;
        lastDetailViewFingerprint = detailFingerprint(capture);

        if (decision.kind === "duplicate") {
          stats.duplicates++;
          updateStats();
          continue;
        }

        seen.add(signature);
        newArtifactsOnPage++;

        const reason = getAutoReviewReason(capture, parsed);
        const needsReview = reason ? true : shouldFlagArtifactForReview(parsed);
        if (reason) {
          await saveAutomaticReviewSample(capture, parsed, `automatic:${reason}:p${page}:r${target.row}c${target.col}`);
        }
        if (await persistParsedArtifact(capture, parsed, "auto-scan", needsReview)) stats.stored++;
        updateStats();
      }

      const endOfPagePlan = buildInventoryPagePlan({
        targets: gridModel.targets.filter((target) => target.row >= pageSkipRows),
        cols: gridModel.cols,
        rows: Math.max(1, gridModel.rows - pageSkipRows),
        totalTargetCount: maxTargets,
        processedTargets: stats.clicked,
        rowsQueued,
      });
      rowsQueued = endOfPagePlan.rowsQueuedAfterPage;

      if (aborted || stats.clicked >= maxTargets || shouldStop() || blockedReason) break;

      if (newArtifactsOnPage === 0 && page > 1) {
        blockedReason = `Seite ${page} hat keine neuen Artifacts geliefert; gestoppt, um nicht dieselbe Seite zu loopen.`;
        break;
      }

      const guardReason = await checkGuard();
      if (guardReason) {
        blockedReason = guardReason;
        aborted = true;
        break;
      }

      const rowsToScroll = endOfPagePlan.scrollRowsAfterPage;
      if (rowsToScroll <= 0) break;
      const scrollNotches = Math.min(60, Math.max(1, rowsToScroll * 10 - 1));
      setReviewStatus(`Automatischer Scan Seite ${page} fertig. Scrolle zur naechsten Inventory-Seite...`);
      appendAutomationLog(`scroll ${scrollNotches} (${rowsToScroll} row(s)) @ ${gridModel.anchorX},${gridModel.anchorY}`);
      const scrollResult = await api.scrollScreen(-scrollNotches, gridModel.anchorX, gridModel.anchorY);
      if (scrollResult.inputBlocked) {
        blockedReason = "Scroll-Input wurde von Windows blockiert. Starte die App als Administrator.";
        break;
      }

      if (page % 12 === 0) {
        appendAutomationLog(`scroll correction p${page}: +1 notch @ ${gridModel.anchorX},${gridModel.anchorY}`);
        const correctionResult = await api.scrollScreen(1, gridModel.anchorX, gridModel.anchorY);
        if (correctionResult.inputBlocked) {
          blockedReason = "Scroll-Korrektur wurde von Windows blockiert.";
          break;
        }
      }

      const scrollWaitStop = await waitDuringScan(760);
      if (scrollWaitStop) {
        blockedReason = scrollWaitStop;
        aborted = true;
        break;
      }

      const beforeScrollFingerprint = currentPageFingerprint || screenFingerprint(currentCapture);
      currentCapture = await captureFastSelectedSource(0, true);
      const afterScrollFingerprint = screenFingerprint(currentCapture);

      if (beforeScrollFingerprint && afterScrollFingerprint && beforeScrollFingerprint === afterScrollFingerprint) {
        blockedReason = "Scrollen hat die sichtbare Inventarseite nicht veraendert.";
        break;
      }

      if (afterScrollFingerprint && seenPageFingerprints.has(afterScrollFingerprint)) {
        blockedReason = "Scrollen hat erneut eine bereits verarbeitete Inventarseite gezeigt.";
        break;
      }

      const refreshedModel = buildGridModel(currentCapture?.inventoryGrid);
      if (!refreshedModel) {
        blockedReason = "Kachel-Grid nach dem Scrollen verloren.";
        break;
      }
      if (refreshedModel.source === "detected" && refreshedModel.confidence >= 72) {
        gridModel = refreshedModel;
      }
    }
  } catch (error) {
    blockedReason = `Fehler waehrend des Scans: ${error instanceof Error ? error.message : String(error)}`;
    appendAutomationLog(blockedReason);
  }

  const status: ScanSummary["status"] = aborted || shouldStop() ? "stopped" : blockedReason ? "blocked" : "done";
  return {
    status,
    stats,
    blockedReason,
    pageCount: page,
    gridLabel: blockedReason || `${page} Seite(n) verarbeitet, Ziel ${maxTargets} Artifacts, ${rowsToSkip} Zeile(n) auf der ersten Seite uebersprungen`,
    targetCount: maxTargets,
  };
}

export function detailFingerprint(capture: CaptureResult | null) {
  if (!capture) return "";
  if (capture.detailDataUrl) return fingerprintDataUrl(capture.detailDataUrl);
  if (capture.dataUrl) return fingerprintDataUrl(capture.dataUrl);
  return "";
}

export function screenFingerprint(capture: CaptureResult | null) {
  if (capture?.inventoryDataUrl) return fingerprintDataUrl(capture.inventoryDataUrl);
  if (capture?.dataUrl) return fingerprintDataUrl(capture.dataUrl);
  return "";
}

export function isRepeatedProcessedPageFingerprint(
  fingerprint: string,
  seenPageFingerprints: ReadonlySet<string>,
  page: number,
) {
  return page > 1 && Boolean(fingerprint) && seenPageFingerprints.has(fingerprint);
}

export function fingerprintDataUrl(dataUrl: string) {
  let hash = 2166136261;
  const stride = Math.max(1, Math.floor(dataUrl.length / 4096));
  for (let index = 0; index < dataUrl.length; index += stride) {
    hash ^= dataUrl.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return `${dataUrl.length.toString(16)}:${(hash >>> 0).toString(16)}`;
}

function wait(ms: number) {
  return new Promise((resolve) => window.setTimeout(resolve, ms));
}
