/**
 * SkillsIndexer — global vector index of the skills library so the
 * agent can find the right skill by meaning ("git rebase recovery"
 * → finds the "fix-broken-rebase" skill even if the slug doesn't
 * say "recovery").
 *
 * GLOBAL scope (surface='skills') — the skills library is shared at
 * the user level, not per-agent. Mirrors ComposioIndexer's shape but
 * smaller: typically <50 skills.
 *
 * Failure policy: best-effort, same as the other indexers.
 *
 * Embedding text: name + description + first ~2KB of content. The
 * skill's name + description carry most of the routing signal; content
 * adds a coarse semantic fingerprint without blowing up the token bill
 * for skills that are tens of KB long.
 */

const DEFAULT_CONTENT_PREVIEW_CHARS = 2000;

export function skillToText(skill, { maxContentChars = DEFAULT_CONTENT_PREVIEW_CHARS } = {}) {
  if (!skill) return '';
  const parts = [];
  if (skill.name) parts.push(String(skill.name));
  if (skill.description) parts.push(String(skill.description));
  const content = String(skill.content || '');
  if (content) {
    parts.push(content.length > maxContentChars ? content.slice(0, maxContentChars) : content);
  }
  return parts.join('\n\n');
}

export class SkillsIndexer {
  constructor({ embeddingService, logger = null, maxContentChars = DEFAULT_CONTENT_PREVIEW_CHARS }) {
    this._svc = embeddingService;
    this._logger = logger;
    this._maxContentChars = maxContentChars;
  }

  get isEnabled() {
    return !!(this._svc && this._svc.isEnabled);
  }

  async indexOne(skill) {
    if (!this.isEnabled) return { indexed: false, reason: 'disabled' };
    if (!skill?.name) return { indexed: false, reason: 'invalid-input' };
    const text = skillToText(skill, { maxContentChars: this._maxContentChars });
    if (!text) return { indexed: false, reason: 'empty-text' };

    try {
      const [vec] = await this._svc.embedDocuments([text], 'document');
      const store = await this._svc.getStore({ surface: 'skills' });
      await store.upsert(skill.name, vec, this._buildMetadata(skill, text));
      return { indexed: true };
    } catch (err) {
      this._logger?.warn?.('[skillsIndexer] indexOne failed', { name: skill?.name, error: err?.message });
      return { indexed: false, reason: 'error', error: err?.message };
    }
  }

  async deleteOne(skillName) {
    if (!this.isEnabled) return { deleted: false, reason: 'disabled' };
    if (!skillName) return { deleted: false, reason: 'invalid-input' };
    try {
      const store = await this._svc.getStore({ surface: 'skills' });
      await store.delete(skillName);
      return { deleted: true };
    } catch (err) {
      this._logger?.warn?.('[skillsIndexer] deleteOne failed', { name: skillName, error: err?.message });
      return { deleted: false, reason: 'error', error: err?.message };
    }
  }

  async backfill(skills) {
    if (!this.isEnabled) return { ok: false, reason: 'disabled', indexed: 0, skipped: 0, failed: 0 };
    if (!Array.isArray(skills)) {
      return { ok: false, reason: 'invalid-input', indexed: 0, skipped: 0, failed: 0 };
    }

    const valid = [];
    let skipped = 0;
    for (const s of skills) {
      if (!s?.name) { skipped++; continue; }
      const text = skillToText(s, { maxContentChars: this._maxContentChars });
      if (!text) { skipped++; continue; }
      valid.push({ skill: s, text });
    }

    const store = await this._svc.getStore({ surface: 'skills' });
    try { await store.drop(); }
    catch (err) {
      this._logger?.warn?.('[skillsIndexer] backfill drop failed', { error: err?.message });
    }
    if (valid.length === 0) return { ok: true, indexed: 0, skipped, failed: 0 };

    let vectors;
    try {
      vectors = await this._svc.embedDocuments(valid.map(v => v.text), 'document');
    } catch (err) {
      this._logger?.warn?.('[skillsIndexer] backfill embed failed', { count: valid.length, error: err?.message });
      return { ok: false, indexed: 0, skipped, failed: valid.length, error: err?.message };
    }

    const rows = valid.map((v, i) => ({
      id: v.skill.name,
      vector: vectors[i],
      metadata: this._buildMetadata(v.skill, v.text),
    }));

    try {
      await store.upsertBatch(rows);
      return { ok: true, indexed: rows.length, skipped, failed: 0 };
    } catch (err) {
      this._logger?.warn?.('[skillsIndexer] backfill upsert failed', { error: err?.message });
      return { ok: false, indexed: 0, skipped, failed: rows.length, error: err?.message };
    }
  }

  async search(query, { topK = 5, hybrid = true } = {}) {
    if (!this.isEnabled) return { enabled: false, hits: [] };
    if (typeof query !== 'string' || !query.trim()) return { enabled: true, hits: [] };
    try {
      const store = await this._svc.getStore({ surface: 'skills' });
      const queryVec = await this._svc.embedQuery(query);
      const raw = hybrid
        ? await store.hybridQuery(queryVec, query, { topK })
        : await store.query(queryVec, { topK });
      return { enabled: true, hits: raw };
    } catch (err) {
      this._logger?.warn?.('[skillsIndexer] search failed', { error: err?.message });
      return { enabled: true, hits: [], error: err?.message };
    }
  }

  async stats() {
    if (!this.isEnabled) return { count: 0, enabled: false };
    try {
      const store = await this._svc.getStore({ surface: 'skills' });
      const s = await store.stats();
      return { ...s, enabled: true };
    } catch (err) {
      return { count: 0, enabled: true, error: err?.message };
    }
  }

  _buildMetadata(skill, text) {
    return {
      skillName: skill.name,
      description: skill.description || '',
      text,
      indexedAt: new Date().toISOString(),
    };
  }
}
