/**
 * HOP Composer — Higher-Order Prompt rendering and nesting
 *
 * Compiles Handlebars templates for agent prompts (HOPs).
 * Supports:
 *   - renderHOP(templatePath, variables) → compiled prompt string
 *   - composeHOPs(hopChain[], variables) → nested HOPs (outer wraps inner)
 *   - listAvailableHOPs() → scan meta-prompts/ and return HOPDescriptor[]
 *
 * Standard HOP variables auto-injected:
 *   FEATURE_NAME, AGENT_ID, MISSION, SPEC_SUMMARY, STANDARDS, TASKS,
 *   DELIVERABLES, CONSTRAINTS, DATE, TIMESTAMP
 */

import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, relative, extname, basename } from 'path';

const META_PROMPTS_DIR = join(process.cwd(), 'framework/templates/meta-prompts');
const REGISTRY_PATH = join(process.cwd(), 'framework/templates/REGISTRY.json');

// ============================================================================
// Handlebars-Compatible Mini-Compiler
// ============================================================================

/**
 * Compile a Handlebars template with variables (subset of Handlebars features)
 * Supports: {{variable}}, {{#if condition}}...{{/if}}, {{#each array}}...{{/each}}
 * Also supports built-in helpers: {{pascalCase}}, {{camelCase}}, {{snakeCase}}
 *
 * @param {string} template - Handlebars template string
 * @param {Object} variables - Variables to inject
 * @returns {string} Compiled template
 */
export function compileTemplate(template, variables) {
  const vars = {
    DATE: new Date().toISOString().split('T')[0],
    TIMESTAMP: new Date().toISOString(),
    ...injectCasedHelpers(variables),
    ...variables
  };

  let result = template;

  // 1. Process {{#if VAR}}...{{/if}} blocks
  result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
    return vars[key] ? content : '';
  });

  // 2. Process {{#each ARRAY}}...{{/each}} blocks
  result = result.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
    const arr = vars[key];
    if (!Array.isArray(arr)) return '';
    return arr.map(item => {
      const itemVars = typeof item === 'object' ? { ...vars, ...item, this: item } : { ...vars, this: item };
      return compileTemplate(content, itemVars);
    }).join('');
  });

  // 3. Process helper calls: {{pascalCase VARIABLE}}, {{camelCase VARIABLE}}, etc.
  result = result.replace(/\{\{(pascalCase|camelCase|snakeCase|titleCase|pluralize)\s+(\w+)\}\}/g, (_, helper, key) => {
    const value = String(vars[key] || '');
    return applyHelper(helper, value);
  });

  // 4. Process simple {{VARIABLE}} substitutions
  result = result.replace(/\{\{(\w+)\}\}/g, (_, key) => {
    return vars[key] !== undefined ? String(vars[key]) : `{{${key}}}`;
  });

  return result;
}

function injectCasedHelpers(variables) {
  const extras = {};
  for (const [key, value] of Object.entries(variables)) {
    if (typeof value === 'string') {
      extras[`${key}_PASCAL`] = applyHelper('pascalCase', value);
      extras[`${key}_CAMEL`] = applyHelper('camelCase', value);
      extras[`${key}_SNAKE`] = applyHelper('snakeCase', value);
    }
  }
  return extras;
}

function applyHelper(helper, value) {
  const words = value.replace(/[-_\s]+/g, ' ').trim().split(' ');
  switch (helper) {
    case 'pascalCase':
      return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
    case 'camelCase':
      return words[0].toLowerCase() + words.slice(1).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
    case 'snakeCase':
      return value.replace(/[-\s]+/g, '_').replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
    case 'titleCase':
      return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
    case 'pluralize':
      const last = value.slice(-1);
      if (last === 'y') return value.slice(0, -1) + 'ies';
      if (last === 's') return value;
      return value + 's';
    default:
      return value;
  }
}

// ============================================================================
// HOP Rendering
// ============================================================================

/**
 * Render a HOP template with variables
 * @param {string} templatePath - Path to template (relative to meta-prompts/ or absolute)
 * @param {Object} variables - Variables to inject
 * @returns {string} Rendered prompt string
 */
export function renderHOP(templatePath, variables = {}) {
  // Resolve template path
  const paths = [
    templatePath,
    join(META_PROMPTS_DIR, templatePath),
    join(META_PROMPTS_DIR, templatePath + '.md'),
    join(process.cwd(), templatePath)
  ];

  let resolved = null;
  for (const p of paths) {
    if (existsSync(p)) {
      resolved = p;
      break;
    }
  }

  if (!resolved) {
    throw new Error(`HOP template not found: ${templatePath}\nSearched: ${paths.join(', ')}`);
  }

  const template = readFileSync(resolved, 'utf8');
  return compileTemplate(template, variables);
}

/**
 * Compose nested HOPs (outer wraps inner, variables bubble down)
 * @param {Array} hopChain - Array of { templatePath, variables } objects (outer first)
 * @param {Object} [sharedVariables] - Variables shared across all HOPs
 * @returns {string} Composed prompt string
 */
export function composeHOPs(hopChain, sharedVariables = {}) {
  if (!hopChain || hopChain.length === 0) {
    throw new Error('hopChain must be a non-empty array');
  }

  if (hopChain.length === 1) {
    return renderHOP(hopChain[0].templatePath, { ...sharedVariables, ...hopChain[0].variables });
  }

  // Render inner HOPs first, then wrap with outer
  let innerContent = '';
  for (let i = hopChain.length - 1; i >= 1; i--) {
    const hop = hopChain[i];
    const vars = { ...sharedVariables, ...hop.variables, INNER_PROMPT: innerContent };
    innerContent = renderHOP(hop.templatePath, vars);
  }

  // Render outermost HOP with inner content
  const outerHop = hopChain[0];
  return renderHOP(outerHop.templatePath, {
    ...sharedVariables,
    ...outerHop.variables,
    INNER_PROMPT: innerContent
  });
}

// ============================================================================
// HOP Discovery
// ============================================================================

/**
 * List all available HOPs from the meta-prompts directory
 * @returns {Array} Array of HOPDescriptor objects
 */
export function listAvailableHOPs() {
  if (!existsSync(META_PROMPTS_DIR)) return [];

  const hops = [];
  scanDir(META_PROMPTS_DIR, META_PROMPTS_DIR, hops);
  return hops;
}

function scanDir(dir, baseDir, hops) {
  if (!existsSync(dir)) return;

  for (const entry of readdirSync(dir, { withFileTypes: true })) {
    const fullPath = join(dir, entry.name);
    if (entry.isDirectory()) {
      scanDir(fullPath, baseDir, hops);
    } else if (entry.name.endsWith('.md') || entry.name.endsWith('.hbs')) {
      const relPath = relative(baseDir, fullPath);
      const content = readFileSync(fullPath, 'utf8');
      const firstLine = content.split('\n')[0] || '';
      const description = firstLine.startsWith('#')
        ? firstLine.replace(/^#+\s*/, '')
        : 'Higher-Order Prompt template';

      // Extract variables from template
      const variables = [...new Set(
        (content.match(/\{\{(\w+)\}\}/g) || [])
          .map(m => m.replace(/\{\{|\}\}/g, ''))
          .filter(v => !['if', 'each', 'this', 'else', 'pascalCase', 'camelCase', 'snakeCase', 'titleCase', 'pluralize'].includes(v))
      )];

      hops.push({
        id: relPath.replace(/\\/g, '/').replace(/\.(md|hbs)$/, ''),
        path: fullPath,
        relativePath: relPath.replace(/\\/g, '/'),
        description,
        variables,
        category: relPath.split(/[\\/]/)[0]
      });
    }
  }
}
