#!/usr/bin/env node
/**
 * JSDoc Example to Vitest Generator
 *
 * Extracts @example blocks from JSDoc comments and generates Vitest test files.
 * Implements the functionality proposed in LX-15 Phase 1.
 *
 * Usage:
 *   node generate-doctests.mjs [config-file]
 */

import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, rmSync } from 'fs';
import { join, dirname, relative, basename } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Default configuration
const DEFAULT_CONFIG = {
  include: ['packages/*/src/**/*.ts'],
  exclude: ['**/*.test.ts', '**/__doctest__/**', '**/node_modules/**'],
  outputDir: '__doctest__',
  framework: 'vitest',
  clean: true,
};

/**
 * Load configuration from file or use defaults
 */
function loadConfig(configPath) {
  if (!configPath) {
    return DEFAULT_CONFIG;
  }

  try {
    const configFile = join(process.cwd(), configPath);
    const config = JSON.parse(readFileSync(configFile, 'utf-8'));
    return { ...DEFAULT_CONFIG, ...config };
  } catch (error) {
    console.warn(`Warning: Could not load config from ${configPath}, using defaults`);
    return DEFAULT_CONFIG;
  }
}

/**
 * Extract @example blocks from JSDoc comments
 */
function extractExamples(content, filePath) {
  const examples = [];

  // Match JSDoc comments: /** ... */
  const jsdocPattern = /\/\*\*[\s\S]*?\*\//g;
  const jsdocComments = content.match(jsdocPattern) || [];

  for (const comment of jsdocComments) {
    // Extract function/variable name after the comment
    const afterComment = content.substring(content.indexOf(comment) + comment.length);
    const nameMatch = afterComment.match(/^\s*(?:export\s+)?(?:async\s+)?(?:function|const|let|var)\s+(\w+)/);

    if (!nameMatch) continue;
    const functionName = nameMatch[1];

    // Extract @example blocks
    const examplePattern = /@example\s+([\s\S]*?)(?=\*\/|@\w+)/g;
    let exampleMatch;
    let exampleIndex = 0;

    while ((exampleMatch = examplePattern.exec(comment)) !== null) {
      let exampleCode = exampleMatch[1];

      // Clean up comment markers (* at start of lines)
      exampleCode = exampleCode
        .split('\n')
        .map(line => line.replace(/^\s*\*\s?/, ''))
        .join('\n')
        .trim();

      if (exampleCode) {
        examples.push({
          functionName,
          code: exampleCode,
          index: exampleIndex++,
        });
      }
    }
  }

  return examples;
}

/**
 * Generate Vitest test file from examples
 */
function generateTestFile(sourceFile, examples, config) {
  const relativePath = relative(process.cwd(), sourceFile);

  // Calculate import path from __doctest__/path/to/file.doctest.js to path/to/file.ts
  // The test file mirrors the source structure under __doctest__/, so we need to go up
  // Example: __doctest__/packages/foo/src/bar.doctest.js -> ../../../../packages/foo/src/bar
  const depth = relativePath.split('/').length;
  const upPath = '../'.repeat(depth);
  const sourceImportPath = upPath + relativePath.replace(/\.ts$/, '');

  let testContent = `/**
 * Auto-generated doctest file from ${relativePath}
 * DO NOT EDIT - Generated by generate-doctests.mjs
 */

import { describe, it, expect } from 'vitest';
import * as sourceModule from '${sourceImportPath}';

// Extract exported functions
const { ${Array.from(new Set(examples.map(e => e.functionName))).join(', ')} } = sourceModule;

`;

  // Group examples by function
  const byFunction = {};
  for (const example of examples) {
    if (!byFunction[example.functionName]) {
      byFunction[example.functionName] = [];
    }
    byFunction[example.functionName].push(example);
  }

  for (const [functionName, functionExamples] of Object.entries(byFunction)) {
    testContent += `describe('${functionName} @example tests', () => {\n`;

    for (const example of functionExamples) {
      const testName = example.index === 0
        ? `should work as documented`
        : `should work as documented (example ${example.index + 1})`;

      testContent += `  it('${testName}', async () => {\n`;
      testContent += `    // Source: ${relativePath}\n`;

      // Parse and execute example code
      // Handle different example formats:
      // 1. Simple expressions: multiply(2, 3) // => 6
      // 2. Assertions: expect(multiply(2, 3)).toBe(6)
      // 3. Multi-line code blocks

      const lines = example.code.split('\n');
      let codeBlock = '';

      for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith('//')) {
          // Comment or empty line
          codeBlock += '    ' + line + '\n';
          continue;
        }

        // Check for assertion syntax: expr // => expected
        const assertMatch = trimmed.match(/^(.+?)\s*\/\/\s*=>\s*(.+)$/);
        if (assertMatch) {
          const expr = assertMatch[1].trim();
          const expected = assertMatch[2].trim();
          codeBlock += `    expect(${expr}).toEqual(${expected});\n`;
        } else {
          // Regular code line
          codeBlock += '    ' + line + '\n';
        }
      }

      testContent += codeBlock;
      testContent += `  });\n\n`;
    }

    testContent += `});\n\n`;
  }

  return testContent;
}

/**
 * Check if file matches glob pattern (simplified)
 */
function matchesGlob(filePath, pattern) {
  // Convert glob pattern to regex (simplified)
  const regexPattern = pattern
    .replace(/\*\*/g, '.*')
    .replace(/\*/g, '[^/]*')
    .replace(/\?/g, '.');

  const regex = new RegExp('^' + regexPattern + '$');
  return regex.test(filePath);
}

/**
 * Check if file should be processed
 */
function shouldProcess(filePath, config) {
  const relativePath = relative(process.cwd(), filePath);

  // Check excludes first
  for (const pattern of config.exclude) {
    if (matchesGlob(relativePath, pattern)) {
      return false;
    }
  }

  // Check includes
  for (const pattern of config.include) {
    if (matchesGlob(relativePath, pattern)) {
      return true;
    }
  }

  return false;
}

/**
 * Recursively find files matching patterns
 */
function findFiles(dir, config) {
  const files = [];

  try {
    const entries = readdirSync(dir);

    for (const entry of entries) {
      const fullPath = join(dir, entry);
      const stat = statSync(fullPath);

      if (stat.isDirectory()) {
        // Skip node_modules and hidden directories
        if (entry === 'node_modules' || entry.startsWith('.')) {
          continue;
        }
        files.push(...findFiles(fullPath, config));
      } else if (stat.isFile() && entry.endsWith('.ts')) {
        if (shouldProcess(fullPath, config)) {
          files.push(fullPath);
        }
      }
    }
  } catch (error) {
    // Skip directories we can't read
  }

  return files;
}

/**
 * Main function
 */
function main() {
  const configPath = process.argv[2];
  const config = loadConfig(configPath);

  console.log('🔍 Scanning for @example blocks...');
  console.log(`   Include: ${config.include.join(', ')}`);
  console.log(`   Exclude: ${config.exclude.join(', ')}`);

  // Clean output directory if configured
  if (config.clean) {
    try {
      const outputPath = join(process.cwd(), config.outputDir);
      rmSync(outputPath, { recursive: true, force: true });
      console.log(`🧹 Cleaned ${config.outputDir}/`);
    } catch (error) {
      // Directory might not exist yet
    }
  }

  // Find all TypeScript files
  const files = findFiles(process.cwd(), config);
  console.log(`📁 Found ${files.length} files to process`);

  let totalExamples = 0;
  let totalFiles = 0;

  for (const file of files) {
    const content = readFileSync(file, 'utf-8');
    const examples = extractExamples(content, file);

    if (examples.length === 0) {
      continue;
    }

    // Generate test file
    const testContent = generateTestFile(file, examples, config);

    // Determine output path
    const relativePath = relative(process.cwd(), file);
    const testFileName = basename(file).replace(/\.ts$/, '.doctest.js');
    const outputPath = join(process.cwd(), config.outputDir, dirname(relativePath), testFileName);

    // Create output directory
    mkdirSync(dirname(outputPath), { recursive: true });

    // Write test file
    writeFileSync(outputPath, testContent);

    console.log(`✅ Generated ${examples.length} tests from ${relativePath}`);
    totalExamples += examples.length;
    totalFiles++;
  }

  console.log('');
  console.log(`✨ Done! Generated ${totalExamples} doctest(s) from ${totalFiles} file(s)`);
  console.log(`📂 Output: ${config.outputDir}/`);
}

main();
