/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { main, setupUnhandledRejectionHandler, validateDnsResolutionOrder, startInteractiveUI, } from './gemini.js';
import { 
// SettingsFile, // Currently unused
loadSettings, } from './config/settings.js';
import { appEvents, AppEvent } from './utils/events.js';
import { FatalConfigError } from '@vybestack/llxprt-code-core';
// Custom error to identify mock process.exit calls
class MockProcessExitError extends Error {
    code;
    constructor(code) {
        super('PROCESS_EXIT_MOCKED');
        this.code = code;
        this.name = 'MockProcessExitError';
    }
}
// Mock dependencies
vi.mock('./config/settings.js', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        loadSettings: vi.fn(),
    };
});
vi.mock('./config/config.js', () => ({
    loadCliConfig: vi.fn().mockResolvedValue({
        config: {
            getSandbox: vi.fn(() => false),
            getQuestion: vi.fn(() => ''),
        },
        modelWasSwitched: false,
        originalModelBeforeSwitch: null,
        finalModel: 'test-model',
    }),
    parseArguments: vi.fn().mockResolvedValue({
        model: undefined,
        sandbox: undefined,
        sandboxImage: undefined,
        debug: undefined,
    }),
}));
vi.mock('read-package-up', () => ({
    readPackageUp: vi.fn().mockResolvedValue({
        packageJson: { name: 'test-pkg', version: 'test-version' },
        path: '/fake/path/package.json',
    }),
}));
vi.mock('update-notifier', () => ({
    default: vi.fn(() => ({
        notify: vi.fn(),
    })),
}));
vi.mock('./utils/events.js', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        appEvents: {
            emit: vi.fn(),
        },
    };
});
vi.mock('./utils/sandbox.js', () => ({
    sandbox_command: vi.fn(() => ''), // Default to no sandbox command
    start_sandbox: vi.fn(() => Promise.resolve()), // Mock as an async function that resolves
}));
describe('gemini.tsx main function', () => {
    let loadSettingsMock;
    let originalEnvGeminiSandbox;
    let originalEnvSandbox;
    let initialUnhandledRejectionListeners = [];
    const processExitSpy = vi
        .spyOn(process, 'exit')
        .mockImplementation((code) => {
        throw new MockProcessExitError(code);
    });
    beforeEach(() => {
        loadSettingsMock = vi.mocked(loadSettings);
        // Store and clear sandbox-related env variables to ensure a consistent test environment
        originalEnvGeminiSandbox = process.env.GEMINI_SANDBOX;
        originalEnvSandbox = process.env.SANDBOX;
        delete process.env.GEMINI_SANDBOX;
        delete process.env.SANDBOX;
        initialUnhandledRejectionListeners =
            process.listeners('unhandledRejection');
    });
    afterEach(() => {
        // Restore original env variables
        if (originalEnvGeminiSandbox !== undefined) {
            process.env.GEMINI_SANDBOX = originalEnvGeminiSandbox;
        }
        else {
            delete process.env.GEMINI_SANDBOX;
        }
        if (originalEnvSandbox !== undefined) {
            process.env.SANDBOX = originalEnvSandbox;
        }
        else {
            delete process.env.SANDBOX;
        }
        const currentListeners = process.listeners('unhandledRejection');
        const addedListener = currentListeners.find((listener) => !initialUnhandledRejectionListeners.includes(listener));
        if (addedListener) {
            process.removeListener('unhandledRejection', addedListener);
        }
        vi.restoreAllMocks();
    });
    it('should throw InvalidConfigurationError if settings have errors', async () => {
        // Settings errors now throw directly from loadSettings
        loadSettingsMock.mockImplementation(() => {
            throw new FatalConfigError('Test settings error');
        });
        await expect(main()).rejects.toThrow(FatalConfigError);
    });
    it('should log unhandled promise rejections and open debug console on first error', async () => {
        const appEventsMock = vi.mocked(appEvents);
        const rejectionError = new Error('Test unhandled rejection');
        setupUnhandledRejectionHandler();
        // Simulate an unhandled rejection.
        // We are not using Promise.reject here as vitest will catch it.
        // Instead we will dispatch the event manually.
        process.emit('unhandledRejection', rejectionError, Promise.resolve());
        // We need to wait for the rejection handler to be called.
        await new Promise(process.nextTick);
        expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvent.OpenDebugConsole);
        expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvent.LogError, expect.stringContaining('Unhandled Promise Rejection'));
        expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvent.LogError, expect.stringContaining('Please file a bug report using the /bug tool.'));
        // Simulate a second rejection
        const secondRejectionError = new Error('Second test unhandled rejection');
        process.emit('unhandledRejection', secondRejectionError, Promise.resolve());
        await new Promise(process.nextTick);
        // Ensure emit was only called once for OpenDebugConsole
        const openDebugConsoleCalls = appEventsMock.emit.mock.calls.filter((call) => call[0] === AppEvent.OpenDebugConsole);
        expect(openDebugConsoleCalls.length).toBe(1);
        // Avoid the process.exit error from being thrown.
        processExitSpy.mockRestore();
    });
});
describe('validateDnsResolutionOrder', () => {
    let consoleWarnSpy;
    beforeEach(() => {
        consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
    });
    afterEach(() => {
        consoleWarnSpy.mockRestore();
    });
    it('should return "ipv4first" when the input is "ipv4first"', () => {
        expect(validateDnsResolutionOrder('ipv4first')).toBe('ipv4first');
        expect(consoleWarnSpy).not.toHaveBeenCalled();
    });
    it('should return "verbatim" when the input is "verbatim"', () => {
        expect(validateDnsResolutionOrder('verbatim')).toBe('verbatim');
        expect(consoleWarnSpy).not.toHaveBeenCalled();
    });
    it('should return the default "ipv4first" when the input is undefined', () => {
        expect(validateDnsResolutionOrder(undefined)).toBe('ipv4first');
        expect(consoleWarnSpy).not.toHaveBeenCalled();
    });
    it('should return the default "ipv4first" and log a warning for an invalid string', () => {
        expect(validateDnsResolutionOrder('invalid-value')).toBe('ipv4first');
        expect(consoleWarnSpy).toHaveBeenCalledOnce();
        expect(consoleWarnSpy).toHaveBeenCalledWith('Invalid value for dnsResolutionOrder in settings: "invalid-value". Using default "ipv4first".');
    });
});
describe('startInteractiveUI', () => {
    // Mock dependencies
    const mockConfig = {
        getProjectRoot: () => '/root',
        getScreenReader: () => false,
    };
    const mockSettings = {
        merged: {
            hideWindowTitle: false,
        },
    };
    const mockStartupWarnings = ['warning1'];
    const mockWorkspaceRoot = '/root';
    vi.mock('./utils/version.js', () => ({
        getCliVersion: vi.fn(() => Promise.resolve('1.0.0')),
    }));
    vi.mock('./ui/utils/kittyProtocolDetector.js', () => ({
        detectAndEnableKittyProtocol: vi.fn(() => Promise.resolve()),
    }));
    vi.mock('./ui/utils/updateCheck.js', () => ({
        checkForUpdates: vi.fn(() => Promise.resolve(null)),
    }));
    vi.mock('./utils/cleanup.js', () => ({
        cleanupCheckpoints: vi.fn(() => Promise.resolve()),
        registerCleanup: vi.fn(),
    }));
    vi.mock('ink', () => ({
        render: vi.fn().mockReturnValue({ unmount: vi.fn() }),
    }));
    beforeEach(() => {
        vi.clearAllMocks();
    });
    it('should render the UI with proper React context and exitOnCtrlC disabled', async () => {
        const { render } = await import('ink');
        const renderSpy = vi.mocked(render);
        await startInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot);
        // Verify render was called with correct options
        expect(renderSpy).toHaveBeenCalledTimes(1);
        const [reactElement, options] = renderSpy.mock.calls[0];
        // Verify render options
        expect(options).toEqual({
            exitOnCtrlC: false,
        });
        // Verify React element structure is valid (but don't deep dive into JSX internals)
        expect(reactElement).toBeDefined();
    });
    it('should perform all startup tasks in correct order', async () => {
        const { getCliVersion } = await import('./utils/version.js');
        const { detectAndEnableKittyProtocol } = await import('./ui/utils/kittyProtocolDetector.js');
        const { checkForUpdates } = await import('./ui/utils/updateCheck.js');
        const { registerCleanup } = await import('./utils/cleanup.js');
        await startInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot);
        // Verify all startup tasks were called
        expect(getCliVersion).toHaveBeenCalledTimes(1);
        expect(detectAndEnableKittyProtocol).toHaveBeenCalledTimes(1);
        expect(registerCleanup).toHaveBeenCalledTimes(1);
        // Verify cleanup handler is registered with unmount function
        const cleanupFn = vi.mocked(registerCleanup).mock.calls[0][0];
        expect(typeof cleanupFn).toBe('function');
        // checkForUpdates should be called asynchronously (not waited for)
        // We need a small delay to let it execute
        await new Promise((resolve) => setTimeout(resolve, 0));
        expect(checkForUpdates).toHaveBeenCalledTimes(1);
    });
});
//# sourceMappingURL=gemini.test.js.map