type Doc<TMeta = Record<string, unknown>> = {
    id: string;
    fields: Record<string, string>;
    meta?: TMeta;
};
interface Signal<TDoc = Doc, TCtx = unknown> {
    id: string;
    compute(doc: TDoc, ctx: TCtx): number;
    meta?: {
        range?: [number, number];
        description?: string;
    };
}
type CombinerKind = 'product' | 'weighted_sum' | 'log_sum';
type Combiner = CombinerKind | ((scores: Record<string, number>, weights: Record<string, number>) => number);

interface StorageAdapter {
    open(name: string): Promise<void>;
    get<T = unknown>(key: string): Promise<T | undefined>;
    set(key: string, value: unknown): Promise<void>;
    delete(key: string): Promise<void>;
    iterate(prefix: string): AsyncIterable<[string, unknown]>;
    close(): Promise<void>;
}
type StorageKind = 'memory' | 'indexeddb' | 'localstorage' | StorageAdapter;

/**
 * Structural interface a catalog implementation must satisfy to feed
 * `Recommend`. The shipped `Search` class implements this naturally, so
 * the most common wiring is still
 *
 *   const cat = createSearch({...});
 *   const rec = createRecommend({ catalog: cat, ... });
 *
 * but the interface lets hosts plug their own catalog backend in —
 * e.g. a remote-paged vector store, a graph-based retriever, or a
 * raw in-memory list with no BM25 layer at all. Whatever holds your
 * docs and can yield their features for similarity matching works.
 */
interface RecommendCatalog {
    /** Look up a single doc by id. */
    getDoc(id: string): Doc | undefined;
    /** Yield every doc the catalog knows about. Order doesn't matter —
     *  `Recommend` scores then re-sorts. */
    allDocs(): Iterable<Doc>;
    /** Return the feature-vector / token list for the doc with this id,
     *  or `undefined` if absent. Used by `contentSimilarity` and the
     *  preference-match scorer to compute Jaccard-style overlap. */
    getFeatures(id: string): string[] | undefined;
}
type Preference = {
    productId: string;
    response: 'yes' | 'no' | 'dismiss';
    ts: number;
    context?: Record<string, unknown>;
};
type RecommendContext = {
    userId?: string;
    source?: 'similarTo' | 'forCustomer' | 'goesWith';
    sourceProductId?: string | string[];
    now: number;
    prefs?: Preference[];
    customMeta?: Record<string, unknown>;
};
type RecommendScorer = Signal<Doc, RecommendContext> & {
    gate?(doc: Doc, ctx: RecommendContext): boolean;
};
type DiversityOpts = {
    /** meta field to diversify on (e.g. 'category'). */
    field: string;
    /** 0..1 — higher = more diversity. Pure MMR lambda is (1 - this). */
    weight?: number;
    /** how many candidates to consider in MMR pool before slicing topK. */
    poolSize?: number;
};
type RecommendOpts = {
    id?: string;
    /**
     * Doc source. Any object with `getDoc` / `allDocs` / `getFeatures` works
     * — the shipped `Search` class satisfies this out of the box, but hosts
     * can pass a custom backend (vector store, graph retriever, etc.).
     */
    catalog: RecommendCatalog;
    customerId?: string;
    storage?: StorageKind;
    signals: RecommendScorer[];
    weights?: Record<string, number>;
    combiner?: Combiner;
    topK?: number;
    diversity?: DiversityOpts;
    prefsSync?: {
        bootstrap?: () => Promise<Preference[]>;
        pushChange?: (p: Preference) => Promise<void>;
    };
};
declare class Recommend {
    private id;
    private catalog;
    private customerId?;
    private storage;
    private signals;
    private weights;
    private combiner;
    private topK;
    private prefs;
    private opened;
    private opts;
    constructor(opts: RecommendOpts);
    init(): Promise<void>;
    recordPreference(p: Omit<Preference, 'ts'> & {
        ts?: number;
    }): Promise<void>;
    similarTo(productId: string, opts?: {
        topK?: number;
    }): Promise<Array<{
        doc: Doc;
        score: number;
    }>>;
    forCustomer(opts?: {
        topK?: number;
    }): Promise<Array<{
        doc: Doc;
        score: number;
    }>>;
    goesWith(productIds: string[], opts?: {
        topK?: number;
    }): Promise<Array<{
        doc: Doc;
        score: number;
    }>>;
    explain(productId: string, ctx?: Partial<RecommendContext>): {
        docId: string;
        total: number;
        gated: boolean;
        contributions: never[];
    } | {
        docId: string;
        total: number;
        contributions: {
            signalId: string;
            value: number;
            weight: number;
        }[];
        gated?: undefined;
    } | undefined;
    private rank;
    dispose(): Promise<void>;
}
declare function contentSimilarity(opts: {
    source: RecommendCatalog;
}): RecommendScorer;
declare function recencyDecay(opts?: {
    field?: string;
    halfLifeDays?: number;
}): RecommendScorer;
declare function coldStartBayesian(opts?: {
    priorWeight?: number;
    priorRate?: number;
    yesField?: string;
    eventsField?: string;
}): RecommendScorer;
declare function saturation(opts: {
    field: string;
    cap?: number;
}): RecommendScorer;
declare function promoBoost(opts?: {
    field?: string;
}): RecommendScorer;
declare function preferenceMatch(opts?: {
    catalog: RecommendCatalog;
    yesBoost?: number;
    noBoost?: number;
}): RecommendScorer;
declare function diversityPenalty(opts: {
    field: string;
    topNPenalty?: number;
}): RecommendScorer;
declare const builtin: {
    contentSimilarity: typeof contentSimilarity;
    recencyDecay: typeof recencyDecay;
    coldStartBayesian: typeof coldStartBayesian;
    saturation: typeof saturation;
    promoBoost: typeof promoBoost;
    preferenceMatch: typeof preferenceMatch;
    diversityPenalty: typeof diversityPenalty;
};
declare function createRecommend(opts: RecommendOpts): Recommend;

export { type DiversityOpts, type Preference, Recommend, type RecommendCatalog, type RecommendContext, type RecommendOpts, type RecommendScorer, builtin, createRecommend };
