// @lifecycle canonical - Registers prompts with the MCP server and manages conversation integration.
/**
 * Prompt Registry Module
 * Handles registering prompts with MCP server using proper MCP protocol and managing conversation history
 */

import { z } from 'zod';

import { type ConfigManager, type Logger } from '../../shared/types/index.js';
import { isChainPrompt } from '../../shared/utils/chainUtils.js';
import { ConversationStore } from '../text-refs/conversation.js';

import type { ConvertedPrompt } from '../../engine/execution/types.js';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
// TemplateProcessor functionality consolidated into UnifiedPromptProcessor

/**
 * Prompt Registry class
 */
type PromptRegistryServer = Pick<McpServer, 'registerPrompt'> & {
  notification?: (notification: { method: string; params?: unknown }) => void;
};

export class PromptRegistry {
  private logger: Logger;
  private mcpServer: PromptRegistryServer;
  private configManager: ConfigManager;
  private conversationStore: ConversationStore;
  // templateProcessor removed - functionality consolidated into UnifiedPromptProcessor
  private registeredPromptIds = new Set<string>(); // Track registered prompt IDs to prevent duplicates
  private exportedPromptIds = new Set<string>(); // Prompt IDs exported as skills (auto-deregistered)

  /**
   * Direct template processing method (minimal implementation)
   * Replaces templateProcessor calls for basic template processing
   */
  private async processTemplateDirect(
    template: string,
    args: Record<string, string>,
    specialContext: Record<string, string> = {}
  ): Promise<string> {
    const { processTemplate } = await import('../../shared/utils/jsonUtils.js');
    return processTemplate(template, args, specialContext);
  }

  constructor(
    logger: Logger,
    mcpServer: PromptRegistryServer,
    configManager: ConfigManager,
    conversationStore: ConversationStore
  ) {
    this.logger = logger;
    this.mcpServer = mcpServer;
    this.configManager = configManager;
    this.conversationStore = conversationStore;
    // templateProcessor removed - functionality consolidated into UnifiedPromptProcessor
  }

  /**
   * Set prompt IDs that have been exported as client skills via skills-sync.
   * Exported prompts are auto-deregistered from MCP to avoid duplication.
   * Format: "category/id" (e.g., "development/review")
   */
  setExportedPromptIds(ids: Set<string>): void {
    this.exportedPromptIds = ids;
  }

  /**
   * Register individual prompts using MCP SDK registerPrompt API
   * This implements the standard MCP prompts protocol using the high-level API
   */
  private registerIndividualPrompts(prompts: ConvertedPrompt[]): void {
    try {
      this.logger.info('Registering individual prompts with MCP SDK...');
      let registeredCount = 0;

      for (const prompt of prompts) {
        // Skip MCP registration if disabled (prompt or category level)
        if (prompt.registerWithMcp === false) {
          this.logger.debug(`Skipping MCP registration: ${prompt.id} (registerWithMcp=false)`);
          continue;
        }

        // Skip if exported as a client skill (auto-deregistered via skills-sync.yaml)
        const exportKey = `${prompt.category}/${prompt.id}`;
        if (this.exportedPromptIds.has(exportKey)) {
          this.logger.debug(`Skipping MCP registration: ${prompt.id} (exported as skill)`);
          continue;
        }

        // Skip if already registered (deduplication guard)
        if (this.registeredPromptIds.has(prompt.id)) {
          this.logger.debug(`Skipping already registered prompt: ${prompt.id}`);
          continue;
        }

        // Create argument schema
        const argsSchema: Record<string, any> = {};
        for (const arg of prompt.arguments) {
          argsSchema[arg.name] = z
            .string()
            .optional()
            .describe(arg.description || `Argument: ${arg.name}`);
        }

        // Register the prompt using the correct MCP SDK API with error recovery
        // Use prompt.id for all MCP registration (slug-based, no spaces)
        try {
          this.mcpServer.registerPrompt(
            prompt.id,
            {
              title: prompt.id,
              description: prompt.description || `Prompt: ${prompt.id}`,
              argsSchema,
            },
            async (args: any) => {
              this.logger.debug(`Executing prompt '${prompt.id}' with args:`, args);
              return await this.executePromptLogic(prompt, args || {});
            }
          );

          // Track the registered prompt
          this.registeredPromptIds.add(prompt.id);
          registeredCount++;
          this.logger.debug(`Registered prompt: ${prompt.id}`);
        } catch (error: any) {
          if (error.message?.includes('already registered')) {
            // Handle MCP SDK's "already registered" error gracefully
            this.logger.warn(
              `Prompt '${prompt.id}' already registered in MCP SDK, skipping re-registration`
            );
            this.registeredPromptIds.add(prompt.id); // Track it anyway
            continue;
          } else {
            // Re-throw other errors
            this.logger.error(`Failed to register prompt '${prompt.id}':`, error.message || error);
            throw error;
          }
        }
      }

      this.logger.info(
        `Successfully registered ${registeredCount} of ${prompts.length} prompts with MCP SDK`
      );
    } catch (error) {
      this.logger.error(
        'Error registering individual prompts:',
        error instanceof Error ? error.message : String(error)
      );
      throw error;
    }
  }

  /**
   * Execute prompt logic (extracted from createPromptHandler for MCP protocol)
   */
  private async executePromptLogic(promptData: ConvertedPrompt, args: any): Promise<any> {
    try {
      this.logger.info(`Executing prompt '${promptData.name}'...`);

      // Check if this is a chain prompt
      if (isChainPrompt(promptData) && promptData.chainSteps && promptData.chainSteps.length > 0) {
        this.logger.info(
          `Prompt '${promptData.name}' is a chain with ${promptData.chainSteps.length} steps. NOT automatically executing the chain.`
        );
        // Note: Chain execution is handled elsewhere
      }

      // Create messages array with only user and assistant roles
      const messages: {
        role: 'user' | 'assistant';
        content: { type: 'text'; text: string };
      }[] = [];

      // Create user message with placeholders replaced
      let userMessageText = promptData.userMessageTemplate;

      // If there's a system message, prepend it to the user message
      if (promptData.systemMessage) {
        userMessageText = `[System Info: ${promptData.systemMessage}]\n\n${userMessageText}`;
      }

      const previousMessageContext = this.conversationStore.getPreviousMessage();

      // Process the template with special context
      // Using direct processing since TemplateProcessor was consolidated
      userMessageText = await this.processTemplateDirect(userMessageText, args, {
        previous_message: previousMessageContext,
      });

      // Store in conversation history for future reference
      this.conversationStore.addToConversationHistory({
        role: 'user',
        content: userMessageText,
        timestamp: Date.now(),
        isProcessedTemplate: true, // Mark as a processed template
      });

      // Push the user message to the messages array
      messages.push({
        role: 'user',
        content: {
          type: 'text',
          text: userMessageText,
        },
      });

      this.logger.debug(`Processed messages for prompt '${promptData.name}':`, messages);
      return { messages };
    } catch (error) {
      this.logger.error(`Error executing prompt '${promptData.name}':`, error);
      throw error; // Re-throw to let the MCP framework handle it
    }
  }

  /**
   * Register all prompts with the MCP server using proper MCP protocol
   */
  async registerAllPrompts(prompts: ConvertedPrompt[]): Promise<number> {
    try {
      this.logger.info(`Registering ${prompts.length} prompts with MCP SDK registerPrompt API...`);

      // Register individual prompts using the correct MCP SDK API
      this.registerIndividualPrompts(prompts);

      this.logger.info(`Successfully registered ${prompts.length} prompts with MCP SDK`);
      return prompts.length;
    } catch (error) {
      this.logger.error(`Error registering prompts:`, error);
      throw error;
    }
  }

  /**
   * Send list_changed notification to clients (for hot-reload)
   * This is the proper MCP way to notify clients about prompt updates
   */
  async notifyPromptsListChanged(): Promise<void> {
    try {
      // Send MCP notification that prompt list has changed
      if (this.mcpServer && typeof this.mcpServer.notification === 'function') {
        this.mcpServer.notification({
          method: 'notifications/prompts/list_changed',
        });
        this.logger.info('✅ Sent prompts/list_changed notification to clients');
      } else {
        this.logger.debug("MCP server doesn't support notifications");
      }
    } catch (error) {
      this.logger.warn('Could not send prompts/list_changed notification:', error);
    }
  }

  // Note: MCP SDK doesn't provide prompt unregistration
  // Hot-reload is handled through list_changed notifications to clients

  /**
   * Execute a prompt directly (for testing or internal use)
   */
  async executePromptDirectly(
    promptId: string,
    args: Record<string, string>,
    prompts: ConvertedPrompt[]
  ): Promise<string> {
    try {
      const convertedPrompt = prompts.find((cp) => cp.id === promptId);
      if (!convertedPrompt) {
        throw new Error(`Could not find prompt with ID: ${promptId}`);
      }

      this.logger.debug(`Running prompt directly: ${promptId} with arguments:`, args);

      // Missing arguments are handled by ArgumentParser's default resolution chain
      // which includes author-defined defaultValue as first priority

      // Process template with context
      // Using direct processing since TemplateProcessor was consolidated
      const userMessageText = await this.processTemplateDirect(
        convertedPrompt.userMessageTemplate,
        args,
        { previous_message: this.conversationStore.getPreviousMessage() }
      );

      // Add the message to conversation history
      this.conversationStore.addToConversationHistory({
        role: 'user',
        content: userMessageText,
        timestamp: Date.now(),
        isProcessedTemplate: true,
      });

      // Generate a response (echo in this MCP implementation)
      const response = `Processed prompt: ${promptId}\nWith message: ${userMessageText}`;

      // Store the response in conversation history
      this.conversationStore.addToConversationHistory({
        role: 'assistant',
        content: response,
        timestamp: Date.now(),
      });

      return response;
    } catch (error) {
      this.logger.error(`Error executing prompt '${promptId}':`, error);
      throw error;
    }
  }

  /**
   * Get registration statistics
   */
  getRegistrationStats(prompts: ConvertedPrompt[]): {
    totalPrompts: number;
    chainPrompts: number;
    regularPrompts: number;
    categoriesCount: number;
    averageArgumentsPerPrompt: number;
  } {
    const chainPrompts = prompts.filter((p) => isChainPrompt(p)).length;
    const categoriesSet = new Set(prompts.map((p) => p.category));
    const totalArguments = prompts.reduce((sum, p) => sum + p.arguments.length, 0);

    return {
      totalPrompts: prompts.length,
      chainPrompts,
      regularPrompts: prompts.length - chainPrompts,
      categoriesCount: categoriesSet.size,
      averageArgumentsPerPrompt: prompts.length > 0 ? totalArguments / prompts.length : 0,
    };
  }
}
