// src/runtime/JavaScriptHandlerCompiler.ts
//
// Vision 45 §10.2: JavaScript Handler Compilation
//
// Brendan Eich (JavaScript Creator):
//   "JavaScript's dynamic nature is a double-edged sword.
//    new Function() gives us EVAL-like power with some sandboxing."
//
// This compiler uses JavaScript's `new Function()` to compile handler code
// at runtime. This is suitable for development and trusted environments.
// For production sandboxing, use LispHandlerCompiler instead.

import type {
  IHandlerCompiler,
  ICompilationResult,
  ICompilationOptions,
  IValidationError,
  ICompiledHandler,
} from './IHandlerCompiler';
import type { IHandlerSchema } from '../serialization/IComponentSchema';

/**
 * JavaScript handler compiler using `new Function()`.
 *
 * Vision 45 §10.1: Development-time compilation strategy.
 *
 * Security considerations:
 * - Uses `new Function()` which is similar to `eval()`
 * - Code runs in global scope (not truly sandboxed)
 * - Suitable for trusted code / development environments
 * - For production with untrusted code, use LispHandlerCompiler
 *
 * @example
 * ```typescript
 * const compiler = new JavaScriptHandlerCompiler();
 *
 * const result = compiler.compile(
 *   'this.count = (this.count || 0) + 1;',
 *   'increment'
 * );
 *
 * if (result.success && result.handler) {
 *   // Call with component as `this`
 *   result.handler.call(component, payload, meta);
 * }
 * ```
 */
export class JavaScriptHandlerCompiler implements IHandlerCompiler {
  readonly language = 'javascript' as const;

  /**
   * Compile JavaScript code string into executable handler.
   *
   * @param code - JavaScript code (function body)
   * @param commandName - Command name for error reporting
   * @param options - Compilation options (unused for JS, reserved for future)
   * @returns Compilation result
   */
  compile(
    code: string,
    commandName: string,
    options?: ICompilationOptions
  ): ICompilationResult {
    try {
      // Validate the code syntax first
      const validationErrors = this.validate(code);
      const hasErrors = validationErrors.some(e => e.severity === 'error');

      if (hasErrors) {
        return {
          success: false,
          error: validationErrors.map(e => e.message).join('; '),
        };
      }

      // The code is a function body that receives (payload, meta)
      // and has access to 'this' (the component)
      // eslint-disable-next-line no-new-func
      const compiledFn = new Function('payload', 'meta', code) as ICompiledHandler;

      // Wrap to preserve 'this' binding when called
      const handler: ICompiledHandler = function(
        this: unknown,
        payload: unknown,
        meta: unknown
      ): unknown {
        return compiledFn.call(this, payload, meta);
      };

      return {
        success: true,
        handler,
      };
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      return {
        success: false,
        error: `Failed to compile handler '${commandName}': ${message}`,
      };
    }
  }

  /**
   * Compile handler from schema definition.
   *
   * @param schema - Handler schema with code and options
   * @param commandName - Command name for error reporting
   * @param options - Additional compilation options
   * @returns Compilation result
   */
  compileFromSchema(
    schema: IHandlerSchema,
    commandName: string,
    options?: ICompilationOptions
  ): ICompilationResult {
    // Verify language matches
    const language = schema.language ?? 'javascript';
    if (language !== 'javascript') {
      return {
        success: false,
        error: `JavaScriptHandlerCompiler cannot compile '${language}' code`,
      };
    }

    // Security context warning
    if (schema.securityContext === 'sandboxed') {
      // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
      console.warn(
        `[JavaScriptHandlerCompiler] Handler '${commandName}' requests sandboxed execution, ` +
        'but JavaScript compiler does not provide true sandboxing. ' +
        'Consider using LispHandlerCompiler for untrusted code.'
      );
    }

    return this.compile(schema.code, commandName, options);
  }

  /**
   * Check if this compiler supports the given language.
   */
  supports(language: string): boolean {
    return language === 'javascript' || language === 'js';
  }

  /**
   * Validate JavaScript code without compiling.
   *
   * Uses `new Function()` in a try-catch to detect syntax errors.
   *
   * @param code - JavaScript code to validate
   * @returns Array of validation errors (empty if valid)
   */
  validate(code: string): IValidationError[] {
    const errors: IValidationError[] = [];

    try {
      // Attempt to parse as function body
      // eslint-disable-next-line no-new-func
      new Function('payload', 'meta', code);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);

      // Try to extract line/column from error
      const location = this._extractLocation(message);

      errors.push({
        message,
        location,
        severity: 'error',
      });
    }

    // Additional warnings for common issues
    errors.push(...this._checkCommonIssues(code));

    return errors;
  }

  /**
   * Extract source location from error message.
   *
   * JavaScript errors sometimes include line/column info.
   */
  private _extractLocation(errorMessage: string): { line: number; column: number } | undefined {
    // Common patterns: "line X", "at line X", "(X:Y)"
    const lineMatch = errorMessage.match(/line\s*(\d+)/i);
    const posMatch = errorMessage.match(/\((\d+):(\d+)\)/);

    if (posMatch) {
      return {
        line: parseInt(posMatch[1], 10),
        column: parseInt(posMatch[2], 10),
      };
    }

    if (lineMatch) {
      return {
        line: parseInt(lineMatch[1], 10),
        column: 1,
      };
    }

    return undefined;
  }

  /**
   * Check for common issues in handler code.
   *
   * These are warnings, not errors - code may still compile.
   */
  private _checkCommonIssues(code: string): IValidationError[] {
    const warnings: IValidationError[] = [];

    // Warn about direct DOM access
    if (/\bdocument\b/.test(code) || /\bwindow\b/.test(code)) {
      warnings.push({
        message: 'Handler accesses global DOM objects (document/window). ' +
                 'Consider using component methods for better testability.',
        severity: 'warning',
      });
    }

    // Warn about async without await
    if (/\basync\b/.test(code) && !/\bawait\b/.test(code)) {
      warnings.push({
        message: 'Async handler without await - did you forget to await a promise?',
        severity: 'warning',
      });
    }

    // Warn about console.log in production code
    if (/\bconsole\.log\b/.test(code)) {
      warnings.push({
        message: 'Handler contains console.log - consider removing for production.',
        severity: 'info',
      });
    }

    return warnings;
  }
}
