/**
 * ReplaceSymbol — replace a function/class/method declaration by symbol path.
 *
 * Backed by the native `replaceSymbol` API in `@singularity-forge/native/edit`,
 * which uses Tree-sitter to find the named declaration and rewrite its body
 * atomically. Falls back to `NativeUnavailableError` when the native engine
 * isn't loaded; callers should catch and degrade to substring-based Edit.
 *
 * Why this beats substring Edit for code:
 *   - Tolerates whitespace / formatting changes around the declaration
 *   - Tolerates rename of unrelated nearby identifiers
 *   - No "make the substring unique" cognitive overhead
 *
 * Languages: inferred from file extension via tree-sitter grammars built into
 * the native engine. Pass `lang` to force a specific grammar.
 */

import { type Static, Type } from "@sinclair/typebox";
import type { AgentTool } from "@singularity-forge/agent-core";
import { replaceSymbol } from "@singularity-forge/native/edit";
import { notifyFileChanged } from "../lsp/client.js";
import { resolveToCwd } from "./path-utils.js";

const replaceSymbolSchema = Type.Object({
	path: Type.String({
		description: "Path to the file to edit (relative or absolute)",
	}),
	symbolPath: Type.String({
		description:
			"Symbol path identifying the declaration. For TypeScript/JavaScript: function/class/method name. May include qualifier (e.g. `ClassName.methodName`) when the language supports it.",
	}),
	newBody: Type.String({
		description:
			"Full replacement body for the matched declaration, including the signature line. The native engine replaces the entire declaration node, not just the body block.",
	}),
	lang: Type.Optional(
		Type.String({
			description:
				"Force a specific tree-sitter grammar. Defaults to inferring from file extension (ts, tsx, js, jsx, py, rs, etc.).",
		}),
	),
});

type ReplaceSymbolParams = Static<typeof replaceSymbolSchema>;

export function createReplaceSymbolTool(
	cwd: string,
): AgentTool<typeof replaceSymbolSchema> {
	return {
		name: "ReplaceSymbol",
		label: "ReplaceSymbol",
		description:
			"Replace a function/class/method declaration by symbol name. Uses Tree-sitter via the native engine — more durable than substring Edit for code, because formatting changes around the declaration don't break the match. Returns matched=true with byteStart/byteEnd of the replaced range when successful, or matched=false when the symbol couldn't be located. Prefer this over Edit for renames, signature changes, or full-body rewrites of named declarations.",
		parameters: replaceSymbolSchema,
		execute: async (
			_toolCallId: string,
			params: ReplaceSymbolParams,
			signal?: AbortSignal,
		) => {
			if (signal?.aborted) {
				throw new Error("ReplaceSymbol cancelled before start");
			}
			const absolutePath = resolveToCwd(cwd, params.path);
			try {
				const result = replaceSymbol(
					absolutePath,
					params.symbolPath,
					params.newBody,
					params.lang ? { lang: params.lang } : undefined,
				);
				if (!result.matched) {
					return {
						content: [
							{
								type: "text" as const,
								text: `ReplaceSymbol: no declaration matched symbolPath="${params.symbolPath}" in ${params.path}. Verify the symbol name (case-sensitive) and that the file's language is supported.`,
							},
						],
						details: { matched: false, symbolPath: params.symbolPath },
					};
				}
				try {
					notifyFileChanged(absolutePath);
				} catch {
					/* best-effort */
				}
				return {
					content: [
						{
							type: "text" as const,
							text: `ReplaceSymbol: replaced ${params.symbolPath} in ${params.path} (byte range ${result.byteStart}..${result.byteEnd}, starting at line ${result.startLine ?? "?"}).`,
						},
					],
					details: {
						matched: true,
						symbolPath: params.symbolPath,
						byteStart: result.byteStart,
						byteEnd: result.byteEnd,
						startLine: result.startLine,
					},
				};
			} catch (err: unknown) {
				const message = err instanceof Error ? err.message : String(err);
				return {
					content: [
						{
							type: "text" as const,
							text: `ReplaceSymbol failed for ${params.path} symbolPath="${params.symbolPath}": ${message}`,
						},
					],
					details: { matched: false, error: message },
					isError: true,
				};
			}
		},
	};
}
