import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { constants } from 'node:fs';
import { access, lstat, readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { Box, Text } from 'ink';
import React from 'react';
import ToolMessage from '../components/tool-message.js';
import { FILE_READ_CHUNK_SIZE_LINES, FILE_READ_CHUNKING_HINT_THRESHOLD_LINES, FILE_READ_METADATA_THRESHOLD_LINES, MAX_LINE_LENGTH_CHARS, } from '../constants.js';
import { ThemeContext } from '../hooks/useTheme.js';
import { jsonSchema, tool } from '../types/core.js';
import { getCachedFileContent } from '../utils/file-cache.js';
import { getFileType } from '../utils/file-type-detector.js';
import { isValidFilePath, resolveFilePath } from '../utils/path-validation.js';
import { calculateTokens } from '../utils/token-calculator.js';
const executeReadFile = async (args) => {
    const absPath = resolve(args.path);
    try {
        // Handle explicit metadata_only request
        if (args.metadata_only) {
            const stats = await lstat(absPath);
            // Determine file type
            let type = 'file';
            if (stats.isSymbolicLink()) {
                type = 'symlink';
            }
            else if (stats.isDirectory()) {
                type = 'directory';
            }
            const lastModified = new Date(stats.mtime).toISOString();
            const size = stats.size;
            let output = `File Information for "${args.path}"\n`;
            output += `${'='.repeat(50)}\n\n`;
            output += `Type: ${type}\n`;
            output += `Size: ${size.toLocaleString()} bytes\n`;
            output += `Last Modified: ${lastModified}\n`;
            // For regular files, try to get additional info
            if (type === 'file') {
                output += `Readable: yes\n`;
                // Try to detect encoding and line count
                try {
                    const cached = await getCachedFileContent(absPath);
                    const lines = cached.lines;
                    const content = cached.content;
                    output += `Lines: ${lines.length.toLocaleString()}\n`;
                    output += `Estimated Tokens: ~${calculateTokens(content).toLocaleString()}\n`;
                    // Detect file type from extension
                    const fileType = getFileType(absPath);
                    output += `File Type: ${fileType}\n`;
                    // Detect likely encoding (simple heuristic)
                    let encoding = 'UTF-8';
                    try {
                        // Try to read as UTF-8
                        await readFile(absPath, 'utf-8');
                    }
                    catch {
                        encoding = 'Binary/Unknown';
                    }
                    output += `Encoding: ${encoding}\n`;
                }
                catch (error) {
                    // If we can't read it, mark as not readable
                    output += `Readable: no\n`;
                    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                    output += `Note: Could not read file - ${errorMessage}\n`;
                }
            }
            else if (type === 'directory') {
                output += `Note: Use list_directory tool to see directory contents\n`;
            }
            else if (type === 'symlink') {
                output += `Note: This is a symbolic link. Size reflects link metadata, not target.\n`;
            }
            output += `\n[Use read_file to view file contents]\n`;
            return output;
        }
        const cached = await getCachedFileContent(absPath);
        const content = cached.content;
        // Check if file is empty (0 tokens)
        if (content.length === 0) {
            throw new Error(`File "${args.path}" exists but is empty (0 tokens)`);
        }
        const lines = cached.lines;
        const totalLines = lines.length;
        const fileSize = content.length;
        const estimatedTokens = calculateTokens(content);
        // Progressive disclosure: metadata first for files >300 lines
        // Small files can be read directly without ranges
        if (args.start_line === undefined &&
            args.end_line === undefined &&
            totalLines > FILE_READ_METADATA_THRESHOLD_LINES) {
            // Return metadata only for medium/large files
            // Detect file type from extension
            const fileType = getFileType(absPath);
            let output = `File: ${args.path}\n`;
            output += `Type: ${fileType}\n`;
            output += `Total lines: ${totalLines.toLocaleString()}\n`;
            output += `Size: ${fileSize.toLocaleString()} bytes\n`;
            output += `Estimated tokens: ~${estimatedTokens.toLocaleString()}\n\n`;
            if (totalLines <= FILE_READ_CHUNKING_HINT_THRESHOLD_LINES) {
                output += `[Medium file - To read specific sections, call read_file with start_line and end_line]\n`;
                output += `[To read entire file progressively, make multiple calls:]\n`;
                output += `  - read_file({path: "${args.path}", start_line: 1, end_line: ${FILE_READ_CHUNK_SIZE_LINES}})\n`;
                output += `  - read_file({path: "${args.path}", start_line: ${FILE_READ_CHUNK_SIZE_LINES + 1}, end_line: ${totalLines}})\n`;
            }
            else {
                output += `[Large file - Choose one approach:]\n`;
                output += `[1. Targeted read: Use search_files to find code, then read specific ranges]\n`;
                output += `[2. Progressive read: Read file in chunks (recommended chunk size: 200-300 lines)]\n`;
                output += `   Example chunks for ${totalLines} lines:\n`;
                const chunkSize = FILE_READ_CHUNK_SIZE_LINES;
                const numChunks = Math.ceil(totalLines / chunkSize);
                for (let i = 0; i < Math.min(numChunks, 3); i++) {
                    const start = i * chunkSize + 1;
                    const end = Math.min((i + 1) * chunkSize, totalLines);
                    output += `   - read_file({path: "${args.path}", start_line: ${start}, end_line: ${end}})\n`;
                }
                if (numChunks > 3) {
                    output += `   ... and ${numChunks - 3} more chunks to complete the file\n`;
                }
            }
            return output;
        }
        // Line ranges specified - read and return content
        const startLine = args.start_line ? Math.max(1, args.start_line) : 1;
        const endLine = args.end_line
            ? Math.min(totalLines, args.end_line)
            : totalLines;
        // Extract the lines to return
        const linesToReturn = lines.slice(startLine - 1, endLine);
        // Return content without line numbers for clean content-based editing
        return linesToReturn.join('\n');
    }
    catch (error) {
        // Handle file not found and other filesystem errors
        if (error &&
            typeof error === 'object' &&
            'code' in error &&
            error.code === 'ENOENT') {
            throw new Error(`File "${args.path}" does not exist`);
        }
        // Re-throw other errors (including our empty file error)
        throw error;
    }
};
const readFileCoreTool = tool({
    description: 'Read file contents with line numbers. AUTO-ACCEPTED (no user approval needed). Use this INSTEAD OF bash cat/head/tail/less commands. PROGRESSIVE DISCLOSURE: Files ≤300 lines return content directly. Files >300 lines return metadata first - then call again with start_line/end_line to read specific sections. Use metadata_only=true for file info (size, lines, type) without reading content. Always prefer this over bash for any file reading.',
    inputSchema: jsonSchema({
        type: 'object',
        properties: {
            path: {
                type: 'string',
                description: 'The path to the file to read.',
            },
            start_line: {
                type: 'number',
                description: 'Optional: Line number to start reading from (1-indexed). Required for files >300 lines. Use with end_line to read specific range.',
            },
            end_line: {
                type: 'number',
                description: 'Optional: Line number to stop reading at (inclusive). Required for files >300 lines. Use with start_line to read specific range.',
            },
            metadata_only: {
                type: 'boolean',
                description: 'Optional: If true, returns only file metadata (size, line count, type, encoding, modification time) without content. Useful for quickly checking file properties.',
            },
        },
        required: ['path'],
    }),
    // Low risk: read-only operation, never requires approval
    needsApproval: false,
    execute: async (args, _options) => {
        return await executeReadFile(args);
    },
});
const ReadFileFormatter = React.memo(({ args, fileInfo, }) => {
    const themeContext = React.useContext(ThemeContext);
    if (!themeContext) {
        throw new Error('ReadFileFormatter must be used within a ThemeProvider');
    }
    const { colors } = themeContext;
    const path = args.path || args.file_path || 'unknown';
    const messageContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.tool, children: "\u2692 read_file" }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Path: " }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: colors.text, children: [path, ' ', fileInfo.isMetadataOnly && (_jsx(Text, { color: colors.info, children: "(metadata only)" }))] }) })] }), fileInfo.isMetadataOnly ? (_jsx(_Fragment, { children: _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Total lines: " }), _jsx(Text, { color: colors.text, children: fileInfo.totalLines.toLocaleString() })] }) })) : (_jsx(_Fragment, { children: _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Lines: " }), _jsxs(Text, { color: colors.text, children: [args.start_line || 1, " - ", args.end_line || fileInfo.totalLines] })] }) })), !fileInfo.isMetadataOnly && (_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Tokens: " }), _jsxs(Text, { color: colors.text, children: ["~", fileInfo.tokens.toLocaleString()] })] }))] }));
    return _jsx(ToolMessage, { message: messageContent, hideBox: true });
});
const readFileFormatter = async (args, result) => {
    // If result is an error message, don't try to read the file
    if (result && result.startsWith('Error:')) {
        return _jsx(_Fragment, {});
    }
    // Load file info to calculate actual read information
    let fileInfo = {
        totalLines: 0,
        readLines: 0,
        tokens: 0,
        isPartialRead: false,
        isMetadataOnly: false,
    };
    try {
        const path = args.path || args.file_path;
        if (path && typeof path === 'string') {
            const absPath = resolve(path);
            const cached = await getCachedFileContent(absPath);
            const content = cached.content;
            const lines = cached.lines;
            const totalLines = lines.length;
            // Detect if this was a metadata-only response
            const isMetadataOnly = (result?.startsWith('File:') ?? false) &&
                !args.start_line &&
                !args.end_line &&
                totalLines > FILE_READ_METADATA_THRESHOLD_LINES;
            // Calculate what was actually read
            const startLine = args.start_line || 1;
            const endLine = args.end_line || totalLines;
            const readLines = endLine - startLine + 1;
            const isPartialRead = startLine > 1 || endLine < totalLines;
            // Calculate tokens
            let tokens;
            if (isMetadataOnly) {
                // For metadata, show estimated tokens of the FULL FILE
                tokens = calculateTokens(content);
            }
            else {
                // For content reads, show tokens of what was actually returned
                tokens = result ? calculateTokens(result) : 0;
            }
            fileInfo = {
                totalLines,
                readLines,
                tokens,
                isPartialRead,
                isMetadataOnly,
            };
        }
    }
    catch {
        // File doesn't exist or can't be read - keep default fileInfo
    }
    return _jsx(ReadFileFormatter, { args: args, fileInfo: fileInfo });
};
const readFileValidator = async (args) => {
    // Validate path boundary first to prevent directory traversal
    if (!isValidFilePath(args.path)) {
        return {
            valid: false,
            error: `⚒ Invalid file path: "${args.path}". Path must be relative and within the project directory.`,
        };
    }
    // Verify the resolved path stays within project boundaries
    try {
        const cwd = process.cwd();
        resolveFilePath(args.path, cwd);
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        return {
            valid: false,
            error: `⚒ Path validation failed: ${errorMessage}`,
        };
    }
    const absPath = resolve(args.path);
    try {
        await access(absPath, constants.F_OK);
        // Validate line range parameters
        if (args.start_line !== undefined && args.start_line < 1) {
            return {
                valid: false,
                error: '⚒ start_line must be >= 1',
            };
        }
        if (args.start_line !== undefined &&
            args.end_line !== undefined &&
            args.end_line < args.start_line) {
            return {
                valid: false,
                error: '⚒ end_line must be >= start_line',
            };
        }
        // Check if end_line exceeds file length
        if (args.end_line !== undefined) {
            const cached = await getCachedFileContent(absPath);
            const totalLines = cached.lines.length;
            if (args.end_line > totalLines) {
                return {
                    valid: false,
                    error: `⚒ end_line (${args.end_line}) exceeds file length (${totalLines} lines)`,
                };
            }
        }
        // Check for minified/binary content (very long lines)
        // Skip this check for metadata_only requests
        if (!args.metadata_only) {
            const cached = await getCachedFileContent(absPath);
            const startLine = args.start_line ? Math.max(1, args.start_line) : 1;
            const endLine = args.end_line
                ? Math.min(cached.lines.length, args.end_line)
                : cached.lines.length;
            for (let i = startLine - 1; i < endLine; i++) {
                const line = cached.lines[i];
                if (line && line.length > MAX_LINE_LENGTH_CHARS) {
                    return {
                        valid: false,
                        error: `⚒ File "${args.path}" contains minified or binary content (line ${i + 1} has ${line.length.toLocaleString()} characters). This file cannot be read as it would consume excessive tokens without providing useful information.`,
                    };
                }
            }
        }
        return { valid: true };
    }
    catch (error) {
        if (error &&
            typeof error === 'object' &&
            'code' in error &&
            error.code === 'ENOENT') {
            return {
                valid: false,
                error: `⚒ File "${args.path}" does not exist`,
            };
        }
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        return {
            valid: false,
            error: `⚒ Cannot access file "${args.path}": ${errorMessage}`,
        };
    }
};
export const readFileTool = {
    name: 'read_file',
    tool: readFileCoreTool,
    formatter: readFileFormatter,
    validator: readFileValidator,
};
//# sourceMappingURL=read-file.js.map