import { existsSync } from "node:fs";
import { join, sep } from "node:path";
import {
	createAstGrepTool,
	createBashTool,
	createEditTool,
	createInsertAroundSymbolTool,
	createReadTool,
	createReplaceSymbolTool,
	createWriteTool,
} from "@singularity-forge/coding-agent";
import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
import { logWarning, setLogBasePath } from "../workflow-logger.js";
/**
 * Resolve the correct DB path for the current working directory.
 * If `basePath` is inside a `.sf/worktrees/<MID>/` directory, returns
 * the project root's `.sf/sf.db` (shared WAL — R012). Otherwise
 * returns `<basePath>/.sf/sf.db`.
 */
export function resolveProjectRootDbPath(basePath) {
	// Detect worktree: look for `.sf/worktrees/` in the path segments.
	// A worktree path looks like: /project/root/.sf/worktrees/M001/...
	// We need to resolve back to /project/root/.sf/sf.db
	const marker = `${sep}.sf${sep}worktrees${sep}`;
	const idx = basePath.indexOf(marker);
	if (idx !== -1) {
		const projectRoot = basePath.slice(0, idx);
		return join(projectRoot, ".sf", "sf.db");
	}
	// Also handle forward-slash paths on all platforms
	const fwdMarker = "/.sf/worktrees/";
	const fwdIdx = basePath.indexOf(fwdMarker);
	if (fwdIdx !== -1) {
		const projectRoot = basePath.slice(0, fwdIdx);
		return join(projectRoot, ".sf", "sf.db");
	}
	// External-state layout: ~/.sf/projects/<hash>/worktrees/<MID>/...
	// Resolve to ~/.sf/projects/<hash>/sf.db (the canonical project DB) (#2952).
	// Must be checked before the generic symlink-resolved handler: both match
	// /.sf/projects/<hash>/worktrees/ but require different resolution targets.
	const extRe = /[/\\]\.sf[/\\]projects[/\\][a-f0-9]+[/\\]worktrees(?:[/\\]|$)/;
	const extMatch = extRe.exec(basePath);
	if (extMatch) {
		const matchStr = extMatch[0];
		// Find the "/worktrees" portion within the match and slice up to it
		const wtIdx = matchStr.search(/[/\\]worktrees(?:[/\\]|$)/);
		const projectStateRoot = basePath.slice(0, extMatch.index + wtIdx);
		return join(projectStateRoot, "sf.db");
	}
	// Symlink-resolved layout: /.sf/projects/<hash>/worktrees/M001/...
	// The project root is everything before /.sf/projects/ (#2517)
	const symlinkMarker = `${sep}.sf${sep}projects${sep}`;
	const symlinkIdx = basePath.indexOf(symlinkMarker);
	if (symlinkIdx !== -1) {
		const afterProjects = basePath.slice(symlinkIdx + symlinkMarker.length);
		// Expect: <hash>/worktrees/...
		const worktreeSeg = `${sep}worktrees${sep}`;
		if (afterProjects.includes(worktreeSeg)) {
			const projectRoot = basePath.slice(0, symlinkIdx);
			return join(projectRoot, ".sf", "sf.db");
		}
	}
	// Forward-slash variant for symlink-resolved layout
	const fwdSymlinkMarker = "/.sf/projects/";
	const fwdSymlinkIdx = basePath.indexOf(fwdSymlinkMarker);
	if (fwdSymlinkIdx !== -1) {
		const afterProjects = basePath.slice(
			fwdSymlinkIdx + fwdSymlinkMarker.length,
		);
		if (afterProjects.includes("/worktrees/")) {
			const projectRoot = basePath.slice(0, fwdSymlinkIdx);
			return join(projectRoot, ".sf", "sf.db");
		}
	}
	return join(basePath, ".sf", "sf.db");
}
export async function ensureDbOpen(basePath = process.cwd()) {
	try {
		const db = await import("../sf-db.js");
		const dbPath = resolveProjectRootDbPath(basePath);
		const sfDir = join(basePath, ".sf");
		// Derive the project root from the DB path (strip .sf/sf.db)
		const projectRoot = join(dbPath, "..", "..");
		// Open existing DB file (may be at project root for worktrees)
		if (existsSync(dbPath)) {
			const opened = db.openDatabase(dbPath);
			if (opened) {
				setLogBasePath(projectRoot);
				try {
					db.backfillUatVerdicts(projectRoot);
				} catch {
					/* non-fatal */
				}
			}
			return opened;
		}
		// No DB file — create + migrate from Markdown if .sf/ has content
		if (existsSync(sfDir)) {
			const hasDecisions = existsSync(join(sfDir, "DECISIONS.md"));
			const hasRequirements = existsSync(join(sfDir, "REQUIREMENTS.md"));
			const hasMilestones = existsSync(join(sfDir, "milestones"));
			if (hasDecisions || hasRequirements || hasMilestones) {
				const opened = db.openDatabase(dbPath);
				if (opened) {
					setLogBasePath(projectRoot);
					try {
						const { migrateFromMarkdown } = await import("../md-importer.js");
						migrateFromMarkdown(basePath);
					} catch (err) {
						logWarning(
							"bootstrap",
							`ensureDbOpen auto-migration failed: ${err.message}`,
						);
					}
					try {
						db.backfillUatVerdicts(projectRoot);
					} catch {
						/* non-fatal */
					}
				}
				return opened;
			}
			// .sf/ exists but has no Markdown content (fresh project) — create empty DB
			const opened = db.openDatabase(dbPath);
			if (opened) setLogBasePath(projectRoot);
			return opened;
		}
		logWarning("bootstrap", "ensureDbOpen failed — no .sf directory found");
		return false;
	} catch (err) {
		logWarning(
			"bootstrap",
			`ensureDbOpen failed: ${err.message ?? String(err)}`,
		);
		return false;
	}
}
export function registerDynamicTools(pi) {
	const baseBash = createBashTool(process.cwd(), {
		spawnHook: (ctx) => ({ ...ctx, cwd: process.cwd() }),
	});
	const dynamicBash = {
		...baseBash,
		execute: async (toolCallId, params, signal, onUpdate, ctx) => {
			const paramsWithTimeout = {
				...params,
				timeout: params.timeout ?? DEFAULT_BASH_TIMEOUT_SECS,
			};
			return baseBash.execute(
				toolCallId,
				paramsWithTimeout,
				signal,
				onUpdate,
				ctx,
			);
		},
	};
	pi.registerTool(dynamicBash);
	const baseWrite = createWriteTool(process.cwd());
	pi.registerTool({
		...baseWrite,
		execute: async (toolCallId, params, signal, onUpdate, ctx) => {
			const fresh = createWriteTool(process.cwd());
			return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
		},
	});
	const baseRead = createReadTool(process.cwd());
	pi.registerTool({
		...baseRead,
		execute: async (toolCallId, params, signal, onUpdate, ctx) => {
			const fresh = createReadTool(process.cwd());
			return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
		},
	});
	const baseEdit = createEditTool(process.cwd());
	pi.registerTool({
		...baseEdit,
		execute: async (toolCallId, params, signal, onUpdate, ctx) => {
			const fresh = createEditTool(process.cwd());
			return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
		},
	});
	const astToolFactories = [
		createReplaceSymbolTool,
		createInsertAroundSymbolTool,
		createAstGrepTool,
	];
	for (const createTool of astToolFactories) {
		const baseTool = createTool(process.cwd());
		pi.registerTool({
			...baseTool,
			execute: async (toolCallId, params, signal, onUpdate, ctx) => {
				const fresh = createTool(process.cwd());
				return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
			},
		});
	}
}
