// Extracted from src/db/repository.ts (mechanical split — bodies unchanged).
// task-search repository domain. Facade: src/db/repository.ts (Repository class).
import Database from 'better-sqlite3';
import { logger } from '../../logger.js';
import { normalizeCommentToIndexText } from '../../engine/task-index/normalize.js';
import * as localTasksRepo from './local-tasks.js';

  /**
   * Construct a WorkspaceTaskSearchIO scoped to the current task + owner.
   * Used by SearchWorkspaceTasks / ReadWorkspaceTaskAround; searchWorkspaceTasks /
   * readWorkspaceTaskAround themselves enforce the space/owner scoping, this is
   * just the binding closure. fts5Available lets the tool degrade gracefully
   * when the FTS5 index table is missing (e.g. pre-migration DB).
   */
  export function makeWorkspaceTaskSearchIO(db: Database.Database, currentTaskId: number, ownerId: string): import('../../engine/tools/core.js').WorkspaceTaskSearchIO {
    const fts5Available = !!db
      .prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='task_comment_fts'")
      .get();
    return {
      fts5Available,
      search: (query, opts) => searchWorkspaceTasks(db, currentTaskId, ownerId, query, opts),
      around: (commentId, before, after) =>
        readWorkspaceTaskAround(db, currentTaskId, ownerId, commentId, before, after),
    };
  }


  /** best-effort に1コメントを索引する。失敗してもコメント書き込みを壊さない。 */
  export function indexTaskComment(db: Database.Database, commentId: number, taskId: number, author: string, kind: string, createdAt: string, body: string, attachments: string | null): void {
    try {
      const text = normalizeCommentToIndexText(kind, body, attachments);
      if (text === null) return;
      db.prepare(
        `INSERT OR IGNORE INTO task_comment_index (comment_id, task_id, author, kind, created_at, text)
         VALUES (?, ?, ?, ?, ?, ?)`,
      ).run(commentId, taskId, author, kind, createdAt, text);
    } catch (err) {
      logger.warn(`[task-index] indexTaskComment failed comment=${commentId}: ${(err as Error).message}`);
    }
  }


  /**
   * 正規化済みの各語を個別に引用して FTS5 MATCH 文字列を組む（Google 的な暗黙 AND）。
   * 各語をダブルクォートで囲むので、`AND`/`OR`/`NEAR`/`*`/`"` などが語に混じっても
   * FTS5 演算子として解釈されず、インジェクションを防ぐ。語同士はこちらが制御する
   * ` AND ` でつなぐため「全語を含む」検索になる。呼び出し側で引用符除去済みを前提とする。
   */
  export function buildFtsMatch(terms: string[]): string {
    return terms.map((t) => `"${t}"`).join(' AND ');
  }


  export async function searchWorkspaceTasks(db: Database.Database, currentTaskId: number, ownerId: string, query: string, opts: { limit?: number; kind?: string; author?: string; taskId?: number } = {}): Promise<Array<{ taskId: number; taskTitle: string; commentId: number; author: string; kind: string; createdAt: string; text: string }>> {
    const q = (query ?? '').trim();
    if (!q) return [];
    const cur = db.prepare('SELECT space_id, owner_id FROM local_tasks WHERE id = ?')
      .get(currentTaskId) as { space_id: string | null; owner_id: string | null } | undefined;
    if (!cur) return [];

    // スペース帰属確認（owner または space_members メンバー）。非帰属なら空。
    if (cur.space_id) {
      const member = db.prepare(
        `SELECT 1 FROM spaces WHERE id = @s AND owner_id = @u
          UNION SELECT 1 FROM space_members WHERE space_id = @s AND user_id = @u`,
      ).get({ s: cur.space_id, u: ownerId });
      if (!member) return [];
    }

    const limit = Math.min(Math.max(opts.limit ?? 10, 1), 30);
    // Google 的な暗黙 AND: 空白区切りの各語を「すべて含む」ものにヒットさせる。
    // 各語から埋め込み二重引用符を除去（空白化）してトリムし、空になった語は捨てる。
    // これで純粋な記号だけの「語」が AND を汚染して常に0件になるのを防ぐ。
    const terms = q.split(/\s+/).map((t) => t.replace(/"/g, ' ').trim()).filter(Boolean);
    if (terms.length === 0) return []; // 引用符・記号のみで実質的な検索語が無い
    // trigram は3文字未満の語を MATCH できないため、短い語が1つでもあれば LIKE 経路
    // （語ごとの AND-of-LIKE。LIKE は語長に依存せず動く）にフォールバックする。
    const useLike = terms.some((t) => t.length < 3);
    const scope = cur.space_id
      ? 'lt.space_id = @spaceId'
      : 'lt.space_id IS NULL AND lt.owner_id = @ownerId';
    const filters = [
      opts.kind ? 'i.kind = @kind' : '',
      opts.author ? 'i.author = @author' : '',
      opts.taskId ? 'i.task_id = @taskFilter' : '',
    ].filter(Boolean).join(' AND ');
    const filterSql = filters ? ` AND ${filters}` : '';

    const params: Record<string, unknown> = {
      cur: currentTaskId, spaceId: cur.space_id, ownerId,
      kind: opts.kind, author: opts.author, taskFilter: opts.taskId, limit,
    };

    let sql: string;
    if (useLike) {
      // 各語を LIKE で AND 結合（位置・順序を問わず全語を含む）。値はパラメータ束縛、
      // 列名・プレースホルダ名は固定文字列なのでインジェクションしない。
      const likeConds = terms.map((_, i) => `i.text LIKE @like${i}`).join(' AND ');
      terms.forEach((t, i) => { params[`like${i}`] = `%${t}%`; });
      sql = `
        SELECT i.task_id AS taskId, lt.title AS taskTitle, i.comment_id AS commentId,
               i.author, i.kind, i.created_at AS createdAt, i.text
          FROM task_comment_index i
          JOIN local_tasks lt ON lt.id = i.task_id
         WHERE (${likeConds}) AND i.task_id != @cur AND (${scope})${filterSql}
         ORDER BY i.created_at DESC LIMIT @limit`;
    } else {
      params.match = buildFtsMatch(terms);
      sql = `
        SELECT i.task_id AS taskId, lt.title AS taskTitle, i.comment_id AS commentId,
               i.author, i.kind, i.created_at AS createdAt, i.text
          FROM task_comment_fts f
          JOIN task_comment_index i ON i.comment_id = f.rowid
          JOIN local_tasks lt ON lt.id = i.task_id
         WHERE f.text MATCH @match AND i.task_id != @cur AND (${scope})${filterSql}
         ORDER BY bm25(task_comment_fts) LIMIT @limit`;
    }
    return db.prepare(sql).all(params) as any;
  }


  export async function readWorkspaceTaskAround(db: Database.Database, currentTaskId: number, ownerId: string, commentId: number, before: number, after: number) {
    const cur = db.prepare('SELECT space_id, owner_id FROM local_tasks WHERE id = ?')
      .get(currentTaskId) as { space_id: string | null; owner_id: string | null } | undefined;
    if (!cur) return null;
    const target = db.prepare('SELECT task_id FROM local_task_comments WHERE id = ?')
      .get(commentId) as { task_id: number } | undefined;
    if (!target) return null;
    const t = db.prepare('SELECT id, title, space_id, owner_id FROM local_tasks WHERE id = ?')
      .get(target.task_id) as { id: number; title: string; space_id: string | null; owner_id: string | null } | undefined;
    if (!t) return null;
    // スコープ確認: 同一スペース（or 同 owner の個人スペース）
    const inScope = cur.space_id ? t.space_id === cur.space_id : (t.space_id === null && t.owner_id === ownerId);
    if (!inScope) return null;

    const all = await localTasksRepo.listLocalTaskComments(db, target.task_id);
    const pos = all.findIndex((x) => x.id === commentId);
    if (pos < 0) return null;
    const slice = all.slice(Math.max(0, pos - before), pos + after + 1);
    return {
      taskId: t.id, taskTitle: t.title,
      comments: slice.map((x) => ({ commentId: x.id, author: x.author, kind: x.kind, createdAt: x.createdAt, body: x.body, isCenter: x.id === commentId })),
    };
  }
