// =============================================================================
// Pure file-system API — no ToolContext coupling.
// Takes a workspaceRoot string and safely-resolves paths beneath it.
// =============================================================================

import * as fs from 'fs';
import * as path from 'path';
import * as cp from 'child_process';
import * as util from 'util';
import { Minimatch } from 'minimatch';
import { safeResolve, EXCLUDED } from './utils';

const execAsync = util.promisify(cp.exec);

export async function readFile(workspaceRoot: string, relPath: string): Promise<unknown> {
    const abs = safeResolve(workspaceRoot, relPath);
    try {
        return fs.readFileSync(abs, 'utf8');
    } catch {
        return { error: `File not found: ${relPath}` };
    }
}

export async function writeFile(workspaceRoot: string, relPath: string, content: string): Promise<unknown> {
    const abs = safeResolve(workspaceRoot, relPath);
    fs.mkdirSync(path.dirname(abs), { recursive: true });
    fs.writeFileSync(abs, content, 'utf8');
    return { success: true, path: relPath, bytes: Buffer.byteLength(content) };
}

export async function replaceInFile(
    workspaceRoot: string,
    relPath: string,
    oldContent: string,
    newContent: string
): Promise<unknown> {
    const abs = safeResolve(workspaceRoot, relPath);
    const current = fs.readFileSync(abs, 'utf8');
    if (!current.includes(oldContent)) {
        return { error: 'old_content not found in file. Read the file again to get the current content.' };
    }
    fs.writeFileSync(abs, current.replace(oldContent, newContent), 'utf8');
    return { success: true, path: relPath };
}

export async function listDirectory(workspaceRoot: string, relPath: string): Promise<unknown> {
    const abs = safeResolve(workspaceRoot, relPath);
    try {
        const entries = fs.readdirSync(abs, { withFileTypes: true });
        return entries
            .filter(e => !EXCLUDED.has(e.name))
            .map(e => e.isDirectory() ? `${e.name}/` : e.name);
    } catch {
        return { error: `Directory not found: ${relPath}` };
    }
}

export async function findFiles(workspaceRoot: string, pattern: string): Promise<unknown> {
    const matcher = new Minimatch(pattern, { dot: false });
    const results: string[] = [];

    function walk(dir: string): void {
        if (results.length >= 200) return;
        let entries: fs.Dirent[];
        try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
        for (const e of entries) {
            if (EXCLUDED.has(e.name)) continue;
            const abs = path.join(dir, e.name);
            const rel = path.relative(workspaceRoot, abs).split(path.sep).join('/');
            if (e.isDirectory()) {
                walk(abs);
            } else if (matcher.match(rel)) {
                results.push(rel);
            }
        }
    }

    walk(workspaceRoot);
    return { files: results, truncated: results.length >= 200 };
}

export async function searchCode(
    workspaceRoot: string,
    query: string,
    fileExtensions?: string[]
): Promise<unknown> {
    const globPatterns = fileExtensions?.map(e => `*.${e}`) || [];
    const rgArgs = ['--line-number', '--no-heading', '--color=never', '--max-count=30'];
    for (const ex of EXCLUDED) { rgArgs.push('--glob', `!${ex}`); }
    if (globPatterns.length > 0) { for (const g of globPatterns) rgArgs.push('--glob', g); }
    rgArgs.push('--fixed-strings', query, workspaceRoot);

    try {
        const { stdout } = await execAsync(`rg ${rgArgs.map(a => `'${a}'`).join(' ')}`, {
            cwd: workspaceRoot, timeout: 15000,
        });
        return stdout.trim().split('\n').filter(Boolean).map(line => {
            const m = line.match(/^(.+?):(\d+):(.*)$/);
            if (!m) return null;
            return {
                file: path.relative(workspaceRoot, m[1]).split(path.sep).join('/'),
                line: parseInt(m[2]),
                content: m[3].trim(),
            };
        }).filter(Boolean);
    } catch (err: any) {
        if (err.code === 1) return []; // ripgrep exit 1 = no matches
        return { error: `Search failed: ${err.message}` };
    }
}
