import { promises as fs } from 'node:fs';
import { dirname, isAbsolute, resolve, sep } from 'node:path';

/**
 * Resolve an ACP-supplied path against the agent worktree and reject anything
 * that escapes it. Mirrors `write_guard.ts`'s check: `resolve()` to normalize
 * `../` segments, then a **separator-bounded** prefix test — a bare
 * `startsWith(root)` wrongly admits a sibling dir like `<root>-evil/...`.
 *
 * No realpath (consistent with `write_guard.ts`: the target may not exist yet on
 * write). This is a containment guard for the ACP fs bridge, not a hard trust
 * boundary — the agent process already runs with host FS access; symlink-swap
 * hardening (`O_NOFOLLOW`/realpath) is out of scope here.
 */
function resolveInWorktree(worktreePath: string, filePath: string): string {
  const root = resolve(worktreePath);
  const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);
  if (absolute !== root && !absolute.startsWith(root + sep)) {
    throw new Error(`path escapes worktree: ${filePath}`);
  }
  return absolute;
}

/** Resolve an ACP path against the agent worktree and read a slice of lines. */
export async function readWorktreeTextFile(
  worktreePath: string,
  filePath: string,
  line?: number | null,
  limit?: number | null,
): Promise<string> {
  const absolute = resolveInWorktree(worktreePath, filePath);
  const raw = await fs.readFile(absolute, 'utf8');
  if (!line && !limit) return raw;
  const lines = raw.split(/\r?\n/);
  const start = Math.max((line ?? 1) - 1, 0);
  const end = limit ? start + limit : undefined;
  return lines.slice(start, end).join('\n');
}

/** Write a file inside the worktree (creates parent dirs). */
export async function writeWorktreeTextFile(
  worktreePath: string,
  filePath: string,
  content: string,
): Promise<void> {
  const absolute = resolveInWorktree(worktreePath, filePath);
  await fs.mkdir(dirname(absolute), { recursive: true });
  await fs.writeFile(absolute, content, 'utf8');
}
