import { LspManager } from "./managers/lspManager.js";
import { configValidator } from "./utils/configValidator.js";
import { btw } from "./services/aiService.js";
import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
import { parseTaskNotificationXml } from "./utils/notificationXml.js";
import { InitializationService } from "./services/initializationService.js";
import { InteractionService } from "./services/interactionService.js";
import { ConfigurationService } from "./services/configurationService.js";
import { setupAgentContainer } from "./utils/containerSetup.js";
export class Agent {
    // Dynamic configuration getter methods
    getGatewayConfig() {
        return this.configurationService.resolveGatewayConfig();
    }
    getModelConfig() {
        return this.configurationService.resolveModelConfig(undefined, undefined, undefined, this.getPermissionMode());
    }
    getMaxInputTokens() {
        return this.configurationService.resolveMaxInputTokens();
    }
    getLanguage() {
        return this.configurationService.resolveLanguage();
    }
    /**
     * Set the active model for the agent session
     * @param model - The ID of the model to use
     */
    setModel(model) {
        this.configurationService.setModel(model);
        this.options.callbacks?.onModelChange?.(model);
    }
    /**
     * Get all configured models from settings.json and defaults
     * @returns Array of model IDs
     */
    getConfiguredModels() {
        return this.configurationService.getConfiguredModels();
    }
    /**
     * Agent constructor - handles configuration resolution and validation
     *
     * IMPORTANT: This constructor is private. Use Agent.create() instead for proper
     * async initialization. Keep this constructor's signature exactly the same as
     * Agent.create() to maintain API consistency.
     *
     * @param options - Configuration options for the Agent instance
     */
    constructor(options) {
        this.bangManager = null;
        // Memory content storage
        this._projectMemoryContent = "";
        this._userMemoryContent = "";
        const { logger, workdir, systemPrompt, stream = true } = options;
        // Set working directory early as we need it for loading configuration
        this.workdir = workdir || process.cwd();
        // Initialize configuration service
        this.configurationService = new ConfigurationService();
        this.configurationService.setOptions(options);
        this.logger = logger; // Save the passed logger
        this.systemPrompt = systemPrompt; // Save custom system prompt
        this.stream = stream; // Save streaming mode flag
        // Store options for dynamic configuration resolution
        this.options = options;
        this.container = setupAgentContainer({
            options,
            workdir: this.workdir,
            configurationService: this.configurationService,
            systemPrompt: this.systemPrompt,
            stream: this.stream,
            onTasksChange: (tasks) => {
                this.options.callbacks?.onTasksChange?.(tasks);
            },
            onBackgroundTasksChange: (tasks) => {
                this.options.callbacks?.onBackgroundTasksChange?.(tasks);
            },
            onPermissionModeChange: (mode) => {
                this.options.callbacks?.onPermissionModeChange?.(mode);
            },
            handlePlanModeTransition: (mode) => {
                this.planManager.handlePlanModeTransition(mode);
            },
            setPermissionMode: (mode) => {
                this.setPermissionMode(mode);
            },
            addPermissionRule: (rule) => this.addPermissionRule(rule),
            addUsage: (usage) => this.messageManager.addUsage(usage),
        });
        // Retrieve managers from container
        this.foregroundTaskManager = this.container.get("ForegroundTaskManager");
        this.memoryRuleManager = this.container.get("MemoryRuleManager");
        this.messageManager = this.container.get("MessageManager");
        this.taskManager = this.container.get("TaskManager");
        this.backgroundTaskManager = this.container.get("BackgroundTaskManager");
        this.mcpManager = this.container.get("McpManager");
        this.lspManager = this.container.get("LspManager");
        this.permissionManager = this.container.get("PermissionManager");
        this.planManager = this.container.get("PlanManager");
        this.hookManager = this.container.get("HookManager");
        this.skillManager = this.container.get("SkillManager");
        this.reversionManager = this.container.get("ReversionManager");
        this.toolManager = this.container.get("ToolManager");
        this.liveConfigManager = this.container.get("LiveConfigManager");
        this.subagentManager = this.container.get("SubagentManager");
        this.forkedAgentManager = this.container.get("ForkedAgentManager");
        this.aiManager = this.container.get("AIManager");
        this.slashCommandManager = this.container.get("SlashCommandManager");
        this.pluginManager = this.container.get("PluginManager");
        this.bangManager = this.container.get("BangManager");
        this.cronManager = this.container.get("CronManager");
        this.notificationQueue = this.container.get("NotificationQueue");
        // Wire up notification queue to trigger AI when notifications arrive while idle
        this.notificationQueue.onNotificationsEnqueued = () => {
            // If the AI is NOT loading (idle), trigger a new AI cycle to process notifications
            if (!this.aiManager.isLoading) {
                this.processPendingNotifications().catch((error) => {
                    this.logger?.error("Failed to process pending notifications:", error);
                });
            }
        };
        // Set initial permission mode if provided
        if (options.permissionMode) {
            this.setPermissionMode(options.permissionMode);
        }
    }
    // Public getter methods
    get sessionId() {
        return this.messageManager.getSessionId();
    }
    get messages() {
        return this.messageManager.getMessages();
    }
    get usages() {
        return this.messageManager.getUsages();
    }
    get sessionFilePath() {
        return this.messageManager.getTranscriptPath();
    }
    get latestTotalTokens() {
        return this.messageManager.getLatestTotalTokens();
    }
    /** Get working directory */
    get workingDirectory() {
        return this.workdir;
    }
    /** Get project memory content */
    get projectMemory() {
        return this._projectMemoryContent;
    }
    /** Get user memory content */
    get userMemory() {
        return this._userMemoryContent;
    }
    /** Get combined memory content (project + user + modular rules) */
    async getCombinedMemory() {
        return this.messageManager.getCombinedMemory();
    }
    /** Get AI loading status */
    get isLoading() {
        return this.aiManager.isLoading;
    }
    /** Get message compression status */
    get isCompressing() {
        return this.aiManager.getIsCompressing();
    }
    /** Get bash command execution status */
    get isCommandRunning() {
        return this.bangManager?.isCommandRunning ?? false;
    }
    /** Get background bash shell output */
    getBackgroundShellOutput(id, filter) {
        return this.backgroundTaskManager.getOutput(id, filter);
    }
    /** Kill background bash shell */
    killBackgroundShell(id) {
        return this.backgroundTaskManager.stopTask(id);
    }
    /** Get background task output */
    getBackgroundTaskOutput(id, filter) {
        return this.backgroundTaskManager.getOutput(id, filter);
    }
    /** Stop background task */
    stopBackgroundTask(id) {
        return this.backgroundTaskManager.stopTask(id);
    }
    /**
     * Static async factory method for creating Agent instances
     *
     * IMPORTANT: Keep this method's signature exactly the same as the constructor
     * to maintain consistency and avoid confusion for users of the API.
     *
     * @param options - Same AgentOptions interface used by constructor
     * @returns Promise<Agent> - Fully initialized Agent instance
     */
    /**
     * Create a new Agent instance with async initialization
     *
     * This is the recommended way to create Agent instances. The constructor is private
     * to ensure proper async initialization of all components.
     *
     * @param options - Configuration options for the Agent instance
     * @param options.apiKey - API key for the AI service (or set WAVE_API_KEY env var)
     * @param options.baseURL - Base URL for the AI service (or set WAVE_BASE_URL env var)
     * @param options.defaultHeaders - Optional HTTP headers to pass to the AI service
     * @param options.fetchOptions - Optional fetch options to pass to the AI service
     * @param options.fetch - Optional custom fetch implementation
     * @param options.callbacks - Optional callbacks for various Agent events
     * @param options.restoreSessionId - Optional session ID to restore from
     * @param options.continueLastSession - Whether to continue the last session automatically
     * @param options.logger - Optional custom logger implementation
     * @param options.messages - Optional initial messages for testing convenience
     * @param options.workdir - Working directory (defaults to process.cwd())
     * @param options.systemPrompt - Optional custom system prompt
     * @returns Promise that resolves to initialized Agent instance
     *
     * @example
     * ```typescript
     * // Basic usage
     * const agent = await Agent.create({
     *   apiKey: 'your-api-key',
     *   baseURL: 'https://api.example.com'
     * });
     * ```
     */
    static async create(options) {
        // Create Agent instance
        const instance = new Agent(options);
        await instance.initialize({
            restoreSessionId: options.restoreSessionId,
            continueLastSession: options.continueLastSession,
            messages: options.messages,
        });
        return instance;
    }
    /**
     * Resolve and validate configuration from constructor args, environment variables,
     * and loaded settings.json.
     *
     * This is called during initialization after settings.json has been loaded.
     */
    resolveAndValidateConfig() {
        // Resolve configuration from constructor args and environment variables (including settings.json)
        const gatewayConfig = this.getGatewayConfig();
        const modelConfig = this.getModelConfig();
        const maxInputTokens = this.getMaxInputTokens();
        // Validate resolved configuration
        configValidator.validateGatewayConfig(gatewayConfig);
        configValidator.validateMaxInputTokens(maxInputTokens);
        configValidator.validateModelConfig(modelConfig);
    }
    /** Private initialization method, handles async initialization logic */
    async initialize(options) {
        await InitializationService.initialize({
            skillManager: this.skillManager,
            subagentManager: this.subagentManager,
            container: this.container,
            toolManager: this.toolManager,
            pluginManager: this.pluginManager,
            options: this.options,
            slashCommandManager: this.slashCommandManager,
            logger: this.logger,
            mcpManager: this.mcpManager,
            workdir: this.workdir,
            lspManager: this.lspManager,
            configurationService: this.configurationService,
            hookManager: this.hookManager,
            messageManager: this.messageManager,
            memoryRuleManager: this.memoryRuleManager,
            liveConfigManager: this.liveConfigManager,
            taskManager: this.taskManager,
            setProjectMemory: (content) => {
                this._projectMemoryContent = content;
            },
            setUserMemory: (content) => {
                this._userMemoryContent = content;
            },
            resolveAndValidateConfig: () => this.resolveAndValidateConfig(),
        }, options);
    }
    /**
     * Restore a session by ID, switching to the target session without destroying the Agent instance
     * @param sessionId - The ID of the session to restore
     */
    async restoreSession(sessionId) {
        await InteractionService.restoreSession({
            messageManager: this.messageManager,
            slashCommandManager: this.slashCommandManager,
            hookManager: this.hookManager,
            workdir: this.workdir,
            configurationService: this.configurationService,
            logger: this.logger,
            aiManager: this.aiManager,
            subagentManager: this.subagentManager,
            taskManager: this.taskManager,
            options: this.options,
            abortMessage: () => this.abortMessage(),
        }, sessionId);
    }
    abortAIMessage() {
        this.aiManager.abortAIMessage();
    }
    /**
     * Process pending background task notifications by injecting them as user messages
     * and triggering a new AI response cycle.
     */
    async processPendingNotifications() {
        const notifications = this.notificationQueue.dequeueAll();
        if (notifications.length === 0)
            return;
        for (const notification of notifications) {
            const block = parseTaskNotificationXml(notification);
            if (block) {
                this.messageManager.addNotificationMessage({
                    taskId: block.taskId,
                    taskType: block.taskType,
                    status: block.status,
                    summary: block.summary,
                    outputFile: block.outputFile,
                });
            }
        }
        // Trigger AI to process the notifications
        await this.aiManager.sendAIMessage({ recursionDepth: 0 });
    }
    /** Execute bash command (bang command) */
    async bang(command) {
        await this.bangManager?.executeCommand(command);
        await this.messageManager.saveSession();
    }
    async clearMessages() {
        await this.slashCommandManager.executeCommand("clear");
    }
    /** Unified interrupt method, interrupts both AI messages and command execution */
    abortMessage() {
        this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
        this.abortBashCommand();
        this.abortSlashCommand();
    }
    /** Interrupt bash command execution */
    abortBashCommand() {
        this.bangManager?.abortCommand();
    }
    /** Interrupt slash command execution */
    abortSlashCommand() {
        this.slashCommandManager.abortCurrentCommand();
    }
    /**
     * Register a foreground task that can be backgrounded
     */
    registerForegroundTask(task) {
        this.foregroundTaskManager.registerForegroundTask(task);
    }
    /**
     * Unregister a foreground task
     */
    unregisterForegroundTask(id) {
        this.foregroundTaskManager.unregisterForegroundTask(id);
    }
    /**
     * Background the current foreground task
     */
    async backgroundCurrentTask() {
        await this.foregroundTaskManager.backgroundCurrentTask();
        this.options.callbacks?.onBackgroundCurrentTask?.();
    }
    /** Destroy managers, clean up resources */
    async destroy() {
        await this.messageManager.saveSession();
        this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
        this.abortBashCommand();
        this.abortSlashCommand();
        this.cronManager.stop();
        // Cleanup background task manager
        this.backgroundTaskManager.cleanup();
        // Cleanup MCP connections
        await this.mcpManager.cleanup();
        // Cleanup LSP connections
        if (this.lspManager instanceof LspManager) {
            await this.lspManager.cleanup();
        }
        // Cleanup subagent manager
        this.subagentManager.cleanup();
        // Cleanup forked agent manager
        this.forkedAgentManager.cleanup();
        // Cleanup skill manager
        await this.skillManager.destroy();
        // Cleanup live configuration reload
        try {
            await this.liveConfigManager.shutdown();
        }
        catch (error) {
            this.logger?.error("Error shutting down live configuration reload:", error);
        }
        // Cleanup memory store
    }
    /**
     * Trigger the rewind UI callback
     */
    triggerShowRewind() {
        this.messageManager.triggerShowRewind();
    }
    /**
     * Ask a side question without tool use
     * @param question - The side question to ask
     * @returns Promise that resolves to the AI's answer
     */
    async askBtw(question) {
        const messages = convertMessagesForAPI(this.messageManager.getMessages());
        const result = await btw({
            gatewayConfig: this.getGatewayConfig(),
            modelConfig: this.getModelConfig(),
            messages,
            question,
        });
        return result.content;
    }
    /**
     * Send a message to the AI agent with optional images
     *
     * @param content - The text content of the message to send
     * @param images - Optional array of images to include with the message
     * @param images[].path - File path to the image or base64 encoded image data
     * @param images[].mimeType - MIME type of the image (e.g., 'image/png', 'image/jpeg')
     * @returns Promise that resolves when the message has been processed
     *
     * @example
     * ```typescript
     * // Send a text message
     * await agent.sendMessage("Hello, how are you?");
     *
     * // Send a message with images using file paths
     * await agent.sendMessage("What do you see in these images?", [
     *   { path: "/path/to/image.png", mimeType: "image/png" },
     *   { path: "/path/to/photo.jpg", mimeType: "image/jpeg" }
     * ]);
     *
     * // Send a message with base64 encoded image
     * await agent.sendMessage("Analyze this image", [
     *   { path: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", mimeType: "image/png" }
     * ]);
     * ```
     */
    async sendMessage(content, images) {
        await InteractionService.sendMessage({
            messageManager: this.messageManager,
            slashCommandManager: this.slashCommandManager,
            hookManager: this.hookManager,
            workdir: this.workdir,
            configurationService: this.configurationService,
            logger: this.logger,
            aiManager: this.aiManager,
            subagentManager: this.subagentManager,
            taskManager: this.taskManager,
            options: this.options,
            abortMessage: () => this.abortMessage(),
        }, content, images);
    }
    // ========== MCP Management Methods ==========
    /** Get all MCP server status */
    getMcpServers() {
        return this.mcpManager.getAllServers();
    }
    /** Connect MCP server */
    async connectMcpServer(serverName) {
        return await this.mcpManager.connectServer(serverName);
    }
    /** Disconnect MCP server */
    async disconnectMcpServer(serverName) {
        return await this.mcpManager.disconnectServer(serverName);
    }
    // ========== Slash Command Management Methods ==========
    /** Get all available slash commands */
    getSlashCommands() {
        return this.slashCommandManager.getCommands();
    }
    /** Check if slash command exists */
    hasSlashCommand(commandId) {
        return this.slashCommandManager.hasCommand(commandId);
    }
    /** Reload custom commands */
    async reloadCustomCommands() {
        await this.skillManager.initialize();
        this.slashCommandManager.reloadCustomCommands();
        this.slashCommandManager.registerSkillCommands(this.skillManager.getAvailableSkills());
    }
    /** Get custom command details */
    getCustomCommand(commandId) {
        return this.slashCommandManager.getCustomCommand(commandId);
    }
    /** Get all custom commands */
    getCustomCommands() {
        return this.slashCommandManager.getCustomCommands();
    }
    /**
     * Register a custom slash command
     */
    registerSlashCommand(command) {
        this.slashCommandManager.registerCommand(command);
    }
    /**
     * Get the current permission mode
     */
    getPermissionMode() {
        return this.toolManager.getPermissionMode();
    }
    /**
     * Set the permission mode
     * @param mode - The new permission mode
     */
    setPermissionMode(mode) {
        this.logger?.debug("Setting permission mode", { mode });
        this.toolManager.setPermissionMode(mode);
        this.planManager.handlePlanModeTransition(mode);
        this.options.callbacks?.onPermissionModeChange?.(mode);
    }
    /**
     * Truncate history to a specific index and revert file changes.
     * @param index - The index of the user message to truncate to.
     */
    async truncateHistory(index) {
        await this.messageManager.truncateHistory(index, this.reversionManager);
        // After truncating history, the task list might have changed, so refresh it.
        // We explicitly load tasks and trigger the callback to ensure UI updates immediately and in order.
        const tasks = await this.taskManager.listTasks();
        this.options.callbacks?.onTasksChange?.(tasks);
    }
    /**
     * Get the full message thread including parent sessions
     */
    async getFullMessageThread() {
        return this.messageManager.getFullMessageThread();
    }
    /**
     * Get the current plan file path (for testing and UI)
     */
    getPlanFilePath() {
        return this.permissionManager.getPlanFilePath();
    }
    /**
     * Get all currently allowed rules (user-defined and default)
     */
    getAllowedRules() {
        return [
            ...this.permissionManager.getAllowedRules(),
            ...this.permissionManager.getDefaultAllowedRules(),
        ];
    }
    /**
     * Get only user-defined allowed rules
     */
    getUserAllowedRules() {
        return this.permissionManager.getAllowedRules();
    }
    /**
     * Check permission for a tool call (for testing)
     */
    async checkPermission(context) {
        return this.permissionManager.checkPermission(context);
    }
    /**
     * Add a persistent permission rule
     * @param rule - The rule to add (e.g., "Bash(ls)")
     */
    async addPermissionRule(rule) {
        await this.permissionManager.addPermissionRule(rule);
    }
    /**
     * Get subagent instance by ID
     * @param subagentId - The ID of the subagent instance
     * @returns The subagent instance or null if not found
     */
    getSubagentInstance(subagentId) {
        return this.subagentManager.getInstance(subagentId);
    }
    /**
     * Get the current task list ID
     */
    get taskListId() {
        return this.taskManager.getTaskListId();
    }
}
