/**
 * SearchWorkspaceTasks — cross-task conversation search within the same workspace/space.
 *
 * Unlike SearchTaskConversation (task-conversation.ts), which is scoped to the
 * CURRENT task's own comments + transcript, this tool searches OTHER tasks'
 * comments in the same space via a shared FTS5 index. Scoping to "same space
 * the owner can see" is enforced entirely by the bound `ctx.workspaceTaskSearch`
 * IO (constructed by the worker layer) — this module never touches the DB
 * directly, so it can't widen or bypass that scope.
 *
 * Degrades gracefully (non-error, friendly message) in two situations:
 *   - `ctx.workspaceTaskSearch` unset (subtask / gitea-issue runs not bound to
 *     a local task + owner).
 *   - `fts5Available === false` (environment's sqlite build lacks FTS5). We
 *     must not call `search()` in that case — the underlying virtual table
 *     doesn't exist and querying it throws "no such table".
 */

import { ToolDef } from '../../llm/openai-compat.js';
import type { ToolContext, ToolResult } from './core.js';
import { excerpt, tokenize } from './text-excerpt.js';

const SEARCH_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'SearchWorkspaceTasks',
    description:
      '同じワークスペース内の他タスクの会話をキーワード横断検索し、出典 ref (comment:<id>) 付きの抜粋を返す。詳細は ReadToolDoc({ name: "SearchWorkspaceTasks" })。',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: '検索キーワード。複数語はスペース区切りで暗黙 AND (全語を含むものにヒット)。around_ref とは排他 (どちらか一方のみ指定)。' },
        around_ref: { type: 'string', description: '"comment:<id>" 形式。指定した comment の前後を読む。query とは排他。' },
        context: { type: 'number', description: '各ヒットに付す前後コメント件数 (grep -C 相当)。既定 0、最大 5。' },
        limit: { type: 'number', description: '最大ヒット件数。既定 10、最大 30。' },
        kind: { type: 'string', description: 'コメント種別で絞り込む (request/comment/interjection/result/ask/progress/handoff)。' },
        author: { type: 'string', description: '発言者で絞り込む。' },
        task_id: { type: 'number', description: '特定タスク ID に絞り込む。' },
      },
    },
  },
};

export const TOOL_DEFS: Record<string, ToolDef> = {
  SearchWorkspaceTasks: SEARCH_DEF,
};

const NO_CONTEXT_MSG =
  'ワークスペース横断検索はこのコンテキストでは利用できません (subtask など local_task と紐付かない実行)。現在見えている文脈で判断してください。';

const FTS5_UNAVAILABLE_MSG =
  'ワークスペース横断検索はこの環境では利用不可です (FTS5 インデックスが存在しません)。現在見えている文脈で判断してください。';

const EXCERPT_MAX = 200;

function clampLimit(v: unknown, def: number, max: number): number {
  const n = typeof v === 'number' && Number.isFinite(v) ? Math.floor(v) : def;
  return Math.min(Math.max(n, 1), max);
}

function clampContext(v: unknown, def: number, max: number): number {
  const n = typeof v === 'number' && Number.isFinite(v) ? Math.floor(v) : def;
  return Math.min(Math.max(n, 0), max);
}

async function search(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
  const io = ctx.workspaceTaskSearch;
  if (!io) return { output: NO_CONTEXT_MSG, isError: false };

  const query = typeof input['query'] === 'string' ? (input['query'] as string) : undefined;
  const aroundRef = typeof input['around_ref'] === 'string' ? (input['around_ref'] as string) : undefined;

  const hasQuery = !!query && query.trim().length > 0;
  const hasAroundRef = !!aroundRef && aroundRef.trim().length > 0;

  if (hasQuery === hasAroundRef) {
    return {
      output: 'query と around_ref はどちらか一方のみ指定してください (両方または両方未指定は不可)。',
      isError: true,
    };
  }

  const context = clampContext(input['context'], 0, 5);

  if (hasAroundRef) {
    return searchAroundRef(aroundRef!, context, io);
  }

  // query mode
  if (io.fts5Available === false) {
    return { output: FTS5_UNAVAILABLE_MSG, isError: false };
  }

  if (!query!.trim()) {
    return { output: 'query が空です。検索キーワードを指定してください。', isError: true };
  }

  const limit = clampLimit(input['limit'], 10, 30);
  const kind = typeof input['kind'] === 'string' ? (input['kind'] as string) : undefined;
  const author = typeof input['author'] === 'string' ? (input['author'] as string) : undefined;
  const taskId = typeof input['task_id'] === 'number' && Number.isFinite(input['task_id']) ? (input['task_id'] as number) : undefined;

  const hits = await io.search(query!, { limit, kind, author, taskId });

  if (hits.length === 0) {
    return { output: `## Workspace Task Search Results\n\n"${query}" に一致する会話は見つかりませんでした。`, isError: false };
  }

  const lines = [`## Workspace Task Search Results (query: "${query}", ${hits.length} hit(s))`, ''];
  for (const h of hits) {
    lines.push(`- task:${h.taskId} "${h.taskTitle}" / comment:${h.commentId} ${h.author}/${h.kind} ${h.createdAt}`);
    lines.push(`  ${excerpt(h.text, tokenize(query!), EXCERPT_MAX)}`);
    if (context > 0) {
      const around = await io.around(h.commentId, context, context);
      if (around) {
        for (const c of around.comments) {
          const marker = c.isCenter ? ' ←' : '';
          lines.push(`    - comment:${c.commentId} ${c.author}/${c.kind} ${c.createdAt}${marker}`);
          lines.push(`      ${excerpt(c.body, [], EXCERPT_MAX)}`);
        }
      }
    }
    lines.push('');
  }
  lines.push('前後の文脈をさらに確認するには around_ref: "comment:<id>" を指定してください。');
  return { output: lines.join('\n'), isError: false };
}

async function searchAroundRef(ref: string, context: number, io: NonNullable<ToolContext['workspaceTaskSearch']>): Promise<ToolResult> {
  const m = /^comment:(\d+)$/.exec(ref.trim());
  if (!m) {
    return { output: 'around_ref が不正です。"comment:<id>" の形式で指定してください。', isError: true };
  }
  const id = Number(m[1]);
  const window = context > 0 ? context : 2;

  const around = await io.around(id, window, window);
  if (!around) {
    return { output: `comment:${id} は見つからないか、このワークスペースのスコープ外です。`, isError: false };
  }

  const lines = [`## Workspace Task Search — task:${around.taskId} "${around.taskTitle}" (comment:${id} 前後 ${window} 件)`, ''];
  for (const c of around.comments) {
    const marker = c.isCenter ? ' ←' : '';
    lines.push(`- comment:${c.commentId} ${c.author}/${c.kind} ${c.createdAt}${marker}`);
    lines.push(`  ${excerpt(c.body, [], EXCERPT_MAX)}`);
  }
  return { output: lines.join('\n'), isError: false };
}

export async function executeTool(
  name: string,
  input: Record<string, unknown>,
  ctx: ToolContext,
): Promise<ToolResult | null> {
  if (name === 'SearchWorkspaceTasks') return search(input, ctx);
  return null;
}
