import { FootnotePageLedger, Layout } from '../../contracts/src/index.js';
export type FootnoteWindowScoreReason = 'globally-safe' | 'cluster-spill' | 'new-mandatory-only' | 'candidate-not-improved' | 'page-count-grew' | 'dead-reserve-bloat';
export type FootnotePreferredReserveCandidate = {
    pageIndex: number;
    anchorIds: string[];
    mandatoryReservePx: number;
    preferredReservePx: number;
    reserveDeltaPx: number;
    actualBandHeightPx: number;
    lastAnchorRenderedLines: number;
};
export type FootnoteWindowStats = {
    totalPages: number;
    mandatoryOnlyCount: number;
    deadReserveSum: number;
    clusterSplitCount: number;
    candidateRenderedLines?: number;
    /**
     * Analyzer-only metric. Runtime layout does not know the Word baseline, but
     * the scorer can carry this when a caller has external alignment data.
     */
    driftEvents?: number;
};
export type FootnoteWindowScoreInput = {
    beforeLayout: Layout;
    afterLayout: Layout;
    candidatePageIndex: number;
    candidateAnchorId?: string;
    beforeLedger: FootnotePageLedger[];
    afterLedger: FootnotePageLedger[];
    windowAhead?: number;
    preferredDeltaThresholdPx?: number;
    mandatoryOnlyTolerancePx?: number;
    deadReserveBloatThresholdPx?: number;
    wholeDocumentDeadReserveBloatThresholdPx?: number;
};
export type FootnoteWindowScoreResult = {
    accept: boolean;
    reason: FootnoteWindowScoreReason;
    before: FootnoteWindowStats;
    after: FootnoteWindowStats;
};
export declare const isMandatoryOnlyFootnotePage: (ledger: FootnotePageLedger, preferredDeltaThresholdPx?: number, mandatoryOnlyTolerancePx?: number) => boolean;
/**
 * SD-2656 (post-Vivienne-feedback): a page whose LAST anchor partially rendered
 * but spilled to a later page. The user-visible bug is a footnote split across
 * pages even when the preferred reserve would fit the whole anchor on the
 * anchor page (Word does keep it together).
 *
 * The "mandatory-only" predicate catches first-line-only splits; this predicate
 * catches partial splits (lastAnchorRenderedLines > 1 but the rest still spilled).
 * Both feed into the same scorer trial. The scorer's accept criteria
 * (no new cluster spills, no new mandatory-only pages, bounded dead-reserve
 * growth, candidate rendered lines improved) still gates whether the bump
 * actually lands.
 */
export declare const isSplitLastAnchorFootnotePage: (ledger: FootnotePageLedger, preferredDeltaThresholdPx?: number) => boolean;
export declare const getPreferredReserveCandidates: (ledgers: FootnotePageLedger[], preferredDeltaThresholdPx?: number, mandatoryOnlyTolerancePx?: number) => FootnotePreferredReserveCandidate[];
export declare const getPreferredReserveTrialTargets: (candidate: FootnotePreferredReserveCandidate, currentReservePx: number, preferredDeltaThresholdPx?: number, maxTargets?: number) => number[];
export declare const collectFootnoteLedgers: (layout: Layout) => FootnotePageLedger[];
export declare const summarizeFootnoteWindow: (layout: Layout, ledgers: FootnotePageLedger[], candidatePageIndex: number, windowAhead?: number, preferredDeltaThresholdPx?: number, mandatoryOnlyTolerancePx?: number, candidateAnchorId?: string) => FootnoteWindowStats;
export declare const scoreFootnoteWindow: (input: FootnoteWindowScoreInput) => FootnoteWindowScoreResult;
//# sourceMappingURL=footnote-scorer.d.ts.map