/**
 * TaskIndexer — keeps a per-agent vector index of the agent's task list
 * so the agent can find related tasks by meaning ("auth refactor work"
 * → all tasks tagged auth, login, sign-in, OAuth) instead of by exact
 * substring on title.
 *
 * Per-agent scope (surface='tasks'). Each agent's tasks are private to
 * that agent — no cross-agent visibility. Mirror of MemoryIndexer's
 * single-item-per-task shape (tasks are short enough not to need
 * chunking).
 *
 * Failure policy: best-effort. Same as the other indexers.
 *
 * Embedding text: title + description + (priority/status tags). Keeps
 * the embedding focused on what the agent typed, with the operational
 * status as a weak signal.
 */

import { makeScoringFn } from '../vectorStore/scoring.js';

export function taskToText(task) {
  if (!task) return '';
  const parts = [];
  if (task.title) parts.push(String(task.title));
  if (task.description) parts.push(String(task.description));
  // Tags as a trailing line — present so they participate in retrieval
  // ("show me pending high-priority tasks about auth") but don't
  // dominate the front of the embedding.
  const tags = [];
  if (task.status) tags.push(`status:${task.status}`);
  if (task.priority) tags.push(`priority:${task.priority}`);
  if (Array.isArray(task.tags) && task.tags.length > 0) tags.push(...task.tags);
  if (tags.length > 0) parts.push(`[${tags.join(' ')}]`);
  return parts.join('\n');
}

export class TaskIndexer {
  constructor({ embeddingService, logger = null }) {
    this._svc = embeddingService;
    this._logger = logger;
  }

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

  async indexOne(agentId, task) {
    if (!this.isEnabled) return { indexed: false, reason: 'disabled' };
    if (!agentId || !task?.id) return { indexed: false, reason: 'invalid-input' };
    const text = taskToText(task);
    if (!text) return { indexed: false, reason: 'empty-text' };

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

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

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

    const valid = [];
    let skipped = 0;
    for (const t of tasks) {
      if (!t?.id) { skipped++; continue; }
      const text = taskToText(t);
      if (!text) { skipped++; continue; }
      valid.push({ task: t, text });
    }

    const store = await this._svc.getStore({ agentId, surface: 'tasks' });
    try { await store.drop(); }
    catch (err) {
      this._logger?.warn?.('[taskIndexer] backfill drop failed', { agentId, 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?.('[taskIndexer] backfill embed failed', { agentId, 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.task.id,
      vector: vectors[i],
      metadata: this._buildMetadata(v.task, v.text),
    }));

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

  async search(agentId, query, { topK = 10, hybrid = true, statusFilter = null, boost = 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({ agentId, surface: 'tasks' });
      const queryVec = await this._svc.embedQuery(query);
      const filter = statusFilter
        ? (m) => m?.status === statusFilter
        : null;
      // Recent tasks tend to be more relevant than a year-old completed
      // one with the same cosine score.
      const scoring = boost ? makeScoringFn({ recency: true }) : null;
      const raw = hybrid
        ? await store.hybridQuery(queryVec, query, { topK, filter, scoring })
        : await store.query(queryVec, { topK, filter, scoring });
      return { enabled: true, hits: raw };
    } catch (err) {
      this._logger?.warn?.('[taskIndexer] search failed', { agentId, error: err?.message });
      return { enabled: true, hits: [], error: err?.message };
    }
  }

  _buildMetadata(task, text) {
    return {
      taskId: task.id,
      title: task.title || '',
      description: task.description || '',
      status: task.status || '',
      priority: task.priority || '',
      createdAt: task.createdAt || new Date().toISOString(),
      text,
    };
  }
}
