// src/lisp/core/Interpreter.ts
// Core LISP interpreter - The evaluation engine for LISP-on-Protocol
//
// This is a pure TypeScript implementation that can run standalone or
// be wrapped by a MatrixActor for distributed evaluation.
//
// Security enforcement per Vision 18 and 27:
// - Primitive-level checks via isPrimitiveAllowed()
// - Call target validation via isCallTargetAllowed()
// - Resource limits (depth, time) enforced during evaluation

import type { SExpr } from '../SExpr';
import type { ILambdaClosure } from '../ILambdaClosure';
import type { IStream } from '../IStream';
import type { IStreamOperator } from '../IStreamOperator';
import type { IParamSchema } from '../../serialization';
import { EvalContext } from './Environment';
import type { ILispSecurityContext } from '../../core/security/LispSecurityContext';
import { isPrimitiveAllowed, isCallTargetAllowed } from '../../core/security/LispSecurityContext';

/**
 * Remote call handler interface
 * Implemented by MatrixActor wrapper for network calls
 */
export interface IRemoteCallHandler {
  call(mount: string, command: string, params: unknown): Promise<unknown>;
  isLocal(mount: string): boolean;
  localCall(mount: string, command: string, params: unknown): Promise<unknown>;
}

/**
 * Interpreter configuration options
 */
export interface IInterpreterOptions {
  /** Handler for remote component calls */
  remoteHandler?: IRemoteCallHandler;
  /** Maximum call depth to prevent infinite recursion */
  maxCallDepth?: number;
  /**
   * Security context for evaluation.
   *
   * Controls which primitives and operations are allowed.
   * When undefined, all operations are permitted (development mode).
   *
   * See: Vision/18-VIRTUAL-LISP-MACHINE.md Section "Security Enforcement"
   */
  securityContext?: ILispSecurityContext;
}

/**
 * LISP Interpreter - Core evaluation engine
 *
 * This class contains the pure evaluation logic extracted from LispEval.
 * It can be used standalone for local evaluation or wrapped by a
 * MatrixActor for distributed evaluation.
 *
 * Security enforcement (Vision 18 & 27):
 * - When securityContext is provided, all primitive operations are checked
 * - `call`, `emit`, `spawn` operations are validated against allowed targets
 * - Resource limits (depth, time) are enforced during evaluation
 */
export class Interpreter {
  private _context: EvalContext;
  private _remoteHandler?: IRemoteCallHandler;
  private _maxCallDepth: number;
  private _callDepth: number = 0;

  /**
   * Security context for this interpreter instance.
   *
   * When set, primitive operations are checked against the allowlist/blocklist,
   * and call targets are validated before execution.
   */
  private _securityContext?: ILispSecurityContext;

  /** Evaluation start time for timeout enforcement */
  private _evalStartTime: number = 0;

  constructor(options: IInterpreterOptions = {}) {
    this._context = new EvalContext();
    this._remoteHandler = options.remoteHandler;
    this._maxCallDepth = options.maxCallDepth ?? 1000;
    this._securityContext = options.securityContext;
  }

  /**
   * Set the security context for this interpreter.
   *
   * @param context - Security context to use for subsequent evaluations
   */
  setSecurityContext(context: ILispSecurityContext | undefined): void {
    this._securityContext = context;
  }

  /**
   * Get the current security context.
   */
  get securityContext(): ILispSecurityContext | undefined {
    return this._securityContext;
  }

  /**
   * Get the evaluation context
   */
  get context(): EvalContext {
    return this._context;
  }

  /**
   * Evaluate an S-expression
   *
   * Security enforcement:
   * - Depth limit from securityContext.maxEvalDepth (if set)
   * - Timeout from securityContext.maxExecutionMs (if set)
   */
  async evaluate(expr: SExpr): Promise<unknown> {
    // Initialize timer on first call (depth = 0)
    if (this._callDepth === 0) {
      this._evalStartTime = Date.now();
    }

    this._callDepth++;

    // Use security context depth limit if available, otherwise use configured max
    const maxDepth = this._securityContext?.maxEvalDepth ?? this._maxCallDepth;
    if (this._callDepth > maxDepth) {
      throw new Error(`Maximum call depth (${maxDepth}) exceeded`);
    }

    try {
      return await this._evaluate(expr);
    } finally {
      this._callDepth--;
    }
  }

  /**
   * Core evaluation logic - recursively evaluates S-expressions
   *
   * Security enforcement (Vision 18 & 27):
   * - Timeout check at start of each evaluation
   * - Primitive allowlist/blocklist check for operations
   */
  private async _evaluate(expr: SExpr): Promise<unknown> {
    // ═══════════════════════════════════════════════════════════════════════
    // SECURITY: Timeout enforcement
    // ═══════════════════════════════════════════════════════════════════════
    if (this._securityContext?.maxExecutionMs) {
      const elapsed = Date.now() - this._evalStartTime;
      if (elapsed > this._securityContext.maxExecutionMs) {
        throw new Error(
          `Execution time (${elapsed}ms) exceeds limit (${this._securityContext.maxExecutionMs}ms)`
        );
      }
    }

    // Numbers, booleans, null evaluate to themselves
    if (typeof expr === 'number' || typeof expr === 'boolean' || expr === null) {
      return expr;
    }

    // Strings: check if it's a variable reference first
    if (typeof expr === 'string') {
      const value = this._context.lookup(expr);
      if (value !== undefined) {
        return value;
      }
      // Otherwise return string literal
      return expr;
    }

    // Empty list
    if (!Array.isArray(expr) || expr.length === 0) {
      return expr;
    }

    // S-expression: ["op", ...args]
    const [op, ...args] = expr;

    // ═══════════════════════════════════════════════════════════════════════
    // SECURITY: Primitive allowlist/blocklist check
    // Mark Miller (Capability Security): "Check BEFORE execution, not after"
    // ═══════════════════════════════════════════════════════════════════════
    if (typeof op === 'string' && this._securityContext) {
      if (!isPrimitiveAllowed(op, this._securityContext)) {
        throw new Error(
          `Security violation: primitive '${op}' is not allowed in context '${this._securityContext.id}'`
        );
      }
    }

    // =========================================================================
    // IMMEDIATE LAMBDA APPLICATION
    // ((lambda (x) (* x x)) 7) -> evaluate the lambda, then apply it
    // =========================================================================
    if (typeof op !== 'string') {
      const fn = await this._evaluate(op);
      if (this._context.isLambda(fn)) {
        const evaluatedArgs = [];
        for (const arg of args) {
          evaluatedArgs.push(await this._evaluate(arg));
        }
        return await this._applyLambda(fn, evaluatedArgs);
      }
      throw new Error(`Invalid operator: ${JSON.stringify(op)}`);
    }

    // =========================================================================
    // SPECIAL FORMS - These don't evaluate their arguments normally
    // =========================================================================

    // QUOTE - Return the argument as data, don't evaluate it
    if (op === 'quote') {
      return args[0];
    }

    // EVAL - Evaluate a quoted expression (the inverse of quote)
    if (op === 'eval') {
      const toEval = await this._evaluate(args[0]);
      return await this._evaluate(toEval as SExpr);
    }

    // ATOM - Is this a primitive (not a list)?
    if (op === 'atom' || op === 'atom?') {
      const val = await this._evaluate(args[0]);
      return !Array.isArray(val);
    }

    // CAR/FIRST - First element of a list
    if (op === 'car' || op === 'first') {
      const list = await this._evaluate(args[0]);
      if (!Array.isArray(list) || list.length === 0) return null;
      return list[0];
    }

    // CDR/REST - Rest of a list (everything but first)
    if (op === 'cdr' || op === 'rest') {
      const list = await this._evaluate(args[0]);
      if (!Array.isArray(list) || list.length === 0) return [];
      return list.slice(1);
    }

    // CADR - (car (cdr x)) - Second element
    if (op === 'cadr') {
      const list = await this._evaluate(args[0]);
      if (!Array.isArray(list) || list.length < 2) return null;
      return list[1];
    }

    // CADDR - (car (cdr (cdr x))) - Third element
    if (op === 'caddr') {
      const list = await this._evaluate(args[0]);
      if (!Array.isArray(list) || list.length < 3) return null;
      return list[2];
    }

    // CADDDR - (car (cdr (cdr (cdr x)))) - Fourth element
    if (op === 'cadddr') {
      const list = await this._evaluate(args[0]);
      if (!Array.isArray(list) || list.length < 4) return null;
      return list[3];
    }

    // EQ/EQ? - Reference equality
    if (op === 'eq' || op === 'eq?') {
      const a = await this._evaluate(args[0]);
      const b = await this._evaluate(args[1]);
      return a === b;
    }

    // PRIMITIVE? - Check if symbol is a primitive operation
    if (op === 'primitive?') {
      const sym = await this._evaluate(args[0]);
      if (typeof sym !== 'string') return false;
      // Check if it's one of the built-in primitives
      const primitives = ['+', '-', '*', '/', '<', '>', '=', '<=', '>=',
        'car', 'cdr', 'cons', 'list', 'atom', 'null?', 'pair?', 'eq?',
        'number?', 'boolean?', 'string?', 'if', 'cond', 'and', 'or', 'not'];
      return primitives.includes(sym);
    }

    // CONS - Construct a list by prepending
    if (op === 'cons') {
      const head = await this._evaluate(args[0]);
      const tail = await this._evaluate(args[1]);
      if (!Array.isArray(tail)) return [head, tail];
      return [head, ...tail];
    }

    // LIST - Create a list from evaluated arguments
    if (op === 'list') {
      const results: unknown[] = [];
      for (const arg of args) {
        results.push(await this._evaluate(arg));
      }
      return results;
    }

    // GET - Property access for objects
    if (op === 'get') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const key = await this._evaluate(args[1]) as string;
      return obj?.[key];
    }

    // LEN/LENGTH - Get length of array or string
    if (op === 'len' || op === 'length') {
      const val = await this._evaluate(args[0]) as { length?: number };
      return val?.length ?? 0;
    }

    // =========================================================================
    // LAMBDA - Function definition (creates a closure)
    // =========================================================================
    if (op === 'lambda' || op === 'fn') {
      const params = args[0] as string[];
      const body = args[1];
      return {
        __type: 'lambda',
        params,
        body,
        env: { ...this._context.env },
      } as ILambdaClosure;
    }

    // =========================================================================
    // LET/LETREC - Variable binding
    // =========================================================================
    if (op === 'let' || op === 'letrec') {
      return await this._evalLet(args);
    }

    // =========================================================================
    // LET* - Sequential let
    // =========================================================================
    if (op === 'let*') {
      return await this._evalLetStar(args);
    }

    // =========================================================================
    // DEF/DEFINE - Named definitions
    // =========================================================================
    if (op === 'def' || op === 'define') {
      const name = args[0] as string;
      const value = await this._evaluate(args[1]);
      this._context.define(name, value);
      return value;
    }

    // =========================================================================
    // APPLY - Apply a function to arguments
    // =========================================================================
    if (op === 'apply') {
      const fn = await this._evaluate(args[0]);
      const fnArgs = await this._evaluate(args[1]) as unknown[];
      return await this._applyLambda(fn as ILambdaClosure, fnArgs);
    }

    // =========================================================================
    // DO/PROGN - Sequential evaluation, returns last
    // =========================================================================
    if (op === 'do' || op === 'progn') {
      let result: unknown = null;
      for (const arg of args) {
        result = await this._evaluate(arg);
      }
      return result;
    }

    // =========================================================================
    // CONDITIONALS
    // =========================================================================
    if (op === 'if') {
      return await this._evalIf(args);
    }

    if (op === 'cond') {
      return await this._evalCond(args);
    }

    if (op === 'when') {
      const condition = await this._evaluate(args[0]);
      if (condition) {
        return await this._evaluate(args[1]);
      }
      return null;
    }

    if (op === 'unless') {
      const condition = await this._evaluate(args[0]);
      if (!condition) {
        return await this._evaluate(args[1]);
      }
      return null;
    }

    if (op === 'case') {
      return await this._evalCase(args);
    }

    // =========================================================================
    // LOGICAL OPERATORS
    // =========================================================================
    if (op === 'and') {
      let result: unknown = true;
      for (const arg of args) {
        result = await this._evaluate(arg);
        if (!result) return false;
      }
      return result;
    }

    if (op === 'or') {
      for (const arg of args) {
        const result = await this._evaluate(arg);
        if (result) return result;
      }
      return false;
    }

    if (op === 'not') {
      const val = await this._evaluate(args[0]);
      return !val;
    }

    // =========================================================================
    // HIGHER-ORDER FUNCTIONS
    // =========================================================================
    if (op === 'map') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      const results = [];
      for (const item of list) {
        results.push(await this._applyLambda(fn, [item]));
      }
      return results;
    }

    if (op === 'filter') {
      const pred = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      const results = [];
      for (const item of list) {
        if (await this._applyLambda(pred, [item])) {
          results.push(item);
        }
      }
      return results;
    }

    if (op === 'reduce') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      let acc = await this._evaluate(args[1]);
      const list = await this._evaluate(args[2]) as unknown[];
      for (const item of list) {
        acc = await this._applyLambda(fn, [acc, item]);
      }
      return acc;
    }

    if (op === 'foreach' || op === 'for-each') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      for (const item of list) {
        await this._applyLambda(fn, [item]);
      }
      return null;
    }

    // COMPOSE - Compose multiple functions right-to-left
    if (op === 'compose') {
      const fns: ILambdaClosure[] = [];
      for (const arg of args) {
        fns.push(await this._evaluate(arg) as ILambdaClosure);
      }
      return {
        __type: 'lambda',
        params: ['x'],
        body: null,
        env: {},
        composed: fns,
      } as ILambdaClosure;
    }

    // PARTIAL - Partially apply arguments to a function
    if (op === 'partial') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const partialArgs: unknown[] = [];
      for (let i = 1; i < args.length; i++) {
        partialArgs.push(await this._evaluate(args[i]));
      }
      return {
        __type: 'lambda',
        params: ['...rest'],
        body: null,
        env: {},
        partial: { fn, args: partialArgs },
      } as ILambdaClosure;
    }

    // IDENTITY - Returns its argument unchanged
    if (op === 'identity') {
      return await this._evaluate(args[0]);
    }

    // CONSTANTLY - Returns a function that always returns the given value
    if (op === 'constantly') {
      const val = await this._evaluate(args[0]);
      return {
        __type: 'lambda',
        params: [],
        body: ['quote', val],
        env: { __constantly_value: val },
      } as ILambdaClosure;
    }

    // COMPLEMENT - Returns negation of predicate
    if (op === 'complement') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      return {
        __type: 'lambda',
        params: ['x'],
        body: null,
        env: {},
        complement: fn,
      } as ILambdaClosure;
    }

    // JUXT - Applies multiple functions to same args, returns array of results
    if (op === 'juxt') {
      const fns: ILambdaClosure[] = [];
      for (const arg of args) {
        fns.push(await this._evaluate(arg) as ILambdaClosure);
      }
      return {
        __type: 'lambda',
        params: ['x'],
        body: null,
        env: {},
        juxt: fns,
      } as ILambdaClosure;
    }

    // EVERY?/ALL? - Test if predicate is true for all elements
    if (op === 'every?' || op === 'all?') {
      const pred = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      for (const item of list) {
        if (!await this._applyLambda(pred, [item])) {
          return false;
        }
      }
      return true;
    }

    // SOME?/ANY? - Test if predicate is true for at least one element
    if (op === 'some?' || op === 'any?') {
      const pred = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      for (const item of list) {
        if (await this._applyLambda(pred, [item])) {
          return true;
        }
      }
      return false;
    }

    // FIND - Find first element matching predicate
    if (op === 'find') {
      const pred = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      for (const item of list) {
        if (await this._applyLambda(pred, [item])) {
          return item;
        }
      }
      return null;
    }

    // GROUP-BY - Group elements by function result
    if (op === 'group-by') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      const groups: Record<string, unknown[]> = {};
      for (const item of list) {
        const key = String(await this._applyLambda(fn, [item]));
        if (!groups[key]) groups[key] = [];
        groups[key].push(item);
      }
      return groups;
    }

    // SORT-BY - Sort by function result
    if (op === 'sort-by') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      if (!Array.isArray(list)) return list;

      // Get keys for all items
      const keyed: Array<{ item: unknown; key: unknown }> = [];
      for (const item of list) {
        keyed.push({ item, key: await this._applyLambda(fn, [item]) });
      }

      // Sort by keys
      keyed.sort((a, b) => {
        const keyA = a.key as string | number;
        const keyB = b.key as string | number;
        if (keyA < keyB) return -1;
        if (keyA > keyB) return 1;
        return 0;
      });

      return keyed.map(k => k.item);
    }

    // =========================================================================
    // VARIABLE LOOKUP AND FUNCTION CALL
    // =========================================================================
    const envVal = this._context.lookup(op);
    if (envVal !== undefined) {
      if (args.length > 0 && this._context.isLambda(envVal)) {
        const evaluatedArgs: unknown[] = [];
        for (const arg of args) {
          evaluatedArgs.push(await this._evaluate(arg));
        }
        return await this._applyLambda(envVal, evaluatedArgs);
      }
      return envVal;
    }

    // =========================================================================
    // ARITHMETIC
    // =========================================================================
    if (op === '+') return await this._evalAdd(args);
    if (op === '-') return await this._evalSub(args);
    if (op === '*') return await this._evalMul(args);
    if (op === '/') return await this._evalDiv(args);
    if (op === 'mod' || op === '%') {
      const [a, b] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return (a as number) % (b as number);
    }

    // =========================================================================
    // COMPARISON
    // =========================================================================
    if (op === '>') {
      const [a, b] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return (a as number) > (b as number);
    }
    if (op === '<') {
      const [a, b] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return (a as number) < (b as number);
    }
    if (op === '=') {
      const [a, b] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return a === b;
    }
    if (op === '>=') {
      const [a, b] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return (a as number) >= (b as number);
    }
    if (op === '<=') {
      const [a, b] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return (a as number) <= (b as number);
    }

    // =========================================================================
    // MATH FUNCTIONS
    // =========================================================================
    if (op === 'abs') {
      const val = await this._evaluate(args[0]) as number;
      return Math.abs(val);
    }
    if (op === 'min') {
      const values = await Promise.all(args.map(a => this._evaluate(a))) as number[];
      return Math.min(...values);
    }
    if (op === 'max') {
      const values = await Promise.all(args.map(a => this._evaluate(a))) as number[];
      return Math.max(...values);
    }
    if (op === 'floor') return Math.floor(await this._evaluate(args[0]) as number);
    if (op === 'ceil' || op === 'ceiling') return Math.ceil(await this._evaluate(args[0]) as number);
    if (op === 'round') return Math.round(await this._evaluate(args[0]) as number);
    if (op === 'sqrt') return Math.sqrt(await this._evaluate(args[0]) as number);
    if (op === 'pow' || op === 'expt') {
      const [base, exp] = await Promise.all(args.slice(0, 2).map(x => this._evaluate(x)));
      return Math.pow(base as number, exp as number);
    }
    if (op === 'exp') return Math.exp(await this._evaluate(args[0]) as number);
    if (op === 'log') return Math.log(await this._evaluate(args[0]) as number);
    if (op === 'log10') return Math.log10(await this._evaluate(args[0]) as number);
    if (op === 'sin') return Math.sin(await this._evaluate(args[0]) as number);
    if (op === 'cos') return Math.cos(await this._evaluate(args[0]) as number);
    if (op === 'tan') return Math.tan(await this._evaluate(args[0]) as number);
    if (op === 'random') {
      if (args.length === 0) return Math.random();
      const max = await this._evaluate(args[0]) as number;
      return Math.floor(Math.random() * max);
    }

    // =========================================================================
    // TYPE PREDICATES
    // =========================================================================
    if (op === 'null?' || op === 'nil?') {
      const val = await this._evaluate(args[0]);
      return val === null || val === undefined;
    }
    if (op === 'list?' || op === 'array?') {
      const val = await this._evaluate(args[0]);
      return Array.isArray(val);
    }
    if (op === 'number?') {
      const val = await this._evaluate(args[0]);
      return typeof val === 'number' && !isNaN(val);
    }
    if (op === 'string?') {
      const val = await this._evaluate(args[0]);
      return typeof val === 'string';
    }
    if (op === 'boolean?') {
      const val = await this._evaluate(args[0]);
      return typeof val === 'boolean';
    }
    if (op === 'function?' || op === 'procedure?' || op === 'lambda?') {
      const val = await this._evaluate(args[0]);
      return this._context.isLambda(val);
    }
    if (op === 'object?' || op === 'map?') {
      const val = await this._evaluate(args[0]);
      return val !== null && typeof val === 'object' && !Array.isArray(val);
    }
    if (op === 'empty?') {
      const val = await this._evaluate(args[0]);
      if (Array.isArray(val)) return val.length === 0;
      if (typeof val === 'string') return val.length === 0;
      if (val && typeof val === 'object') return Object.keys(val).length === 0;
      return val === null || val === undefined;
    }
    if (op === 'pair?' || op === 'cons?') {
      const val = await this._evaluate(args[0]);
      return Array.isArray(val) && val.length >= 1;
    }

    // =========================================================================
    // LIST OPERATIONS
    // =========================================================================
    if (op === 'append') {
      const lists = await Promise.all(args.map(a => this._evaluate(a))) as unknown[][];
      return lists.flat();
    }
    if (op === 'reverse') {
      const list = await this._evaluate(args[0]) as unknown[];
      return Array.isArray(list) ? [...list].reverse() : list;
    }
    if (op === 'nth') {
      const n = await this._evaluate(args[0]) as number;
      const list = await this._evaluate(args[1]) as unknown[];
      return Array.isArray(list) ? list[n] : undefined;
    }
    if (op === 'last') {
      const list = await this._evaluate(args[0]) as unknown[];
      return Array.isArray(list) && list.length > 0 ? list[list.length - 1] : null;
    }
    if (op === 'butlast' || op === 'init') {
      const list = await this._evaluate(args[0]) as unknown[];
      return Array.isArray(list) ? list.slice(0, -1) : [];
    }
    if (op === 'member' || op === 'member?' || op === 'includes?') {
      const item = await this._evaluate(args[0]);
      const list = await this._evaluate(args[1]) as unknown[];
      return Array.isArray(list) && list.includes(item);
    }
    if (op === 'range') {
      return await this._evalRange(args);
    }
    if (op === 'take') {
      const n = await this._evaluate(args[0]) as number;
      const list = await this._evaluate(args[1]) as unknown[];
      return Array.isArray(list) ? list.slice(0, n) : [];
    }
    if (op === 'drop') {
      const n = await this._evaluate(args[0]) as number;
      const list = await this._evaluate(args[1]) as unknown[];
      return Array.isArray(list) ? list.slice(n) : [];
    }
    if (op === 'sort') {
      const list = await this._evaluate(args[0]) as unknown[];
      if (!Array.isArray(list)) return list;
      return [...list].sort((a, b) => (a as number) < (b as number) ? -1 : (a as number) > (b as number) ? 1 : 0);
    }
    if (op === 'zip') {
      const lists = await Promise.all(args.map(a => this._evaluate(a))) as unknown[][];
      if (lists.length === 0) return [];
      const minLen = Math.min(...lists.map(l => Array.isArray(l) ? l.length : 0));
      const result = [];
      for (let i = 0; i < minLen; i++) {
        result.push(lists.map(l => l[i]));
      }
      return result;
    }
    if (op === 'unique' || op === 'distinct' || op === 'distinct-until-changed') {
      const list = await this._evaluate(args[0]) as unknown[];
      return Array.isArray(list) ? [...new Set(list)] : list;
    }
    if (op === 'flatten') {
      const list = await this._evaluate(args[0]) as unknown[];
      const depth = args.length > 1 ? await this._evaluate(args[1]) as number : Infinity;
      return Array.isArray(list) ? list.flat(depth) : [list];
    }
    if (op === 'index-of' || op === 'find-index') {
      const item = await this._evaluate(args[0]);
      const list = await this._evaluate(args[1]) as unknown[];
      if (!Array.isArray(list)) return -1;
      return list.indexOf(item);
    }
    if (op === 'assoc') {
      // For lists: replace element at index
      const list = await this._evaluate(args[0]) as unknown[];
      const idx = await this._evaluate(args[1]) as number;
      const val = await this._evaluate(args[2]);
      if (!Array.isArray(list)) return list;
      const result = [...list];
      result[idx] = val;
      return result;
    }
    if (op === 'repeat') {
      const n = await this._evaluate(args[0]) as number;
      const val = await this._evaluate(args[1]);
      return Array(n).fill(val);
    }
    if (op === 'partition') {
      const n = await this._evaluate(args[0]) as number;
      const list = await this._evaluate(args[1]) as unknown[];
      if (!Array.isArray(list)) return [];
      const result: unknown[][] = [];
      for (let i = 0; i < list.length; i += n) {
        result.push(list.slice(i, i + n));
      }
      return result;
    }
    if (op === 'partition-by') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const list = await this._evaluate(args[1]) as unknown[];
      if (!Array.isArray(list) || list.length === 0) return [];
      const result: unknown[][] = [];
      let currentGroup: unknown[] = [list[0]];
      let currentKey = await this._applyLambda(fn, [list[0]]);
      for (let i = 1; i < list.length; i++) {
        const key = await this._applyLambda(fn, [list[i]]);
        if (key === currentKey) {
          currentGroup.push(list[i]);
        } else {
          result.push(currentGroup);
          currentGroup = [list[i]];
          currentKey = key;
        }
      }
      result.push(currentGroup);
      return result;
    }
    if (op === 'interleave') {
      const lists = await Promise.all(args.map(a => this._evaluate(a))) as unknown[][];
      if (lists.length === 0) return [];
      const minLen = Math.min(...lists.map(l => Array.isArray(l) ? l.length : 0));
      const result: unknown[] = [];
      for (let i = 0; i < minLen; i++) {
        for (const list of lists) {
          result.push(list[i]);
        }
      }
      return result;
    }
    if (op === 'interpose') {
      const sep = await this._evaluate(args[0]);
      const list = await this._evaluate(args[1]) as unknown[];
      if (!Array.isArray(list) || list.length <= 1) return list;
      const result: unknown[] = [];
      for (let i = 0; i < list.length; i++) {
        result.push(list[i]);
        if (i < list.length - 1) result.push(sep);
      }
      return result;
    }

    // =========================================================================
    // STRING OPERATIONS
    // =========================================================================
    if (op === 'concat' || op === 'str') {
      const values = await Promise.all(args.map(a => this._evaluate(a)));
      return values.map(v => v === null || v === undefined ? '' : String(v)).join('');
    }
    if (op === 'substring' || op === 'substr') {
      const s = await this._evaluate(args[0]) as string;
      const start = await this._evaluate(args[1]) as number;
      const end = args.length > 2 ? await this._evaluate(args[2]) as number : undefined;
      return typeof s === 'string' ? s.substring(start, end) : '';
    }
    if (op === 'string-split' || op === 'split') {
      const s = await this._evaluate(args[0]) as string;
      const sep = args.length > 1 ? await this._evaluate(args[1]) as string : '';
      return typeof s === 'string' ? s.split(sep) : [];
    }
    if (op === 'string-join' || op === 'join') {
      const list = await this._evaluate(args[0]) as string[];
      const sep = args.length > 1 ? await this._evaluate(args[1]) as string : '';
      return Array.isArray(list) ? list.join(sep) : String(list);
    }
    if (op === 'string-upcase' || op === 'upcase' || op === 'upper') {
      const s = await this._evaluate(args[0]) as string;
      return typeof s === 'string' ? s.toUpperCase() : s;
    }
    if (op === 'string-downcase' || op === 'downcase' || op === 'lower') {
      const s = await this._evaluate(args[0]) as string;
      return typeof s === 'string' ? s.toLowerCase() : s;
    }
    if (op === 'string-trim' || op === 'trim') {
      const s = await this._evaluate(args[0]) as string;
      return typeof s === 'string' ? s.trim() : s;
    }
    if (op === 'replace' || op === 'string-replace') {
      const s = await this._evaluate(args[0]) as string;
      const from = await this._evaluate(args[1]) as string;
      const to = await this._evaluate(args[2]) as string;
      if (typeof s !== 'string') return s;
      return s.split(from).join(to); // Replace all occurrences
    }
    if (op === 'starts-with?' || op === 'string-starts-with?' || op === 'string-starts?') {
      const s = await this._evaluate(args[0]) as string;
      const prefix = await this._evaluate(args[1]) as string;
      return typeof s === 'string' && s.startsWith(prefix);
    }
    if (op === 'ends-with?' || op === 'string-ends-with?' || op === 'string-ends?') {
      const s = await this._evaluate(args[0]) as string;
      const suffix = await this._evaluate(args[1]) as string;
      return typeof s === 'string' && s.endsWith(suffix);
    }
    if (op === 'contains?' || op === 'string-contains?') {
      const s = await this._evaluate(args[0]) as string;
      const sub = await this._evaluate(args[1]) as string;
      return typeof s === 'string' && s.includes(sub);
    }
    if (op === 'char-at') {
      const s = await this._evaluate(args[0]) as string;
      const idx = await this._evaluate(args[1]) as number;
      return typeof s === 'string' ? s.charAt(idx) : '';
    }
    if (op === 'format') {
      // Simple format: (format "Hello {0}!" "World") -> "Hello World!"
      let template = await this._evaluate(args[0]) as string;
      for (let i = 1; i < args.length; i++) {
        const val = await this._evaluate(args[i]);
        template = template.replace(new RegExp(`\\{${i - 1}\\}`, 'g'), String(val));
      }
      return template;
    }
    if (op === 'pad-left' || op === 'string-pad-left') {
      const s = await this._evaluate(args[0]) as string;
      const len = await this._evaluate(args[1]) as number;
      const char = args.length > 2 ? await this._evaluate(args[2]) as string : ' ';
      return String(s).padStart(len, char);
    }
    if (op === 'pad-right' || op === 'string-pad-right') {
      const s = await this._evaluate(args[0]) as string;
      const len = await this._evaluate(args[1]) as number;
      const char = args.length > 2 ? await this._evaluate(args[2]) as string : ' ';
      return String(s).padEnd(len, char);
    }

    // =========================================================================
    // OBJECT OPERATIONS
    // =========================================================================
    if (op === 'hash-map' || op === 'object' || op === 'obj') {
      const result: Record<string, unknown> = {};
      for (let i = 0; i < args.length; i += 2) {
        const key = await this._evaluate(args[i]) as string;
        const val = await this._evaluate(args[i + 1]);
        result[key] = val;
      }
      return result;
    }
    if (op === 'keys') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      return obj && typeof obj === 'object' ? Object.keys(obj) : [];
    }
    if (op === 'values') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      return obj && typeof obj === 'object' ? Object.values(obj) : [];
    }
    if (op === 'entries' || op === 'pairs') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      return obj && typeof obj === 'object' ? Object.entries(obj) : [];
    }
    if (op === 'merge') {
      const objs = await Promise.all(args.map(a => this._evaluate(a)));
      return Object.assign({}, ...objs as object[]);
    }
    if (op === 'assoc-set' || op === 'set') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const key = await this._evaluate(args[1]) as string;
      const val = await this._evaluate(args[2]);
      return { ...obj, [key]: val };
    }
    if (op === 'dissoc' || op === 'remove-key') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const key = await this._evaluate(args[1]) as string;
      if (!obj || typeof obj !== 'object') return obj;
      const result = { ...obj };
      delete result[key];
      return result;
    }
    if (op === 'get-in') {
      let obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const path = await this._evaluate(args[1]) as string[];
      for (const key of path) {
        if (obj === null || obj === undefined) return null;
        obj = obj[key] as Record<string, unknown>;
      }
      return obj;
    }
    if (op === 'assoc-in' || op === 'set-in') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const path = await this._evaluate(args[1]) as string[];
      const val = await this._evaluate(args[2]);
      if (path.length === 0) return val;

      const setPath = (o: Record<string, unknown>, p: string[], v: unknown): Record<string, unknown> => {
        if (p.length === 1) {
          return { ...o, [p[0]]: v };
        }
        const [head, ...tail] = p;
        return { ...o, [head]: setPath((o[head] || {}) as Record<string, unknown>, tail, v) };
      };

      return setPath(obj || {}, path, val);
    }
    if (op === 'select-keys') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const keys = await this._evaluate(args[1]) as string[];
      if (!obj || typeof obj !== 'object') return {};
      const result: Record<string, unknown> = {};
      for (const key of keys) {
        if (key in obj) result[key] = obj[key];
      }
      return result;
    }
    if (op === 'update') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const key = await this._evaluate(args[1]) as string;
      const fn = await this._evaluate(args[2]) as ILambdaClosure;
      if (!obj || typeof obj !== 'object') return obj;
      const newVal = await this._applyLambda(fn, [obj[key]]);
      return { ...obj, [key]: newVal };
    }
    if (op === 'update-in') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const path = await this._evaluate(args[1]) as string[];
      const fn = await this._evaluate(args[2]) as ILambdaClosure;

      const updatePath = async (o: Record<string, unknown>, p: string[]): Promise<Record<string, unknown>> => {
        if (p.length === 1) {
          const newVal = await this._applyLambda(fn, [o[p[0]]]);
          return { ...o, [p[0]]: newVal };
        }
        const [head, ...tail] = p;
        return { ...o, [head]: await updatePath((o[head] || {}) as Record<string, unknown>, tail) };
      };

      return await updatePath(obj || {}, path);
    }
    if (op === 'has-key?' || op === 'contains-key?') {
      const obj = await this._evaluate(args[0]) as Record<string, unknown>;
      const key = await this._evaluate(args[1]) as string;
      return obj && typeof obj === 'object' && key in obj;
    }
    if (op === 'from-entries') {
      const entries = await this._evaluate(args[0]) as [string, unknown][];
      const result: Record<string, unknown> = {};
      for (const [k, v] of entries) {
        result[k] = v;
      }
      return result;
    }

    // =========================================================================
    // MACROS
    // =========================================================================
    if (op === 'defmacro') {
      const name = args[0] as string;
      const params = args[1] as string[];
      const body = args[2];
      this._context.defineMacro(name, params, body);
      return name;
    }

    if (op === 'macroexpand' || op === 'macroexpand-1') {
      const toExpand = args[0] as SExpr[];
      if (!Array.isArray(toExpand)) return toExpand;
      const macroName = toExpand[0] as string;
      const macro = this._context.getMacro(macroName);
      if (!macro) return toExpand;
      return this._expandMacro(macro, toExpand.slice(1));
    }

    // Check if it's a macro call
    const macro = this._context.getMacro(op);
    if (macro) {
      const expanded = this._expandMacro(macro, args);
      return await this._evaluate(expanded);
    }

    // =========================================================================
    // QUASIQUOTE
    // =========================================================================
    if (op === 'quasiquote' || op === '`') {
      return await this._processQuasiquote(args[0]);
    }

    // =========================================================================
    // GENSYM
    // =========================================================================
    if (op === 'gensym') {
      const prefix = args.length > 0 ? await this._evaluate(args[0]) : 'G';
      return `${prefix}__${Date.now()}__${Math.random().toString(36).slice(2)}`;
    }

    // =========================================================================
    // ERROR HANDLING
    // =========================================================================
    if (op === 'try') {
      try {
        return await this._evaluate(args[0]);
      } catch (err: unknown) {
        if (args.length > 1) {
          const catchExpr = args[1] as SExpr[];
          if (Array.isArray(catchExpr) && catchExpr[0] === 'catch') {
            const errVar = catchExpr[1] as string;
            const catchBody = catchExpr[2];
            const { restore } = this._context.pushScope();
            this._context.bind(errVar, (err as Error).message);
            try {
              return await this._evaluate(catchBody);
            } finally {
              restore();
            }
          }
        }
        throw err;
      }
    }
    if (op === 'throw' || op === 'error') {
      const msg = await this._evaluate(args[0]);
      throw new Error(String(msg));
    }

    // =========================================================================
    // TYPE CONVERSION
    // =========================================================================
    if (op === 'number' || op === 'parse-int' || op === 'int') {
      const val = await this._evaluate(args[0]);
      return parseInt(String(val), 10);
    }
    if (op === 'float' || op === 'parse-float') {
      const val = await this._evaluate(args[0]);
      return parseFloat(String(val));
    }
    if (op === 'string' || op === 'to-string') {
      const val = await this._evaluate(args[0]);
      return String(val);
    }
    if (op === 'boolean' || op === 'bool') {
      const val = await this._evaluate(args[0]);
      return Boolean(val);
    }
    if (op === 'json-parse') {
      const s = await this._evaluate(args[0]) as string;
      return JSON.parse(s);
    }
    if (op === 'json-stringify') {
      const val = await this._evaluate(args[0]);
      return JSON.stringify(val);
    }

    // =========================================================================
    // TIME
    // =========================================================================
    if (op === 'now' || op === 'current-time') {
      return Date.now();
    }
    if (op === 'sleep' || op === 'delay') {
      const ms = await this._evaluate(args[0]) as number;
      await new Promise(resolve => setTimeout(resolve, ms));
      return null;
    }

    // =========================================================================
    // DEBUG
    // =========================================================================
    if (op === 'print' || op === 'println') {
      const values = await Promise.all(args.map(a => this._evaluate(a)));
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log('[LISP]', ...values);
      return values.length === 1 ? values[0] : values;
    }
    if (op === 'debug') {
      const val = await this._evaluate(args[0]);
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log('[LISP DEBUG]', JSON.stringify(val, null, 2));
      return val;
    }

    // =========================================================================
    // REACTIVE STREAMS
    // These create IStream objects that are executed by IStreamRuntime
    // =========================================================================

    // STREAM / STREAM-OF - Create a stream from a topic
    if (op === 'stream' || op === 'stream-of') {
      const topic = await this._evaluate(args[0]) as string;
      return {
        __type: 'stream',
        source: topic,
        operators: [],
      } as IStream;
    }

    // MAP-STREAM - Transform each value in the stream
    if (op === 'map-stream' || op === '|map' || op === '|>map') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const stream = await this._evaluate(args[1]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('map-stream requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'map', fn } as IStreamOperator],
      };
    }

    // FILTER-STREAM - Filter values in the stream
    if (op === 'filter-stream' || op === '|filter' || op === '|>filter') {
      const pred = await this._evaluate(args[0]) as ILambdaClosure;
      const stream = await this._evaluate(args[1]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('filter-stream requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'filter', fn: pred } as IStreamOperator],
      };
    }

    // SCAN - Accumulate values over time
    if (op === 'scan' || op === '|>scan') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const initial = await this._evaluate(args[1]);
      const stream = await this._evaluate(args[2]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('scan requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'scan', fn, initial } as IStreamOperator],
      };
    }

    // DEBOUNCE - Debounce stream values
    if (op === 'debounce' || op === '|>debounce') {
      const ms = await this._evaluate(args[0]) as number;
      const stream = await this._evaluate(args[1]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('debounce requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'debounce', ms } as IStreamOperator],
      };
    }

    // THROTTLE - Throttle stream values
    if (op === 'throttle' || op === '|>throttle') {
      const ms = await this._evaluate(args[0]) as number;
      const stream = await this._evaluate(args[1]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('throttle requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'throttle', ms } as IStreamOperator],
      };
    }

    // TAKE - Take only n values from stream
    if (op === 'take-stream' || op === '|take' || op === '|>take') {
      const n = await this._evaluate(args[0]) as number;
      const stream = await this._evaluate(args[1]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('take-stream requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'take', n } as IStreamOperator],
      };
    }

    // DISTINCT - Only emit distinct values
    if (op === 'distinct-stream' || op === '|distinct' || op === '|>distinct') {
      const stream = await this._evaluate(args[0]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('distinct-stream requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'distinct' } as IStreamOperator],
      };
    }

    // TAP - Side effect without changing value
    if (op === 'tap' || op === '|>tap') {
      const fn = await this._evaluate(args[0]) as ILambdaClosure;
      const stream = await this._evaluate(args[1]) as IStream;
      if (!stream || stream.__type !== 'stream') {
        throw new Error('tap requires a stream');
      }
      return {
        ...stream,
        operators: [...stream.operators, { type: 'tap', fn } as IStreamOperator],
      };
    }

    // MERGE - Merge multiple streams
    if (op === 'merge-streams' || op === 'merge') {
      const streams: IStream[] = [];
      for (const arg of args) {
        const s = await this._evaluate(arg) as IStream;
        if (s && s.__type === 'stream') {
          streams.push(s);
        }
      }
      if (streams.length === 0) {
        throw new Error('merge requires at least one stream');
      }
      // Return a merged stream with multiple sources
      return {
        __type: 'stream',
        source: streams.map(s => s.source),
        operators: [],
        merged: streams,
      } as IStream;
    }

    // STREAM? - Check if value is a stream
    if (op === 'stream?') {
      const val = await this._evaluate(args[0]);
      return val !== null && typeof val === 'object' && (val as IStream).__type === 'stream';
    }

    // SUBSCRIBE - Activate a stream (handled by LispEval/IStreamRuntime)
    if (op === 'subscribe') {
      // Returns the stream config - actual subscription happens in LispEval
      const stream = await this._evaluate(args[0]) as IStream;
      const outputTopic = args.length > 1 ? await this._evaluate(args[1]) as string : undefined;
      return { __type: 'subscription_request', stream, outputTopic };
    }

    // UNSUBSCRIBE - Deactivate a stream subscription
    if (op === 'unsubscribe') {
      const subscriptionId = await this._evaluate(args[0]) as string;
      return { __type: 'unsubscription_request', subscriptionId };
    }

    // LIST-SUBSCRIPTIONS - List all active subscriptions
    if (op === 'list-subscriptions') {
      return { __type: 'list_subscriptions_request' };
    }

    // UNSUBSCRIBE-ALL - Deactivate all subscriptions
    if (op === 'unsubscribe-all') {
      return { __type: 'unsubscribe_all_request' };
    }

    // =========================================================================
    // PARALLEL EVALUATION
    // =========================================================================
    if (op === 'parallel') {
      // Evaluate all arguments concurrently
      const results = await Promise.all(args.map(arg => this._evaluate(arg)));
      return results;
    }

    // =========================================================================
    // THREAD-FIRST for piping
    // =========================================================================
    if (op === '->' || op === 'thread-first') {
      let result: unknown = await this._evaluate(args[0]);
      for (let i = 1; i < args.length; i++) {
        const opExpr = args[i] as SExpr[];
        if (Array.isArray(opExpr)) {
          const [opName, ...opArgs] = opExpr;
          result = await this._evaluate([opName, result as SExpr, ...opArgs]);
        }
      }
      return result;
    }

    // =========================================================================
    // MATRIX BRIDGE PRIMITIVES (require remoteHandler)
    // Security: call targets and emit/spawn permissions validated per Vision 27
    // =========================================================================

    // (call mount method params) - Remote component invocation
    if (op === 'call') {
      if (!this._remoteHandler) {
        throw new Error('call requires a remote handler');
      }
      const mount = await this._evaluate(args[0]) as string;

      // SECURITY: Check call target allowlist
      if (this._securityContext) {
        if (!isCallTargetAllowed(mount, this._securityContext)) {
          throw new Error(
            `Security violation: call to '${mount}' is not allowed in context '${this._securityContext.id}'`
          );
        }
      }

      const method = await this._evaluate(args[1]) as string;
      const params = args[2] ? await this._evaluate(args[2]) : {};
      return await this._remoteHandler.call(mount, method, params);
    }

    // (emit mount event payload) - Emit event to topic
    if (op === 'emit') {
      // SECURITY: Check emit permission
      if (this._securityContext && !this._securityContext.emitAllowed) {
        throw new Error(
          `Security violation: 'emit' is not allowed in context '${this._securityContext.id}'`
        );
      }

      if (!this._remoteHandler) {
        throw new Error('emit requires a remote handler');
      }
      const mount = await this._evaluate(args[0]) as string;
      const event = await this._evaluate(args[1]) as string;
      const payload = args[2] ? await this._evaluate(args[2]) : {};
      // emit is fire-and-forget - just log for now since we don't have transport directly
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`[Interpreter emit] ${mount}.${event}:`, payload);
      return { emitted: `${mount}.${event}` };
    }

    // (introspect mount) - Get component schema
    if (op === 'introspect') {
      if (!this._remoteHandler) {
        throw new Error('introspect requires a remote handler');
      }
      const mount = await this._evaluate(args[0]) as string;
      try {
        return await this._remoteHandler.call(mount, 'introspect', {});
      } catch {
        return null;
      }
    }

    // =========================================================================
    // REMOTE CALLS (if handler available)
    // =========================================================================
    const looksLikeRemoteMount =
      op.startsWith('./') ||
      op.startsWith('../') ||
      op.includes('/');
    if (looksLikeRemoteMount && this._remoteHandler) {
      const evaluatedArgs = await Promise.all(args.map(a => this._evaluate(a)));
      return await this._remoteHandler.call(op, 'eval', { expr: evaluatedArgs });
    }

    // =========================================================================
    // SERIALIZATION PRIMITIVES - 5-Representation Parity
    // =========================================================================
    if (op === 'to-sexpr' || op === 'to-s-expr' || op === 'schema->sexpr') {
      const schema = await this._evaluate(args[0]);
      if (!schema || typeof schema !== 'object') {
        throw new Error('to-sexpr: expected component schema object');
      }
      const { toSExpr } = await import('../../serialization');
      const normalized = this._normalizeToIComponentSchema(schema as Record<string, unknown>);
      return toSExpr(normalized);
    }

    if (op === 'to-html' || op === 'schema->html') {
      const schema = await this._evaluate(args[0]);
      if (!schema || typeof schema !== 'object') {
        throw new Error('to-html: expected component schema object');
      }
      const { toHtml } = await import('../../serialization');
      const normalized = this._normalizeToIComponentSchema(schema as Record<string, unknown>);
      return toHtml(normalized);
    }

    if (op === 'to-json' || op === 'schema->json') {
      const schema = await this._evaluate(args[0]);
      if (!schema || typeof schema !== 'object') {
        throw new Error('to-json: expected component schema object');
      }
      const { toJson } = await import('../../serialization');
      const normalized = this._normalizeToIComponentSchema(schema as Record<string, unknown>);
      return toJson(normalized);
    }

    if (op === 'to-fluent' || op === 'schema->fluent') {
      const schema = await this._evaluate(args[0]);
      if (!schema || typeof schema !== 'object') {
        throw new Error('to-fluent: expected component schema object');
      }
      const { toFluent } = await import('../../serialization');
      const normalized = this._normalizeToIComponentSchema(schema as Record<string, unknown>);
      return toFluent(normalized);
    }

    if (op === 'to-topics' || op === 'schema->topics') {
      const schema = await this._evaluate(args[0]);
      if (!schema || typeof schema !== 'object') {
        throw new Error('to-topics: expected component schema object');
      }
      const { toTopics } = await import('../../serialization');
      const normalized = this._normalizeToIComponentSchema(schema as Record<string, unknown>);
      return toTopics(normalized);
    }

    if (op === 'serialize') {
      const schema = await this._evaluate(args[0]);
      const format = await this._evaluate(args[1]) as string;
      if (!schema || typeof schema !== 'object') {
        throw new Error('serialize: expected component schema object as first argument');
      }
      if (!format || typeof format !== 'string') {
        throw new Error('serialize: expected format string as second argument');
      }
      // Validate format is a valid SerializationFormat
      const validFormats = ['html', 'fluent', 'sexpr', 'topics', 'json'] as const;
      if (!validFormats.includes(format as typeof validFormats[number])) {
        throw new Error(`serialize: invalid format '${format}'. Expected one of: ${validFormats.join(', ')}`);
      }
      const { serialize } = await import('../../serialization');
      const normalized = this._normalizeToIComponentSchema(schema as Record<string, unknown>);
      return serialize(normalized, format as typeof validFormats[number]);
    }

    throw new Error(`Unknown operator: ${op}`);
  }

  // ===========================================================================
  // Helper methods
  // ===========================================================================

  /**
   * Normalize introspect result or user-provided object to IComponentSchema format
   */
  private _normalizeToIComponentSchema(input: Record<string, unknown>): import('../../serialization').IComponentSchema {
    // Get name from either 'name' or 'componentClass' field
    const name = (input.componentClass as string) || (input.name as string) || 'Unknown';

    // Normalize accepts/emits to required format
    const normalizeParams = (params: Record<string, unknown> | undefined): Record<string, IParamSchema> => {
      if (!params) return {};
      const result: Record<string, IParamSchema> = {};
      for (const [key, value] of Object.entries(params)) {
        if (typeof value === 'object' && value !== null) {
          const p = value as Record<string, unknown>;
          result[key] = { type: (p.type as IParamSchema['type']) || 'any', optional: p.optional as boolean | undefined };
        } else if (typeof value === 'string') {
          result[key] = { type: value as IParamSchema['type'] };
        } else {
          result[key] = { type: 'any' };
        }
      }
      return result;
    };

    const normalizeMethods = (obj: Record<string, unknown> | undefined): Record<string, { params: Record<string, IParamSchema> }> => {
      if (!obj) return {};
      const result: Record<string, { params: Record<string, IParamSchema> }> = {};
      for (const [key, value] of Object.entries(obj)) {
        if (typeof value === 'object' && value !== null) {
          const m = value as Record<string, unknown>;
          result[key] = { params: normalizeParams(m.params as Record<string, unknown>) };
        } else {
          result[key] = { params: {} };
        }
      }
      return result;
    };

    const normalizeEvents = (obj: Record<string, unknown> | undefined): Record<string, { payload: Record<string, IParamSchema> }> => {
      if (!obj) return {};
      const result: Record<string, { payload: Record<string, IParamSchema> }> = {};
      for (const [key, value] of Object.entries(obj)) {
        if (typeof value === 'object' && value !== null) {
          const e = value as Record<string, unknown>;
          result[key] = { payload: normalizeParams(e.payload as Record<string, unknown>) };
        } else {
          result[key] = { payload: {} };
        }
      }
      return result;
    };

    return {
      name,
      mount: input.mount as string | undefined,
      accepts: normalizeMethods(input.accepts as Record<string, unknown>),
      emits: normalizeEvents(input.emits as Record<string, unknown>),
      children: input.children as import('../../serialization').IComponentSchema[] | undefined,
    };
  }

  private async _evalAdd(args: SExpr[]): Promise<number> {
    let sum = 0;
    for (const arg of args) {
      sum += await this._evaluate(arg) as number;
    }
    return sum;
  }

  private async _evalSub(args: SExpr[]): Promise<number> {
    if (args.length === 0) return 0;
    const first = await this._evaluate(args[0]) as number;
    if (args.length === 1) return -first;
    let result = first;
    for (let i = 1; i < args.length; i++) {
      result -= await this._evaluate(args[i]) as number;
    }
    return result;
  }

  private async _evalMul(args: SExpr[]): Promise<number> {
    let product = 1;
    for (const arg of args) {
      product *= await this._evaluate(arg) as number;
    }
    return product;
  }

  private async _evalDiv(args: SExpr[]): Promise<number> {
    if (args.length === 0) return 0;
    const first = await this._evaluate(args[0]) as number;
    if (args.length === 1) return 1 / first;
    let result = first;
    for (let i = 1; i < args.length; i++) {
      result /= await this._evaluate(args[i]) as number;
    }
    return result;
  }

  private async _evalIf(args: SExpr[]): Promise<unknown> {
    const [condExpr, thenExpr, elseExpr] = args;
    const condition = await this._evaluate(condExpr);
    if (condition) {
      return await this._evaluate(thenExpr);
    } else if (elseExpr !== undefined) {
      return await this._evaluate(elseExpr);
    }
    return null;
  }

  private async _evalCond(args: SExpr[]): Promise<unknown> {
    for (const clause of args) {
      const [test, result] = clause as SExpr[];
      if (test === 'else' || await this._evaluate(test)) {
        return await this._evaluate(result);
      }
    }
    return null;
  }

  private async _evalCase(args: SExpr[]): Promise<unknown> {
    const val = await this._evaluate(args[0]);
    for (let i = 1; i < args.length; i++) {
      const clause = args[i] as SExpr[];
      const [testVal, result] = clause;
      if (testVal === 'else' || await this._evaluate(testVal) === val) {
        return await this._evaluate(result);
      }
    }
    return null;
  }

  private async _evalLet(args: SExpr[]): Promise<unknown> {
    const rawBindings = args[0];
    const body = args[1];

    // Normalize bindings to object format
    let bindings: Record<string, SExpr>;
    if (Array.isArray(rawBindings)) {
      bindings = {};
      for (const pair of rawBindings) {
        if (Array.isArray(pair) && pair.length >= 2) {
          bindings[pair[0] as string] = pair[1];
        }
      }
    } else {
      bindings = rawBindings as unknown as Record<string, SExpr>;
    }

    const { restore } = this._context.pushScope();

    // Add placeholders first (for mutual recursion)
    for (const key of Object.keys(bindings)) {
      this._context.bind(key, undefined);
    }

    // Evaluate each binding
    const lambdas: Array<{ key: string; lambda: ILambdaClosure }> = [];
    for (const [key, val] of Object.entries(bindings)) {
      const result = await this._evaluate(val);
      this._context.bind(key, result);
      if (this._context.isLambda(result)) {
        lambdas.push({ key, lambda: result });
      }
    }

    // Patch lambdas for recursion
    for (const { lambda } of lambdas) {
      for (const [k] of Object.entries(bindings)) {
        lambda.env[k] = this._context.lookup(k);
      }
    }

    try {
      return await this._evaluate(body);
    } finally {
      restore();
    }
  }

  private async _evalLetStar(args: SExpr[]): Promise<unknown> {
    const bindings = args[0] as [string, SExpr][];
    const body = args[1];
    const { restore } = this._context.pushScope();

    try {
      for (const [key, val] of bindings) {
        this._context.bind(key, await this._evaluate(val));
      }
      return await this._evaluate(body);
    } finally {
      restore();
    }
  }

  private async _evalRange(args: SExpr[]): Promise<number[]> {
    const start = args.length > 1 ? await this._evaluate(args[0]) as number : 0;
    const end = args.length > 1 ? await this._evaluate(args[1]) as number : await this._evaluate(args[0]) as number;
    const step = args.length > 2 ? await this._evaluate(args[2]) as number : 1;
    const result = [];
    for (let i = start; step > 0 ? i < end : i > end; i += step) {
      result.push(i);
    }
    return result;
  }

  /**
   * Apply a lambda (closure) to arguments
   */
  private async _applyLambda(fn: ILambdaClosure, args: unknown[]): Promise<unknown> {
    if (!fn || fn.__type !== 'lambda') {
      throw new Error(`Not a function: ${JSON.stringify(fn)}`);
    }

    // Handle special composed functions
    if (fn.composed) {
      let result = args[0];
      for (let i = fn.composed.length - 1; i >= 0; i--) {
        result = await this._applyLambda(fn.composed[i], [result]);
      }
      return result;
    }

    // Handle partial application
    if (fn.partial) {
      const allArgs = [...fn.partial.args, ...args];
      return await this._applyLambda(fn.partial.fn, allArgs);
    }

    // Handle complement (negation)
    if (fn.complement) {
      const result = await this._applyLambda(fn.complement, args);
      return !result;
    }

    // Handle juxt (apply all fns, return array)
    if (fn.juxt) {
      const results = [];
      for (const f of fn.juxt) {
        results.push(await this._applyLambda(f, args));
      }
      return results;
    }

    const { params, body, env } = fn;

    // Bind arguments to parameters
    const bindings: Record<string, unknown> = { ...env };
    for (let i = 0; i < params.length; i++) {
      bindings[params[i]] = args[i];
    }

    // Evaluate body with extended environment
    const savedEnv = this._context.env;
    this._context.env = bindings;
    try {
      return await this._evaluate(body);
    } finally {
      this._context.env = savedEnv;
    }
  }

  /**
   * Process quasiquote - handle unquote and unquote-splicing
   */
  private async _processQuasiquote(expr: SExpr): Promise<unknown> {
    if (!Array.isArray(expr)) {
      return expr;
    }

    // Check for unquote
    if (expr.length === 2 && (expr[0] === 'unquote' || expr[0] === ',')) {
      return await this._evaluate(expr[1]);
    }

    // Process each element, handling unquote-splicing
    const result: unknown[] = [];
    for (const item of expr) {
      if (Array.isArray(item) && item.length === 2 &&
          (item[0] === 'unquote-splicing' || item[0] === ',@')) {
        const spliced = await this._evaluate(item[1]) as unknown[];
        if (Array.isArray(spliced)) {
          result.push(...spliced);
        } else {
          result.push(spliced);
        }
      } else if (Array.isArray(item) && item.length === 2 &&
                 (item[0] === 'unquote' || item[0] === ',')) {
        result.push(await this._evaluate(item[1]));
      } else if (Array.isArray(item)) {
        result.push(await this._processQuasiquote(item));
      } else {
        result.push(item);
      }
    }
    return result;
  }

  /**
   * Expand a macro - substitute arguments into body
   */
  private _expandMacro(macro: { params: string[]; body: SExpr }, args: SExpr[]): SExpr {
    const { params, body } = macro;
    const subs: Record<string, SExpr> = {};
    for (let i = 0; i < params.length; i++) {
      subs[params[i]] = args[i];
    }
    return this._substituteInExpr(body, subs);
  }

  /**
   * Substitute variables in an expression (for macro expansion)
   */
  private _substituteInExpr(expr: SExpr, subs: Record<string, SExpr>): SExpr {
    if (typeof expr === 'string') {
      return subs[expr] !== undefined ? subs[expr] : expr;
    }
    if (!Array.isArray(expr)) {
      return expr;
    }
    return expr.map(e => this._substituteInExpr(e, subs));
  }
}
