// @lifecycle canonical - Converts prompt markdown files into structured prompt data.
/**
 * Prompt Converter Module
 * Handles converting markdown prompts to JSON structure with validation
 */

import * as path from 'node:path';

import { PromptLoader } from './loader.js';
import { isChainPrompt } from '../../shared/utils/chainUtils.js';
import {
  ScriptToolDefinitionLoader,
  createScriptToolDefinitionLoader,
} from '../automation/core/script-definition-loader.js';

import type { PromptData } from './types.js';
import type { ConvertedPrompt } from '../../engine/execution/types.js';
import type { PromptArgument, Logger } from '../../shared/types/index.js';

/**
 * Resolve the registerWithMcp value using the priority chain:
 * 1. Prompt-level override (highest priority)
 * 2. Category-level default (from _categoryRegisterWithMcp)
 * 3. Global config default (from config.json prompts.registerWithMcp)
 * 4. Hard-coded default: true (register with MCP)
 */
function resolveRegisterWithMcp(
  promptData: PromptData & { _categoryRegisterWithMcp?: boolean },
  globalRegisterWithMcp?: boolean
): boolean {
  // 1. Prompt-level override takes precedence
  if (promptData.registerWithMcp !== undefined) {
    return promptData.registerWithMcp;
  }
  // 2. Category-level default (attached by loader)
  if (promptData._categoryRegisterWithMcp !== undefined) {
    return promptData._categoryRegisterWithMcp;
  }
  // 3. Global config default
  if (globalRegisterWithMcp !== undefined) {
    return globalRegisterWithMcp;
  }
  // 4. Hard-coded default: register with MCP
  return true;
}

/**
 * Prompt Converter class
 */
export class PromptConverter {
  private logger: Logger;
  private loader: PromptLoader;
  private globalRegisterWithMcp: boolean | undefined;
  private scriptToolLoader: ScriptToolDefinitionLoader;

  constructor(logger: Logger, loader?: PromptLoader, globalRegisterWithMcp?: boolean) {
    this.logger = logger;
    this.loader = loader || new PromptLoader(logger);
    this.scriptToolLoader = createScriptToolDefinitionLoader({ debug: false });
    if (globalRegisterWithMcp !== undefined) {
      this.globalRegisterWithMcp = globalRegisterWithMcp;
    }
  }

  /**
   * Get the script tool loader instance.
   * Exposed for hot-reload integration.
   */
  getScriptToolLoader(): ScriptToolDefinitionLoader {
    return this.scriptToolLoader;
  }

  /**
   * Set the global registerWithMcp default value
   */
  setGlobalRegisterWithMcp(value: boolean | undefined): void {
    if (value !== undefined) {
      this.globalRegisterWithMcp = value;
    }
  }

  /**
   * Convert markdown prompts to JSON structure in memory
   */
  async convertMarkdownPromptsToJson(
    promptsData: PromptData[],
    basePath?: string
  ): Promise<ConvertedPrompt[]> {
    const convertedPrompts: ConvertedPrompt[] = [];

    this.logger.info(`Converting ${promptsData.length} markdown prompts to JSON structure...`);

    for (const promptData of promptsData) {
      try {
        // Determine base path for loading files - basePath is required
        if (basePath == null || basePath === '') {
          throw new Error(
            `Cannot convert prompt '${promptData.id}': basePath is required for file loading`
          );
        }
        const fileBasePath = basePath;

        // Load the prompt file content using the loader
        const promptFile = await this.loader.loadPromptFile(promptData.file, fileBasePath);

        // Load chain steps from markdown-embedded format
        let chainSteps = promptFile.chainSteps || [];

        // Create converted prompt structure
        const convertedPrompt: ConvertedPrompt = {
          id: promptData.id,
          name: promptData.name,
          description: promptData.description,
          category: promptData.category,
          systemMessage: promptFile.systemMessage ?? '',
          userMessageTemplate: promptFile.userMessageTemplate,
          arguments: promptData.arguments.map((arg) => {
            const mappedArgument: PromptArgument = {
              name: arg.name,
              required: arg.required ?? false,
            };

            if (arg.description !== undefined) {
              mappedArgument.description = arg.description;
            }
            if (arg.type !== undefined) {
              mappedArgument.type = arg.type;
            }
            if (arg.defaultValue !== undefined) {
              mappedArgument.defaultValue = arg.defaultValue;
            }
            if (arg.validation) {
              mappedArgument.validation = arg.validation;
            }

            return mappedArgument;
          }),
          // Include chain information from markdown-embedded chainSteps
          chainSteps: chainSteps,
          // Resolve MCP registration from prompt/category/global defaults
          registerWithMcp: resolveRegisterWithMcp(promptData, this.globalRegisterWithMcp),
        };

        if (promptData.subagentModel != null) {
          convertedPrompt.subagentModel = promptData.subagentModel;
        }

        if (promptFile.gateConfiguration) {
          convertedPrompt.gateConfiguration = promptFile.gateConfiguration;
        }

        // Load script tools if prompt declares any (Phase 2 integration)
        if (promptData.tools && promptData.tools.length > 0) {
          try {
            // Get the prompt directory from the file path
            const fullFilePath = path.join(fileBasePath, promptData.file);
            const promptDir = promptData.file.endsWith('.yaml')
              ? path.dirname(fullFilePath)
              : path.dirname(path.dirname(fullFilePath)); // For {id}/prompt.yaml

            // Load the declared tools
            const loadedTools = this.scriptToolLoader.loadToolsForPrompt(
              promptDir,
              promptData.tools,
              promptData.id
            );

            if (loadedTools.length > 0) {
              convertedPrompt.scriptTools = loadedTools;
              this.logger.debug(
                `[PromptConverter] Loaded ${loadedTools.length} script tools for '${promptData.id}'`
              );
            }

            // Store promptDir for script reference resolution ({{script:id}} syntax)
            convertedPrompt.promptDir = promptDir;
          } catch (toolLoadError) {
            this.logger.warn(
              `[PromptConverter] Failed to load script tools for '${promptData.id}':`,
              toolLoadError
            );
            // Continue without script tools - non-blocking error
          }
        }

        // NOTE: All chains now use markdown-embedded format
        // Modular chain system has been removed - chains are defined inline within markdown files
        if (isChainPrompt(convertedPrompt) && chainSteps.length === 0) {
          this.logger.debug(
            `Chain prompt '${convertedPrompt.id}' has no embedded chain steps - will be treated as single prompt`
          );
        }

        // Validate the converted prompt
        const validation = this.validateConvertedPrompt(convertedPrompt);
        if (!validation.isValid) {
          this.logger.warn(
            `Prompt ${promptData.id} has validation issues: ${validation.errors.join(', ')}`
          );
          // Continue processing even with warnings
        }

        convertedPrompts.push(convertedPrompt);
      } catch (error) {
        this.logger.error(`Error converting prompt ${promptData.id}:`, error);
        // Continue with other prompts even if one fails
      }
    }

    this.logger.info(`Successfully converted ${convertedPrompts.length} prompts`);
    return convertedPrompts;
  }

  /**
   * Validate a converted prompt
   */
  validateConvertedPrompt(prompt: ConvertedPrompt): {
    isValid: boolean;
    errors: string[];
    warnings: string[];
  } {
    const errors: string[] = [];
    const warnings: string[] = [];

    // Check required fields
    if (!prompt.id) {
      errors.push('Missing required field: id');
    }
    if (!prompt.name) {
      errors.push('Missing required field: name');
    }
    if (!prompt.category) {
      errors.push('Missing required field: category');
    }

    // Check that either userMessageTemplate exists, it's a valid chain, or has systemMessage
    const hasUserMessage = prompt.userMessageTemplate.length > 0;
    const hasChainSteps = (prompt.chainSteps?.length ?? 0) > 0;
    const hasSystemMessage = prompt.systemMessage !== undefined && prompt.systemMessage.length > 0;
    if (!hasUserMessage && !hasChainSteps && !hasSystemMessage) {
      errors.push('Prompt must have userMessageTemplate, chainSteps, or systemMessage defined');
    }

    // Validate chain prompts
    if ((prompt.chainSteps?.length || 0) > 0) {
      if (!prompt.chainSteps || prompt.chainSteps.length === 0) {
        errors.push('Chain prompt must have at least one chain step');
      } else {
        // Validate each chain step
        prompt.chainSteps.forEach((step, index) => {
          if (!step.promptId) {
            errors.push(`Chain step ${index + 1} missing promptId`);
          }
          if (!step.stepName) {
            errors.push(`Chain step ${index + 1} missing stepName`);
          }
        });
      }
    }

    // Validate arguments
    if (prompt.arguments) {
      prompt.arguments.forEach((arg, index) => {
        if (!arg.name) {
          errors.push(`Argument ${index + 1} missing name`);
        }
        if (typeof arg.required !== 'boolean') {
          warnings.push(`Argument ${arg.name || index + 1} has invalid required value`);
        }
      });
    }

    // Check for placeholder validation in template
    if (prompt.userMessageTemplate) {
      // Validate template syntax - reject Handlebars syntax
      if (
        prompt.userMessageTemplate.includes('{{#if') ||
        prompt.userMessageTemplate.includes('{{/if') ||
        prompt.userMessageTemplate.includes('{{#each') ||
        prompt.userMessageTemplate.includes('{{/each') ||
        prompt.userMessageTemplate.includes('{{#unless') ||
        prompt.userMessageTemplate.includes('{{/unless')
      ) {
        errors.push(
          `Handlebars syntax detected in template. This system uses Nunjucks syntax.\n` +
            `Replace: {{#if condition}} → {% if condition %}\n` +
            `Replace: {{/if}} → {% endif %}\n` +
            `Replace: {{#each items}} → {% for item in items %}\n` +
            `Replace: {{/each}} → {% endfor %}`
        );
      }

      const placeholders = this.extractPlaceholders(prompt.userMessageTemplate);
      const argumentNames = prompt.arguments.map((arg) => arg.name);

      // Find placeholders that don't have corresponding arguments
      const orphanedPlaceholders = placeholders.filter(
        (placeholder) =>
          !argumentNames.includes(placeholder) && !this.isSpecialPlaceholder(placeholder)
      );

      if (orphanedPlaceholders.length > 0) {
        warnings.push(
          `Template has placeholders without arguments: ${orphanedPlaceholders.join(', ')}`
        );
      }

      // Find arguments that aren't used in the template
      const unusedArguments = argumentNames.filter((argName) => !placeholders.includes(argName));

      if (unusedArguments.length > 0) {
        warnings.push(`Arguments not used in template: ${unusedArguments.join(', ')}`);
      }
    }

    return {
      isValid: errors.length === 0,
      errors,
      warnings,
    };
  }

  /**
   * Extract placeholders from a template string
   */
  private extractPlaceholders(template: string): string[] {
    const placeholderRegex = /\{\{([^}]+)\}\}/g;
    const placeholders: string[] = [];
    let match;

    while ((match = placeholderRegex.exec(template)) !== null) {
      const placeholder = (match[1] ?? '').trim();
      if (!placeholders.includes(placeholder)) {
        placeholders.push(placeholder);
      }
    }

    return placeholders;
  }

  /**
   * Check if a placeholder is a special system placeholder.
   * These are injected at runtime and don't require argument definitions.
   */
  private isSpecialPlaceholder(placeholder: string): boolean {
    const specialPlaceholders = [
      'previous_message', // Last non-template user message (ConversationStore)
      // Chain step variables (ChainSessionManager.getChainContext)
      'chain_id',
      'input', // Current step's arguments object
      'previous_step_result',
      'step_results',
    ];

    // Also match stepN_result patterns (step1_result, step2_result, etc.)
    const isStepResultPattern = /^step\d+_result$/.test(placeholder);

    return (
      specialPlaceholders.includes(placeholder) ||
      isStepResultPattern ||
      placeholder.startsWith('ref:')
    );
  }

  /**
   * Get conversion statistics
   */
  getConversionStats(
    originalCount: number,
    convertedPrompts: ConvertedPrompt[]
  ): {
    totalOriginal: number;
    totalConverted: number;
    successRate: number;
    chainPrompts: number;
    regularPrompts: number;
    totalArguments: number;
  } {
    const chainPrompts = convertedPrompts.filter((p) => isChainPrompt(p)).length;
    const regularPrompts = convertedPrompts.length - chainPrompts;
    const totalArguments = convertedPrompts.reduce((sum, p) => sum + p.arguments.length, 0);

    return {
      totalOriginal: originalCount,
      totalConverted: convertedPrompts.length,
      successRate: originalCount > 0 ? convertedPrompts.length / originalCount : 0,
      chainPrompts,
      regularPrompts,
      totalArguments,
    };
  }
}
