import { readdirSync, readFileSync, statSync } from "node:fs";
import { basename, dirname, relative, resolve, sep } from "node:path";
import { BestAgentError, isRecord as isPlainObject } from "@best-agent/foundation";
import ts from "typescript";
import { z } from "zod";
import { toolInputSchema, typedToolInputSchema } from "./schema.js";
const TS_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
const PYTHON_EXTENSIONS = new Set([".py", ".pyi"]);
const IGNORED_DIRECTORIES = new Set([
    ".best-agent",
    ".git",
    ".state",
    "coverage",
    "dist",
    "node_modules",
]);
const MAX_REFERENCE_RESULTS = 50;
const MAX_DIAGNOSTICS_PER_FILE = 10;
const MAX_DIAGNOSTICS_TOTAL = 30;
const MAX_SYMBOL_RESULTS = 50;
const LSP_LOCATION_INPUT_SCHEMA = toolInputSchema(z.strictObject({
    character: z.int(),
    file_path: z.string(),
    line: z.int(),
}));
const LSP_REFERENCE_INPUT_SCHEMA = typedToolInputSchema(z.strictObject({
    character: z.int(),
    file_path: z.string(),
    include_declaration: z.boolean().default(true),
    line: z.int(),
}));
const LSP_DIAGNOSTICS_INPUT_SCHEMA = toolInputSchema(z.strictObject({
    file_path: z.string().optional(),
}));
const LSP_SYMBOL_SEARCH_INPUT_SCHEMA = typedToolInputSchema(z.strictObject({
    limit: z.int().min(1).max(MAX_SYMBOL_RESULTS).default(20),
    query: z.string(),
}));
export function createLspTools() {
    return [
        {
            description: "Go to the definition of a symbol in a TypeScript or JavaScript file.",
            effect: "read",
            executionClass: "read-only",
            idempotent: true,
            inputSchema: LSP_LOCATION_INPUT_SCHEMA,
            name: "lsp-goto-definition",
            source: "built-in",
            capabilities(input, context) {
                const normalized = normalizeLspLocationInput("lsp-goto-definition", input, context);
                return [
                    {
                        group: "filesystem",
                        action: "read",
                        path: normalized.absolutePath,
                        roots: [context.cwd],
                    },
                ];
            },
            execute(input, context) {
                return executeGotoDefinition(normalizeLspLocationInput("lsp-goto-definition", input, context), context);
            },
            formatInput(input) {
                const normalized = input;
                return `${normalized.file_path}:${normalized.line}:${normalized.character}`;
            },
            legacyAdapter: {
                adapt(input, context) {
                    const normalized = normalizeLspLocationInput("lsp-goto-definition", input, context);
                    return {
                        character: normalized.character,
                        file_path: normalized.file_path,
                        line: normalized.line,
                    };
                },
            },
        },
        {
            description: "Find references to a symbol in a TypeScript or JavaScript file.",
            effect: "search",
            executionClass: "read-only",
            idempotent: true,
            inputSchema: LSP_REFERENCE_INPUT_SCHEMA,
            name: "lsp-find-references",
            source: "built-in",
            capabilities(input, context) {
                const normalized = normalizeLspReferenceInput(input, context);
                return [
                    {
                        group: "filesystem",
                        action: "read",
                        path: normalized.absolutePath,
                        roots: [context.cwd],
                    },
                ];
            },
            execute(input, context) {
                return executeFindReferences(normalizeLspReferenceInput(input, context), context);
            },
            formatInput(input) {
                const normalized = input;
                return `${normalized.file_path}:${normalized.line}:${normalized.character}`;
            },
            legacyAdapter: {
                adapt(input, context) {
                    const normalized = normalizeLspReferenceInput(input, context);
                    return {
                        character: normalized.character,
                        file_path: normalized.file_path,
                        include_declaration: normalized.include_declaration,
                        line: normalized.line,
                    };
                },
            },
        },
        {
            description: "Retrieve TypeScript, JavaScript, or Python diagnostics for a file or workspace.",
            effect: "read",
            executionClass: "read-only",
            idempotent: true,
            inputSchema: LSP_DIAGNOSTICS_INPUT_SCHEMA,
            name: "lsp-diagnostics",
            source: "built-in",
            capabilities(input, context) {
                const normalized = normalizeLspDiagnosticsInput(input, context);
                return [
                    {
                        group: "filesystem",
                        action: "read",
                        path: normalized.absolutePath ?? context.cwd,
                        roots: [context.cwd],
                    },
                ];
            },
            execute(input, context) {
                return executeDiagnostics(normalizeLspDiagnosticsInput(input, context), context);
            },
            formatInput(input) {
                const normalized = input;
                return normalized.file_path ?? "workspace";
            },
            legacyAdapter: {
                adapt(input, context) {
                    const normalized = normalizeLspDiagnosticsInput(input, context);
                    return { file_path: normalized.file_path };
                },
            },
        },
        {
            description: "Search workspace symbols across TypeScript, JavaScript, and Python files.",
            effect: "search",
            executionClass: "read-only",
            idempotent: true,
            inputSchema: LSP_SYMBOL_SEARCH_INPUT_SCHEMA,
            name: "lsp-symbol-search",
            source: "built-in",
            capabilities(_input, context) {
                return [
                    {
                        group: "filesystem",
                        action: "read",
                        path: context.cwd,
                        roots: [context.cwd],
                    },
                ];
            },
            execute(input, context) {
                return executeSymbolSearch(normalizeLspSymbolSearchInput(input), context);
            },
            formatInput(input) {
                const normalized = input;
                return normalized.limit === 20
                    ? normalized.query
                    : `${normalized.query} (limit ${normalized.limit})`;
            },
            legacyAdapter: {
                adapt(input) {
                    return normalizeLspSymbolSearchInput(input);
                },
            },
        },
    ];
}
export async function renderPassiveLspDiagnostics(context, candidatePaths) {
    const normalizedPaths = normalizePassiveDiagnosticPaths(context.cwd, candidatePaths);
    if (normalizedPaths.length === 0) {
        return undefined;
    }
    const diagnostics = [];
    let total = 0;
    for (const file_path of normalizedPaths) {
        if (total >= MAX_DIAGNOSTICS_TOTAL) {
            break;
        }
        const result = await collectDiagnosticsForPath(file_path, {
            cwd: context.cwd,
            sandbox: context.sandbox,
            threadDir: context.threadPaths.threadDir,
        });
        const errors = result.items
            .filter((item) => item.severity === "error")
            .slice(0, Math.max(0, MAX_DIAGNOSTICS_TOTAL - total));
        if (errors.length === 0) {
            continue;
        }
        diagnostics.push({
            file_path: result.file_path,
            items: errors,
        });
        total += errors.length;
    }
    if (diagnostics.length === 0) {
        return undefined;
    }
    return [
        "[LSP Diagnostics - errors detected after recent edits]",
        formatDiagnostics(diagnostics),
    ].join("\n\n");
}
function executeGotoDefinition(input, context) {
    if (!isTypeScriptFile(input.absolutePath) && !isPythonFile(input.absolutePath)) {
        return renderUnavailableForExtension(input.absolutePath);
    }
    if (isPythonFile(input.absolutePath)) {
        return "LSP navigation is currently available for TypeScript and JavaScript files. Python support in this slice is diagnostics-only.";
    }
    const project = createTsProject(context.cwd, context.threadPaths.threadDir, input.absolutePath);
    const position = toTsPosition(project.service, input.absolutePath, input.line, input.character);
    const definition = project.service.getDefinitionAndBoundSpan(input.absolutePath, position);
    const definitions = definition?.definitions ?? [];
    if (!definition || definitions.length === 0) {
        return `No definition found for symbol at ${input.file_path}:${input.line}:${input.character}.`;
    }
    const sourceText = readFileSync(input.absolutePath, "utf8");
    const symbolName = definition.textSpan
        ? sourceText.slice(definition.textSpan.start, definition.textSpan.start + definition.textSpan.length)
        : "symbol";
    const formatted = definitions
        .filter((entry) => isWithinRoot(entry.fileName, context.cwd))
        .map((entry) => formatDefinitionLocation(entry.fileName, entry.textSpan.start, input.absolutePath, context.cwd));
    if (formatted.length === 0) {
        return `No definition found for symbol at ${input.file_path}:${input.line}:${input.character}.`;
    }
    context.recordWorkspaceRead(input.absolutePath);
    for (const location of formatted) {
        context.recordWorkspaceRead(resolve(context.cwd, location.file_path));
    }
    return [
        `Definition of \`${symbolName || "symbol"}\`:`,
        ...formatted.map((location) => `  ${location.file_path}:${location.line}:${location.character}`),
    ].join("\n");
}
function executeFindReferences(input, context) {
    if (!isTypeScriptFile(input.absolutePath) && !isPythonFile(input.absolutePath)) {
        return renderUnavailableForExtension(input.absolutePath);
    }
    if (isPythonFile(input.absolutePath)) {
        return "LSP references are currently available for TypeScript and JavaScript files. Python support in this slice is diagnostics-only.";
    }
    const project = createTsProject(context.cwd, context.threadPaths.threadDir, input.absolutePath);
    const position = toTsPosition(project.service, input.absolutePath, input.line, input.character);
    const definition = project.service.getDefinitionAndBoundSpan(input.absolutePath, position);
    const sourceText = readFileSync(input.absolutePath, "utf8");
    const symbolName = definition?.textSpan
        ? sourceText.slice(definition.textSpan.start, definition.textSpan.start + definition.textSpan.length)
        : "symbol";
    const references = project.service.findReferences(input.absolutePath, position) ?? [];
    const flattened = references.flatMap((reference) => reference.references.map((entry) => ({
        fileName: entry.fileName,
        isDefinition: entry.isDefinition,
        textSpan: entry.textSpan,
    })));
    const filtered = flattened.filter((entry) => {
        if (!isWithinRoot(entry.fileName, context.cwd)) {
            return false;
        }
        if (!input.include_declaration && entry.isDefinition) {
            return false;
        }
        return true;
    });
    if (filtered.length === 0) {
        return `No references found for symbol at ${input.file_path}:${input.line}:${input.character}.`;
    }
    const shown = filtered.slice(0, MAX_REFERENCE_RESULTS);
    const lines = shown.map((entry) => {
        const location = formatDefinitionLocation(entry.fileName, entry.textSpan.start, input.absolutePath, context.cwd);
        const content = readFileSync(entry.fileName, "utf8");
        const lineText = content.split(/\r?\n/)[location.line - 1]?.trim() ?? "";
        context.recordWorkspaceRead(entry.fileName);
        return `  ${location.file_path}:${location.line}:${location.character} — ${lineText}`;
    });
    const header = filtered.length > MAX_REFERENCE_RESULTS
        ? `References to \`${symbolName || "symbol"}\` (${MAX_REFERENCE_RESULTS} shown of ${filtered.length} found):`
        : `References to \`${symbolName || "symbol"}\` (${filtered.length} found):`;
    return [header, ...lines].join("\n");
}
async function executeDiagnostics(input, context) {
    if (input.absolutePath) {
        context.recordWorkspaceRead(input.absolutePath);
        const diagnostics = await collectDiagnosticsForPath(input.absolutePath, {
            cwd: context.cwd,
            sandbox: context.sandbox,
            threadDir: context.threadPaths.threadDir,
        });
        if (diagnostics.items.length === 0) {
            return "No diagnostics found.";
        }
        return formatDiagnostics([diagnostics]);
    }
    const files = [
        ...listWorkspaceFiles(context.cwd, context.threadPaths.threadDir, TS_EXTENSIONS),
        ...listWorkspaceFiles(context.cwd, context.threadPaths.threadDir, PYTHON_EXTENSIONS),
    ];
    const diagnostics = [];
    let total = 0;
    for (const file_path of files) {
        if (total >= MAX_DIAGNOSTICS_TOTAL) {
            break;
        }
        const result = await collectDiagnosticsForPath(file_path, {
            cwd: context.cwd,
            sandbox: context.sandbox,
            threadDir: context.threadPaths.threadDir,
        });
        if (result.items.length === 0) {
            continue;
        }
        diagnostics.push({
            file_path: result.file_path,
            items: result.items.slice(0, Math.max(0, MAX_DIAGNOSTICS_TOTAL - total)),
        });
        total += diagnostics.at(-1)?.items.length ?? 0;
        context.recordWorkspaceRead(file_path);
    }
    if (diagnostics.length === 0) {
        return "No diagnostics found.";
    }
    return formatDiagnostics(diagnostics);
}
function executeSymbolSearch(input, context) {
    const results = [
        ...collectTypeScriptSymbols(context.cwd, context.threadPaths.threadDir, input.query, input.limit),
        ...collectPythonSymbols(context.cwd, context.threadPaths.threadDir, input.query, input.limit),
    ]
        .sort((left, right) => left.file_path.localeCompare(right.file_path) || left.line - right.line)
        .slice(0, input.limit);
    if (results.length === 0) {
        return `No symbols matching "${input.query}" found.`;
    }
    for (const result of results) {
        context.recordWorkspaceRead(resolve(context.cwd, result.file_path));
    }
    return [
        `Symbols matching "${input.query}" (${results.length} found):`,
        ...results.map((result) => `  [${result.kind}] ${result.name} — ${result.file_path}:${result.line}`),
    ].join("\n");
}
function normalizeLspLocationInput(toolName, input, context) {
    if (!isPlainObject(input)) {
        throw new BestAgentError("TOOL_INPUT_INVALID", `${toolName} requires an object input.`);
    }
    const file_path = normalizeRequiredString(input.file_path, `${toolName} requires file_path.`);
    const line = normalizePositiveInteger(input.line, `${toolName} requires a positive line number.`);
    const character = normalizePositiveInteger(input.character ?? input.column, `${toolName} requires a positive character number.`);
    const absolutePath = resolveWorkspaceFile(context.cwd, file_path, toolName);
    return {
        absolutePath,
        character,
        file_path: toWorkspaceRelative(context.cwd, absolutePath),
        line,
    };
}
function normalizeLspReferenceInput(input, context) {
    const normalized = normalizeLspLocationInput("lsp-find-references", input, context);
    const include_declaration = isPlainObject(input) && typeof input.include_declaration === "boolean"
        ? input.include_declaration
        : true;
    return {
        ...normalized,
        include_declaration,
    };
}
function normalizeLspDiagnosticsInput(input, context) {
    if (input === undefined || input === null || input === "") {
        return {};
    }
    if (!isPlainObject(input)) {
        throw new BestAgentError("TOOL_INPUT_INVALID", "lsp-diagnostics requires an object input.");
    }
    const rawPath = typeof input.file_path === "string" ? input.file_path : undefined;
    if (!rawPath?.trim()) {
        return {};
    }
    const absolutePath = resolveWorkspaceFile(context.cwd, rawPath, "lsp-diagnostics");
    return {
        absolutePath,
        file_path: toWorkspaceRelative(context.cwd, absolutePath),
    };
}
function normalizeLspSymbolSearchInput(input) {
    if (typeof input === "string") {
        const query = input.trim();
        if (!query) {
            throw new BestAgentError("TOOL_INPUT_INVALID", "lsp-symbol-search requires a non-empty query.");
        }
        return { limit: 20, query };
    }
    if (!isPlainObject(input)) {
        throw new BestAgentError("TOOL_INPUT_INVALID", "lsp-symbol-search requires an object input.");
    }
    const query = normalizeRequiredString(input.query, "lsp-symbol-search requires query.");
    const rawLimit = typeof input.limit === "number" ? Math.trunc(input.limit) : 20;
    return {
        limit: Math.min(Math.max(rawLimit, 1), MAX_SYMBOL_RESULTS),
        query,
    };
}
function resolveWorkspaceFile(cwd, file_path, toolName) {
    const absolutePath = resolve(cwd, file_path);
    if (!isWithinRoot(absolutePath, cwd)) {
        throw new BestAgentError("TOOL_INPUT_INVALID", `${toolName} requires a workspace-relative file path.`);
    }
    let stats;
    try {
        stats = statSync(absolutePath);
    }
    catch {
        throw new BestAgentError("TOOL_INPUT_NOT_FOUND", `Path ${absolutePath} does not exist in the workspace.`);
    }
    if (stats.isDirectory()) {
        throw new BestAgentError("TOOL_INPUT_NOT_FOUND", `Path ${absolutePath} is a directory.`);
    }
    return absolutePath;
}
function createTsProject(cwd, threadDir, focusFile) {
    const configPath = findTsConfigPath(focusFile ? dirname(focusFile) : cwd);
    let fileNames;
    let options;
    if (configPath) {
        const parsed = ts.getParsedCommandLineOfConfigFile(configPath, { allowJs: true }, {
            ...ts.sys,
            onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
                throw new BestAgentError("TOOL_RUNTIME", ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"));
            },
        });
        if (!parsed) {
            throw new BestAgentError("TOOL_RUNTIME", `Unable to load TypeScript config at ${configPath}.`);
        }
        fileNames = parsed.fileNames.filter(isTypeScriptFile);
        if (focusFile && isTypeScriptFile(focusFile) && !fileNames.includes(focusFile)) {
            fileNames.push(focusFile);
        }
        options = parsed.options;
    }
    else {
        fileNames = listWorkspaceFiles(cwd, threadDir, TS_EXTENSIONS);
        if (focusFile && isTypeScriptFile(focusFile) && !fileNames.includes(focusFile)) {
            fileNames.push(focusFile);
        }
        options = {
            allowJs: true,
            jsx: ts.JsxEmit.Preserve,
            module: ts.ModuleKind.ESNext,
            moduleResolution: ts.ModuleResolutionKind.Bundler,
            skipLibCheck: true,
            target: ts.ScriptTarget.ES2022,
        };
    }
    const versions = new Map(fileNames.map((fileName) => [fileName, String(statSync(fileName).mtimeMs)]));
    const host = {
        directoryExists: ts.sys.directoryExists,
        fileExists: ts.sys.fileExists,
        getCompilationSettings: () => options,
        getCurrentDirectory: () => cwd,
        getDefaultLibFileName: ts.getDefaultLibFilePath,
        getDirectories: ts.sys.getDirectories,
        getNewLine: () => ts.sys.newLine,
        getScriptFileNames: () => fileNames,
        getScriptSnapshot: (fileName) => {
            if (!ts.sys.fileExists(fileName)) {
                return undefined;
            }
            return ts.ScriptSnapshot.fromString(readFileSync(fileName, "utf8"));
        },
        getScriptVersion: (fileName) => versions.get(fileName) ?? "0",
        readDirectory: ts.sys.readDirectory,
        readFile: ts.sys.readFile,
        useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
    };
    return {
        fileNames,
        service: ts.createLanguageService(host, ts.createDocumentRegistry()),
    };
}
async function collectDiagnosticsForPath(file_path, context) {
    if (isPythonFile(file_path)) {
        return collectPythonDiagnostics(file_path, context);
    }
    if (!isTypeScriptFile(file_path)) {
        return {
            file_path: toWorkspaceRelative(context.cwd, file_path),
            items: [],
        };
    }
    const project = createTsProject(context.cwd, context.threadDir, file_path);
    const diagnostics = [
        ...project.service.getSyntacticDiagnostics(file_path),
        ...project.service.getSemanticDiagnostics(file_path),
    ];
    return {
        file_path: toWorkspaceRelative(context.cwd, file_path),
        items: diagnostics
            .filter((diagnostic) => diagnostic.start !== undefined)
            .slice(0, MAX_DIAGNOSTICS_PER_FILE)
            .map((diagnostic) => {
            const source = ts.getLineAndCharacterOfPosition(diagnostic.file ??
                ts.createSourceFile(file_path, readFileSync(file_path, "utf8"), ts.ScriptTarget.Latest, true), diagnostic.start ?? 0);
            return {
                line: source.line + 1,
                message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
                severity: diagnostic.category === ts.DiagnosticCategory.Warning ? "warning" : "error",
            };
        }),
    };
}
async function collectPythonDiagnostics(file_path, context) {
    const script = [
        "import ast, json, pathlib, sys",
        "path = pathlib.Path(sys.argv[1])",
        "source = path.read_text(encoding='utf-8')",
        "try:",
        "    ast.parse(source, filename=str(path))",
        "    print('[]')",
        "except SyntaxError as error:",
        "    payload = [{",
        "        'line': error.lineno or 1,",
        "        'message': error.msg or 'Syntax error',",
        "        'severity': 'error'",
        "    }]",
        "    print(json.dumps(payload))",
    ].join("\n");
    const output = await context.sandbox.runCommand(["python3", "-c", script, file_path], context.cwd);
    const items = JSON.parse(output);
    return {
        file_path: toWorkspaceRelative(context.cwd, file_path),
        items: items.slice(0, MAX_DIAGNOSTICS_PER_FILE),
    };
}
function normalizePassiveDiagnosticPaths(cwd, candidatePaths) {
    const normalized = [];
    for (const candidatePath of candidatePaths) {
        const trimmed = candidatePath.trim();
        if (!trimmed) {
            continue;
        }
        const absolutePath = resolve(cwd, trimmed);
        if (!isWithinRoot(absolutePath, cwd)) {
            continue;
        }
        if (!isTypeScriptFile(absolutePath) && !isPythonFile(absolutePath)) {
            continue;
        }
        try {
            if (!statSync(absolutePath).isFile()) {
                continue;
            }
        }
        catch {
            continue;
        }
        normalized.push(absolutePath);
    }
    return Array.from(new Set(normalized)).slice(-6);
}
function collectTypeScriptSymbols(cwd, threadDir, query, limit) {
    const matches = [];
    for (const file_path of listWorkspaceFiles(cwd, threadDir, TS_EXTENSIONS)) {
        if (matches.length >= limit) {
            break;
        }
        const sourceText = readFileSync(file_path, "utf8");
        const sourceFile = ts.createSourceFile(file_path, sourceText, ts.ScriptTarget.Latest, true);
        collectTsSymbolsFromNode(sourceFile, sourceFile, query, matches, cwd, limit);
    }
    return matches;
}
function collectTsSymbolsFromNode(node, sourceFile, query, matches, cwd, limit) {
    if (matches.length >= limit) {
        return;
    }
    const namedDeclaration = getNamedDeclaration(node);
    if (namedDeclaration) {
        const { kind, name, position } = namedDeclaration;
        if (name.toLowerCase().includes(query.toLowerCase())) {
            const line = sourceFile.getLineAndCharacterOfPosition(position).line + 1;
            matches.push({
                file_path: toWorkspaceRelative(cwd, sourceFile.fileName),
                kind,
                line,
                name,
            });
        }
    }
    ts.forEachChild(node, (child) => collectTsSymbolsFromNode(child, sourceFile, query, matches, cwd, limit));
}
function getNamedDeclaration(node) {
    if ((ts.isFunctionDeclaration(node) ||
        ts.isClassDeclaration(node) ||
        ts.isInterfaceDeclaration(node) ||
        ts.isTypeAliasDeclaration(node) ||
        ts.isEnumDeclaration(node)) &&
        node.name) {
        return {
            kind: tsKindLabel(node),
            name: node.name.getText(),
            position: node.name.getStart(),
        };
    }
    if (ts.isMethodDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
        return {
            kind: "method",
            name: node.name.text,
            position: node.name.getStart(),
        };
    }
    if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
        return {
            kind: "variable",
            name: node.name.text,
            position: node.name.getStart(),
        };
    }
    return undefined;
}
function collectPythonSymbols(cwd, threadDir, query, limit) {
    const matches = [];
    const patterns = [
        { kind: "class", regex: /^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)/ },
        { kind: "function", regex: /^\s*(?:async\s+def|def)\s+([A-Za-z_][A-Za-z0-9_]*)/ },
        { kind: "variable", regex: /^([A-Za-z_][A-Za-z0-9_]*)\s*=/ },
    ];
    for (const file_path of listWorkspaceFiles(cwd, threadDir, PYTHON_EXTENSIONS)) {
        if (matches.length >= limit) {
            break;
        }
        const lines = readFileSync(file_path, "utf8").split(/\r?\n/);
        lines.forEach((line, index) => {
            if (matches.length >= limit) {
                return;
            }
            for (const pattern of patterns) {
                const match = line.match(pattern.regex);
                if (!match?.[1]) {
                    continue;
                }
                if (!match[1].toLowerCase().includes(query.toLowerCase())) {
                    continue;
                }
                matches.push({
                    file_path: toWorkspaceRelative(cwd, file_path),
                    kind: pattern.kind,
                    line: index + 1,
                    name: match[1],
                });
                break;
            }
        });
    }
    return matches;
}
function listWorkspaceFiles(cwd, threadDir, extensions) {
    const files = [];
    walkDirectory(cwd, (entryPath) => {
        if (entryPath === threadDir || isWithinRoot(entryPath, threadDir)) {
            return;
        }
        if (!extensions.has(extensionKey(entryPath))) {
            return;
        }
        files.push(entryPath);
    });
    return files;
}
function walkDirectory(root, visitFile) {
    for (const entry of readdirSync(root, { withFileTypes: true })) {
        const entryPath = resolve(root, entry.name);
        if (entry.isDirectory()) {
            if (IGNORED_DIRECTORIES.has(entry.name)) {
                continue;
            }
            walkDirectory(entryPath, visitFile);
            continue;
        }
        if (entry.isFile()) {
            visitFile(entryPath);
        }
    }
}
function formatDefinitionLocation(file_path, start, fallbackPath, cwd) {
    const sourceText = readFileSync(file_path, "utf8");
    const sourceFile = ts.createSourceFile(file_path || fallbackPath, sourceText, ts.ScriptTarget.Latest, true);
    const position = sourceFile.getLineAndCharacterOfPosition(start);
    return {
        character: position.character + 1,
        file_path: toWorkspaceRelative(cwd, file_path),
        line: position.line + 1,
    };
}
function formatDiagnostics(diagnostics) {
    return diagnostics
        .map((entry) => [
        `${entry.file_path} (${entry.items.length} diagnostics):`,
        ...entry.items.map((item) => `  [${item.severity}] L${item.line}: ${item.message}`),
    ].join("\n"))
        .join("\n\n");
}
function toTsPosition(service, file_path, line, character) {
    const program = service.getProgram();
    const sourceFile = program?.getSourceFile(file_path);
    if (!sourceFile) {
        throw new BestAgentError("TOOL_RUNTIME", `Unable to load TypeScript source for ${file_path}.`);
    }
    const lineStarts = sourceFile.getLineStarts();
    const safeLine = Math.min(Math.max(line - 1, 0), Math.max(lineStarts.length - 1, 0));
    const lineStart = lineStarts[safeLine] ?? 0;
    const nextLineStart = safeLine + 1 < lineStarts.length
        ? (lineStarts[safeLine + 1] ?? sourceFile.text.length)
        : sourceFile.text.length;
    const safeCharacter = Math.min(Math.max(character - 1, 0), Math.max(nextLineStart - lineStart - 1, 0));
    return lineStart + safeCharacter;
}
function findTsConfigPath(startDir) {
    let current = startDir;
    while (true) {
        for (const configName of ["tsconfig.json", "jsconfig.json"]) {
            const candidate = resolve(current, configName);
            try {
                if (statSync(candidate).isFile()) {
                    return candidate;
                }
            }
            catch {
                // continue walking upward
            }
        }
        const parent = dirname(current);
        if (parent === current) {
            return undefined;
        }
        current = parent;
    }
}
function isTypeScriptFile(file_path) {
    return TS_EXTENSIONS.has(extensionKey(file_path));
}
function isPythonFile(file_path) {
    return PYTHON_EXTENSIONS.has(extensionKey(file_path));
}
function renderUnavailableForExtension(file_path) {
    return `LSP is not available for ${extensionKey(file_path)} files. Use content-search or workspace-search instead.`;
}
function extensionKey(file_path) {
    const base = basename(file_path);
    if (base.endsWith(".d.ts")) {
        return ".ts";
    }
    return file_path.slice(file_path.lastIndexOf(".")).toLowerCase();
}
function toWorkspaceRelative(cwd, absolutePath) {
    return relative(cwd, absolutePath).split(sep).join("/");
}
function tsKindLabel(node) {
    if (ts.isFunctionDeclaration(node)) {
        return "function";
    }
    if (ts.isClassDeclaration(node)) {
        return "class";
    }
    if (ts.isInterfaceDeclaration(node)) {
        return "interface";
    }
    if (ts.isTypeAliasDeclaration(node)) {
        return "type";
    }
    if (ts.isEnumDeclaration(node)) {
        return "enum";
    }
    return "symbol";
}
function normalizeRequiredString(value, message) {
    const normalized = typeof value === "string" ? value.trim() : "";
    if (!normalized) {
        throw new BestAgentError("TOOL_INPUT_INVALID", message);
    }
    return normalized;
}
function normalizePositiveInteger(value, message) {
    if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
        throw new BestAgentError("TOOL_INPUT_INVALID", message);
    }
    return value;
}
function isWithinRoot(path, root) {
    return path === root || path.startsWith(`${root}${sep}`) || path.startsWith(`${root}/`);
}
//# sourceMappingURL=lsp.js.map