// src/core/lisp/LispDispatchStrategy.ts
//
// Vision 27 §11.11: Environment-based security configuration
//
// Robert C. Martin (SOLID, "Clean Code"):
//   "Dependency on abstraction (getDefaultSecurityContext), not concretion (DEVELOPER_CONTEXT)."

import type { IDispatchStrategy } from '../actor/IDispatchStrategy';
import type { IMessageMeta } from '../actor/IMessageMeta';

/** @deprecated Use IMessageMeta instead */
type MessageMeta = IMessageMeta;
import type { ILispEnvironment } from '../../engine/core/ILispEnvironment';
import type { ILispSecurityContext } from '../security/LispSecurityContext';
import { getDefaultSecurityContext } from '../security/SecurityConfiguration';

/**
 * LispDispatchStrategy - LISP handler dispatch for ActorBehavior.
 *
 * IMPORTANT: This instance is PRIVATE to each actor.
 * Each actor creates its own LispDispatchStrategy - never shared!
 *
 * This implements per-actor LISP handler isolation while sharing
 * the global LISP builtins (arithmetic, list ops, etc.).
 *
 * Per the expert panel consensus:
 * - Per-actor strategy instances
 * - Shared builtins (read-only)
 * - Isolated handler storage
 * - Runtime-configurable priority
 *
 * @example
 * ```typescript
 * class MyActor extends MatrixActor {
 *   private lisp = new LispDispatchStrategy(this.context.lispEnv);
 *
 *   onBootstrap() {
 *     this._actor?.addDispatchStrategy(this.lisp);
 *     this.lisp.registerHandler('calculate', '(+ (:x payload) (:y payload))');
 *   }
 * }
 * ```
 */
export class LispDispatchStrategy implements IDispatchStrategy {
  readonly name = 'lisp';
  priority: number;

  private readonly mount: string;
  private readonly handlers = new Map<string, string>();
  private readonly env?: ILispEnvironment;

  /**
   * Security context for Lisp evaluation.
   *
   * Controls what primitives and operations are allowed.
   * Default: Environment-based (COMPONENT_CONTEXT in prod, DEVELOPER_CONTEXT in dev).
   *
   * Mark Miller (Capability Security): "Security context travels with computation."
   *
   * Vision 27 §11.11: Environment determines default context.
   */
  private _securityContext: ILispSecurityContext = getDefaultSecurityContext();

  /**
   * Create a LispDispatchStrategy for an actor.
   *
   * @param mount - The actor's mount path
   * @param env - Optional LISP environment (from context.lispEnv)
   * @param priority - Dispatch priority (default: 10, after system commands)
   * @param securityContext - Optional security context (default: environment-based)
   */
  constructor(
    mount: string,
    env?: ILispEnvironment,
    priority = 10,
    securityContext?: ILispSecurityContext
  ) {
    this.mount = mount;
    this.env = env;
    this.priority = priority;
    if (securityContext) {
      this._securityContext = securityContext;
    }
  }

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

  /**
   * Set the security context for this dispatch strategy.
   *
   * This controls what primitives and operations are allowed during
   * LISP handler execution.
   *
   * @param context - The security context to use
   */
  setSecurityContext(context: ILispSecurityContext): void {
    this._securityContext = context;
  }

  /**
   * Check if a LISP handler exists for this command.
   */
  canHandle(command: string): boolean {
    // Check local handlers first
    if (this.handlers.has(command)) {
      return true;
    }
    // Then check global env (if available)
    if (this.env) {
      return this.env.hasHandler(this.mount, command);
    }
    return false;
  }

  /**
   * Execute the LISP handler for a command.
   *
   * Security context is passed to the environment for primitive-level
   * enforcement per Vision 18 and 27.
   */
  async handle(command: string, payload: unknown, meta: MessageMeta): Promise<unknown> {
    // If we have a local handler, use that
    const localHandler = this.handlers.get(command);
    if (localHandler && this.env) {
      // Register the handler in the environment and invoke
      this.env.registerHandler(this.mount, command, localHandler);
      const result = await this.env.invoke(this.mount, command, payload, {
        ts: meta.ts,
        topic: meta.topic,
        securityContext: this._securityContext,
      });
      return result;
    }

    // Fall back to global env handler
    if (this.env && this.env.hasHandler(this.mount, command)) {
      return this.env.invoke(this.mount, command, payload, {
        ts: meta.ts,
        topic: meta.topic,
        securityContext: this._securityContext,
      });
    }

    throw new Error(`[LispDispatchStrategy] No handler for command: ${command}`);
  }

  /**
   * Register a LISP handler expression for a command.
   *
   * The handler is stored locally and will be used when the command
   * is dispatched. This enables per-actor handler isolation.
   *
   * @param command - Command name
   * @param handler - LISP expression string
   */
  registerHandler(command: string, handler: string): void {
    this.handlers.set(command, handler);
    // Also register in global env if available
    if (this.env) {
      this.env.registerHandler(this.mount, command, handler);
    }
  }

  /**
   * Unregister a LISP handler.
   *
   * @param command - Command name
   */
  unregisterHandler(command: string): void {
    this.handlers.delete(command);
  }

  /**
   * Get list of registered handler commands.
   */
  listHandlers(): string[] {
    return Array.from(this.handlers.keys());
  }

  /**
   * Check if a specific handler is registered locally.
   */
  hasHandler(command: string): boolean {
    return this.handlers.has(command);
  }

  /**
   * Get the handler expression for a command.
   */
  getHandler(command: string): string | undefined {
    return this.handlers.get(command);
  }

  /**
   * Dispose and clean up.
   *
   * Unregisters all handlers from the global environment.
   */
  dispose(): void {
    if (this.env) {
      this.env.unregisterComponent(this.mount);
    }
    this.handlers.clear();
  }
}
