// src/lisp/components/LispEval.ts
// LispEval MatrixActor - JSON Wire Format Evaluator
//
// Evaluates LISP expressions in JSON wire format: ["+", 1, 2, 3]
// This is the main evaluator component that bridges LISP to Matrix.
//
// Features:
// - Core LISP evaluation via Interpreter
// - Remote call via RequestReply (transport-based)
// - Stream runtime integration
//
// ═══════════════════════════════════════════════════════════════════════════
// ✅ UI CONTROL INVERSION: All calls go through transport
// ═══════════════════════════════════════════════════════════════════════════
//
// Previously had _localServices pattern that bypassed transport for "optimization".
// This violated the architectural rule that all cross-component calls use transport.
// Now uses RequestReply for ALL calls - transport can optimize local calls internally.
//
// Reference: Vision/15-LISP-ON-PROTOCOL.md

import { MatrixActor } from '../../core/MatrixActor';
import { RequestReply } from '../../engine/remoting/RequestReply';
import type { SExpr } from '../SExpr';
import type { IStream } from '../IStream';
import type { IActiveSubscription } from '../IActiveSubscription';
import type { ILambdaClosure } from '../ILambdaClosure';
import { Interpreter } from '../core/Interpreter';
import type { IRemoteCallHandler } from '../core/Interpreter';
import { StreamRuntime } from '../core/StreamRuntime';
import { getLispConfig } from '../config';

/**
 * LispEval - Evaluates LISP expressions in JSON wire format
 *
 * The wire format is simple: LISP as JSON arrays
 *   ["+", 1, 2, 3] → 6
 *   ["call", "service", "method", {...params}]
 *
 * Mount: {namespace}.lisp
 *
 * @example
 * await callComponent('flowpad.lisp', 'eval', {
 *   expr: ["+", 1, 2, 3]
 * })
 * // → 6
 */
export class LispEval extends MatrixActor {
  static accepts = {
    eval: { expr: 'any' },
    define: { name: 'string', value: 'any' },
    getEnv: {},
    // Service registration for local call optimization hints
    registerService: { mount: 'string' },
    listServices: {},
    // Stream operations
    subscribe: { stream: 'any', outputTopic: 'string?' },
    unsubscribe: { subscriptionId: 'string' },
    listSubscriptions: {},
  };

  static emits = {
    evaluated: { expr: 'any', result: 'any', durationMs: 'number' },
    evalError: { expr: 'any', error: 'string' },
    defined: { name: 'string', value: 'any' },
    // Stream events
    'stream.subscribed': { subscriptionId: 'string', outputTopic: 'any' },
    'stream.unsubscribed': { subscriptionId: 'string' },
    'stream.value': { subscriptionId: 'string', value: 'any' },
    'stream.error': { subscriptionId: 'string', error: 'string' },
  };

  // The core interpreter
  private _interpreter!: Interpreter;

  // Stream runtime for reactive pipelines
  private _streamRuntime?: StreamRuntime;

  // Registered local service mounts (for routing optimization hints)
  // NOTE: All calls still go through transport - this is just metadata
  // that could be used for future transport-level optimization
  private _localServices = new Set<string>();

  protected async onBootstrap(): Promise<void> {
    const config = getLispConfig();

    // ═══════════════════════════════════════════════════════════════════════
    // ✅ UI CONTROL INVERSION: All calls go through transport via RequestReply
    // ═══════════════════════════════════════════════════════════════════════
    const remoteHandler: IRemoteCallHandler = {
      call: async (mount: string, command: string, params: unknown) => {
        // ALL calls go through transport - no local bypass
        // Transport can optimize same-process calls internally if needed
        return RequestReply.execute(this._context!, mount, command, params, {
          timeoutMs: config.rpcTimeoutMs,
        });
      },
      // No local optimization at this layer - let transport handle it
      isLocal: () => false,
      localCall: async (mount: string, command: string, params: unknown) => {
        // Redirect to transport-based call
        return RequestReply.execute(this._context!, mount, command, params, {
          timeoutMs: config.rpcTimeoutMs,
        });
      },
    };

    // Create interpreter with Matrix context handlers
    this._interpreter = new Interpreter({
      remoteHandler,
    });

    // Initialize stream runtime
    this._streamRuntime = new StreamRuntime(
      this._context!.transport,
      async (fn: ILambdaClosure, args: unknown[]) => {
        // Use interpreter to apply lambda
        return await this._interpreter.evaluate(['apply', fn as unknown as SExpr, args as unknown as SExpr]);
      },
      (event: string, data: Record<string, unknown>) => {
        this.emit(event, data);
      }
    );

    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log(`[LispEval] Initialized at ${this._context?.mount}`);
  }

  /**
   * Evaluate a LISP expression
   */
  async onEval(params: { expr: SExpr }): Promise<unknown> {
    const startTime = performance.now();
    const { expr } = params;

    try {
      // Evaluate using the interpreter
      const result = await this._interpreter.evaluate(expr);
      const durationMs = performance.now() - startTime;

      this.emit('evaluated', { expr, result, durationMs });
      return result;
    } catch (err: unknown) {
      const error = err instanceof Error ? err.message : String(err);
      this.emit('evalError', { expr, error });
      throw err;
    }
  }

  /**
   * Define a global binding
   */
  async onDefine(params: { name: string; value: unknown }): Promise<void> {
    const { name, value } = params;
    this._interpreter.context.define(name, value);
    this.emit('defined', { name, value });
  }

  /**
   * Get current environment (for debugging)
   */
  async onGetEnv(): Promise<Record<string, unknown>> {
    return { ...this._interpreter.context.definitions };
  }

  /**
   * Register a local service mount for routing optimization hints.
   * All calls still go through transport - this is metadata that could
   * be used for future transport-level optimization (e.g., priority routing).
   */
  onRegisterService(params: { mount: string }): void {
    const { mount } = params;
    this._localServices.add(mount);
    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log(`[LispEval] Registered local service: ${mount}`);
  }

  /**
   * List all registered local services
   */
  onListServices(): { services: string[] } {
    return { services: Array.from(this._localServices) };
  }

  /**
   * Subscribe to a stream and activate the pipeline
   */
  async onSubscribe(params: { stream: IStream; outputTopic?: string }): Promise<{ subscriptionId: string }> {
    const { stream, outputTopic } = params;
    if (!this._streamRuntime) {
      throw new Error('Stream runtime not initialized');
    }
    const subscriptionId = await this._streamRuntime.activateStream(stream, outputTopic);
    return { subscriptionId };
  }

  /**
   * Unsubscribe from a stream
   */
  async onUnsubscribe(params: { subscriptionId: string }): Promise<{ success: boolean }> {
    const { subscriptionId } = params;
    if (!this._streamRuntime) {
      return { success: false };
    }
    const success = this._streamRuntime.deactivateStream(subscriptionId);
    return { success };
  }

  /**
   * List all active stream subscriptions
   */
  async onListSubscriptions(): Promise<{ subscriptions: IActiveSubscription[] }> {
    if (!this._streamRuntime) {
      return { subscriptions: [] };
    }
    return { subscriptions: this._streamRuntime.listSubscriptions() };
  }

}
