/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach, afterEach, vi, } from 'vitest';
import { detectLineEnding } from '../utils/textUtils.js';
import { WriteFileTool } from './write-file.js';
import { EditTool } from './edit.js';
import { ApprovalMode } from '../policy/types.js';
import { ToolConfirmationOutcome } from './tools.js';
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { GeminiClient } from '../core/client.js';
import { ensureCorrectFileContent } from '../utils/editCorrector.js';
import { StandardFileSystemService } from '../services/fileSystemService.js';
import { WorkspaceContext } from '../utils/workspaceContext.js';
import { createMockMessageBus, getMockMessageBusInstance, } from '../test-utils/mock-message-bus.js';
const rootDir = path.resolve(os.tmpdir(), 'gemini-cli-line-ending-test-root');
// --- MOCKS ---
vi.mock('../core/client.js');
vi.mock('../utils/editCorrector.js');
vi.mock('../ide/ide-client.js', () => ({
    IdeClient: {
        getInstance: vi.fn().mockResolvedValue({
            openDiff: vi.fn(),
            isDiffingEnabled: vi.fn().mockReturnValue(false),
        }),
    },
}));
let mockGeminiClientInstance;
let mockBaseLlmClientInstance;
const mockEnsureCorrectFileContent = vi.fn();
// Mock Config
const fsService = new StandardFileSystemService();
const mockConfigInternal = {
    getTargetDir: () => rootDir,
    getApprovalMode: vi.fn(() => ApprovalMode.DEFAULT),
    setApprovalMode: vi.fn(),
    getGeminiClient: vi.fn(),
    getBaseLlmClient: vi.fn(),
    getFileSystemService: () => fsService,
    getIdeMode: vi.fn(() => false),
    getWorkspaceContext: () => new WorkspaceContext(rootDir),
    getApiKey: () => 'test-key',
    getModel: () => 'test-model',
    getSandbox: () => false,
    getDebugMode: () => false,
    getQuestion: () => undefined,
    getToolDiscoveryCommand: () => undefined,
    getToolCallCommand: () => undefined,
    getMcpServerCommand: () => undefined,
    getMcpServers: () => undefined,
    getUserAgent: () => 'test-agent',
    getUserMemory: () => '',
    setUserMemory: vi.fn(),
    getGeminiMdFileCount: () => 0,
    setGeminiMdFileCount: vi.fn(),
    getDisableLLMCorrection: vi.fn(() => false),
    getActiveModel: () => 'test-model',
    validatePathAccess: vi.fn().mockReturnValue(null),
    getToolRegistry: () => ({
        registerTool: vi.fn(),
        discoverTools: vi.fn(),
    }),
    isInteractive: () => false,
    isPlanMode: () => false,
    storage: {
        getPlansDir: () => '/tmp/plans',
    },
};
const mockConfig = mockConfigInternal;
vi.mock('../telemetry/loggers.js', () => ({
    logFileOperation: vi.fn(),
    logEditStrategy: vi.fn(),
    logEditCorrectionEvent: vi.fn(),
}));
// --- END MOCKS ---
describe('Line Ending Preservation', () => {
    let tempDir;
    beforeEach(() => {
        vi.clearAllMocks();
        tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'line-ending-test-external-'));
        if (!fs.existsSync(rootDir)) {
            fs.mkdirSync(rootDir, { recursive: true });
        }
        mockGeminiClientInstance = new (vi.mocked(GeminiClient))(mockConfig);
        vi.mocked(GeminiClient).mockImplementation(() => mockGeminiClientInstance);
        mockBaseLlmClientInstance = {
            generateJson: vi.fn(),
        };
        vi.mocked(ensureCorrectFileContent).mockImplementation(mockEnsureCorrectFileContent);
        mockConfigInternal.getGeminiClient.mockReturnValue(mockGeminiClientInstance);
        mockConfigInternal.getBaseLlmClient.mockReturnValue(mockBaseLlmClientInstance);
    });
    afterEach(() => {
        if (fs.existsSync(tempDir)) {
            fs.rmSync(tempDir, { recursive: true, force: true });
        }
        if (fs.existsSync(rootDir)) {
            fs.rmSync(rootDir, { recursive: true, force: true });
        }
        vi.restoreAllMocks();
    });
    describe('detectLineEnding', () => {
        it('should detect CRLF', () => {
            expect(detectLineEnding('line1\r\nline2')).toBe('\r\n');
            expect(detectLineEnding('line1\r\n')).toBe('\r\n');
        });
        it('should detect LF', () => {
            expect(detectLineEnding('line1\nline2')).toBe('\n');
            expect(detectLineEnding('line1\n')).toBe('\n');
            expect(detectLineEnding('line1')).toBe('\n'); // Default to LF if no newline
        });
    });
    describe('WriteFileTool', () => {
        let tool;
        const abortSignal = new AbortController().signal;
        beforeEach(() => {
            const bus = createMockMessageBus();
            getMockMessageBusInstance(bus).defaultToolDecision = 'ask_user';
            tool = new WriteFileTool(mockConfig, bus);
        });
        it('should preserve CRLF when overwriting an existing file', async () => {
            const filePath = path.join(rootDir, 'crlf_file.txt');
            const originalContent = 'line1\r\nline2\r\n';
            fs.writeFileSync(filePath, originalContent); // Write with CRLF (or however Node writes binary buffer)
            // Ensure strictly CRLF
            fs.writeFileSync(filePath, Buffer.from('line1\r\nline2\r\n'));
            // Proposed content from LLM (usually LF)
            const proposedContent = 'line1\nline2\nline3\n';
            // Mock corrections to return proposed content as-is (but usually normalized)
            mockEnsureCorrectFileContent.mockResolvedValue(proposedContent);
            const params = { file_path: filePath, content: proposedContent };
            const invocation = tool.build(params);
            // Force approval
            const confirmDetails = await invocation.shouldConfirmExecute(abortSignal);
            if (confirmDetails &&
                typeof confirmDetails === 'object' &&
                'onConfirm' in confirmDetails) {
                await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce);
            }
            await invocation.execute(abortSignal);
            const writtenContent = fs.readFileSync(filePath, 'utf8');
            // Expect all newlines to be CRLF
            expect(writtenContent).toBe('line1\r\nline2\r\nline3\r\n');
        });
        it('should use OS EOL for new files', async () => {
            const filePath = path.join(rootDir, 'new_os_eol_file.txt');
            const proposedContent = 'line1\nline2\n';
            mockEnsureCorrectFileContent.mockResolvedValue(proposedContent);
            const params = { file_path: filePath, content: proposedContent };
            const invocation = tool.build(params);
            const confirmDetails = await invocation.shouldConfirmExecute(abortSignal);
            if (confirmDetails &&
                typeof confirmDetails === 'object' &&
                'onConfirm' in confirmDetails) {
                await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce);
            }
            await invocation.execute(abortSignal);
            const writtenContent = fs.readFileSync(filePath, 'utf8');
            if (os.EOL === '\r\n') {
                expect(writtenContent).toBe('line1\r\nline2\r\n');
            }
            else {
                expect(writtenContent).toBe('line1\nline2\n');
            }
        });
    });
    describe('EditTool', () => {
        let tool;
        const abortSignal = new AbortController().signal;
        beforeEach(() => {
            const bus = createMockMessageBus();
            getMockMessageBusInstance(bus).defaultToolDecision = 'ask_user';
            tool = new EditTool(mockConfig, bus);
        });
        it('should preserve CRLF when editing a file', async () => {
            const filePath = path.join(rootDir, 'edit_crlf.txt');
            const originalContent = 'line1\r\nline2\r\nline3\r\n';
            fs.writeFileSync(filePath, Buffer.from(originalContent));
            const oldString = 'line2';
            const newString = 'modified';
            const params = {
                file_path: filePath,
                old_string: oldString,
                new_string: newString,
                instruction: 'Change line2 to modified',
            };
            const invocation = tool.build(params);
            // Force approval
            const confirmDetails = await invocation.shouldConfirmExecute(abortSignal);
            if (confirmDetails &&
                typeof confirmDetails === 'object' &&
                'onConfirm' in confirmDetails) {
                await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce);
            }
            await invocation.execute(abortSignal);
            const writtenContent = fs.readFileSync(filePath, 'utf8');
            expect(writtenContent).toBe('line1\r\nmodified\r\nline3\r\n');
        });
    });
});
//# sourceMappingURL=line-endings.test.js.map