import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { constants, existsSync } from 'node:fs';
import { access, readFile, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { highlight } from 'cli-highlight';
import { Box, Text } from 'ink';
import React from 'react';
import ToolMessage from '../../components/tool-message.js';
import { DEFAULT_TERMINAL_COLUMNS } from '../../constants.js';
import { ThemeContext } from '../../hooks/useTheme.js';
import { jsonSchema, tool } from '../../types/core.js';
import { truncateAnsi } from '../../utils/ansi-truncate.js';
import { formatError } from '../../utils/error-formatter.js';
import { getCachedFileContent, invalidateCache } from '../../utils/file-cache.js';
import { normalizeIndentation } from '../../utils/indentation-normalizer.js';
import { validatePath } from '../../utils/path-validators.js';
import { getLanguageFromExtension } from '../../utils/programming-language-helper.js';
import { hasSeenFile, markFileSeen } from '../../utils/read-tracker.js';
import { calculateTokens } from '../../utils/token-calculator.js';
import { createFileToolApproval } from '../../utils/tool-approval.js';
import { ensureString } from '../../utils/type-helpers.js';
import { closeDiffInVSCode, isVSCodeConnected, sendFileChangeToVSCode, } from '../../vscode/index.js';
const executeWriteFile = async (args) => {
    const absPath = resolve(args.path);
    const fileExists = existsSync(absPath);
    // Type guard: ensure content is string for write operation
    // Storage is safe (fs.writeFile ensures string-only), but we need to convert for safety
    const contentStr = ensureString(args.content);
    await writeFile(absPath, contentStr, 'utf-8');
    // Invalidate cache after write
    invalidateCache(absPath);
    // The file's contents are now known to the model (it just wrote them), so a
    // follow-up edit or rewrite is not blind.
    markFileSeen(absPath);
    // Read back to verify and show actual content
    const actualContent = await readFile(absPath, 'utf-8');
    const lines = actualContent.split('\n');
    const lineCount = lines.length;
    const charCount = actualContent.length;
    const estimatedTokens = calculateTokens(actualContent);
    // Generate full file contents to show the model the current file state
    let fileContext = '\n\nFile contents after write:\n';
    for (let i = 0; i < lines.length; i++) {
        const lineNumStr = String(i + 1).padStart(4, ' ');
        const line = lines[i] || '';
        fileContext += `${lineNumStr}: ${line}\n`;
    }
    const action = fileExists ? 'overwritten' : 'written';
    return `File ${action} successfully (${lineCount} lines, ${charCount} characters, ~${estimatedTokens} tokens).${fileContext}`;
};
const writeFileCoreTool = tool({
    description: 'Write content to a file (creates new file or overwrites existing file). Use this for complete file rewrites, generated code, or when most of the file needs to change. For small targeted edits, use string_replace instead.',
    inputSchema: jsonSchema({
        // Note: change to unknown
        type: 'object',
        properties: {
            path: {
                type: 'string',
                description: 'The path to the file to write.',
            },
            content: {
                type: 'string', // Guide LLM to send strings
                description: 'The complete content to write to the file.',
            },
        },
        required: ['path', 'content'],
    }),
    execute: async (args, _options) => {
        return await executeWriteFile(args);
    },
});
// Create a component that will re-render when theme changes
const WriteFileFormatter = React.memo(({ args }) => {
    const themeContext = React.useContext(ThemeContext);
    if (!themeContext) {
        throw new Error('ThemeContext is required');
    }
    const { colors } = themeContext;
    const path = args.path || args.file_path || 'unknown';
    const newContent = ensureString(args.content);
    const lineCount = newContent.split('\n').length;
    const charCount = newContent.length;
    // Estimate tokens (rough approximation: ~4 characters per token)
    const estimatedTokens = calculateTokens(newContent);
    // Normalize indentation for display
    const lines = newContent.split('\n');
    const normalizedLines = normalizeIndentation(lines);
    // Calculate available width for line content (terminal width - line number prefix - padding)
    const terminalWidth = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
    const lineNumPrefixWidth = 6; // "1234 " = 5 chars + 1 for safety
    const availableWidth = Math.max(terminalWidth - lineNumPrefixWidth - 2, 20);
    const messageContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.tool, children: "\u2692 write_file" }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Path: " }), _jsx(Text, { wrap: "truncate-end", color: colors.text, children: path })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Size: " }), _jsxs(Text, { color: colors.text, children: [lineCount, " lines, ", charCount, " characters (~", estimatedTokens, " tokens)"] })] }), newContent.length > 0 ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: colors.text, children: "File content:" }), normalizedLines.map((line, i) => {
                        const lineNumStr = String(i + 1).padStart(4, ' ');
                        const ext = path.split('.').pop()?.toLowerCase() ?? '';
                        const language = getLanguageFromExtension(ext);
                        try {
                            const highlighted = highlight(line, { language, theme: 'default' });
                            const truncated = truncateAnsi(highlighted, availableWidth);
                            return (_jsxs(Box, { children: [_jsxs(Text, { color: colors.secondary, children: [lineNumStr, " "] }), _jsx(Text, { wrap: "truncate-end", children: truncated })] }, i));
                        }
                        catch {
                            const truncated = line.length > availableWidth
                                ? line.slice(0, availableWidth - 1) + '…'
                                : line;
                            return (_jsxs(Box, { children: [_jsxs(Text, { color: colors.secondary, children: [lineNumStr, " "] }), _jsx(Text, { wrap: "truncate-end", children: truncated })] }, i));
                        }
                    })] })) : (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.secondary, children: "File will be empty" }) }))] }));
    return _jsx(ToolMessage, { message: messageContent, hideBox: true });
});
// Track VS Code change IDs for cleanup
const vscodeChangeIds = new Map();
const writeFileFormatter = async (args, result) => {
    const path = args.path || args.file_path || '';
    const absPath = resolve(path);
    // Send diff to VS Code during preview phase (before execution)
    if (result === undefined && isVSCodeConnected()) {
        const content = args.content || '';
        // Get original content if file exists (use cache if available)
        let originalContent = '';
        if (existsSync(absPath)) {
            try {
                const cached = await getCachedFileContent(absPath);
                originalContent = cached.content;
            }
            catch {
                // File might exist but not be readable
            }
        }
        const changeId = sendFileChangeToVSCode(absPath, originalContent, content, 'write_file', {
            path,
            content,
        });
        if (changeId) {
            vscodeChangeIds.set(absPath, changeId);
        }
    }
    else if (result !== undefined && isVSCodeConnected()) {
        // Tool was executed (confirmed or rejected), close the diff
        const changeId = vscodeChangeIds.get(absPath);
        if (changeId) {
            closeDiffInVSCode(changeId);
            vscodeChangeIds.delete(absPath);
        }
    }
    return _jsx(WriteFileFormatter, { args: args });
};
const writeFileValidator = async (args) => {
    const pathResult = validatePath(args.path);
    if (!pathResult.valid)
        return pathResult;
    const absPath = resolve(args.path);
    // Check if parent directory exists
    const parentDir = dirname(absPath);
    try {
        await access(parentDir, constants.F_OK);
    }
    catch (error) {
        if (error && typeof error === 'object' && 'code' in error) {
            if (error.code === 'ENOENT') {
                return {
                    valid: false,
                    error: `Parent directory does not exist: "${parentDir}"`,
                };
            }
        }
        const errorMessage = formatError(error);
        return {
            valid: false,
            error: `Cannot access parent directory "${parentDir}": ${errorMessage}`,
        };
    }
    // Check if content is valid (not null/undefined)
    if (args.content === null || args.content === undefined) {
        return {
            valid: false,
            error: `Invalid content: content cannot be null or undefined.`,
        };
    }
    // Read-before-overwrite: refuse to clobber an existing file the model has
    // not seen this session. Creating new files is always allowed; rewriting a
    // file the model has already read (or previously written) is allowed. This
    // preserves the legitimate read-then-rewrite path while blocking blind
    // overwrites that would destroy contents the model never looked at.
    if (existsSync(absPath) && !hasSeenFile(absPath)) {
        return {
            valid: false,
            error: `"${args.path}" already exists and you have not read it this session. Call read_file on it first (so you don't discard existing content), then retry. For small changes, prefer string_replace over a full overwrite.`,
        };
    }
    // Allow empty strings (intentional file creation)
    // Only reject null/undefined, which we already checked above
    // Check for invalid path characters or attempts to write to system directories
    const invalidPatterns = [
        /^\/etc\//i,
        /^\/sys\//i,
        /^\/proc\//i,
        /^\/dev\//i,
        /^\/boot\//i,
        /^C:\\Windows\\/i,
        /^C:\\Program Files\\/i,
    ];
    for (const pattern of invalidPatterns) {
        if (pattern.test(absPath)) {
            return {
                valid: false,
                error: `Cannot write files to system directory: "${args.path}"`,
            };
        }
    }
    return { valid: true };
};
export const writeFileTool = {
    name: 'write_file',
    tool: writeFileCoreTool,
    formatter: writeFileFormatter,
    validator: writeFileValidator,
    approval: createFileToolApproval('write_file'),
};
//# sourceMappingURL=write-file.js.map