/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import {} from 'react';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import { MessageType } from '../types.js';
import { completeExtensions, completeExtensionsAndScopes, extensionsCommand, } from './extensionsCommand.js';
import { ConfigExtensionDialog, } from '../components/ConfigExtensionDialog.js';
import { ExtensionRegistryView, } from '../components/views/ExtensionRegistryView.js';
import {} from './types.js';
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
import {} from '../state/extensions.js';
import { ExtensionManager, inferInstallMetadata, } from '../../config/extension-manager.js';
import { SettingScope } from '../../config/settings.js';
import { stat } from 'node:fs/promises';
import {} from '../../config/extensionRegistryClient.js';
import { waitFor } from '../../test-utils/async.js';
vi.mock('../../config/extension-manager.js', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        inferInstallMetadata: vi.fn(),
    };
});
import open from 'open';
vi.mock('open', () => ({
    default: vi.fn(),
}));
vi.mock('node:fs/promises', () => ({
    stat: vi.fn(),
}));
vi.mock('../../config/extensions/extensionSettings.js', () => ({
    ExtensionSettingScope: {
        USER: 'user',
        WORKSPACE: 'workspace',
    },
    getScopedEnvContents: vi.fn().mockResolvedValue({}),
    promptForSetting: vi.fn(),
    updateSetting: vi.fn(),
}));
vi.mock('prompts', () => ({
    default: vi.fn(),
}));
vi.mock('../../config/extensions/update.js', () => ({
    updateExtension: vi.fn(),
    checkForAllExtensionUpdates: vi.fn(),
}));
const mockDisableExtension = vi.fn();
const mockEnableExtension = vi.fn();
const mockInstallExtension = vi.fn();
const mockUninstallExtension = vi.fn();
const mockGetExtensions = vi.fn();
const inactiveExt = {
    name: 'ext-one',
    id: 'ext-one-id',
    version: '1.0.0',
    isActive: false, // should suggest disabled extensions
    path: '/test/dir/ext-one',
    contextFiles: [],
    installMetadata: {
        type: 'git',
        autoUpdate: false,
        source: 'https://github.com/some/extension.git',
    },
};
const activeExt = {
    name: 'ext-two',
    id: 'ext-two-id',
    version: '1.0.0',
    isActive: true, // should not suggest enabled extensions
    path: '/test/dir/ext-two',
    contextFiles: [],
    installMetadata: {
        type: 'git',
        autoUpdate: false,
        source: 'https://github.com/some/extension.git',
    },
};
const allExt = {
    name: 'all-ext',
    id: 'all-ext-id',
    version: '1.0.0',
    isActive: true,
    path: '/test/dir/all-ext',
    contextFiles: [],
    installMetadata: {
        type: 'git',
        autoUpdate: false,
        source: 'https://github.com/some/extension.git',
    },
};
describe('extensionsCommand', () => {
    let mockContext;
    const mockDispatchExtensionState = vi.fn();
    let mockExtensionLoader;
    let mockReloadSkills;
    let mockReloadAgents;
    beforeEach(() => {
        vi.resetAllMocks();
        mockExtensionLoader = Object.create(ExtensionManager.prototype);
        Object.assign(mockExtensionLoader, {
            enableExtension: mockEnableExtension,
            disableExtension: mockDisableExtension,
            installOrUpdateExtension: mockInstallExtension,
            uninstallExtension: mockUninstallExtension,
            getExtensions: mockGetExtensions,
            loadExtensionConfig: vi.fn().mockResolvedValue({
                name: 'test-ext',
                settings: [{ name: 'setting1', envVar: 'SETTING1' }],
            }),
        });
        mockGetExtensions.mockReturnValue([inactiveExt, activeExt, allExt]);
        vi.mocked(open).mockClear();
        mockReloadAgents = vi.fn().mockResolvedValue(undefined);
        mockReloadSkills = vi.fn().mockResolvedValue(undefined);
        mockContext = createMockCommandContext({
            services: {
                config: {
                    getExtensions: mockGetExtensions,
                    getExtensionLoader: vi.fn().mockReturnValue(mockExtensionLoader),
                    getWorkingDir: () => '/test/dir',
                    reloadSkills: mockReloadSkills,
                    getAgentRegistry: vi.fn().mockReturnValue({
                        reload: mockReloadAgents,
                    }),
                },
            },
            ui: {
                dispatchExtensionStateUpdate: mockDispatchExtensionState,
                removeComponent: vi.fn(),
            },
        });
    });
    afterEach(() => {
        // Restore any stubbed environment variables, similar to docsCommand.test.ts
        vi.unstubAllEnvs();
    });
    describe('list', () => {
        it('should add an EXTENSIONS_LIST item to the UI', async () => {
            const command = extensionsCommand();
            if (!command.action)
                throw new Error('Action not defined');
            await command.action(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
        });
        it('should show a message if no extensions are installed', async () => {
            mockGetExtensions.mockReturnValue([]);
            const command = extensionsCommand();
            if (!command.action)
                throw new Error('Action not defined');
            await command.action(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: 'No extensions installed. Run `/extensions explore` to check out the gallery.',
            });
        });
    });
    describe('completeExtensions', () => {
        it.each([
            {
                description: 'should return matching extension names',
                partialArg: 'ext',
                expected: ['ext-one', 'ext-two'],
            },
            {
                description: 'should return --all when partialArg matches',
                partialArg: '--al',
                expected: ['--all'],
            },
            {
                description: 'should return both extension names and --all when both match',
                partialArg: 'all',
                expected: ['--all', 'all-ext'],
            },
            {
                description: 'should return an empty array if no matches',
                partialArg: 'nomatch',
                expected: [],
            },
            {
                description: 'should suggest only disabled extension names for the enable command',
                partialArg: 'ext',
                expected: ['ext-one'],
                command: 'enable',
            },
            {
                description: 'should suggest only enabled extension names for the disable command',
                partialArg: 'ext',
                expected: ['ext-two'],
                command: 'disable',
            },
        ])('$description', async ({ partialArg, expected, command }) => {
            if (command) {
                mockContext.invocation.name = command;
            }
            const suggestions = completeExtensions(mockContext, partialArg);
            expect(suggestions).toEqual(expected);
        });
    });
    describe('completeExtensionsAndScopes', () => {
        it('expands the list of suggestions with --scope args', () => {
            const suggestions = completeExtensionsAndScopes(mockContext, 'ext');
            expect(suggestions).toEqual([
                'ext-one --scope user',
                'ext-one --scope workspace',
                'ext-one --scope session',
                'ext-two --scope user',
                'ext-two --scope workspace',
                'ext-two --scope session',
            ]);
        });
    });
    describe('update', () => {
        const updateAction = extensionsCommand().subCommands?.find((cmd) => cmd.name === 'update')?.action;
        if (!updateAction) {
            throw new Error('Update action not found');
        }
        it('should show usage if no args are provided', async () => {
            await updateAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Usage: /extensions update <extension-names>|--all',
            });
        });
        it('should show a message if no extensions are installed', async () => {
            mockGetExtensions.mockReturnValue([]);
            await updateAction(mockContext, 'ext-one');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: 'No extensions installed. Run `/extensions explore` to check out the gallery.',
            });
        });
        it('should inform user if there are no extensions to update with --all', async () => {
            mockDispatchExtensionState.mockImplementationOnce(async (action) => {
                if (action.type === 'SCHEDULE_UPDATE') {
                    action.payload.onComplete([]);
                }
            });
            await updateAction(mockContext, '--all');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: 'No extensions to update.',
            });
        });
        it('should call setPendingItem and addItem in a finally block on success', async () => {
            mockDispatchExtensionState.mockImplementationOnce(async (action) => {
                if (action.type === 'SCHEDULE_UPDATE') {
                    action.payload.onComplete([
                        {
                            name: 'ext-one',
                            originalVersion: '1.0.0',
                            updatedVersion: '1.0.1',
                        },
                        {
                            name: 'ext-two',
                            originalVersion: '2.0.0',
                            updatedVersion: '2.0.1',
                        },
                    ]);
                }
            });
            await updateAction(mockContext, '--all');
            expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
            expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith(null);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
        });
        it('should call setPendingItem and addItem in a finally block on failure', async () => {
            mockDispatchExtensionState.mockImplementationOnce((_) => {
                throw new Error('Something went wrong');
            });
            await updateAction(mockContext, '--all');
            expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
            expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith(null);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Something went wrong',
            });
        });
        it('should update a single extension by name', async () => {
            mockDispatchExtensionState.mockImplementationOnce(async (action) => {
                if (action.type === 'SCHEDULE_UPDATE') {
                    action.payload.onComplete([
                        {
                            name: 'ext-one',
                            originalVersion: '1.0.0',
                            updatedVersion: '1.0.1',
                        },
                    ]);
                }
            });
            await updateAction(mockContext, 'ext-one');
            expect(mockDispatchExtensionState).toHaveBeenCalledWith({
                type: 'SCHEDULE_UPDATE',
                payload: {
                    all: false,
                    names: ['ext-one'],
                    onComplete: expect.any(Function),
                },
            });
        });
        it('should update multiple extensions by name', async () => {
            mockDispatchExtensionState.mockImplementationOnce(async (action) => {
                if (action.type === 'SCHEDULE_UPDATE') {
                    action.payload.onComplete([
                        {
                            name: 'ext-one',
                            originalVersion: '1.0.0',
                            updatedVersion: '1.0.1',
                        },
                        {
                            name: 'ext-two',
                            originalVersion: '1.0.0',
                            updatedVersion: '1.0.1',
                        },
                    ]);
                }
            });
            await updateAction(mockContext, 'ext-one ext-two');
            expect(mockDispatchExtensionState).toHaveBeenCalledWith({
                type: 'SCHEDULE_UPDATE',
                payload: {
                    all: false,
                    names: ['ext-one', 'ext-two'],
                    onComplete: expect.any(Function),
                },
            });
            expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
            expect(mockContext.ui.setPendingItem).toHaveBeenCalledWith(null);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.EXTENSIONS_LIST,
                extensions: expect.any(Array),
            });
        });
    });
    describe('explore', () => {
        const exploreAction = extensionsCommand().subCommands?.find((cmd) => cmd.name === 'explore')?.action;
        if (!exploreAction) {
            throw new Error('Explore action not found');
        }
        it('should return ExtensionRegistryView custom dialog when experimental.extensionRegistry is true', async () => {
            mockContext.services.settings.merged.experimental.extensionRegistry = true;
            const result = await exploreAction(mockContext, '');
            expect(result).toBeDefined();
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const component = result.component;
            expect(component.type).toBe(ExtensionRegistryView);
            expect(component.props.extensionManager).toBe(mockExtensionLoader);
        });
        it('should handle onSelect and onClose in ExtensionRegistryView', async () => {
            mockContext.services.settings.merged.experimental.extensionRegistry = true;
            const result = await exploreAction(mockContext, '');
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const component = result.component;
            const extension = {
                extensionName: 'test-ext',
                url: 'https://github.com/test/ext.git',
            };
            vi.mocked(inferInstallMetadata).mockResolvedValue({
                source: extension.url,
                type: 'git',
            });
            mockInstallExtension.mockResolvedValue({ name: extension.url });
            // Call onSelect
            component.props.onSelect?.(extension);
            await waitFor(() => {
                expect(inferInstallMetadata).toHaveBeenCalledWith(extension.url);
                expect(mockInstallExtension).toHaveBeenCalledWith({
                    source: extension.url,
                    type: 'git',
                });
            });
            expect(mockContext.ui.removeComponent).toHaveBeenCalledTimes(1);
            // Call onClose
            component.props.onClose?.();
            expect(mockContext.ui.removeComponent).toHaveBeenCalledTimes(2);
        });
        it("should add an info message and call 'open' in a non-sandbox environment", async () => {
            // Ensure no special environment variables that would affect behavior
            vi.stubEnv('NODE_ENV', '');
            vi.stubEnv('SANDBOX', '');
            await exploreAction(mockContext, '');
            const extensionsUrl = 'https://geminicli.com/extensions/';
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Opening extensions page in your browser: ${extensionsUrl}`,
            });
            expect(open).toHaveBeenCalledWith(extensionsUrl);
        });
        it('should only add an info message in a sandbox environment', async () => {
            // Simulate a sandbox environment
            vi.stubEnv('NODE_ENV', '');
            vi.stubEnv('SANDBOX', 'gemini-sandbox');
            const extensionsUrl = 'https://geminicli.com/extensions/';
            await exploreAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `View available extensions at ${extensionsUrl}`,
            });
            // Ensure 'open' was not called in the sandbox
            expect(open).not.toHaveBeenCalled();
        });
        it('should add an info message and not call open in NODE_ENV test environment', async () => {
            vi.stubEnv('NODE_ENV', 'test');
            vi.stubEnv('SANDBOX', '');
            const extensionsUrl = 'https://geminicli.com/extensions/';
            await exploreAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`,
            });
            // Ensure 'open' was not called in test environment
            expect(open).not.toHaveBeenCalled();
        });
        it('should handle errors when opening the browser', async () => {
            vi.stubEnv('NODE_ENV', '');
            const extensionsUrl = 'https://geminicli.com/extensions/';
            const errorMessage = 'Failed to open browser';
            vi.mocked(open).mockRejectedValue(new Error(errorMessage));
            await exploreAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: `Failed to open browser. Check out the extensions gallery at ${extensionsUrl}`,
            });
        });
    });
    describe('when enableExtensionReloading is true', () => {
        it('should include enable, disable, install, link, and uninstall subcommands', () => {
            const command = extensionsCommand(true);
            const subCommandNames = command.subCommands?.map((cmd) => cmd.name);
            expect(subCommandNames).toContain('enable');
            expect(subCommandNames).toContain('disable');
            expect(subCommandNames).toContain('install');
            expect(subCommandNames).toContain('link');
            expect(subCommandNames).toContain('uninstall');
        });
    });
    describe('when enableExtensionReloading is false', () => {
        it('should not include enable, disable, install, link, and uninstall subcommands', () => {
            const command = extensionsCommand(false);
            const subCommandNames = command.subCommands?.map((cmd) => cmd.name);
            expect(subCommandNames).not.toContain('enable');
            expect(subCommandNames).not.toContain('disable');
            expect(subCommandNames).not.toContain('install');
            expect(subCommandNames).not.toContain('link');
            expect(subCommandNames).not.toContain('uninstall');
        });
    });
    describe('when enableExtensionReloading is not provided', () => {
        it('should not include enable, disable, install, link, and uninstall subcommands by default', () => {
            const command = extensionsCommand();
            const subCommandNames = command.subCommands?.map((cmd) => cmd.name);
            expect(subCommandNames).not.toContain('enable');
            expect(subCommandNames).not.toContain('disable');
            expect(subCommandNames).not.toContain('install');
            expect(subCommandNames).not.toContain('link');
            expect(subCommandNames).not.toContain('uninstall');
        });
    });
    describe('install', () => {
        let installAction;
        beforeEach(() => {
            installAction = extensionsCommand(true).subCommands?.find((cmd) => cmd.name === 'install')?.action;
            expect(installAction).not.toBeNull();
            mockContext.invocation.name = 'install';
        });
        it('should show usage if no extension name is provided', async () => {
            await installAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Usage: /extensions install <source>',
            });
            expect(mockInstallExtension).not.toHaveBeenCalled();
        });
        it('should call installExtension and show success message', async () => {
            const packageName = 'test-extension-package';
            vi.mocked(inferInstallMetadata).mockResolvedValue({
                source: packageName,
                type: 'git',
            });
            mockInstallExtension.mockResolvedValue({ name: packageName });
            await installAction(mockContext, packageName);
            expect(inferInstallMetadata).toHaveBeenCalledWith(packageName);
            expect(mockInstallExtension).toHaveBeenCalledWith({
                source: packageName,
                type: 'git',
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Installing extension from "${packageName}"...`,
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Extension "${packageName}" installed successfully.`,
            });
        });
        it('should show error message on installation failure', async () => {
            const packageName = 'failed-extension';
            const errorMessage = 'install failed';
            vi.mocked(inferInstallMetadata).mockResolvedValue({
                source: packageName,
                type: 'git',
            });
            mockInstallExtension.mockRejectedValue(new Error(errorMessage));
            await installAction(mockContext, packageName);
            expect(inferInstallMetadata).toHaveBeenCalledWith(packageName);
            expect(mockInstallExtension).toHaveBeenCalledWith({
                source: packageName,
                type: 'git',
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: `Failed to install extension from "${packageName}": ${errorMessage}`,
            });
        });
        it('should show error message for invalid source', async () => {
            const invalidSource = 'a;b';
            await installAction(mockContext, invalidSource);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: `Invalid source: ${invalidSource}`,
            });
            expect(mockInstallExtension).not.toHaveBeenCalled();
        });
    });
    describe('link', () => {
        let linkAction;
        beforeEach(() => {
            linkAction = extensionsCommand(true).subCommands?.find((cmd) => cmd.name === 'link')?.action;
            expect(linkAction).not.toBeNull();
            mockContext.invocation.name = 'link';
        });
        it('should show usage if no extension is provided', async () => {
            await linkAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Usage: /extensions link <source>',
            });
            expect(mockInstallExtension).not.toHaveBeenCalled();
        });
        it('should call installExtension and show success message', async () => {
            const packageName = 'test-extension-package';
            mockInstallExtension.mockResolvedValue({ name: packageName });
            vi.mocked(stat).mockResolvedValue({
                size: 100,
            });
            await linkAction(mockContext, packageName);
            expect(mockInstallExtension).toHaveBeenCalledWith({
                source: packageName,
                type: 'link',
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Linking extension from "${packageName}"...`,
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Extension "${packageName}" linked successfully.`,
            });
        });
        it('should show error message on linking failure', async () => {
            const packageName = 'test-extension-package';
            const errorMessage = 'link failed';
            mockInstallExtension.mockRejectedValue(new Error(errorMessage));
            vi.mocked(stat).mockResolvedValue({
                size: 100,
            });
            await linkAction(mockContext, packageName);
            expect(mockInstallExtension).toHaveBeenCalledWith({
                source: packageName,
                type: 'link',
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: `Failed to link extension from "${packageName}": ${errorMessage}`,
            });
        });
        it('should show error message for invalid source', async () => {
            const packageName = 'test-extension-package';
            const errorMessage = 'invalid path';
            vi.mocked(stat).mockRejectedValue(new Error(errorMessage));
            await linkAction(mockContext, packageName);
            expect(mockInstallExtension).not.toHaveBeenCalled();
        });
    });
    describe('uninstall', () => {
        let uninstallAction;
        beforeEach(() => {
            uninstallAction = extensionsCommand(true).subCommands?.find((cmd) => cmd.name === 'uninstall')?.action;
            expect(uninstallAction).not.toBeNull();
            mockContext.invocation.name = 'uninstall';
        });
        it('should show usage if no extension name is provided', async () => {
            await uninstallAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Usage: /extensions uninstall <extension-name>',
            });
            expect(mockUninstallExtension).not.toHaveBeenCalled();
        });
        it('should call uninstallExtension and show success message', async () => {
            const extensionName = 'test-extension';
            await uninstallAction(mockContext, extensionName);
            expect(mockUninstallExtension).toHaveBeenCalledWith(extensionName, false);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Uninstalling extension "${extensionName}"...`,
            });
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: `Extension "${extensionName}" uninstalled successfully.`,
            });
        });
        it('should show error message on uninstallation failure', async () => {
            const extensionName = 'failed-extension';
            const errorMessage = 'uninstall failed';
            mockUninstallExtension.mockRejectedValue(new Error(errorMessage));
            await uninstallAction(mockContext, extensionName);
            expect(mockUninstallExtension).toHaveBeenCalledWith(extensionName, false);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: `Failed to uninstall extension "${extensionName}": ${errorMessage}`,
            });
        });
    });
    describe('enable', () => {
        let enableAction;
        beforeEach(() => {
            enableAction = extensionsCommand(true).subCommands?.find((cmd) => cmd.name === 'enable')?.action;
            expect(enableAction).not.toBeNull();
            mockContext.invocation.name = 'enable';
        });
        it('should show usage if no extension name is provided', async () => {
            await enableAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Usage: /extensions enable <extension> [--scope=<user|workspace|session>]',
            });
        });
        it('should call enableExtension with the provided scope', async () => {
            await enableAction(mockContext, `${inactiveExt.name} --scope=user`);
            expect(mockEnableExtension).toHaveBeenCalledWith(inactiveExt.name, SettingScope.User);
            await enableAction(mockContext, `${inactiveExt.name} --scope workspace`);
            expect(mockEnableExtension).toHaveBeenCalledWith(inactiveExt.name, SettingScope.Workspace);
        });
        it('should support --all', async () => {
            mockGetExtensions.mockReturnValue([
                inactiveExt,
                { ...inactiveExt, name: 'another-inactive-ext' },
            ]);
            await enableAction(mockContext, '--all --scope session');
            expect(mockEnableExtension).toHaveBeenCalledWith(inactiveExt.name, SettingScope.Session);
            expect(mockEnableExtension).toHaveBeenCalledWith('another-inactive-ext', SettingScope.Session);
        });
    });
    describe('disable', () => {
        let disableAction;
        beforeEach(() => {
            disableAction = extensionsCommand(true).subCommands?.find((cmd) => cmd.name === 'disable')?.action;
            expect(disableAction).not.toBeNull();
            mockContext.invocation.name = 'disable';
        });
        it('should show usage if no extension name is provided', async () => {
            await disableAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Usage: /extensions disable <extension> [--scope=<user|workspace|session>]',
            });
        });
        it('should call disableExtension with the provided scope', async () => {
            await disableAction(mockContext, `${activeExt.name} --scope=user`);
            expect(mockDisableExtension).toHaveBeenCalledWith(activeExt.name, SettingScope.User);
            await disableAction(mockContext, `${activeExt.name} --scope workspace`);
            expect(mockDisableExtension).toHaveBeenCalledWith(activeExt.name, SettingScope.Workspace);
        });
        it('should support --all', async () => {
            mockGetExtensions.mockReturnValue([
                activeExt,
                { ...activeExt, name: 'another-active-ext' },
            ]);
            await disableAction(mockContext, '--all --scope session');
            expect(mockDisableExtension).toHaveBeenCalledWith(activeExt.name, SettingScope.Session);
            expect(mockDisableExtension).toHaveBeenCalledWith('another-active-ext', SettingScope.Session);
        });
    });
    describe('restart', () => {
        let restartAction;
        let mockRestartExtension;
        beforeEach(() => {
            restartAction = extensionsCommand().subCommands?.find((c) => c.name === 'restart')?.action;
            expect(restartAction).not.toBeNull();
            mockRestartExtension = vi.fn();
            mockContext.services.config.getExtensionLoader = vi
                .fn()
                .mockImplementation(() => ({
                getExtensions: mockGetExtensions,
                restartExtension: mockRestartExtension,
            }));
            mockContext.invocation.name = 'restart';
        });
        it('should show a message if no extensions are installed', async () => {
            mockContext.services.config.getExtensionLoader = vi
                .fn()
                .mockImplementation(() => ({
                getExtensions: () => [],
                restartExtension: mockRestartExtension,
            }));
            await restartAction(mockContext, '--all');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.INFO,
                text: 'No extensions installed. Run `/extensions explore` to check out the gallery.',
            });
        });
        it('restarts all active extensions when --all is provided', async () => {
            const mockExtensions = [
                { name: 'ext1', isActive: true },
                { name: 'ext2', isActive: true },
                { name: 'ext3', isActive: false },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            await restartAction(mockContext, '--all');
            expect(mockRestartExtension).toHaveBeenCalledTimes(2);
            expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[0]);
            expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[1]);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.INFO,
                text: 'Restarting 2 extensions...',
            }));
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.INFO,
                text: '2 extensions restarted successfully.',
            }));
            expect(mockContext.ui.dispatchExtensionStateUpdate).toHaveBeenCalledWith({
                type: 'RESTARTED',
                payload: { name: 'ext1' },
            });
            expect(mockContext.ui.dispatchExtensionStateUpdate).toHaveBeenCalledWith({
                type: 'RESTARTED',
                payload: { name: 'ext2' },
            });
            expect(mockReloadSkills).toHaveBeenCalled();
            expect(mockReloadAgents).toHaveBeenCalled();
        });
        it('handles errors during skill or agent reload', async () => {
            const mockExtensions = [
                { name: 'ext1', isActive: true },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            mockReloadSkills.mockRejectedValue(new Error('Failed to reload skills'));
            await restartAction(mockContext, '--all');
            expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[0]);
            expect(mockReloadSkills).toHaveBeenCalled();
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.ERROR,
                text: 'Failed to reload skills or agents: Failed to reload skills',
            }));
        });
        it('restarts only specified active extensions', async () => {
            const mockExtensions = [
                { name: 'ext1', isActive: false },
                { name: 'ext2', isActive: true },
                { name: 'ext3', isActive: true },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            await restartAction(mockContext, 'ext1 ext3');
            expect(mockRestartExtension).toHaveBeenCalledTimes(1);
            expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[2]);
            expect(mockContext.ui.dispatchExtensionStateUpdate).toHaveBeenCalledWith({
                type: 'RESTARTED',
                payload: { name: 'ext3' },
            });
        });
        it('shows an error if no extension loader is available', async () => {
            mockContext.services.config.getExtensionLoader = vi.fn();
            await restartAction(mockContext, '--all');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.ERROR,
                text: "Extensions are not yet loaded, can't restart yet",
            }));
            expect(mockRestartExtension).not.toHaveBeenCalled();
        });
        it('shows usage error for no arguments', async () => {
            await restartAction(mockContext, '');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.ERROR,
                text: 'Usage: /extensions restart <extension-names>|--all',
            }));
            expect(mockRestartExtension).not.toHaveBeenCalled();
        });
        it('handles errors during extension restart', async () => {
            const mockExtensions = [
                { name: 'ext1', isActive: true },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            mockRestartExtension.mockRejectedValue(new Error('Failed to restart'));
            await restartAction(mockContext, '--all');
            expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[0]);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.ERROR,
                text: 'Failed to restart some extensions:\n  ext1: Failed to restart',
            }));
        });
        it('shows a warning if an extension is not found', async () => {
            const mockExtensions = [
                { name: 'ext1', isActive: true },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            await restartAction(mockContext, 'ext1 ext2');
            expect(mockRestartExtension).toHaveBeenCalledTimes(1);
            expect(mockRestartExtension).toHaveBeenCalledWith(mockExtensions[0]);
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.WARNING,
                text: 'Extension(s) not found or not active: ext2',
            }));
        });
        it('does not restart any extensions if none are found', async () => {
            const mockExtensions = [
                { name: 'ext1', isActive: true },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            await restartAction(mockContext, 'ext2 ext3');
            expect(mockRestartExtension).not.toHaveBeenCalled();
            expect(mockContext.ui.addItem).toHaveBeenCalledWith(expect.objectContaining({
                type: MessageType.WARNING,
                text: 'Extension(s) not found or not active: ext2, ext3',
            }));
        });
        it('should suggest only enabled extension names for the restart command', async () => {
            mockContext.invocation.name = 'restart';
            const mockExtensions = [
                { name: 'ext1', isActive: true },
                { name: 'ext2', isActive: false },
            ];
            mockGetExtensions.mockReturnValue(mockExtensions);
            const suggestions = completeExtensions(mockContext, 'ext');
            expect(suggestions).toEqual(['ext1']);
        });
    });
    describe('config', () => {
        let configAction;
        beforeEach(async () => {
            configAction = extensionsCommand(true).subCommands?.find((cmd) => cmd.name === 'config')?.action;
            expect(configAction).not.toBeNull();
            mockContext.invocation.name = 'config';
            const prompts = (await import('prompts')).default;
            vi.mocked(prompts).mockResolvedValue({ overwrite: true });
            const { getScopedEnvContents } = await import('../../config/extensions/extensionSettings.js');
            vi.mocked(getScopedEnvContents).mockResolvedValue({});
        });
        it('should return dialog to configure all extensions if no args provided', async () => {
            const result = await configAction(mockContext, '');
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const dialogResult = result;
            const component = dialogResult.component;
            expect(component.type).toBe(ConfigExtensionDialog);
            expect(component.props.configureAll).toBe(true);
            expect(component.props.extensionManager).toBeDefined();
        });
        it('should return dialog to configure specific extension', async () => {
            const result = await configAction(mockContext, 'ext-one');
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const dialogResult = result;
            const component = dialogResult.component;
            expect(component.type).toBe(ConfigExtensionDialog);
            expect(component.props.extensionName).toBe('ext-one');
            expect(component.props.settingKey).toBeUndefined();
            expect(component.props.configureAll).toBe(false);
        });
        it('should return dialog to configure specific setting for an extension', async () => {
            const result = await configAction(mockContext, 'ext-one SETTING1');
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const dialogResult = result;
            const component = dialogResult.component;
            expect(component.type).toBe(ConfigExtensionDialog);
            expect(component.props.extensionName).toBe('ext-one');
            expect(component.props.settingKey).toBe('SETTING1');
            expect(component.props.scope).toBe('user'); // Default scope
        });
        it('should respect scope argument passed to dialog', async () => {
            const result = await configAction(mockContext, 'ext-one SETTING1 --scope=workspace');
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const dialogResult = result;
            const component = dialogResult.component;
            expect(component.props.scope).toBe('workspace');
        });
        it('should show error for invalid extension name', async () => {
            await configAction(mockContext, '../invalid');
            expect(mockContext.ui.addItem).toHaveBeenCalledWith({
                type: MessageType.ERROR,
                text: 'Invalid extension name. Names cannot contain path separators or "..".',
            });
        });
        // "should inform if extension has no settings" - This check is now inside ConfigExtensionDialog logic.
        // We can test that we still return a dialog, and the dialog will handle logical checks via utils.ts
        // For unit testing extensionsCommand, we just ensure delegation.
        it('should return dialog even if extension has no settings (dialog handles logic)', async () => {
            const result = await configAction(mockContext, 'ext-one');
            if (result?.type !== 'custom_dialog') {
                throw new Error('Expected custom_dialog');
            }
            const dialogResult = result;
            const component = dialogResult.component;
            expect(component.type).toBe(ConfigExtensionDialog);
        });
    });
});
//# sourceMappingURL=extensionsCommand.test.js.map