"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/toolbox/recommend/index.ts
var index_exports = {};
__export(index_exports, {
  Recommend: () => Recommend,
  builtin: () => builtin,
  createRecommend: () => createRecommend
});
module.exports = __toCommonJS(index_exports);

// src/toolbox/common/types.ts
function combine(kind, scores, weights) {
  if (typeof kind === "function") return kind(scores, weights);
  const ids = Object.keys(scores);
  if (kind === "product") {
    let p = 1;
    for (const id of ids) {
      const w = weights[id] ?? 1;
      p *= Math.pow(Math.max(0, scores[id] ?? 0), w);
    }
    return p;
  }
  if (kind === "log_sum") {
    let s2 = 0;
    for (const id of ids) {
      const w = weights[id] ?? 1;
      s2 += w * Math.log(Math.max(1e-9, scores[id] ?? 0));
    }
    return Math.exp(s2);
  }
  let s = 0;
  for (const id of ids) {
    const w = weights[id] ?? 1;
    s += w * (scores[id] ?? 0);
  }
  return s;
}

// src/utils/storage/memory.ts
function memoryAdapter() {
  const store = /* @__PURE__ */ new Map();
  return {
    async open() {
    },
    async get(key) {
      return store.get(key);
    },
    async set(key, value) {
      store.set(key, value);
    },
    async delete(key) {
      store.delete(key);
    },
    async *iterate(prefix) {
      for (const [k, v] of store) if (k.startsWith(prefix)) yield [k, v];
    },
    async close() {
      store.clear();
    }
  };
}

// src/utils/storage/local-storage.ts
function localStorageAdapter(prefix = "dddk:") {
  const fullKey = (k) => prefix + k;
  return {
    async open() {
      if (typeof localStorage === "undefined") {
        throw new Error("localStorageAdapter: localStorage is unavailable in this runtime");
      }
    },
    async get(key) {
      const raw = localStorage.getItem(fullKey(key));
      if (raw === null) return void 0;
      try {
        return JSON.parse(raw);
      } catch {
        return raw;
      }
    },
    async set(key, value) {
      localStorage.setItem(fullKey(key), JSON.stringify(value));
    },
    async delete(key) {
      localStorage.removeItem(fullKey(key));
    },
    async *iterate(p) {
      const full = prefix + p;
      for (let i = 0; i < localStorage.length; i++) {
        const k = localStorage.key(i);
        if (!k || !k.startsWith(full)) continue;
        const raw = localStorage.getItem(k);
        if (raw === null) continue;
        let val;
        try {
          val = JSON.parse(raw);
        } catch {
          val = raw;
        }
        yield [k.slice(prefix.length), val];
      }
    },
    async close() {
    }
  };
}

// src/utils/storage/indexeddb.ts
var STORE = "kv";
function promisify(req) {
  return new Promise((resolve, reject) => {
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}
function indexedDBAdapter(dbName = "dddk-toolbox") {
  let db;
  return {
    async open() {
      if (typeof indexedDB === "undefined") {
        throw new Error("indexedDBAdapter: IndexedDB unavailable; falling back recommended for SSR / Node");
      }
      db = await new Promise((resolve, reject) => {
        const req = indexedDB.open(dbName, 1);
        req.onupgradeneeded = () => {
          const d = req.result;
          if (!d.objectStoreNames.contains(STORE)) d.createObjectStore(STORE);
        };
        req.onsuccess = () => resolve(req.result);
        req.onerror = () => reject(req.error);
      });
    },
    async get(key) {
      if (!db) throw new Error("indexedDBAdapter: not opened");
      const tx = db.transaction(STORE, "readonly");
      return await promisify(tx.objectStore(STORE).get(key));
    },
    async set(key, value) {
      if (!db) throw new Error("indexedDBAdapter: not opened");
      const tx = db.transaction(STORE, "readwrite");
      await promisify(tx.objectStore(STORE).put(value, key));
    },
    async delete(key) {
      if (!db) throw new Error("indexedDBAdapter: not opened");
      const tx = db.transaction(STORE, "readwrite");
      await promisify(tx.objectStore(STORE).delete(key));
    },
    async *iterate(prefix) {
      if (!db) throw new Error("indexedDBAdapter: not opened");
      const tx = db.transaction(STORE, "readonly");
      const store = tx.objectStore(STORE);
      const range = IDBKeyRange.bound(prefix, prefix + "\uFFFF");
      const cursorReq = store.openCursor(range);
      while (true) {
        const cur = await new Promise((resolve, reject) => {
          cursorReq.onsuccess = () => resolve(cursorReq.result);
          cursorReq.onerror = () => reject(cursorReq.error);
        });
        if (!cur) break;
        yield [String(cur.key), cur.value];
        cur.continue();
      }
    },
    async close() {
      db?.close();
      db = void 0;
    }
  };
}

// src/utils/storage/index.ts
function resolveStorage(kind) {
  if (!kind || kind === "memory") return memoryAdapter();
  if (kind === "localstorage") return localStorageAdapter();
  if (kind === "indexeddb") {
    if (typeof indexedDB === "undefined") return memoryAdapter();
    return indexedDBAdapter();
  }
  return kind;
}

// src/toolbox/recommend/index.ts
var Recommend = class {
  id;
  catalog;
  customerId;
  storage;
  signals;
  weights;
  combiner;
  topK;
  prefs = [];
  opened = false;
  opts;
  constructor(opts) {
    this.id = opts.id ?? "default";
    this.catalog = opts.catalog;
    this.customerId = opts.customerId;
    this.storage = resolveStorage(opts.storage ?? "indexeddb");
    this.signals = opts.signals;
    this.weights = opts.weights ?? {};
    this.combiner = opts.combiner ?? "product";
    this.topK = opts.topK ?? 10;
    this.opts = opts;
  }
  async init() {
    await this.storage.open(`dddk-recommend-${this.id}`);
    this.opened = true;
    const snap = await this.storage.get("recommend:prefs");
    if (snap) this.prefs = snap;
    else if (this.opts.prefsSync?.bootstrap) {
      this.prefs = await this.opts.prefsSync.bootstrap();
      await this.storage.set("recommend:prefs", this.prefs);
    }
  }
  async recordPreference(p) {
    const pref = { ...p, ts: p.ts ?? Date.now() };
    this.prefs.push(pref);
    if (this.opened) await this.storage.set("recommend:prefs", this.prefs);
    if (this.opts.prefsSync?.pushChange) {
      try {
        await this.opts.prefsSync.pushChange(pref);
      } catch {
      }
    }
  }
  async similarTo(productId, opts = {}) {
    const ctx = {
      userId: this.customerId,
      source: "similarTo",
      sourceProductId: productId,
      now: Date.now(),
      prefs: this.prefs
    };
    return this.rank(ctx, (d) => d.id !== productId, opts.topK ?? this.topK);
  }
  async forCustomer(opts = {}) {
    const ctx = {
      userId: this.customerId,
      source: "forCustomer",
      now: Date.now(),
      prefs: this.prefs
    };
    return this.rank(ctx, () => true, opts.topK ?? this.topK);
  }
  async goesWith(productIds, opts = {}) {
    const ctx = {
      userId: this.customerId,
      source: "goesWith",
      sourceProductId: productIds,
      now: Date.now(),
      prefs: this.prefs
    };
    const set = new Set(productIds);
    return this.rank(ctx, (d) => !set.has(d.id), opts.topK ?? this.topK);
  }
  explain(productId, ctx) {
    const doc = this.catalog.getDoc(productId);
    if (!doc) return void 0;
    const full = {
      userId: this.customerId,
      source: "forCustomer",
      now: Date.now(),
      prefs: this.prefs,
      ...ctx
    };
    const partial = {};
    for (const s of this.signals) {
      if (s.gate && !s.gate(doc, full)) return { docId: productId, total: 0, gated: true, contributions: [] };
      partial[s.id] = s.compute(doc, full);
    }
    const total = combine(this.combiner, partial, this.weights);
    return {
      docId: productId,
      total,
      contributions: Object.entries(partial).map(([signalId, value]) => ({
        signalId,
        value,
        weight: this.weights[signalId] ?? 1
      }))
    };
  }
  rank(ctx, filter, topK) {
    const results = [];
    for (const doc of this.catalog.allDocs()) {
      if (!filter(doc)) continue;
      let passed = true;
      const partial = {};
      for (const s of this.signals) {
        if (s.gate && !s.gate(doc, ctx)) {
          passed = false;
          break;
        }
        partial[s.id] = s.compute(doc, ctx);
      }
      if (!passed) continue;
      const score = combine(this.combiner, partial, this.weights);
      results.push({ doc, score });
    }
    results.sort((a, b) => b.score - a.score);
    const div = this.opts.diversity;
    if (div) {
      const pool = results.slice(0, div.poolSize ?? topK * 5);
      const picked = [];
      const lambda = 1 - (div.weight ?? 0.3);
      while (picked.length < topK && pool.length > 0) {
        let bestIdx = 0;
        let bestMmr = -Infinity;
        for (let i = 0; i < pool.length; i++) {
          const cand = pool[i];
          const candValue = String(cand.doc.meta?.[div.field] ?? "");
          let maxSim = 0;
          for (const p of picked) {
            const pv = String(p.doc.meta?.[div.field] ?? "");
            if (pv && candValue && pv === candValue) {
              maxSim = 1;
              break;
            }
          }
          const mmr = lambda * cand.score - (1 - lambda) * maxSim * (results[0]?.score ?? 1);
          if (mmr > bestMmr) {
            bestMmr = mmr;
            bestIdx = i;
          }
        }
        picked.push(pool.splice(bestIdx, 1)[0]);
      }
      return picked;
    }
    return results.slice(0, topK);
  }
  async dispose() {
    if (this.opened) await this.storage.close();
    this.opened = false;
  }
};
function contentSimilarity(opts) {
  return {
    id: "content_similarity",
    compute(doc, ctx) {
      const sourceId = Array.isArray(ctx.sourceProductId) ? ctx.sourceProductId[0] : ctx.sourceProductId;
      if (!sourceId) return 0;
      const sFeats = opts.source.getFeatures(sourceId);
      const tFeats = opts.source.getFeatures(doc.id);
      if (!sFeats || !tFeats) return 0;
      const sSet = new Set(sFeats);
      let overlap = 0;
      for (const f of tFeats) if (sSet.has(f)) overlap++;
      const denom = Math.sqrt(sFeats.length * tFeats.length) || 1;
      return overlap / denom;
    }
  };
}
function recencyDecay(opts = {}) {
  const field = opts.field ?? "listed_at";
  const half = (opts.halfLifeDays ?? 90) * 864e5;
  return {
    id: "recency_decay",
    compute(doc, ctx) {
      const ts = Number(doc.meta?.[field] ?? 0);
      if (!ts) return 1;
      const age = Math.max(0, ctx.now - ts);
      return Math.pow(0.5, age / half);
    }
  };
}
function coldStartBayesian(opts = {}) {
  const k = opts.priorWeight ?? 50;
  const prior = opts.priorRate ?? 0.5;
  const yes = opts.yesField ?? "lifetime_yes_count";
  const events = opts.eventsField ?? "lifetime_event_count";
  return {
    id: "cold_start_bayesian",
    compute(doc) {
      const y = Number(doc.meta?.[yes] ?? 0);
      const e = Number(doc.meta?.[events] ?? 0);
      return (y + prior * k) / (e + k);
    }
  };
}
function saturation(opts) {
  const cap = opts.cap ?? 200;
  return {
    id: "saturation_" + opts.field,
    compute(doc) {
      const v = Number(doc.meta?.[opts.field] ?? 0);
      return cap * (1 - Math.exp(-v / cap));
    }
  };
}
function promoBoost(opts = {}) {
  const field = opts.field ?? "promo_boost";
  return {
    id: "promo_boost",
    compute(doc) {
      const v = Number(doc.meta?.[field] ?? 1);
      return v > 0 ? v : 1;
    }
  };
}
function preferenceMatch(opts = {}) {
  const yesBoost = opts.yesBoost ?? 2;
  const noBoost = opts.noBoost ?? 0.3;
  return {
    id: "preference_match",
    compute(doc, ctx) {
      const prefs = ctx.prefs ?? [];
      if (prefs.length === 0) return 1;
      const targetFeats = new Set(opts.catalog.getFeatures(doc.id) ?? []);
      let yesOverlap = 0;
      let noOverlap = 0;
      for (const p of prefs) {
        const f = opts.catalog.getFeatures(p.productId);
        if (!f) continue;
        let overlap = 0;
        for (const x of f) if (targetFeats.has(x)) overlap++;
        if (p.response === "yes") yesOverlap += overlap;
        else if (p.response === "no") noOverlap += overlap;
      }
      const yesFactor = 1 + (yesBoost - 1) * Math.tanh(yesOverlap / 10);
      const noFactor = noBoost + (1 - noBoost) * Math.exp(-noOverlap / 10);
      return yesFactor * noFactor;
    }
  };
}
function diversityPenalty(opts) {
  void opts;
  return {
    id: "diversity_penalty",
    compute: () => 1
  };
}
var builtin = {
  contentSimilarity,
  recencyDecay,
  coldStartBayesian,
  saturation,
  promoBoost,
  preferenceMatch,
  diversityPenalty
};
function createRecommend(opts) {
  return new Recommend(opts);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Recommend,
  builtin,
  createRecommend
});
