/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fsPromises from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { loadServerHierarchicalMemory } from './memoryDiscovery.js';
import { GEMINI_CONFIG_DIR, setGeminiMdFilename, DEFAULT_CONTEXT_FILENAME, } from '../tools/memoryTool.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
vi.mock('os', async (importOriginal) => {
    const actualOs = await importOriginal();
    return {
        ...actualOs,
        homedir: vi.fn(),
    };
});
describe('loadServerHierarchicalMemory', () => {
    let testRootDir;
    let cwd;
    let projectRoot;
    let homedir;
    async function createEmptyDir(fullPath) {
        await fsPromises.mkdir(fullPath, { recursive: true });
        return fullPath;
    }
    async function createTestFile(fullPath, fileContents) {
        await fsPromises.mkdir(path.dirname(fullPath), { recursive: true });
        await fsPromises.writeFile(fullPath, fileContents);
        return path.resolve(testRootDir, fullPath);
    }
    beforeEach(async () => {
        testRootDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'folder-structure-test-'));
        vi.resetAllMocks();
        // Set environment variables to indicate test environment
        process.env.NODE_ENV = 'test';
        process.env.VITEST = 'true';
        projectRoot = await createEmptyDir(path.join(testRootDir, 'project'));
        cwd = await createEmptyDir(path.join(projectRoot, 'src'));
        homedir = await createEmptyDir(path.join(testRootDir, 'userhome'));
        vi.mocked(os.homedir).mockReturnValue(homedir);
    });
    afterEach(async () => {
        // Some tests set this to a different value.
        setGeminiMdFilename(DEFAULT_CONTEXT_FILENAME);
        // Clean up the temporary directory to prevent resource leaks.
        await fsPromises.rm(testRootDir, { recursive: true, force: true });
    });
    it('should return empty memory and count if no context files are found', async () => {
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: '',
            fileCount: 0,
        });
    });
    it('should load only the global context file if present and others are not (default filename)', async () => {
        const defaultContextFile = await createTestFile(path.join(homedir, GEMINI_CONFIG_DIR, DEFAULT_CONTEXT_FILENAME), 'default context content');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---\ndefault context content\n--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---`,
            fileCount: 1,
        });
    });
    it('should load only the global custom context file if present and filename is changed', async () => {
        const customFilename = 'CUSTOM_AGENTS.md';
        setGeminiMdFilename(customFilename);
        const customContextFile = await createTestFile(path.join(homedir, GEMINI_CONFIG_DIR, customFilename), 'custom context content');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, customContextFile)} ---\ncustom context content\n--- End of Context from: ${path.relative(cwd, customContextFile)} ---`,
            fileCount: 1,
        });
    });
    it('should load context files by upward traversal with custom filename', async () => {
        const customFilename = 'PROJECT_CONTEXT.md';
        setGeminiMdFilename(customFilename);
        const projectContextFile = await createTestFile(path.join(projectRoot, customFilename), 'project context content');
        const cwdContextFile = await createTestFile(path.join(cwd, customFilename), 'cwd context content');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, projectContextFile)} ---\nproject context content\n--- End of Context from: ${path.relative(cwd, projectContextFile)} ---\n\n--- Context from: ${path.relative(cwd, cwdContextFile)} ---\ncwd context content\n--- End of Context from: ${path.relative(cwd, cwdContextFile)} ---`,
            fileCount: 2,
        });
    });
    it('should load context files by downward traversal with custom filename', async () => {
        const customFilename = 'LOCAL_CONTEXT.md';
        setGeminiMdFilename(customFilename);
        await createTestFile(path.join(cwd, 'subdir', customFilename), 'Subdir custom memory');
        await createTestFile(path.join(cwd, customFilename), 'CWD custom memory');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${customFilename} ---\nCWD custom memory\n--- End of Context from: ${customFilename} ---\n\n--- Context from: ${path.join('subdir', customFilename)} ---\nSubdir custom memory\n--- End of Context from: ${path.join('subdir', customFilename)} ---`,
            fileCount: 2,
        });
    });
    it('should load ORIGINAL_GEMINI_MD_FILENAME files by upward traversal from CWD to project root', async () => {
        const projectRootGeminiFile = await createTestFile(path.join(projectRoot, DEFAULT_CONTEXT_FILENAME), 'Project root memory');
        const srcGeminiFile = await createTestFile(path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'Src directory memory');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, projectRootGeminiFile)} ---\nProject root memory\n--- End of Context from: ${path.relative(cwd, projectRootGeminiFile)} ---\n\n--- Context from: ${path.relative(cwd, srcGeminiFile)} ---\nSrc directory memory\n--- End of Context from: ${path.relative(cwd, srcGeminiFile)} ---`,
            fileCount: 2,
        });
    });
    it('should load ORIGINAL_GEMINI_MD_FILENAME files by downward traversal from CWD', async () => {
        await createTestFile(path.join(cwd, 'subdir', DEFAULT_CONTEXT_FILENAME), 'Subdir memory');
        await createTestFile(path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'CWD memory');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${DEFAULT_CONTEXT_FILENAME} ---\nCWD memory\n--- End of Context from: ${DEFAULT_CONTEXT_FILENAME} ---\n\n--- Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---\nSubdir memory\n--- End of Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---`,
            fileCount: 2,
        });
    });
    it('should load and correctly order global, upward, and downward ORIGINAL_GEMINI_MD_FILENAME files', async () => {
        const defaultContextFile = await createTestFile(path.join(homedir, GEMINI_CONFIG_DIR, DEFAULT_CONTEXT_FILENAME), 'default context content');
        const rootGeminiFile = await createTestFile(path.join(testRootDir, DEFAULT_CONTEXT_FILENAME), 'Project parent memory');
        const projectRootGeminiFile = await createTestFile(path.join(projectRoot, DEFAULT_CONTEXT_FILENAME), 'Project root memory');
        const cwdGeminiFile = await createTestFile(path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'CWD memory');
        const subDirGeminiFile = await createTestFile(path.join(cwd, 'sub', DEFAULT_CONTEXT_FILENAME), 'Subdir memory');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---\ndefault context content\n--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---\n\n--- Context from: ${path.relative(cwd, rootGeminiFile)} ---\nProject parent memory\n--- End of Context from: ${path.relative(cwd, rootGeminiFile)} ---\n\n--- Context from: ${path.relative(cwd, projectRootGeminiFile)} ---\nProject root memory\n--- End of Context from: ${path.relative(cwd, projectRootGeminiFile)} ---\n\n--- Context from: ${path.relative(cwd, cwdGeminiFile)} ---\nCWD memory\n--- End of Context from: ${path.relative(cwd, cwdGeminiFile)} ---\n\n--- Context from: ${path.relative(cwd, subDirGeminiFile)} ---\nSubdir memory\n--- End of Context from: ${path.relative(cwd, subDirGeminiFile)} ---`,
            fileCount: 5,
        });
    });
    it('should ignore specified directories during downward scan', async () => {
        await createEmptyDir(path.join(projectRoot, '.git'));
        await createTestFile(path.join(projectRoot, '.gitignore'), 'node_modules');
        await createTestFile(path.join(cwd, 'node_modules', DEFAULT_CONTEXT_FILENAME), 'Ignored memory');
        const regularSubDirGeminiFile = await createTestFile(path.join(cwd, 'my_code', DEFAULT_CONTEXT_FILENAME), 'My code memory');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot), [], 'tree', {
            respectGitIgnore: true,
            respectGeminiIgnore: true,
        }, 200);
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---\nMy code memory\n--- End of Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---`,
            fileCount: 1,
        });
    });
    it('should respect the maxDirs parameter during downward scan', async () => {
        const consoleDebugSpy = vi
            .spyOn(console, 'debug')
            .mockImplementation(() => { });
        for (let i = 0; i < 100; i++) {
            await createEmptyDir(path.join(cwd, `deep_dir_${i}`));
        }
        // Pass the custom limit directly to the function
        await loadServerHierarchicalMemory(cwd, [], true, new FileDiscoveryService(projectRoot), [], 'tree', // importFormat
        {
            respectGitIgnore: true,
            respectGeminiIgnore: true,
        }, 50);
        expect(consoleDebugSpy).toHaveBeenCalledWith(expect.stringContaining('[DEBUG] [BfsFileSearch]'), expect.stringContaining('Scanning [50/50]:'));
        vi.mocked(console.debug).mockRestore();
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: '',
            fileCount: 0,
        });
    });
    it('should load extension context file paths', async () => {
        const extensionFilePath = await createTestFile(path.join(testRootDir, 'extensions/ext1/QWEN.md'), 'Extension memory content');
        const result = await loadServerHierarchicalMemory(cwd, [], false, new FileDiscoveryService(projectRoot), [extensionFilePath]);
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, extensionFilePath)} ---\nExtension memory content\n--- End of Context from: ${path.relative(cwd, extensionFilePath)} ---`,
            fileCount: 1,
        });
    });
    it('should load memory from included directories', async () => {
        const includedDir = await createEmptyDir(path.join(testRootDir, 'included'));
        const includedFile = await createTestFile(path.join(includedDir, DEFAULT_CONTEXT_FILENAME), 'included directory memory');
        const result = await loadServerHierarchicalMemory(cwd, [includedDir], false, new FileDiscoveryService(projectRoot));
        expect(result).toEqual({
            memoryContent: `--- Context from: ${path.relative(cwd, includedFile)} ---\nincluded directory memory\n--- End of Context from: ${path.relative(cwd, includedFile)} ---`,
            fileCount: 1,
        });
    });
});
//# sourceMappingURL=memoryDiscovery.test.js.map