/**
 * file-provenance.ts — GetFileProvenance / ListWorkspaceFiles.
 *
 * META tools (always available) that let the agent ask WHO created or last
 * modified a workspace file, so it can avoid clobbering another task's inputs
 * or outputs in a persistent (shared) workspace.
 *
 * SECURITY / SCOPING GUARANTEE
 * ----------------------------
 * All reads go through `ctx.fileProvenance`, an IO the worker binds to THIS
 * run's `workspace_path` only. Every underlying query is `WHERE workspace_path
 * = <bound path>`, so a tool call can never surface ANOTHER WORKSPACE's rows.
 * Within one persistent (space-shared) workspace, rows are shared across the
 * tasks — possibly of different users — that already share that physical tree,
 * so `created_by_task_id` can be another (even private) task's opaque ID. That
 * is metadata about files the caller can already Read; the tools expose only
 * task IDs (opaque integers) — NEVER task titles or user IDs — so nothing
 * human-identifying leaks beyond what the plain file listing already reveals.
 * When the IO is unbound (subtask / gitea-issue runs) the tools degrade to a
 * friendly no-op rather than reaching into shared logs.
 */

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

const GET_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'GetFileProvenance',
    description:
      'ワークスペース内の1ファイルの来歴（どのタスクが作成/アップロード/最終変更したか、種別）を返す。共有ワークスペースで他タスクのファイルを誤って上書きする前に確認する。詳細は ReadToolDoc({ name: "GetFileProvenance" })。',
    parameters: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'workspace 相対パス（例 output/report.md, input/data.csv）。' },
      },
      required: ['path'],
    },
  },
};

const LIST_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'ListWorkspaceFiles',
    description:
      'ワークスペースの既知ファイルを来歴サマリ付きで一覧する（source種別/作成タスク/最終変更タスクで絞り込み可、件数上限あり・本文は返さない）。詳細は ReadToolDoc({ name: "ListWorkspaceFiles" })。',
    parameters: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'パス接頭辞で絞り込む（例 output/）。' },
        sourceKind: {
          type: 'string',
          description: 'source_kind で絞り込む（user_input / agent_output / agent_edit / bash_generated / subtask_output / imported_existing / unknown）。',
        },
        createdByTaskId: { type: 'number', description: '作成タスク ID で絞り込む。' },
        lastModifiedByTaskId: { type: 'number', description: '最終変更タスク ID で絞り込む。' },
        includeUnknown: { type: 'boolean', description: 'false で unknown / imported_existing を除外（既定 true）。' },
        limit: { type: 'number', description: '最大件数（既定 50、最大 200）。' },
      },
    },
  },
};

export const TOOL_DEFS: Record<string, ToolDef> = {
  GetFileProvenance: GET_DEF,
  ListWorkspaceFiles: LIST_DEF,
};

const NO_CONTEXT_MSG =
  'ファイル来歴はこのコンテキストでは利用できません（subtask など永続ワークスペースに紐付かない実行）。見えている文脈で判断してください。';

/** Normalize a caller path to the workspace-relative form the ledger stores. */
function normalizeRelPath(p: string): string {
  return p.trim().replace(/^\.\//, '').replace(/^\/+/, '').replace(/\/+$/, '');
}

/** Format one record compactly (task/job IDs only — never titles/user ids). */
function formatRecord(rec: FileProvenanceRecord): string {
  const lines = [
    `- path: ${rec.relPath}`,
    `  source_kind: ${rec.sourceKind}`,
  ];
  if (rec.createdByTaskId != null) lines.push(`  created_by_task_id: ${rec.createdByTaskId}`);
  if (rec.createdByPiece) lines.push(`  created_by_piece: ${rec.createdByPiece}${rec.createdByMovement ? `/${rec.createdByMovement}` : ''}`);
  if (rec.firstSeenAt) lines.push(`  first_seen_at: ${rec.firstSeenAt}`);
  if (rec.lastModifiedByTaskId != null) lines.push(`  last_modified_by_task_id: ${rec.lastModifiedByTaskId}`);
  if (rec.lastModifiedAt) lines.push(`  last_modified_at: ${rec.lastModifiedAt}`);
  return lines.join('\n');
}

function get(input: Record<string, unknown>, ctx: ToolContext): ToolResult {
  const io = ctx.fileProvenance;
  if (!io) return { output: NO_CONTEXT_MSG, isError: false };
  const raw = typeof input['path'] === 'string' ? (input['path'] as string) : '';
  const relPath = normalizeRelPath(raw);
  if (!relPath) return { output: 'path が空です。workspace 相対パスを指定してください。', isError: true };
  const rec = io.get(relPath);
  if (!rec) {
    return {
      output:
        `## File provenance — ${relPath}\n\n` +
        'この機能が導入される前から存在するか、来歴が未記録です（source_kind=unknown 相当）。' +
        '共有ワークスペースでは、他タスクの成果物の可能性があるため上書き前に内容を確認してください。',
      isError: false,
    };
  }
  return { output: `## File provenance — ${relPath}\n\n${formatRecord(rec)}`, isError: false };
}

function list(input: Record<string, unknown>, ctx: ToolContext): ToolResult {
  const io = ctx.fileProvenance;
  if (!io) return { output: NO_CONTEXT_MSG, isError: false };
  const rawLimit = typeof input['limit'] === 'number' ? Math.floor(input['limit'] as number) : 50;
  const limit = Math.min(Math.max(rawLimit, 1), 200);
  const records = io.list({
    pathPrefix: typeof input['path'] === 'string' ? normalizeRelPath(input['path'] as string) + '/' : undefined,
    sourceKind: typeof input['sourceKind'] === 'string' ? (input['sourceKind'] as string) : undefined,
    createdByTaskId: typeof input['createdByTaskId'] === 'number' ? (input['createdByTaskId'] as number) : undefined,
    lastModifiedByTaskId: typeof input['lastModifiedByTaskId'] === 'number' ? (input['lastModifiedByTaskId'] as number) : undefined,
    includeUnknown: input['includeUnknown'] === false ? false : undefined,
    limit,
  });
  if (records.length === 0) {
    return { output: '## Workspace files (provenance)\n\n該当する来歴レコードはありません。', isError: false };
  }
  const shown = records.slice(0, limit);
  const lines = [`## Workspace files (provenance) — ${shown.length} 件${records.length > limit ? `（上限 ${limit}）` : ''}`, ''];
  for (const r of shown) {
    const created = r.createdByTaskId != null ? `task ${r.createdByTaskId}` : '不明';
    const modified = r.lastModifiedByTaskId != null ? `task ${r.lastModifiedByTaskId}` : '—';
    lines.push(`- ${r.relPath}  [${r.sourceKind}]  created_by=${created}  last_modified_by=${modified}`);
  }
  return { output: lines.join('\n'), isError: false };
}

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