import fs from 'node:fs';
import path from 'node:path';
import { LANGUAGE_REGISTRY } from '../domain/parser.js';
import { debug } from '../infrastructure/logger.js';
/**
 * Resolve a file path relative to repoRoot, rejecting traversal outside the repo.
 * Returns null if the resolved path escapes repoRoot.
 */
export function safePath(repoRoot, file) {
    const resolved = path.resolve(repoRoot, file);
    if (!resolved.startsWith(repoRoot + path.sep) && resolved !== repoRoot)
        return null;
    return resolved;
}
export function readSourceRange(repoRoot, file, startLine, endLine, opts = {}) {
    try {
        const absPath = safePath(repoRoot, file);
        if (!absPath)
            return null;
        const content = fs.readFileSync(absPath, 'utf-8');
        const lines = content.split('\n');
        const excerptLines = opts.excerptLines ?? 50;
        const start = Math.max(0, (startLine || 1) - 1);
        const end = Math.min(lines.length, endLine || (startLine || 1) + excerptLines);
        return lines.slice(start, end).join('\n');
    }
    catch (e) {
        debug(`readSourceRange failed for ${file}: ${e.message}`);
        return null;
    }
}
/** Truncate text to maxChars, appending "..." if truncated. */
function truncate(text, maxChars) {
    return text.length > maxChars ? `${text.slice(0, maxChars)}...` : text;
}
/** Try to extract a single-line comment (// or #) above the definition. */
function extractSingleLineComment(fileLines, idx, scanLines, maxChars) {
    for (let i = idx; i >= Math.max(0, idx - scanLines); i--) {
        const trimmed = fileLines[i].trim();
        if (trimmed.endsWith('*/'))
            return null; // hit a block comment — defer to JSDoc extractor
        if (trimmed.startsWith('//') || trimmed.startsWith('#')) {
            const text = trimmed
                .replace(/^\/\/\s*/, '')
                .replace(/^#\s*/, '')
                .trim();
            return truncate(text, maxChars);
        }
        if (trimmed !== '' && !trimmed.startsWith('*') && !trimmed.startsWith('/*'))
            return null;
    }
    return null;
}
/** Find the line index where a block comment (*​/) ends, scanning upward from idx. */
function findJsdocEndLine(fileLines, idx, scanLines) {
    for (let i = idx; i >= Math.max(0, idx - scanLines); i--) {
        const trimmed = fileLines[i].trim();
        if (trimmed.endsWith('*/'))
            return i;
        if (trimmed !== '' &&
            !trimmed.startsWith('*') &&
            !trimmed.startsWith('/*') &&
            !trimmed.startsWith('//') &&
            !trimmed.startsWith('#')) {
            break;
        }
    }
    return -1;
}
/** Extract the first description line from a JSDoc block ending at jsdocEnd. */
function extractJsdocDescription(fileLines, jsdocEnd, openScanLines, maxChars) {
    for (let i = jsdocEnd; i >= Math.max(0, jsdocEnd - openScanLines); i--) {
        if (!fileLines[i].trim().startsWith('/**'))
            continue;
        for (let j = i + 1; j <= jsdocEnd; j++) {
            const docLine = fileLines[j].trim()
                .replace(/^\*\s?/, '')
                .trim();
            if (docLine && !docLine.startsWith('@') && docLine !== '/' && docLine !== '*/') {
                return truncate(docLine, maxChars);
            }
        }
        break;
    }
    return null;
}
export function extractSummary(fileLines, line, opts = {}) {
    if (!fileLines || !line || line <= 1)
        return null;
    const idx = line - 2; // line above the definition (0-indexed)
    const jsdocEndScanLines = opts.jsdocEndScanLines ?? 10;
    const jsdocOpenScanLines = opts.jsdocOpenScanLines ?? 20;
    const summaryMaxChars = opts.summaryMaxChars ?? 100;
    // Try single-line comment first
    const singleLine = extractSingleLineComment(fileLines, idx, jsdocEndScanLines, summaryMaxChars);
    if (singleLine)
        return singleLine;
    // Try JSDoc block comment
    const jsdocEnd = findJsdocEndLine(fileLines, idx, jsdocEndScanLines);
    if (jsdocEnd >= 0) {
        return extractJsdocDescription(fileLines, jsdocEnd, jsdocOpenScanLines, summaryMaxChars);
    }
    return null;
}
/** Per-language signature patterns. Each entry has a regex and an extractor for return type. */
const SIGNATURE_PATTERNS = [
    // JS/TS: function name(params) or async function
    {
        regex: /(?:export\s+)?(?:async\s+)?function\s*\*?\s*\w*\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/,
        returnType: (m) => (m[2] ? m[2].trim().replace(/\s*\{$/, '') : null),
    },
    // Arrow: const name = (params) => or (params):ReturnType =>
    {
        regex: /=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*([^=>\n{]+))?\s*=>/,
        returnType: (m) => (m[2] ? m[2].trim() : null),
    },
    // Python: def name(params) -> return:
    {
        regex: /def\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^:\n]+))?/,
        returnType: (m) => (m[2] ? m[2].trim() : null),
    },
    // Go: func (recv) name(params) (returns)
    {
        regex: /func\s+(?:\([^)]*\)\s+)?\w+\s*\(([^)]*)\)\s*(?:\(([^)]+)\)|(\w[^\n{]*))?/,
        returnType: (m) => (m[2] || m[3] || '').trim() || null,
    },
    // Rust: fn name(params) -> ReturnType
    {
        regex: /fn\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^\n{]+))?/,
        returnType: (m) => (m[2] ? m[2].trim() : null),
    },
];
export function extractSignature(fileLines, line, opts = {}) {
    if (!fileLines || !line)
        return null;
    const idx = line - 1;
    const signatureGatherLines = opts.signatureGatherLines ?? 5;
    const chunk = fileLines
        .slice(idx, Math.min(fileLines.length, idx + signatureGatherLines))
        .join('\n');
    for (const pattern of SIGNATURE_PATTERNS) {
        const m = chunk.match(pattern.regex);
        if (m) {
            return {
                params: m[1].trim() || null,
                returnType: pattern.returnType(m),
            };
        }
    }
    return null;
}
export function createFileLinesReader(repoRoot) {
    const cache = new Map();
    return function getFileLines(file) {
        if (cache.has(file))
            return cache.get(file);
        try {
            const absPath = safePath(repoRoot, file);
            if (!absPath) {
                cache.set(file, null);
                return null;
            }
            const lines = fs.readFileSync(absPath, 'utf-8').split('\n');
            cache.set(file, lines);
            return lines;
        }
        catch (e) {
            debug(`getFileLines failed for ${file}: ${e.message}`);
            cache.set(file, null);
            return null;
        }
    };
}
export function isFileLikeTarget(target) {
    if (target.includes('/') || target.includes('\\'))
        return true;
    const ext = path.extname(target).toLowerCase();
    if (!ext)
        return false;
    for (const entry of LANGUAGE_REGISTRY) {
        if (entry.extensions.includes(ext))
            return true;
    }
    return false;
}
//# sourceMappingURL=file-utils.js.map