/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getCoreSystemPrompt, resolvePathFromEnv } from './prompts.js';
import { isGitRepository } from '../utils/gitUtils.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
import { GEMINI_DIR } from '../utils/paths.js';
// Mock tool names if they are dynamically generated or complex
vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } }));
vi.mock('../tools/edit', () => ({ EditTool: { Name: 'replace' } }));
vi.mock('../tools/glob', () => ({ GlobTool: { Name: 'glob' } }));
vi.mock('../tools/grep', () => ({ GrepTool: { Name: 'search_file_content' } }));
vi.mock('../tools/read-file', () => ({ ReadFileTool: { Name: 'read_file' } }));
vi.mock('../tools/read-many-files', () => ({
    ReadManyFilesTool: { Name: 'read_many_files' },
}));
vi.mock('../tools/shell', () => ({
    ShellTool: { Name: 'run_shell_command' },
}));
vi.mock('../tools/write-file', () => ({
    WriteFileTool: { Name: 'write_file' },
}));
vi.mock('../agents/codebase-investigator.js', () => ({
    CodebaseInvestigatorAgent: { name: 'codebase_investigator' },
}));
vi.mock('../utils/gitUtils', () => ({
    isGitRepository: vi.fn(),
}));
vi.mock('node:fs');
describe('Core System Prompt (prompts.ts)', () => {
    let mockConfig;
    beforeEach(() => {
        vi.resetAllMocks();
        vi.stubEnv('GEMINI_SYSTEM_MD', undefined);
        vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', undefined);
        mockConfig = {
            getToolRegistry: vi.fn().mockReturnValue({
                getAllToolNames: vi.fn().mockReturnValue([]),
            }),
            getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
            storage: {
                getProjectTempDir: vi.fn().mockReturnValue('/tmp/project-temp'),
            },
            isInteractive: vi.fn().mockReturnValue(true),
            isInteractiveShellEnabled: vi.fn().mockReturnValue(true),
        };
    });
    it('should return the base prompt when no userMemory is provided', () => {
        vi.stubEnv('SANDBOX', undefined);
        const prompt = getCoreSystemPrompt(mockConfig, '');
        expect(prompt).not.toContain('---\n\n'); // Separator should not be present
        expect(prompt).toContain('You are an interactive CLI agent'); // Check for core content
        expect(prompt).toMatchSnapshot(); // Use snapshot for base prompt structure
    });
    it('should return the base prompt when userMemory is empty string', () => {
        vi.stubEnv('SANDBOX', undefined);
        const prompt = getCoreSystemPrompt(mockConfig, '');
        expect(prompt).not.toContain('---\n\n');
        expect(prompt).toContain('You are an interactive CLI agent');
        expect(prompt).toMatchSnapshot();
    });
    it('should return the base prompt when userMemory is whitespace only', () => {
        vi.stubEnv('SANDBOX', undefined);
        const prompt = getCoreSystemPrompt(mockConfig, '   \n  \t ');
        expect(prompt).not.toContain('---\n\n');
        expect(prompt).toContain('You are an interactive CLI agent');
        expect(prompt).toMatchSnapshot();
    });
    it('should append userMemory with separator when provided', () => {
        vi.stubEnv('SANDBOX', undefined);
        const memory = 'This is custom user memory.\nBe extra polite.';
        const expectedSuffix = `\n\n---\n\n${memory}`;
        const prompt = getCoreSystemPrompt(mockConfig, memory);
        expect(prompt.endsWith(expectedSuffix)).toBe(true);
        expect(prompt).toContain('You are an interactive CLI agent'); // Ensure base prompt follows
        expect(prompt).toMatchSnapshot(); // Snapshot the combined prompt
    });
    it('should include sandbox-specific instructions when SANDBOX env var is set', () => {
        vi.stubEnv('SANDBOX', 'true'); // Generic sandbox value
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# Sandbox');
        expect(prompt).not.toContain('# macOS Seatbelt');
        expect(prompt).not.toContain('# Outside of Sandbox');
        expect(prompt).toMatchSnapshot();
    });
    it('should include seatbelt-specific instructions when SANDBOX env var is "sandbox-exec"', () => {
        vi.stubEnv('SANDBOX', 'sandbox-exec');
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# macOS Seatbelt');
        expect(prompt).not.toContain('# Sandbox');
        expect(prompt).not.toContain('# Outside of Sandbox');
        expect(prompt).toMatchSnapshot();
    });
    it('should include non-sandbox instructions when SANDBOX env var is not set', () => {
        vi.stubEnv('SANDBOX', undefined); // Ensure it\'s not set
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# Outside of Sandbox');
        expect(prompt).not.toContain('# Sandbox');
        expect(prompt).not.toContain('# macOS Seatbelt');
        expect(prompt).toMatchSnapshot();
    });
    it('should include git instructions when in a git repo', () => {
        vi.stubEnv('SANDBOX', undefined);
        vi.mocked(isGitRepository).mockReturnValue(true);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).toContain('# Git Repository');
        expect(prompt).toMatchSnapshot();
    });
    it('should not include git instructions when not in a git repo', () => {
        vi.stubEnv('SANDBOX', undefined);
        vi.mocked(isGitRepository).mockReturnValue(false);
        const prompt = getCoreSystemPrompt(mockConfig);
        expect(prompt).not.toContain('# Git Repository');
        expect(prompt).toMatchSnapshot();
    });
    it('should return the interactive avoidance prompt when in non-interactive mode', () => {
        vi.stubEnv('SANDBOX', undefined);
        mockConfig.isInteractive = vi.fn().mockReturnValue(false);
        const prompt = getCoreSystemPrompt(mockConfig, '');
        expect(prompt).toContain('**Interactive Commands:**'); // Check for interactive prompt
        expect(prompt).toMatchSnapshot(); // Use snapshot for base prompt structure
    });
    describe('with CodebaseInvestigator enabled', () => {
        beforeEach(() => {
            mockConfig = {
                getToolRegistry: vi.fn().mockReturnValue({
                    getAllToolNames: vi
                        .fn()
                        .mockReturnValue([CodebaseInvestigatorAgent.name]),
                }),
                getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
                storage: {
                    getProjectTempDir: vi.fn().mockReturnValue('/tmp/project-temp'),
                },
                isInteractive: vi.fn().mockReturnValue(false),
                isInteractiveShellEnabled: vi.fn().mockReturnValue(false),
            };
        });
        it('should include CodebaseInvestigator instructions in the prompt', () => {
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).toContain(`your **first and primary tool** must be '${CodebaseInvestigatorAgent.name}'`);
            expect(prompt).toContain(`do not ignore the output of '${CodebaseInvestigatorAgent.name}'`);
            expect(prompt).not.toContain("Use 'search_file_content' and 'glob' search tools extensively");
        });
    });
    describe('with CodebaseInvestigator disabled', () => {
        // No beforeEach needed, will use the default from the parent describe
        it('should include standard tool instructions in the prompt', () => {
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(prompt).not.toContain(`your **first and primary tool** must be '${CodebaseInvestigatorAgent.name}'`);
            expect(prompt).toContain("Use 'search_file_content' and 'glob' search tools extensively");
        });
    });
    describe('GEMINI_SYSTEM_MD environment variable', () => {
        it('should use default prompt when GEMINI_SYSTEM_MD is "false"', () => {
            vi.stubEnv('GEMINI_SYSTEM_MD', 'false');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).not.toHaveBeenCalled();
            expect(prompt).not.toContain('custom system prompt');
        });
        it('should use default prompt when GEMINI_SYSTEM_MD is "0"', () => {
            vi.stubEnv('GEMINI_SYSTEM_MD', '0');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).not.toHaveBeenCalled();
            expect(prompt).not.toContain('custom system prompt');
        });
        it('should throw error if GEMINI_SYSTEM_MD points to a non-existent file', () => {
            const customPath = '/non/existent/path/system.md';
            vi.stubEnv('GEMINI_SYSTEM_MD', customPath);
            vi.mocked(fs.existsSync).mockReturnValue(false);
            expect(() => getCoreSystemPrompt(mockConfig)).toThrow(`missing system prompt file '${path.resolve(customPath)}'`);
        });
        it('should read from default path when GEMINI_SYSTEM_MD is "true"', () => {
            const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
            vi.stubEnv('GEMINI_SYSTEM_MD', 'true');
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(defaultPath, 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
        it('should read from default path when GEMINI_SYSTEM_MD is "1"', () => {
            const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
            vi.stubEnv('GEMINI_SYSTEM_MD', '1');
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(defaultPath, 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
        it('should read from custom path when GEMINI_SYSTEM_MD provides one, preserving case', () => {
            const customPath = path.resolve('/custom/path/SyStEm.Md');
            vi.stubEnv('GEMINI_SYSTEM_MD', customPath);
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(customPath, 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
        it('should expand tilde in custom path when GEMINI_SYSTEM_MD is set', () => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const customPath = '~/custom/system.md';
            const expectedPath = path.join(homeDir, 'custom/system.md');
            vi.stubEnv('GEMINI_SYSTEM_MD', customPath);
            vi.mocked(fs.existsSync).mockReturnValue(true);
            vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
            const prompt = getCoreSystemPrompt(mockConfig);
            expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(expectedPath), 'utf8');
            expect(prompt).toBe('custom system prompt');
        });
    });
    describe('GEMINI_WRITE_SYSTEM_MD environment variable', () => {
        it('should not write to file when GEMINI_WRITE_SYSTEM_MD is "false"', () => {
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', 'false');
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).not.toHaveBeenCalled();
        });
        it('should not write to file when GEMINI_WRITE_SYSTEM_MD is "0"', () => {
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '0');
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).not.toHaveBeenCalled();
        });
        it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "true"', () => {
            const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', 'true');
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(defaultPath, expect.any(String));
        });
        it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "1"', () => {
            const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '1');
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(defaultPath, expect.any(String));
        });
        it('should write to custom path when GEMINI_WRITE_SYSTEM_MD provides one', () => {
            const customPath = path.resolve('/custom/path/system.md');
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', customPath);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(customPath, expect.any(String));
        });
        it('should expand tilde in custom path when GEMINI_WRITE_SYSTEM_MD is set', () => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const customPath = '~/custom/system.md';
            const expectedPath = path.join(homeDir, 'custom/system.md');
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', customPath);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(path.resolve(expectedPath), expect.any(String));
        });
        it('should expand tilde in custom path when GEMINI_WRITE_SYSTEM_MD is just ~', () => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const customPath = '~';
            const expectedPath = homeDir;
            vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', customPath);
            getCoreSystemPrompt(mockConfig);
            expect(fs.writeFileSync).toHaveBeenCalledWith(path.resolve(expectedPath), expect.any(String));
        });
    });
});
describe('resolvePathFromEnv helper function', () => {
    beforeEach(() => {
        vi.resetAllMocks();
    });
    describe('when envVar is undefined, empty, or whitespace', () => {
        it('should return null for undefined', () => {
            const result = resolvePathFromEnv(undefined);
            expect(result).toEqual({
                isSwitch: false,
                value: null,
                isDisabled: false,
            });
        });
        it('should return null for empty string', () => {
            const result = resolvePathFromEnv('');
            expect(result).toEqual({
                isSwitch: false,
                value: null,
                isDisabled: false,
            });
        });
        it('should return null for whitespace only', () => {
            const result = resolvePathFromEnv('   \n\t  ');
            expect(result).toEqual({
                isSwitch: false,
                value: null,
                isDisabled: false,
            });
        });
    });
    describe('when envVar is a boolean-like string', () => {
        it('should handle "0" as disabled switch', () => {
            const result = resolvePathFromEnv('0');
            expect(result).toEqual({
                isSwitch: true,
                value: '0',
                isDisabled: true,
            });
        });
        it('should handle "false" as disabled switch', () => {
            const result = resolvePathFromEnv('false');
            expect(result).toEqual({
                isSwitch: true,
                value: 'false',
                isDisabled: true,
            });
        });
        it('should handle "1" as enabled switch', () => {
            const result = resolvePathFromEnv('1');
            expect(result).toEqual({
                isSwitch: true,
                value: '1',
                isDisabled: false,
            });
        });
        it('should handle "true" as enabled switch', () => {
            const result = resolvePathFromEnv('true');
            expect(result).toEqual({
                isSwitch: true,
                value: 'true',
                isDisabled: false,
            });
        });
        it('should be case-insensitive for boolean values', () => {
            expect(resolvePathFromEnv('FALSE')).toEqual({
                isSwitch: true,
                value: 'false',
                isDisabled: true,
            });
            expect(resolvePathFromEnv('TRUE')).toEqual({
                isSwitch: true,
                value: 'true',
                isDisabled: false,
            });
        });
    });
    describe('when envVar is a file path', () => {
        it('should resolve absolute paths', () => {
            const result = resolvePathFromEnv('/absolute/path/file.txt');
            expect(result).toEqual({
                isSwitch: false,
                value: path.resolve('/absolute/path/file.txt'),
                isDisabled: false,
            });
        });
        it('should resolve relative paths', () => {
            const result = resolvePathFromEnv('relative/path/file.txt');
            expect(result).toEqual({
                isSwitch: false,
                value: path.resolve('relative/path/file.txt'),
                isDisabled: false,
            });
        });
        it('should expand tilde to home directory', () => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const result = resolvePathFromEnv('~/documents/file.txt');
            expect(result).toEqual({
                isSwitch: false,
                value: path.resolve(path.join(homeDir, 'documents/file.txt')),
                isDisabled: false,
            });
        });
        it('should handle standalone tilde', () => {
            const homeDir = '/Users/test';
            vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
            const result = resolvePathFromEnv('~');
            expect(result).toEqual({
                isSwitch: false,
                value: path.resolve(homeDir),
                isDisabled: false,
            });
        });
        it('should handle os.homedir() errors gracefully', () => {
            vi.spyOn(os, 'homedir').mockImplementation(() => {
                throw new Error('Cannot resolve home directory');
            });
            const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
            const result = resolvePathFromEnv('~/documents/file.txt');
            expect(result).toEqual({
                isSwitch: false,
                value: null,
                isDisabled: false,
            });
            expect(consoleSpy).toHaveBeenCalledWith('Could not resolve home directory for path: ~/documents/file.txt', expect.any(Error));
            consoleSpy.mockRestore();
        });
    });
});
//# sourceMappingURL=prompts.test.js.map