// src/lisp/vlm/VLMBootstrapNode.ts
// VLM Bootstrap Node - Minimal evaluator with join support
//
// The "last turtle" - implements McCarthy's 7 primitives plus
// essential additions for practical use. Includes Matrix bridge
// primitives for component communication.
//
// Reference: Vision/18-VIRTUAL-LISP-MACHINE.md

import { MatrixActor } from '../../core/MatrixActor';
import { RequestReply } from '../../engine/remoting/RequestReply';
import { TopicRouter } from '../../engine/messaging/TopicRouter';
import type { MxEnvelope } from '../../engine/messaging/MxEnvelope';
import type { SExpr } from '../SExpr';
import type { IAtomizedMessage } from './IAtomizedMessage';
import type { ILispSecurityContext } from '../../core/security/LispSecurityContext';
import type { IParamSchema, IComponentSchema } from '../../serialization';
import { getVLMNamespace } from './VLMRouter';
import { parseSExprSingle } from '../core/SExprParser';
import { isPrimitiveAllowed, isCallTargetAllowed } from '../../core/security/LispSecurityContext';

// Debug flag - set to true for verbose logging (EXPENSIVE - uses JSON.stringify)
const VLM_DEBUG = false;

// Dynamic component registry - stores spawned components per context
interface DynamicHandler {
  mount: string;
  handlers: Record<string, LambdaValue>;
  accepts: Record<string, { params: string }>;
  createdAt: number;
  unsubs: (() => void)[];
}

// Helper to make RPC calls
async function rpc<TReq, TRes>(
  ctx: unknown,
  targetMount: string,
  op: string,
  params: TReq
): Promise<TRes> {
  return RequestReply.execute<TReq, TRes>(
    ctx as Parameters<typeof RequestReply.execute>[0],
    targetMount,
    op,
    params,
    { timeoutMs: 30000 }
  );
}

// Auto-detect and parse S-expression strings
function autoparse(expr: SExpr | string): SExpr {
  if (typeof expr === 'string' && expr.trim().startsWith('(')) {
    return parseSExprSingle(expr);
  }
  return expr;
}

/**
 * VLMBootstrapNode - Minimal evaluator that reports to join coordinator
 *
 * Features:
 * - McCarthy's 7 primitives (quote, atom, eq, car, cdr, cons, cond)
 * - Essential additions (null?, pair?, list, number?, boolean?, etc.)
 * - Matrix bridge primitives (call, emit, introspect, spawn, destroy)
 * - Higher-order functions (map, filter, reduce)
 * - Arithmetic and comparison operators
 *
 * Mount: {namespace}.vlm-node-{id}
 */
export class VLMBootstrapNode extends MatrixActor {
  private _nodeId!: string;
  private _heartbeatInterval?: ReturnType<typeof setInterval>;
  protected _ns!: ReturnType<typeof getVLMNamespace>;
  private _dynamicRegistry: Map<string, DynamicHandler> = new Map();

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

  static accepts = {
    eval: { expr: 'any' },
    evalAtomized: {
      expr: 'any',
      eval: 'any?',
      continuation: 'string?',
      join: 'object?',
      trace: 'object?',
    },
  };

  static emits = {
    evaluated: { nodeId: 'string', expr: 'any', result: 'any', durationMs: 'number' },
    evalError: { nodeId: 'string', expr: 'any', error: 'string' },
  };

  protected async onBootstrap(): Promise<void> {
    this._ns = getVLMNamespace(this._context!.mount);
    this._nodeId = this._context!.mount.split('.').pop() || 'unknown';
    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log(`[VLMBootstrapNode] Node ${this._nodeId} initialized at ${this._context!.mount}`);

    // Register with router
    await rpc(this._context!, this._ns.router, 'register', {
      nodeId: this._nodeId,
      mount: this._context!.mount,
    });

    // Start heartbeat (30s interval - router considers unhealthy after 30s without heartbeat)
    this._heartbeatInterval = setInterval(() => {
      rpc(this._context!, this._ns.router, 'heartbeat', {
        nodeId: this._nodeId,
        load: 0,
      }).catch(() => {});
    }, 30000);
  }

  onDispose(): void {
    if (this._heartbeatInterval) {
      clearInterval(this._heartbeatInterval);
    }
    rpc(this._context!, this._ns.router, 'unregister', {
      nodeId: this._nodeId,
    }).catch(() => {});
  }

  async onEval(params: { expr: SExpr | string; securityContext?: ILispSecurityContext }): Promise<unknown> {
    const expr = autoparse(params.expr);
    // Reset eval start time for timeout enforcement
    this._evalStartTime = Date.now();
    return this._bootstrapEvalAsync(expr, {}, params.securityContext);
  }

  async onEvalAtomized(params: IAtomizedMessage): Promise<unknown> {
    const { expr, eval: travelingEval, join, trace, securityContext } = params;
    const startTime = performance.now();

    // Reset eval start time for timeout enforcement
    this._evalStartTime = Date.now();

    // Log proof of traveling interpreter (EXPENSIVE - only in debug mode)
    if (VLM_DEBUG) {
      const evalSize = travelingEval ? JSON.stringify(travelingEval).length : 0;
      const secCtxId = securityContext?.id ?? 'none';
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`[VLM PROOF] Node ${this._nodeId}: Expr=${JSON.stringify(expr)}, Interpreter=${evalSize} chars, SecurityContext=${secCtxId}`);
    }

    try {
      let result: unknown;
      if (travelingEval) {
        result = await this._bootstrapEvalAsync(travelingEval, { e: expr }, securityContext);
      } else {
        result = await this._bootstrapEvalAsync(expr, {}, securityContext);
      }

      const durationMs = performance.now() - startTime;
      if (VLM_DEBUG) {
        // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
        console.log(`[VLM RESULT] Node ${this._nodeId}: ${JSON.stringify(expr)} = ${JSON.stringify(result)} (${durationMs.toFixed(1)}ms)`);
      }

      this.emit('evaluated', { nodeId: this._nodeId, expr, result, durationMs });

      // Report to join coordinator if this was an atomized child
      if (join) {
        await rpc(this._context!, this._ns.join, 'slotResult', {
          joinId: join.id,
          slot: join.slot,
          value: result,
        });
      }

      return result;
    } catch (error) {
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.error(`[VLMBootstrapNode:${this._nodeId}] Error: ${error}`);
      this.emit('evalError', { nodeId: this._nodeId, expr, error: String(error) });
      throw error;
    }
  }

  // =========================================================================
  // Bootstrap Evaluator - The "last turtle"
  //
  // Mark Miller (Capability Security): Security context travels with computation.
  // The securityContext parameter enables primitive-level enforcement.
  // =========================================================================

  private async _bootstrapEvalAsync(
    expr: SExpr,
    env: Record<string, unknown>,
    securityContext?: ILispSecurityContext
  ): Promise<unknown> {
    // ═══════════════════════════════════════════════════════════════════════
    // SECURITY: Timeout enforcement
    // ═══════════════════════════════════════════════════════════════════════
    if (securityContext?.maxExecutionMs) {
      const elapsed = Date.now() - this._evalStartTime;
      if (elapsed > securityContext.maxExecutionMs) {
        throw new Error(
          `[VLM Security] Execution time (${elapsed}ms) exceeds limit (${securityContext.maxExecutionMs}ms) in context '${securityContext.id}'`
        );
      }
    }

    // Self-evaluating
    if (typeof expr === 'number' || typeof expr === 'boolean' || expr === null) {
      return expr;
    }

    // Variable lookup
    if (typeof expr === 'string') {
      if (expr in env) return env[expr];
      return expr;
    }

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

    const [op, ...args] = expr;

    // ═══════════════════════════════════════════════════════════════════════
    // SECURITY: Primitive allowlist/blocklist check
    // ═══════════════════════════════════════════════════════════════════════
    if (typeof op === 'string' && securityContext) {
      if (!isPrimitiveAllowed(op, securityContext)) {
        throw new Error(
          `[VLM Security] Primitive '${op}' is not allowed in context '${securityContext.id}'`
        );
      }
    }

    // Handle immediate lambda application
    if (Array.isArray(op) && op[0] === 'lambda') {
      const fn = await this._bootstrapEvalAsync(op, env, securityContext);
      const evaluatedArgs = await Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext)));
      return this._applyFnAsync(fn as LambdaValue, evaluatedArgs, securityContext);
    }

    if (typeof op !== 'string') {
      throw new Error(`Bootstrap: invalid op ${JSON.stringify(op)}`);
    }

    // =========================================================================
    // MATRIX BRIDGE PRIMITIVES
    // Mark Miller (Capability Security): These are the enforcement points.
    // =========================================================================

    if (op === 'call') {
      const mount = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const method = await this._bootstrapEvalAsync(args[1], env, securityContext) as string;
      const params = args[2] ? await this._bootstrapEvalAsync(args[2], env, securityContext) : {};

      // ═══════════════════════════════════════════════════════════════════════
      // SECURITY: Call target verification
      // ═══════════════════════════════════════════════════════════════════════
      if (securityContext) {
        if (!isCallTargetAllowed(mount, securityContext)) {
          throw new Error(
            `[VLM Security] Call to '${mount}' is not allowed in context '${securityContext.id}'`
          );
        }
      }

      return rpc(this._context!, mount, method, params);
    }

    if (op === 'emit') {
      // ═══════════════════════════════════════════════════════════════════════
      // SECURITY: Emit permission check
      // ═══════════════════════════════════════════════════════════════════════
      if (securityContext && !securityContext.emitAllowed) {
        throw new Error(
          `[VLM Security] 'emit' is not allowed in context '${securityContext.id}'`
        );
      }

      const mount = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const event = await this._bootstrapEvalAsync(args[1], env, securityContext) as string;
      const payload = args[2] ? await this._bootstrapEvalAsync(args[2], env, securityContext) : {};
      const eventsTopic = TopicRouter.events(mount);
      const envelope: MxEnvelope = { op: event, payload: payload ?? {}, from: mount };
      this._context!.transport.publish(eventsTopic, envelope);
      return { emitted: eventsTopic };
    }

    if (op === 'introspect') {
      const mount = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      try {
        return await rpc(this._context!, mount, 'introspect', {});
      } catch {
        return null;
      }
    }

    // SERIALIZATION PRIMITIVES - 5-Representation Parity
    // These allow viewing components in different formats
    if (op === 'to-sexpr' || op === 'to-s-expr' || op === 'schema->sexpr') {
      const schema = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const { toSExpr } = await import('../../serialization');
      const normalized = this._normalizeToComponentSchema(schema as Record<string, unknown>);
      return toSExpr(normalized);
    }

    if (op === 'to-html' || op === 'schema->html') {
      const schema = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const { toHtml } = await import('../../serialization');
      const normalized = this._normalizeToComponentSchema(schema as Record<string, unknown>);
      return toHtml(normalized);
    }

    if (op === 'to-json' || op === 'schema->json') {
      const schema = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const { toJson } = await import('../../serialization');
      const normalized = this._normalizeToComponentSchema(schema as Record<string, unknown>);
      return toJson(normalized);
    }

    if (op === 'to-fluent' || op === 'schema->fluent') {
      const schema = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const { toFluent } = await import('../../serialization');
      const normalized = this._normalizeToComponentSchema(schema as Record<string, unknown>);
      return toFluent(normalized);
    }

    if (op === 'to-topics' || op === 'schema->topics') {
      const schema = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const { toTopics } = await import('../../serialization');
      const normalized = this._normalizeToComponentSchema(schema as Record<string, unknown>);
      return toTopics(normalized);
    }

    if (op === 'serialize') {
      const schema = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const format = await this._bootstrapEvalAsync(args[1], env, securityContext) as string;
      const { serialize } = await import('../../serialization');
      const normalized = this._normalizeToComponentSchema(schema as Record<string, unknown>);
      return serialize(normalized, format as 'sexpr' | 'html' | 'json' | 'fluent' | 'topics');
    }

    // =========================================================================
    // Vision 52: CROSS-DOMAIN EVAL - eval-at primitive
    // =========================================================================
    // (eval-at domain mount expr)
    // Routes expression to the specified LISP domain via Matrix transport.
    //
    // Domains:
    //   "vlm" - Route to VLM for parallel distributed eval
    //   "shadowlisp" - Route to ShadowLisp for DOM operations
    //   "fold" - Route to FoldVLM for filesystem operations
    //
    // Example: (eval-at "shadowlisp" "app.sidebar" '(reparent "widget" "header"))
    if (op === 'eval-at') {
      const domain = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const mount = await this._bootstrapEvalAsync(args[1], env, securityContext) as string;
      const expr = await this._bootstrapEvalAsync(args[2], env, securityContext);

      // Validate domain
      const validDomains = ['vlm', 'shadowlisp', 'fold'];
      if (!validDomains.includes(domain)) {
        throw new Error(
          `[VLM eval-at] Invalid domain '${domain}'. Valid domains: ${validDomains.join(', ')}`
        );
      }

      // Security check: verify call target is allowed
      if (securityContext) {
        if (!isCallTargetAllowed(mount, securityContext)) {
          throw new Error(
            `[VLM Security] eval-at to '${mount}' is not allowed in context '${securityContext.id}'`
          );
        }
      }

      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(
        `%c[VLM eval-at] ${domain} → ${mount}`,
        'color: #00bcd4; font-weight: bold',
        expr
      );

      // Route to target via Matrix transport
      // Uses request/reply pattern for synchronous result
      return rpc(this._context!, mount, 'eval', {
        expr,
        type: domain
      });
    }

    // (spawn name handlers) - Create a dynamic Matrix component
    // Example: (spawn "my-agent"
    //            (obj "greet" (lambda (p) (concat "Hello, " (get p "name") "!"))
    //                 "add"   (lambda (p) (+ (get p "a") (get p "b")))))
    // Returns: { mount, handlers: [...], createdAt }
    if (op === 'spawn') {
      // ═══════════════════════════════════════════════════════════════════════
      // SECURITY: Spawn permission check
      // ═══════════════════════════════════════════════════════════════════════
      if (securityContext && !securityContext.spawnAllowed) {
        throw new Error(
          `[VLM Security] 'spawn' is not allowed in context '${securityContext.id}'`
        );
      }

      const requestedName = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const handlers = await this._bootstrapEvalAsync(args[1], env, securityContext) as Record<string, LambdaValue>;

      // Validate and namespace the mount
      const parentMount = this._context!.mount;
      let finalMount: string;
      if (requestedName.startsWith('')) {
        // Full mount provided - validate it's under our namespace
        const validPrefix = `${parentMount}.spawned.`;
        if (!requestedName.startsWith(validPrefix)) {
          throw new Error(`Spawn mount must be under ${validPrefix}, got: ${requestedName}`);
        }
        finalMount = requestedName;
      } else {
        // Short name - auto-namespace under parent.spawned.{name}
        finalMount = `${parentMount}.spawned.${requestedName}`;
      }

      // Check for conflicts
      if (this._dynamicRegistry.has(finalMount)) {
        throw new Error(`Component already exists at ${finalMount}`);
      }

      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`%c[VLM SPAWN] Creating dynamic component: ${finalMount}`, 'color: #00e676; font-weight: bold');
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`  Handlers: ${Object.keys(handlers).join(', ')}`);

      // Build accepts spec from handlers
      const accepts: Record<string, { params: string }> = {};
      for (const name of Object.keys(handlers)) {
        accepts[name] = { params: 'any' };
      }

      // Bind handlers to transport via RequestReply.bind
      const unsubs: (() => void)[] = [];
      const HANDLER_TIMEOUT_MS = 5000;

      for (const [handlerName, lambdaFn] of Object.entries(handlers)) {
        const unsub = RequestReply.bind(
          this._context!,
          finalMount,
          handlerName,
          async (params: unknown) => {
            return Promise.race([
              this._applyFnAsync(lambdaFn, [params], securityContext),
              new Promise((_, reject) =>
                setTimeout(() => reject(new Error(`Handler '${handlerName}' timeout after ${HANDLER_TIMEOUT_MS}ms`)), HANDLER_TIMEOUT_MS)
              )
            ]);
          }
        );
        unsubs.push(unsub);
      }

      // CRITICAL: Bind introspect handler so (introspect mount) works on spawned components!
      // Without this, Factory/Self-Build examples timeout when calling introspect.
      const introspectUnsub = RequestReply.bind(
        this._context!,
        finalMount,
        'introspect',
        async () => ({
          mount: finalMount,
          componentClass: 'DynamicComponent',
          handlers: Object.keys(handlers),
          accepts: Object.keys(accepts),
          emits: [],
          createdAt: Date.now(),
          spawnedBy: this._context!.mount,
        })
      );
      unsubs.push(introspectUnsub);

      // Emit lifecycle.available via transport
      const transport = this._context!.transport;
      transport.publish(TopicRouter.events(finalMount), {
        op: 'lifecycle.available',
        payload: { name: requestedName, tag: 'dynamic' },
        from: finalMount,
      } as MxEnvelope);

      const component: DynamicHandler = {
        mount: finalMount,
        handlers,
        accepts,
        createdAt: Date.now(),
        unsubs,
      };

      this._dynamicRegistry.set(finalMount, component);

      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`  ✅ Component spawned ON TRANSPORT! Can now call: (call "${finalMount}" "handlerName" params)`);
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`  📋 Available handlers: ${Object.keys(handlers).join(', ')}`);

      return {
        mount: finalMount,
        handlers: Object.keys(handlers),
        createdAt: component.createdAt,
      };
    }

    // (list-spawned) - List all dynamically created components
    if (op === 'list-spawned') {
      const list = [];
      for (const [mount, comp] of this._dynamicRegistry) {
        list.push({
          mount,
          handlers: Object.keys(comp.handlers),
          createdAt: comp.createdAt,
        });
      }
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`%c[VLM LIST-SPAWNED] ${list.length} dynamic components`, 'color: #00e676');
      return list;
    }

    // (destroy mount) - Remove a dynamic component
    if (op === 'destroy') {
      const mount = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const component = this._dynamicRegistry.get(mount);

      if (!component) {
        // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
        console.warn(`[VLM DESTROY] Component not found: ${mount}`);
        return { destroyed: false, error: 'not found' };
      }

      // Unsubscribe all handlers
      for (const unsub of component.unsubs) {
        unsub();
      }

      // Remove from registry
      this._dynamicRegistry.delete(mount);

      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log(`%c[VLM DESTROY] Component destroyed: ${mount}`, 'color: #ff5722');
      return { destroyed: true, mount };
    }

    // =========================================================================
    // OBJECT OPERATIONS
    // =========================================================================

    if (op === 'get') {
      const obj = await this._bootstrapEvalAsync(args[0], env, securityContext) as Record<string, unknown>;
      const key = await this._bootstrapEvalAsync(args[1], env, securityContext) as string;
      return obj?.[key];
    }

    if (op === 'keys') {
      const obj = await this._bootstrapEvalAsync(args[0], env, securityContext);
      if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
        return Object.keys(obj);
      }
      return [];
    }

    if (op === 'obj') {
      const result: Record<string, unknown> = {};
      for (let i = 0; i < args.length; i += 2) {
        const key = await this._bootstrapEvalAsync(args[i], env, securityContext) as string;
        const val = await this._bootstrapEvalAsync(args[i + 1], env, securityContext);
        result[key] = val;
      }
      return result;
    }

    if (op === 'log') {
      const values = await Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext)));
      // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
      console.log('[VLM LOG]', ...values);
      return values[values.length - 1];
    }

    // =========================================================================
    // LIST UTILITY OPERATIONS
    // =========================================================================

    // (length list) - Get list length
    if (op === 'length') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return Array.isArray(list) ? list.length : 0;
    }

    // (nth n list) - Get nth element (0-indexed)
    if (op === 'nth') {
      const n = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];
      return Array.isArray(list) && n < list.length ? list[n] : null;
    }

    // (append list1 list2) - Concatenate lists
    if (op === 'append') {
      const list1 = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const list2 = await this._bootstrapEvalAsync(args[1], env, securityContext);
      const a = Array.isArray(list1) ? list1 : [];
      const b = Array.isArray(list2) ? list2 : [];
      return [...a, ...b];
    }

    // (sum list) - Sum numbers in list
    if (op === 'sum') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext) as number[];
      if (!Array.isArray(list)) return 0;
      return list.reduce((a, b) => a + (typeof b === 'number' ? b : 0), 0);
    }

    // (max list) - Maximum in list
    if (op === 'max') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext) as number[];
      if (!Array.isArray(list) || list.length === 0) return null;
      return Math.max(...list.filter(x => typeof x === 'number'));
    }

    // (min list) - Minimum in list
    if (op === 'min') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext) as number[];
      if (!Array.isArray(list) || list.length === 0) return null;
      return Math.min(...list.filter(x => typeof x === 'number'));
    }

    // (sort-by fn list) - Sort list by key function
    if (op === 'sort-by') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext) as LambdaValue;
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];
      if (!Array.isArray(list)) return [];

      const keyed = await Promise.all(list.map(async item => ({
        item,
        key: await this._applyFnAsync(fn, [item], securityContext)
      })));

      keyed.sort((a, b) => (b.key as number) - (a.key as number));
      return keyed.map(x => x.item);
    }

    // (take n list) - Take first n items
    if (op === 'take') {
      const n = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];
      return Array.isArray(list) ? list.slice(0, n) : [];
    }

    // (pluck key list) - Extract property from each object in list
    if (op === 'pluck') {
      const key = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as Record<string, unknown>[];
      if (!Array.isArray(list)) return [];
      return list.map(item => item && typeof item === 'object' ? item[key] : null);
    }

    // (group-by fn list) - Group items by key function result
    if (op === 'group-by') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext) as LambdaValue;
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];
      if (!Array.isArray(list)) return {};

      const groups: Record<string, unknown[]> = {};
      for (const item of list) {
        const key = String(await this._applyFnAsync(fn, [item], securityContext));
        if (!groups[key]) groups[key] = [];
        groups[key].push(item);
      }
      return groups;
    }

    // (find fn list) - Find first matching item
    if (op === 'find') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext) as LambdaValue;
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];
      if (!Array.isArray(list)) return null;
      for (const item of list) {
        if (await this._applyFnAsync(fn, [item], securityContext)) return item;
      }
      return null;
    }

    // (format template ...args) - Simple string formatting
    if (op === 'format') {
      const template = await this._bootstrapEvalAsync(args[0], env, securityContext) as string;
      const values = await Promise.all(args.slice(1).map(a => this._bootstrapEvalAsync(a, env, securityContext)));
      let result = String(template);
      values.forEach((v, i) => {
        result = result.replace(`{${i}}`, String(v));
      });
      return result;
    }

    // =========================================================================
    // STANDARD LISP PRIMITIVES
    // =========================================================================

    if (op === 'quote') return args[0];

    if (op === 'car') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return Array.isArray(list) ? list[0] : null;
    }
    if (op === 'cdr') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return Array.isArray(list) ? list.slice(1) : [];
    }
    if (op === 'cadr') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext) as unknown[];
      return Array.isArray(list) && list.length > 1 ? list[1] : null;
    }
    if (op === 'caddr') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext) as unknown[];
      return Array.isArray(list) && list.length > 2 ? list[2] : null;
    }
    if (op === 'cadddr') {
      const list = await this._bootstrapEvalAsync(args[0], env, securityContext) as unknown[];
      return Array.isArray(list) && list.length > 3 ? list[3] : null;
    }

    if (op === 'cons') {
      const head = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const tail = await this._bootstrapEvalAsync(args[1], env, securityContext);
      return [head, ...(Array.isArray(tail) ? tail : [tail])];
    }
    if (op === 'list') {
      return Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext)));
    }

    // Type checks
    if (op === 'atom?' || op === 'atom') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return !Array.isArray(val);
    }
    if (op === 'pair?') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return Array.isArray(val) && val.length > 0;
    }
    if (op === 'null?') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return val === null || (Array.isArray(val) && val.length === 0);
    }
    if (op === 'number?') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return typeof val === 'number';
    }
    if (op === 'boolean?') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return typeof val === 'boolean';
    }
    if (op === 'string?') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return typeof val === 'string';
    }
    if (op === 'object?') {
      const val = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return val !== null && typeof val === 'object' && !Array.isArray(val);
    }

    if (op === 'eq?' || op === 'eq') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext);
      return a === b;
    }

    // Conditionals
    if (op === 'cond') {
      for (const clause of args) {
        const [test, result] = clause as SExpr[];
        if (test === 'else' || await this._bootstrapEvalAsync(test, env, securityContext)) {
          return this._bootstrapEvalAsync(result, env, securityContext);
        }
      }
      return null;
    }
    if (op === 'if') {
      const test = await this._bootstrapEvalAsync(args[0], env, securityContext);
      return test
        ? this._bootstrapEvalAsync(args[1], env, securityContext)
        : this._bootstrapEvalAsync(args[2] ?? null, env);
    }

    // Boolean ops
    if (op === 'and') {
      for (const arg of args) {
        if (!await this._bootstrapEvalAsync(arg, env, securityContext)) return false;
      }
      return true;
    }
    if (op === 'or') {
      for (const arg of args) {
        const val = await this._bootstrapEvalAsync(arg, env, securityContext);
        if (val) return val;
      }
      return false;
    }
    if (op === 'not') {
      return !await this._bootstrapEvalAsync(args[0], env, securityContext);
    }

    // Arithmetic
    if (op === '+') {
      let sum = 0;
      for (const a of args) {
        sum += await this._bootstrapEvalAsync(a, env) as number;
      }
      return sum;
    }
    if (op === '-') {
      const vals = await Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext))) as number[];
      return vals.length === 1 ? -vals[0] : vals.reduce((a, b) => a - b);
    }
    if (op === '*') {
      let product = 1;
      for (const a of args) {
        product *= await this._bootstrapEvalAsync(a, env) as number;
      }
      return product;
    }
    if (op === '/') {
      const vals = await Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext))) as number[];
      return vals.reduce((a, b) => a / b);
    }
    if (op === '<') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext) as number;
      return a < b;
    }
    if (op === '>') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext) as number;
      return a > b;
    }
    if (op === '=') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext);
      return a === b;
    }
    if (op === '<=') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext) as number;
      return a <= b;
    }
    if (op === '>=') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext) as number;
      return a >= b;
    }
    if (op === '%') {
      const a = await this._bootstrapEvalAsync(args[0], env, securityContext) as number;
      const b = await this._bootstrapEvalAsync(args[1], env, securityContext) as number;
      return a % b;
    }

    // Lambda
    if (op === 'lambda') {
      return {
        __type: 'lambda',
        params: args[0],
        body: args[1],
        env: this._serializableEnv(env)
      };
    }

    // Let binding
    if (op === 'let') {
      const bindings = args[0] as SExpr[];
      const body = args[1];
      const newEnv = { ...env };

      for (const binding of bindings) {
        const [name, valueExpr] = binding as SExpr[];
        newEnv[name as string] = await this._bootstrapEvalAsync(valueExpr, newEnv, securityContext);
      }

      return this._bootstrapEvalAsync(body, newEnv, securityContext);
    }

    // Apply
    if (op === 'apply') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const fnArgs = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];

      if (typeof fn === 'string') {
        return this._bootstrapEvalAsync([fn, ...fnArgs] as SExpr, env, securityContext);
      }

      return this._applyFnAsync(fn as LambdaValue, fnArgs, securityContext);
    }

    // Map
    if (op === 'map') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];

      if (!Array.isArray(list)) return [];

      const results = [];
      for (const item of list) {
        results.push(await this._applyFnAsync(fn as LambdaValue, [item], securityContext));
      }
      return results;
    }

    // Filter
    if (op === 'filter') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext);
      const list = await this._bootstrapEvalAsync(args[1], env, securityContext) as unknown[];
      if (!Array.isArray(list)) return [];

      const results = [];
      for (const item of list) {
        if (await this._applyFnAsync(fn as LambdaValue, [item], securityContext)) {
          results.push(item);
        }
      }
      return results;
    }

    // Reduce
    if (op === 'reduce') {
      const fn = await this._bootstrapEvalAsync(args[0], env, securityContext);
      let acc = await this._bootstrapEvalAsync(args[1], env, securityContext);
      const list = await this._bootstrapEvalAsync(args[2], env, securityContext) as unknown[];
      if (!Array.isArray(list)) return acc;
      for (const item of list) {
        acc = await this._applyFnAsync(fn as LambdaValue, [acc, item], securityContext);
      }
      return acc;
    }

    // Letrec
    if (op === 'letrec') {
      const bindings = args[0] as SExpr[];
      const body = args[1];
      const newEnv = { ...env };

      // First pass: placeholders
      for (const binding of bindings) {
        const [name] = binding as SExpr[];
        newEnv[name as string] = null;
      }

      // Second pass: evaluate and update closures
      for (const binding of bindings) {
        const [name, valueExpr] = binding as SExpr[];
        const value = await this._bootstrapEvalAsync(valueExpr, newEnv, securityContext) as LambdaValue;
        newEnv[name as string] = value;
        if (value && (value as LambdaValue).__type === 'lambda') {
          (value as LambdaValue).env = newEnv;
        }
      }

      return this._bootstrapEvalAsync(body, newEnv, securityContext);
    }

    // Begin
    if (op === 'begin') {
      let result: unknown = null;
      for (const arg of args) {
        result = await this._bootstrapEvalAsync(arg, env, securityContext);
      }
      return result;
    }

    // Concat
    if (op === 'concat') {
      const parts = await Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext)));
      return parts.join('');
    }

    // Function call from env
    const envVal = env[op] as LambdaValue | undefined;
    if (envVal && envVal.__type === 'lambda') {
      const evaluatedArgs = await Promise.all(args.map(a => this._bootstrapEvalAsync(a, env, securityContext)));
      return this._applyFnAsync(envVal, evaluatedArgs, securityContext);
    }

    throw new Error(`Bootstrap: unknown operation '${op}'`);
  }

  private async _applyFnAsync(
    fn: LambdaValue,
    args: unknown[],
    securityContext?: ILispSecurityContext
  ): Promise<unknown> {
    if (!fn || fn.__type !== 'lambda') {
      throw new Error(`Bootstrap: cannot apply non-function: ${JSON.stringify(fn)}`);
    }
    const newEnv = { ...fn.env };
    const params = fn.params as string[];
    params.forEach((p, i) => { newEnv[p] = args[i]; });
    return this._bootstrapEvalAsync(fn.body, newEnv, securityContext);
  }

  private _isSerializable(v: unknown, depth = 0): boolean {
    if (depth > 100) return false;
    if (v === null || v === undefined) return true;
    if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return true;
    if (Array.isArray(v)) return v.every(x => this._isSerializable(x, depth + 1));
    if (typeof v === 'object') {
      const obj = v as Record<string, unknown>;
      if (obj.__type === 'lambda' && 'env' in obj) {
        return this._isSerializable(obj.env, depth + 1);
      }
      return Object.values(obj).every(x => this._isSerializable(x, depth + 1));
    }
    return false;
  }

  private _serializableEnv(env: Record<string, unknown>): Record<string, unknown> {
    const result: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(env)) {
      if (this._isSerializable(v)) {
        result[k] = v;
      } else {
        // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
        console.warn(`[VLM] Dropping non-serializable env binding: ${k}`);
      }
    }
    return result;
  }

  /**
   * Normalize introspect result to ComponentSchema format for serialization
   */
  private _normalizeToComponentSchema(schema: Record<string, unknown>): IComponentSchema {
    const name = (schema.name || schema.componentClass || 'Component') as string;
    const mount = (schema.mount || '') as string;

    // Handle various introspect formats
    const rawAccepts = schema.accepts || schema.handlers || {};
    const rawEmits = schema.emits || {};

    // Normalize accepts
    const accepts: Record<string, { params: Record<string, IParamSchema> }> = {};
    if (Array.isArray(rawAccepts)) {
      for (const name of rawAccepts) {
        accepts[name as string] = { params: {} };
      }
    } else if (typeof rawAccepts === 'object') {
      for (const [methodName, methodDef] of Object.entries(rawAccepts as Record<string, unknown>)) {
        if (typeof methodDef === 'object' && methodDef !== null) {
          const def = methodDef as Record<string, unknown>;
          accepts[methodName] = {
            params: (def.params || {}) as Record<string, IParamSchema>,
          };
        } else {
          accepts[methodName] = { params: {} };
        }
      }
    }

    // Normalize emits
    const emits: Record<string, { payload: Record<string, IParamSchema> }> = {};
    if (Array.isArray(rawEmits)) {
      for (const name of rawEmits) {
        emits[name as string] = { payload: {} };
      }
    } else if (typeof rawEmits === 'object') {
      for (const [eventName, eventDef] of Object.entries(rawEmits as Record<string, unknown>)) {
        if (typeof eventDef === 'object' && eventDef !== null) {
          const def = eventDef as Record<string, unknown>;
          emits[eventName] = {
            payload: (def.payload || {}) as Record<string, IParamSchema>,
          };
        } else {
          emits[eventName] = { payload: {} };
        }
      }
    }

    return { name, mount, accepts, emits };
  }
}

// Internal type for lambda closures
interface LambdaValue {
  __type: 'lambda';
  params: string[];
  body: SExpr;
  env: Record<string, unknown>;
}
