type Doc<TMeta = Record<string, unknown>> = {
    id: string;
    fields: Record<string, string>;
    meta?: TMeta;
};
interface Adapter<R, D = Doc> {
    fromRow(row: R): D;
    toRow?(doc: D, original?: R): R;
}
declare const identityAdapter: Adapter<Doc, Doc>;
type DeltaOp<TRow = unknown> = {
    op: 'add';
    row: TRow;
} | {
    op: 'update';
    row: TRow;
} | {
    op: 'remove';
    id: string;
};
type DeltaPayload<TRow = unknown> = {
    version: string | number;
    changes: DeltaOp<TRow>[];
};
type BootstrapPayload<TRow = unknown> = {
    version: string | number;
    rows: TRow[];
};
type SyncConfig<TRow = unknown> = {
    bootstrap?: () => Promise<BootstrapPayload<TRow>>;
    fetchDelta?: (sinceVersion: string | number) => Promise<DeltaPayload<TRow>>;
    pollIntervalMs?: number;
    subscribe?: (onChange: (delta: DeltaPayload<TRow>) => void) => () => void;
};
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);
type ScoreExplanation = {
    docId: string;
    total: number;
    contributions: Array<{
        signalId: string;
        value: number;
        weight: number;
    }>;
};
type IdentityProvider = () => Record<string, unknown> | undefined;
declare function nfkc(s: string): string;
declare function clamp(n: number, lo: number, hi: number): number;
declare function combine(kind: Combiner, scores: Record<string, number>, weights: Record<string, number>): number;

type Posting = {
    docId: string;
    fieldFreqs: Record<string, number>;
};
/** Tokeniser signature — feeds the inverted index. */
type Extract = (text: string) => string[];
declare class SearchStore {
    postings: Map<string, Posting[]>;
    docs: Map<string, Doc>;
    fieldStats: Map<string, {
        totalLength: number;
        docCount: number;
    }>;
    totalDocs: number;
    features: Map<string, string[]>;
    add(doc: Doc, extract: Extract): void;
    remove(docId: string): void;
    candidates(queryFeats: string[]): Set<string>;
    postingsFor(feature: string): Posting[] | undefined;
    avgFieldLen(field: string): 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;

type SearchContext = {
    query: string;
    queryFeatures: string[];
    now: number;
    userId?: string;
    customMeta?: Record<string, unknown>;
    /** Internal — injected by Search.query for scorers that need store access (e.g. bm25Field). */
    _store?: SearchStore;
};
type SearchScorer = Signal<Doc, SearchContext> & {
    /**
     * Optional gate: if returns false, the doc is filtered out entirely
     * (e.g. inventoryFilter for out-of-stock items).
     */
    gate?(doc: Doc, ctx: SearchContext): boolean;
};
type SearchOpts<TRow = unknown> = {
    id?: string;
    adapter?: Adapter<TRow, Doc>;
    storage?: StorageKind;
    sync?: SyncConfig<TRow>;
    /**
     * Optional host-supplied tokeniser. Same function is used to index docs
     * AND to parse queries — symmetry is what makes BM25 matching work.
     *
     * Default: a Unicode-aware universal tokeniser (NFKC normalise +
     * whitespace split for whitespace scripts; codepoint + bigram for CJK,
     * Thai, Lao, Khmer, Burmese; lowercase throughout). Handles 200+
     * languages out of the box with no per-locale packs.
     *
     * Plug in your own when you have domain-specific tokenisation rules
     * (e.g. medical term splitter, product SKU boundaries, ICD codes).
     */
    extractFeatures?: (text: string) => string[];
    scorers: SearchScorer[];
    combiner?: Combiner;
    weights?: Record<string, number>;
    topK?: number;
    scoreThreshold?: number;
};
type SearchResult<TDoc = Doc> = {
    doc: TDoc;
    score: number;
    explanation?: ScoreExplanation;
};
type QueryOpts = {
    topK?: number;
    explain?: boolean;
    scoreThreshold?: number;
    customMeta?: Record<string, unknown>;
};

type FieldWeightConfig = {
    weight: number;
    saturation?: number;
};
type BM25FieldOpts = {
    weights: Record<string, FieldWeightConfig>;
    k1?: number;
    b?: number;
};
/**
 * Per-field BM25 with saturation. Reads the store from ctx._store (injected by Search).
 */
declare function bm25Field(opts: BM25FieldOpts): SearchScorer;
type RecencyOpts = {
    field: string;
    halfLifeDays?: number;
};
declare function recencyDecay$1(opts: RecencyOpts): SearchScorer;
type NewProductBoostOpts = {
    field: string;
    withinDays?: number;
    multiplier?: number;
};
declare function newProductBoost(opts: NewProductBoostOpts): SearchScorer;
type BestsellerBoostOpts = {
    field: string;
    method?: 'log_normalize' | 'rank_inverse';
    maxBoost?: number;
};
declare function bestsellerBoost(opts: BestsellerBoostOpts): SearchScorer;
declare function promoBoost$1(opts?: {
    field?: string;
}): SearchScorer;
declare function inventoryFilter(opts?: {
    field?: string;
    min?: number;
}): SearchScorer;
declare function activeFilter(opts?: {
    field?: string;
}): SearchScorer;

/**
 * learn-to-rank — logistic regression over per-field BM25 contributions.
 *
 * Input: click events `{query, shown: [{docId, fields_score: {title, brand, ...}}], clicked: docId}`
 * Output: per-field weights that maximize click-through.
 *
 * Algorithm: minibatch gradient descent on binary cross-entropy with L2.
 * Pure TS, no external ML deps. Good enough for ~100k events.
 */
type ClickEvent = {
    query: string;
    shown: Array<{
        docId: string;
        fieldScores: Record<string, number>;
    }>;
    clickedDocId: string;
};
type LearnOpts = {
    fields: string[];
    iterations?: number;
    learningRate?: number;
    l2?: number;
    batchSize?: number;
};
type LearnedWeights = {
    weights: Record<string, number>;
    loss: number;
    iterations: number;
};
declare function learnRankingWeights(events: ClickEvent[], opts: LearnOpts): LearnedWeights;

declare const builtin$1: {
    bm25Field: typeof bm25Field;
    recencyDecay: typeof recencyDecay$1;
    newProductBoost: typeof newProductBoost;
    bestsellerBoost: typeof bestsellerBoost;
    promoBoost: typeof promoBoost$1;
    inventoryFilter: typeof inventoryFilter;
    activeFilter: typeof activeFilter;
};

declare class Search<TRow = unknown> {
    private id;
    private store;
    private adapter;
    private storage;
    private scorers;
    private weights;
    private extract;
    private combiner;
    private topK;
    private scoreThreshold;
    private opts;
    private opened;
    private lowConfHook?;
    private pollTimer?;
    private unsubscribe?;
    constructor(opts: SearchOpts<TRow>);
    init(): Promise<void>;
    addDoc(row: TRow): Promise<void>;
    addDocs(rows: TRow[]): Promise<void>;
    removeDoc(id: string): Promise<void>;
    updateDoc(row: TRow): Promise<void>;
    query(text: string, opts?: QueryOpts): Promise<SearchResult[]>;
    explain(text: string, docId: string): {
        docId: string;
        total: number;
        gated: boolean;
        contributions: never[];
    } | {
        docId: string;
        total: number;
        contributions: {
            signalId: string;
            value: number;
            weight: number;
        }[];
        gated?: undefined;
    } | undefined;
    onLowConfidence(fn: (q: string, c: SearchResult[]) => Promise<SearchResult[]>): void;
    snapshot(): Promise<{
        docs: Doc[];
    }>;
    restore(snap: {
        docs: Doc[];
    }): Promise<void>;
    dispose(): Promise<void>;
    getDoc(id: string): Doc | undefined;
    allDocs(): Iterable<Doc>;
    getFeatures(id: string): string[] | undefined;
    _store(): SearchStore;
    private persist;
    private startSync;
    private applyDeltaPayload;
}
declare function createSearch<TRow = unknown>(opts: SearchOpts<TRow>): Search<TRow>;

type index$2_ClickEvent = ClickEvent;
type index$2_LearnOpts = LearnOpts;
type index$2_LearnedWeights = LearnedWeights;
type index$2_QueryOpts = QueryOpts;
type index$2_Search<TRow = unknown> = Search<TRow>;
declare const index$2_Search: typeof Search;
type index$2_SearchContext = SearchContext;
type index$2_SearchOpts<TRow = unknown> = SearchOpts<TRow>;
type index$2_SearchResult<TDoc = Doc> = SearchResult<TDoc>;
type index$2_SearchScorer = SearchScorer;
declare const index$2_createSearch: typeof createSearch;
declare const index$2_learnRankingWeights: typeof learnRankingWeights;
declare namespace index$2 {
  export { type index$2_ClickEvent as ClickEvent, type index$2_LearnOpts as LearnOpts, type index$2_LearnedWeights as LearnedWeights, type index$2_QueryOpts as QueryOpts, index$2_Search as Search, type index$2_SearchContext as SearchContext, type index$2_SearchOpts as SearchOpts, type index$2_SearchResult as SearchResult, type index$2_SearchScorer as SearchScorer, builtin$1 as builtin, index$2_createSearch as createSearch, index$2_learnRankingWeights as learnRankingWeights };
}

/**
 * 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;

type index$1_DiversityOpts = DiversityOpts;
type index$1_Preference = Preference;
type index$1_Recommend = Recommend;
declare const index$1_Recommend: typeof Recommend;
type index$1_RecommendCatalog = RecommendCatalog;
type index$1_RecommendContext = RecommendContext;
type index$1_RecommendOpts = RecommendOpts;
type index$1_RecommendScorer = RecommendScorer;
declare const index$1_builtin: typeof builtin;
declare const index$1_createRecommend: typeof createRecommend;
declare namespace index$1 {
  export { type index$1_DiversityOpts as DiversityOpts, type index$1_Preference as Preference, index$1_Recommend as Recommend, type index$1_RecommendCatalog as RecommendCatalog, type index$1_RecommendContext as RecommendContext, type index$1_RecommendOpts as RecommendOpts, type index$1_RecommendScorer as RecommendScorer, index$1_builtin as builtin, index$1_createRecommend as createRecommend };
}

/**
 * Toolbox-internal shared types (Doc / Adapter / Combiner / sync payloads).
 * No language or scoring logic — those live in `dddk/utils/text` so the
 * agent layer can use them without depending on toolbox.
 */

type index_Adapter<R, D = Doc> = Adapter<R, D>;
type index_BootstrapPayload<TRow = unknown> = BootstrapPayload<TRow>;
type index_Combiner = Combiner;
type index_CombinerKind = CombinerKind;
type index_DeltaOp<TRow = unknown> = DeltaOp<TRow>;
type index_DeltaPayload<TRow = unknown> = DeltaPayload<TRow>;
type index_Doc<TMeta = Record<string, unknown>> = Doc<TMeta>;
type index_IdentityProvider = IdentityProvider;
type index_ScoreExplanation = ScoreExplanation;
type index_Signal<TDoc = Doc, TCtx = unknown> = Signal<TDoc, TCtx>;
type index_SyncConfig<TRow = unknown> = SyncConfig<TRow>;
declare const index_clamp: typeof clamp;
declare const index_combine: typeof combine;
declare const index_identityAdapter: typeof identityAdapter;
declare const index_nfkc: typeof nfkc;
declare namespace index {
  export { type index_Adapter as Adapter, type index_BootstrapPayload as BootstrapPayload, type index_Combiner as Combiner, type index_CombinerKind as CombinerKind, type index_DeltaOp as DeltaOp, type index_DeltaPayload as DeltaPayload, type index_Doc as Doc, type index_IdentityProvider as IdentityProvider, type index_ScoreExplanation as ScoreExplanation, type index_Signal as Signal, type index_SyncConfig as SyncConfig, index_clamp as clamp, index_combine as combine, index_identityAdapter as identityAdapter, index_nfkc as nfkc };
}

export { index as common, index$1 as recommend, index$2 as search };
