/**
 * Atomic LSP-style text edits backed by the Rust native engine.
 *
 * Purpose: let agents apply LSP WorkspaceEdit/TextEdit output without a
 * read-splice-write loop in JavaScript.
 *
 * Consumer: SF agent editing flows that receive semantic edits from LSP and
 * need to commit them to disk as one native operation.
 */

import { EventEmitter } from "node:events";
import { native } from "../native.js";
import type {
	ApplyEditsOptions,
	ApplyEditsResult,
	ApplyWorkspaceEditResult,
	InsertAroundSymbolOptions,
	InsertAroundSymbolResult,
	InsertPosition,
	ReplaceSymbolOptions,
	ReplaceSymbolResult,
	TextDocumentEdit,
	TextEdit,
	WatchEvent,
	WatchHandle,
	WatchOptions,
	WorkspaceEditLike,
} from "./types.js";

export type {
	ApplyEditsOptions,
	ApplyEditsResult,
	ApplyWorkspaceEditResult,
	InsertAroundSymbolOptions,
	InsertAroundSymbolResult,
	InsertPosition,
	Position,
	Range,
	ReplaceSymbolOptions,
	ReplaceSymbolResult,
	TextDocumentEdit,
	TextEdit,
	WatchEvent,
	WatchEventKind,
	WatchHandle,
	WatchOptions,
	WorkspaceEditLike,
} from "./types.js";

/**
 * Apply LSP-style TextEdit entries to one file atomically.
 *
 * Purpose: preserve LSP range semantics while avoiding JavaScript string
 * slicing and partial writes for agent-driven edits.
 *
 * Consumer: agent edit tools that hand LSP rename/code-action results to the
 * native module for durable file updates.
 */
export function applyEdits(
	filePath: string,
	edits: readonly TextEdit[],
	options?: ApplyEditsOptions,
): ApplyEditsResult {
	return native.applyEdits(filePath, [...edits], options) as ApplyEditsResult;
}

/**
 * Apply LSP-style text edits across multiple files with native two-phase staging.
 *
 * Purpose: commit semantic edits from LSP rename/code-action results with one
 * native call and no per-file JavaScript splice loop.
 *
 * Consumer: agent refactor flows that receive a WorkspaceEdit-shaped object
 * from the LSP client.
 */
export function applyWorkspaceEdit(
	workspaceEdit: readonly TextDocumentEdit[] | WorkspaceEditLike,
	options?: ApplyEditsOptions,
): ApplyWorkspaceEditResult {
	return native.applyWorkspaceEdit(
		normalizeWorkspaceEdit(workspaceEdit),
		options,
	) as ApplyWorkspaceEditResult;
}

/**
 * Replace the declaration matched by a symbol path.
 *
 * Purpose: let agent code target stable semantic names instead of stale line
 * numbers or fragile string matches.
 *
 * Consumer: symbol-aware edit flows for TypeScript/JavaScript/TSX files.
 */
export function replaceSymbol(
	filePath: string,
	symbolPath: string,
	newBody: string,
	options?: ReplaceSymbolOptions,
): ReplaceSymbolResult {
	return native.replaceSymbol(
		filePath,
		symbolPath,
		newBody,
		options,
	) as ReplaceSymbolResult;
}

/**
 * Insert code around a declaration matched by a symbol path.
 *
 * Purpose: support common agent edits such as adding adjacent declarations or
 * declaration comments without regex-based placement.
 *
 * Consumer: symbol-aware edit flows for TypeScript/JavaScript/TSX files.
 */
export function insertAroundSymbol(
	filePath: string,
	symbolPath: string,
	position: InsertPosition,
	code: string,
	options?: InsertAroundSymbolOptions,
): InsertAroundSymbolResult {
	return native.insertAroundSymbol(
		filePath,
		symbolPath,
		position,
		code,
		options,
	) as InsertAroundSymbolResult;
}

/**
 * Watch a directory tree with native notify/globset filtering.
 *
 * Purpose: keep file-change pressure out of the JavaScript event loop while
 * still presenting a Node-friendly EventEmitter surface.
 *
 * Consumer: long-running agent sessions and UI flows that need debounced file
 * change batches.
 */
export function watchTree(root: string, options?: WatchOptions): WatchHandle {
	const emitter = new EventEmitter();
	const handle = native.watchTree(
		root,
		options ?? null,
		(err: unknown, events?: unknown[]) => {
			if (err) {
				emitter.emit("error", err);
				return;
			}
			emitter.emit("events", (events ?? []) as WatchEvent[]);
		},
	);
	let stopped = false;

	return {
		stop() {
			if (!stopped) {
				stopped = true;
				native.stopWatch(handle);
				emitter.removeAllListeners();
			}
		},
		on(event: "events", listener: (events: WatchEvent[]) => void) {
			emitter.on(event, listener);
			return this;
		},
	};
}

function normalizeWorkspaceEdit(
	workspaceEdit: readonly TextDocumentEdit[] | WorkspaceEditLike,
): TextDocumentEdit[] {
	if (Array.isArray(workspaceEdit)) {
		return workspaceEdit.map((entry) => ({
			filePath: entry.filePath,
			edits: [...entry.edits],
		}));
	}
	const edit = workspaceEdit as WorkspaceEditLike;
	if (Array.isArray(edit.documentChanges)) {
		return edit.documentChanges.map((entry: TextDocumentEdit) => ({
			filePath: entry.filePath,
			edits: [...entry.edits],
		}));
	}
	return Object.entries(edit.changes ?? {}).map(([filePath, edits]) => ({
		filePath,
		edits: [...edits],
	}));
}
