import type { ServerResponse } from 'node:http';
import { z } from 'zod';
import type { Finding } from '../types.js';
import type { ScanProgressEvent } from '../rothunter.js';
/**
 * Scan lifecycle store — in-memory cache, SSE fan-out, FIFO queue,
 * per-workspace history cache, on-disk persistence under
 * `<workspace>/.rothunter/scans/<scanId>.json`.
 *
 * All scan-related mutable state lives in this module. The route layer
 * holds no scan state of its own — it calls into these helpers + reads
 * via the `scans` map. Single-thread Node guarantees the bookkeeping
 * is race-free; the queue is FIFO (acquireScanSlot returns immediately
 * when no scan is running, otherwise enqueues a resolver).
 */
/**
 * SSE event shape (server → browser). Single source of truth for the
 * scan-lifecycle wire format. The zod schema is exported so the UI can
 * `z.infer<typeof ScanSseEventSchema>` for its TS type (eliminates the
 * old hand-maintained mirror in the React app that would silently drift
 * when a new state was added on the server).
 */
export declare const ScanVerdictSchema: z.ZodObject<{
    detectorId: z.ZodString;
    race: z.ZodBoolean;
    confidence: z.ZodNumber;
    reason: z.ZodString;
    latencyMs: z.ZodNumber;
    cluster: z.ZodOptional<z.ZodString>;
}, z.core.$strip>;
export declare const ScanSseEventSchema: z.ZodObject<{
    scanId: z.ZodString;
    ts: z.ZodNumber;
    state: z.ZodEnum<{
        error: "error";
        parsing: "parsing";
        detecting: "detecting";
        "llm-start": "llm-start";
        "llm-verdict": "llm-verdict";
        done: "done";
        queued: "queued";
    }>;
    files: z.ZodOptional<z.ZodNumber>;
    symbols: z.ZodOptional<z.ZodNumber>;
    detector: z.ZodOptional<z.ZodString>;
    llmDone: z.ZodOptional<z.ZodNumber>;
    llmTotal: z.ZodOptional<z.ZodNumber>;
    verdict: z.ZodOptional<z.ZodObject<{
        detectorId: z.ZodString;
        race: z.ZodBoolean;
        confidence: z.ZodNumber;
        reason: z.ZodString;
        latencyMs: z.ZodNumber;
        cluster: z.ZodOptional<z.ZodString>;
    }, z.core.$strip>>;
    findings: z.ZodOptional<z.ZodNumber>;
    durationMs: z.ZodOptional<z.ZodNumber>;
    error: z.ZodOptional<z.ZodString>;
}, z.core.$strip>;
export type ScanSseEvent = z.infer<typeof ScanSseEventSchema>;
export type ScanVerdict = z.infer<typeof ScanVerdictSchema>;
/**
 * Aggregate LLM telemetry computed from `verdictLog`. Surfaced in the
 * History view + on the per-scan detail page so operators can see where
 * inference time is going. Kept on `ScanRecord` so historical scans
 * remembered from disk show the same numbers without recomputing the
 * percentiles every request.
 */
export interface LlmStats {
    /** Total verdicts emitted in this scan. */
    calls: number;
    /** Sum of per-verdict latencies, milliseconds. */
    totalLatencyMs: number;
    /** Mean latency per verdict, ms. */
    meanLatencyMs: number;
    /** Median (50th percentile) verdict latency, ms. */
    p50LatencyMs: number;
    /** 95th-percentile verdict latency, ms — the tail that defines UX. */
    p95LatencyMs: number;
    /** Per-detector breakdown. */
    byDetector: Record<string, {
        calls: number;
        totalLatencyMs: number;
        p95LatencyMs: number;
    }>;
}
export declare function summarizeLlmStats(verdicts: ReadonlyArray<ScanVerdict>): LlmStats;
export interface ScanRecord {
    scanId: string;
    workspaceRoot: string;
    state: ScanSseEvent['state'];
    startedAt: number;
    finishedAt?: number;
    detectorsAllow?: string[];
    detectorsDeny?: string[];
    minConfidence: number;
    progress?: ScanSseEvent;
    verdictLog: Array<NonNullable<ScanSseEvent['verdict']>>;
    findings?: Finding[];
    /**
     * Findings whose fingerprint is in the workspace-scoped FP set. They
     * are excluded from the main `findings` array and surfaced in their
     * own section so the operator can still see what was detected without
     * mixing them into the real bug list.
     */
    falsePositives?: Finding[];
    symbolsCount?: number;
    /** File count observed during parsing — used to seed the live dashboard on reconnect (the parsing event itself is one-shot and would be missed by a late subscriber otherwise). */
    filesCount?: number;
    /** Detectors that have completed during the run. Lets the live dashboard repaint the pipeline progress after a reload mid-scan. */
    doneDetectors?: string[];
    /** Currently running detector. Used together with `doneDetectors` to keep the pipeline view accurate on reconnect. */
    activeDetector?: string;
    /** Latest LLM verdict progress — replayed to late subscribers so the counter doesn't reset to 0 on reload. */
    llmDone?: number;
    llmTotal?: number;
    /** Aggregate LLM telemetry, filled at persist time. Older scans on disk lack this — callers should treat it as optional. */
    llmStats?: LlmStats;
    error?: string;
}
export declare const scans: Map<string, ScanRecord>;
export declare const sseClients: Map<string, Set<ServerResponse<import("node:http").IncomingMessage>>>;
export declare const cancelledScans: Set<string>;
export declare function evictOldScans(): void;
export declare const SCAN_QUEUE_LIMIT = 8;
export declare function getRunningScanId(): string | null;
export declare function getScanQueueLength(): number;
export declare function acquireScanSlot(scanId: string): Promise<void>;
export declare function releaseScanSlot(): void;
export declare function dropQueuedScan(scanId: string): boolean;
export declare function broadcast(scanId: string, event: ScanSseEvent): void;
export declare function applyProgressToRecord(record: ScanRecord, event: ScanProgressEvent): ScanSseEvent;
/**
 * Per-workspace cached scan history. Keyed on workspace root + mtime of
 * the scans/ directory; invalidated on persistScan and on workspace
 * switch. Avoids re-reading + JSON-parsing every scan record on every
 * /api/scans, /api/scans/series, /api/symbols/tree call.
 */
interface ScanHistoryCacheEntry {
    mtimeMs: number;
    records: ScanRecord[];
}
export declare const scanHistoryCache: Map<string, ScanHistoryCacheEntry>;
export declare function persistScan(record: ScanRecord): Promise<void>;
export declare function loadScanHistory(workspaceRoot: string): Promise<ScanRecord[]>;
export {};
//# sourceMappingURL=scan-store.d.ts.map