import { MemoryWorthResult } from './memory-worth.js';

/**
 * Issue #560 PR 4 — Memory Worth recall filter.
 *
 * Pure helper that multiplies candidate recall scores by a Memory Worth
 * factor (from `computeMemoryWorth`, PR 2) so memories with a history of
 * failed sessions sink in the ranking. Reading the per-memory counters is
 * the caller's job — this module does no I/O and depends only on PR 2's
 * pure scorer.
 *
 * The filter is feature-flagged (`recallMemoryWorthFilterEnabled` on
 * PluginConfig, default `false` in this PR) so operators can A/B it safely
 * before PR 5 flips the default.
 *
 * Intentional properties:
 *   - Pure function. No side effects, no I/O. Tested directly.
 *   - Candidates with no counters (empty `counters` map entry) score exactly
 *     the same as they did pre-filter (multiplier = 0.5, but we renormalize
 *     so the neutral prior stays neutral — see the "neutral prior preserves
 *     ranking among unseen memories" test below).
 *   - Stable: a strictly sorted input stays in its original order among
 *     items that all score to the prior. This matters because an ad-hoc
 *     sort that returns 0 for ties on some comparators but not others
 *     produces non-deterministic ordering (CLAUDE.md rule 19).
 *   - Does not mutate inputs — returns a new array.
 *
 * How the multiplier is applied:
 *   `new_score = old_score * (p_success / PRIOR)`
 *   where PRIOR = 0.5. This way an uninstrumented memory (p_success = 0.5)
 *   gets a multiplier of exactly 1.0 (no penalty, no boost), a memory that
 *   always succeeds gets a multiplier approaching 2.0 (boosted) and a
 *   memory that always fails gets a multiplier approaching 0.0 (sunk).
 *   Using the ratio instead of raw `p_success` keeps the filter from
 *   accidentally halving every un-instrumented memory the moment it ships.
 *
 * Out of scope:
 *   - Reading counters from storage (caller does that once per recall).
 *   - Orchestrator wiring / config plumbing (separate commit in this PR).
 *   - Default flip to `true` (PR 5 once benchmark confirms a win).
 */

/**
 * One memory's outcome history, keyed by memory path so the filter can look
 * up a candidate's counters in O(1). `lastAccessed` is passed through to
 * `computeMemoryWorth` where it drives optional recency decay.
 */
interface MemoryWorthCounters {
    mw_success?: number;
    mw_fail?: number;
    lastAccessed?: string | null;
}
/**
 * A scored recall candidate. Defined locally (rather than importing
 * `QmdSearchResult`) so the filter can be reused by any caller that has a
 * `{ path, score }` shape — e.g. unit tests, bench fixtures, and future
 * non-QMD retrieval backends.
 */
interface MemoryWorthFilterCandidate {
    path: string;
    score: number;
}
interface MemoryWorthFilterOptions {
    /**
     * Map from memory path → outcome counters. Candidates whose path is not
     * in this map score at the neutral prior (multiplier = 1.0).
     */
    counters: ReadonlyMap<string, MemoryWorthCounters>;
    /**
     * Current time reference — passed through to `computeMemoryWorth` for
     * decay math. Required (not defaulted) so tests and deterministic bench
     * runs don't depend on the wall clock.
     */
    now: Date;
    /**
     * Half-life for outcome decay, in milliseconds. Optional; when omitted,
     * decay is disabled and raw counters are used.
     */
    halfLifeMs?: number;
    /**
     * Re-sort the candidates by descending filtered score before returning.
     * When `false`, the original input order is preserved (but the `.score`
     * fields still reflect the multiplier). Default `true` because most
     * callers want a ranked result; a few tests / bench fixtures want the
     * order preserved so they can assert on position.
     */
    reorder?: boolean;
}
/**
 * Output of `applyMemoryWorthFilter`. `worth` surfaces the computed
 * `{ score, p_success, confidence }` for each candidate so observability /
 * xray layers can report why each item moved without re-deriving it.
 */
interface MemoryWorthFilterResultItem {
    path: string;
    /** Final score after the Memory Worth multiplier is applied. */
    score: number;
    /** The untouched input score — useful for telemetry and xray. */
    originalScore: number;
    /** The multiplier that was applied (1.0 for uninstrumented memories). */
    multiplier: number;
    /** The Memory Worth result (`score`, `p_success`, `confidence`). */
    worth: MemoryWorthResult;
}
/**
 * Apply the Memory Worth multiplier to each candidate's score and (by
 * default) re-sort the list by descending score.
 *
 * When `counters` is empty, every candidate gets a multiplier of 1.0 — the
 * function is safe to call unconditionally when the feature flag is on,
 * even for namespaces that have zero instrumented memories.
 */
declare function applyMemoryWorthFilter(candidates: readonly MemoryWorthFilterCandidate[], options: MemoryWorthFilterOptions): MemoryWorthFilterResultItem[];
/**
 * Convenience lookup helper for callers that already have an array of
 * memory files with `path` and frontmatter fields on each. Keeps the map
 * construction in one place so call sites don't drift.
 */
declare function buildMemoryWorthCounterMap(memories: readonly {
    path: string;
    frontmatter: {
        mw_success?: number;
        mw_fail?: number;
        lastAccessed?: string | null;
    };
}[]): Map<string, MemoryWorthCounters>;

export { type MemoryWorthCounters, type MemoryWorthFilterCandidate, type MemoryWorthFilterOptions, type MemoryWorthFilterResultItem, applyMemoryWorthFilter, buildMemoryWorthCounterMap };
