export type AutoScanStats = {
  clicked: number;
  attempted: number;
  verified: number;
  parsed: number;
  stored: number;
  review: number;
  duplicates: number;
  misses: number;
  pages: number;
  elapsedMs: number;
  activeScanMs: number;
  writeFlushMs: number;
  averageMsPerParsed: number;
  activeAverageMsPerParsed: number;
  artifactsPerMinute: number;
  activeArtifactsPerMinute: number;
  projectedMsFor100: number;
  activeProjectedMsFor100: number;
  captureMs: number;
  captureRoundTripMs: number;
  captureRoundTripOverheadMs: number;
  ocrMs: number;
  averageCaptureMs: number;
  averageCaptureRoundTripMs: number;
  averageCaptureRoundTripOverheadMs: number;
  averageOcrMs: number;
  clickMs: number;
  averageClickMs: number;
  parseMs: number;
  averageParseMs: number;
  loopOverheadMs: number;
  averageLoopOverheadMs: number;
  captureP50Ms: number;
  captureP90Ms: number;
  ocrP50Ms: number;
  ocrP90Ms: number;
  cardReadyMs: number;
  cardReadyCount: number;
  averageCardReadyMs: number;
  scrollReadyMs: number;
  scrollReadyCount: number;
  averageScrollReadyMs: number;
};

export type ScanSummary = AutoScanStats & {
  mode: string;
  status: "done" | "stopped" | "blocked";
  targetCount?: number;
  gridLabel?: string;
};

export const emptyAutoScanStats: AutoScanStats = {
  clicked: 0,
  attempted: 0,
  verified: 0,
  parsed: 0,
  stored: 0,
  review: 0,
  duplicates: 0,
  misses: 0,
  pages: 0,
  elapsedMs: 0,
  activeScanMs: 0,
  writeFlushMs: 0,
  averageMsPerParsed: 0,
  activeAverageMsPerParsed: 0,
  artifactsPerMinute: 0,
  activeArtifactsPerMinute: 0,
  projectedMsFor100: 0,
  activeProjectedMsFor100: 0,
  captureMs: 0,
  captureRoundTripMs: 0,
  captureRoundTripOverheadMs: 0,
  ocrMs: 0,
  averageCaptureMs: 0,
  averageCaptureRoundTripMs: 0,
  averageCaptureRoundTripOverheadMs: 0,
  averageOcrMs: 0,
  clickMs: 0,
  averageClickMs: 0,
  parseMs: 0,
  averageParseMs: 0,
  loopOverheadMs: 0,
  averageLoopOverheadMs: 0,
  captureP50Ms: 0,
  captureP90Ms: 0,
  ocrP50Ms: 0,
  ocrP90Ms: 0,
  cardReadyMs: 0,
  cardReadyCount: 0,
  averageCardReadyMs: 0,
  scrollReadyMs: 0,
  scrollReadyCount: 0,
  averageScrollReadyMs: 0,
};

const timingSamples = new WeakMap<AutoScanStats, { captureMs: number[]; ocrMs: number[] }>();

export function updateScanTiming(
  stats: AutoScanStats,
  startedAt: number,
  now = Date.now(),
  options: { preserveActiveScanMs?: boolean } = {},
) {
  const elapsedMs = Math.max(0, Math.round(now - startedAt));
  stats.elapsedMs = elapsedMs;
  if (options.preserveActiveScanMs) {
    if (!stats.activeScanMs || stats.activeScanMs > elapsedMs) stats.activeScanMs = elapsedMs;
  } else {
    stats.activeScanMs = elapsedMs;
  }
  stats.averageMsPerParsed = stats.parsed > 0 ? Math.round(elapsedMs / stats.parsed) : 0;
  stats.activeAverageMsPerParsed = stats.parsed > 0 ? Math.round(stats.activeScanMs / stats.parsed) : 0;
  stats.artifactsPerMinute = elapsedMs > 0 && stats.parsed > 0
    ? Math.round((stats.parsed * 60000 / elapsedMs) * 10) / 10
    : 0;
  stats.activeArtifactsPerMinute = stats.activeScanMs > 0 && stats.parsed > 0
    ? Math.round((stats.parsed * 60000 / stats.activeScanMs) * 10) / 10
    : 0;
  stats.projectedMsFor100 = stats.averageMsPerParsed > 0 ? stats.averageMsPerParsed * 100 : 0;
  stats.activeProjectedMsFor100 = stats.activeAverageMsPerParsed > 0 ? stats.activeAverageMsPerParsed * 100 : 0;
  stats.averageCaptureMs = stats.verified > 0 ? Math.round(stats.captureMs / stats.verified) : 0;
  stats.averageCaptureRoundTripMs = stats.verified > 0 ? Math.round(stats.captureRoundTripMs / stats.verified) : 0;
  stats.captureRoundTripOverheadMs = Math.max(0, stats.captureRoundTripMs - stats.captureMs);
  stats.averageCaptureRoundTripOverheadMs = stats.verified > 0 ? Math.round(stats.captureRoundTripOverheadMs / stats.verified) : 0;
  stats.averageOcrMs = stats.verified > 0 ? Math.round(stats.ocrMs / stats.verified) : 0;
  stats.averageClickMs = stats.clicked > 0 ? Math.round(stats.clickMs / stats.clicked) : 0;
  stats.averageParseMs = stats.parsed > 0 ? Math.round(stats.parseMs / stats.parsed) : 0;
  stats.loopOverheadMs = Math.max(0, stats.activeScanMs - stats.captureMs - stats.clickMs - stats.parseMs - stats.cardReadyMs - stats.scrollReadyMs);
  stats.averageLoopOverheadMs = stats.parsed > 0 ? Math.round(stats.loopOverheadMs / stats.parsed) : 0;
  const samples = timingSamples.get(stats);
  stats.captureP50Ms = percentile(samples?.captureMs, 50);
  stats.captureP90Ms = percentile(samples?.captureMs, 90);
  stats.ocrP50Ms = percentile(samples?.ocrMs, 50);
  stats.ocrP90Ms = percentile(samples?.ocrMs, 90);
  stats.averageCardReadyMs = stats.cardReadyCount > 0 ? Math.round(stats.cardReadyMs / stats.cardReadyCount) : 0;
  stats.averageScrollReadyMs = stats.scrollReadyCount > 0 ? Math.round(stats.scrollReadyMs / stats.scrollReadyCount) : 0;
  return stats;
}

export function addCaptureTiming(
  stats: AutoScanStats,
  timing?: { totalMs?: number; ocrMs?: number } | null,
  elapsedMs?: number | null,
) {
  if (!timing) return stats;
  const captureMs = Math.max(0, Math.round(timing.totalMs ?? 0));
  const ocrMs = Math.max(0, Math.round(timing.ocrMs ?? 0));
  stats.captureMs += captureMs;
  if (typeof elapsedMs === "number" && Number.isFinite(elapsedMs)) {
    stats.captureRoundTripMs += Math.max(0, Math.round(elapsedMs));
  }
  stats.ocrMs += ocrMs;
  const samples = timingSamples.get(stats) ?? { captureMs: [], ocrMs: [] };
  samples.captureMs.push(captureMs);
  samples.ocrMs.push(ocrMs);
  timingSamples.set(stats, samples);
  return stats;
}

export function addCardReadyTiming(stats: AutoScanStats, elapsedMs: number) {
  stats.cardReadyMs += Math.max(0, Math.round(elapsedMs));
  stats.cardReadyCount += 1;
  return stats;
}

export function addScrollReadyTiming(stats: AutoScanStats, elapsedMs: number) {
  stats.scrollReadyMs += Math.max(0, Math.round(elapsedMs));
  stats.scrollReadyCount += 1;
  return stats;
}

function percentile(values: readonly number[] | undefined, p: number) {
  if (!values || values.length === 0) return 0;
  const sorted = [...values].sort((left, right) => left - right);
  const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
  return sorted[index];
}

export function clampScanLimit(value: number) {
  return Math.max(1, Math.min(1800, Math.round(value || 1)));
}

export function clampSkipRows(value: number) {
  return Math.max(0, Math.min(8, Math.round(value || 0)));
}

export function resolveScanTargetCount(scanLimit: number, detectedInventoryCount?: number | null) {
  const detected = Number.isFinite(detectedInventoryCount) ? Number(detectedInventoryCount) : 0;
  const requested = clampScanLimit(scanLimit);
  if (detected > 0) return clampScanLimit(Math.min(requested, detected));
  return requested;
}
