import { Type } from '@sinclair/typebox';
import { constants } from 'fs';
import { access as fsAccess, readFile as fsReadFile } from 'fs/promises';
import { resolveReadPath } from '../../shared/files/path-utils.js';
import { detectSupportedImageMimeTypeFromFile } from '../../shared/images/mime.js';
import { formatDimensionNote, resizeImage } from '../../shared/images/resize.js';
import { READ } from '../../tool-names.js';
import { withAbort } from '../../utils/abort.js';
import { DEFAULT_MAX_CHARS, formatSize, truncateHead } from '../../utils/truncate.js';
import { enforceFilesystemPolicy } from '../permissions/filesystem-policy.js';
import { requirePermissionPolicy } from '../permissions/require-policy.js';
const readSchema = Type.Object({
    path: Type.String({ description: 'Path to the file to read (relative or absolute)' }),
    offset: Type.Optional(Type.Number({ description: 'Line number to start reading from (1-indexed)' })),
    limit: Type.Optional(Type.Number({ description: 'Maximum number of lines to read' }))
});
const defaultReadOperations = {
    readFile: (path) => fsReadFile(path),
    access: (path) => fsAccess(path, constants.R_OK),
    detectImageMimeType: detectSupportedImageMimeTypeFromFile
};
export function createReadTool(cwd, options) {
    const autoResizeImages = options?.autoResizeImages ?? true;
    const ops = options?.operations ?? defaultReadOperations;
    return {
        name: READ,
        label: READ,
        isReadOnly: true,
        description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_CHARS} characters. Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
        parameters: readSchema,
        execute: async (_toolCallId, { path, offset, limit }, signal, _onUpdate, _ctx) => {
            const absolutePath = resolveReadPath(path, cwd);
            const permissionPolicy = requirePermissionPolicy(_ctx, READ);
            await enforceFilesystemPolicy({
                path: absolutePath,
                policy: permissionPolicy.filesystem,
                access: 'read',
                sessionState: _ctx?.sessionState,
                approvalGrants: _ctx?.approvalGrants?.filesystem
            });
            return withAbort(signal, async (checkAborted) => {
                await ops.access(absolutePath);
                checkAborted();
                const mimeType = ops.detectImageMimeType
                    ? await ops.detectImageMimeType(absolutePath)
                    : undefined;
                let content;
                let details;
                if (mimeType) {
                    const buffer = await ops.readFile(absolutePath);
                    const base64 = buffer.toString('base64');
                    if (autoResizeImages) {
                        const resized = await resizeImage({ type: 'image', data: base64, mimeType });
                        const dimensionNote = formatDimensionNote(resized);
                        let textNote = `Read image file [${resized.mimeType}]`;
                        if (dimensionNote)
                            textNote += `\n${dimensionNote}`;
                        content = [
                            { type: 'text', text: textNote },
                            { type: 'image', data: resized.data, mimeType: resized.mimeType }
                        ];
                    }
                    else {
                        content = [
                            { type: 'text', text: `Read image file [${mimeType}]` },
                            { type: 'image', data: base64, mimeType }
                        ];
                    }
                }
                else {
                    const buffer = await ops.readFile(absolutePath);
                    const textContent = buffer.toString('utf-8');
                    const allLines = textContent.split('\n');
                    const totalFileLines = allLines.length;
                    const startLine = offset ? Math.max(0, offset - 1) : 0;
                    const startLineDisplay = startLine + 1;
                    if (startLine >= allLines.length) {
                        throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
                    }
                    let selectedContent;
                    let userLimitedLines;
                    if (limit !== undefined) {
                        const endLine = Math.min(startLine + limit, allLines.length);
                        selectedContent = allLines.slice(startLine, endLine).join('\n');
                        userLimitedLines = endLine - startLine;
                    }
                    else {
                        selectedContent = allLines.slice(startLine).join('\n');
                    }
                    const truncation = truncateHead(selectedContent);
                    let outputText;
                    if (truncation.truncated) {
                        const outputLineCount = truncation.content.split('\n').length;
                        const endLineDisplay = startLineDisplay + outputLineCount - 1;
                        const nextOffset = endLineDisplay + 1;
                        outputText = truncation.content;
                        outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_CHARS)} limit). Use offset=${nextOffset} to continue.]`;
                        details = { truncation };
                    }
                    else if (userLimitedLines !== undefined &&
                        startLine + userLimitedLines < allLines.length) {
                        const remaining = allLines.length - (startLine + userLimitedLines);
                        const nextOffset = startLine + userLimitedLines + 1;
                        outputText = truncation.content;
                        outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;
                    }
                    else {
                        outputText = truncation.content;
                    }
                    content = [{ type: 'text', text: outputText }];
                }
                checkAborted();
                return { content, details };
            });
        }
    };
}
//# sourceMappingURL=read.js.map