// src/framework/shadowlisp/primitives/SlLambda.ts
//
// Vision 49: Lambda element - anonymous function - IS a Matrix component
//
// Gerald Sussman (SICP co-author):
//   "Lambda is the essence of abstraction. It creates a procedure
//    that captures its environment."
//
// <sl-lambda params="x y" mount="app.lambda-1">
//   <body>
// </sl-lambda>
//
// As a Matrix component:
//   mount: app.lambda-1
//   accepts: eval (returns closure), apply (evaluates body with args)
//   The closure stores the lambda's mount for remote application

import type { SExpr } from '../../../lisp/SExpr';
import type { ISlClosure, SlEnv } from '../SlBootstrap';
import { SlMatrixElement, getElementByMount } from '../SlMatrixElement';
import { RequestReply } from '../../../engine/remoting/RequestReply';

/**
 * SlLambda - Lambda (anonymous function) element that IS a Matrix component.
 *
 * Creates a closure that captures the current environment.
 * When called, the closure extends its captured environment with
 * the parameter bindings and evaluates the body.
 *
 * The closure stores this lambda's mount path, so it can be applied
 * via Matrix transport from anywhere.
 *
 * @example
 * <sl-lambda params="x" mount="app.lambda-1">
 *   <sl-add><sl-ref>x</sl-ref><sl-num>1</sl-num></sl-add>
 * </sl-lambda>
 *
 * Callable via Matrix MCP:
 * mcp__matrix_call({ mount: "matrix/app.lambda-1", command: "eval" })
 * => { type: "closure", params: ["x"], lambdaMount: "app.lambda-1", ... }
 *
 * mcp__matrix_call({ mount: "matrix/app.lambda-1", command: "apply", params: { args: [5] } })
 * => 6  (5 + 1)
 */
export class SlLambda extends SlMatrixElement {
  /**
   * Get the parameter names.
   */
  get params(): string[] {
    const paramsAttr = this.getAttribute('params') ?? '';
    if (!paramsAttr.trim()) {
      return [];
    }
    return paramsAttr.split(/\s+/).filter(p => p.length > 0);
  }

  /**
   * Set the parameter names.
   */
  set params(names: string[]) {
    this.setAttribute('params', names.join(' '));
  }

  /**
   * Get the body element.
   */
  get body(): Element | null {
    return this.firstElementChild;
  }

  /**
   * Lambda creates a closure that captures the current environment.
   * Called via Matrix transport when someone sends eval command.
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const bodyEl = this.body;
    if (!bodyEl) {
      throw new Error('sl-lambda requires a body element');
    }

    // Get the body's mount for remote evaluation
    const bodyMount = bodyEl.getAttribute('mount') ?? undefined;

    // Get the body as S-expression for serialization
    const bodySExpr = (bodyEl instanceof SlMatrixElement)
      ? bodyEl.toSExpr()
      : null;

    // Create closure with captured environment and mount references
    const closure: ISlClosure = {
      type: 'closure',
      params: this.params,
      body: bodySExpr,
      bodyMount,
      lambdaMount: this._mount,  // Store this lambda's mount for apply
      env: env as SlEnv,         // Capture current environment
    };

    return closure as unknown as SExpr;
  }

  /**
   * Apply this lambda to arguments.
   * Supports PARTIAL APPLICATION - if fewer args than params, returns curried closure.
   *
   * @param args - Arguments to apply
   * @param capturedEnv - The captured environment from the closure
   * @param remainingParams - For curried closures: the params still needed (defaults to this.params)
   */
  async apply(args: SExpr[], capturedEnv: SlEnv, remainingParams?: string[]): Promise<SExpr> {
    const bodyEl = this.body;
    if (!bodyEl) {
      throw new Error('sl-lambda requires a body element');
    }

    // Use closure's remaining params if provided, otherwise use original params
    const params = remainingParams ?? this.params;

    // PARTIAL APPLICATION: fewer args than params = return curried closure
    if (args.length < params.length) {
      const boundParams = params.slice(0, args.length);
      const remainingParams = params.slice(args.length);

      // Extend env with bound args
      const newEnv: SlEnv = [...capturedEnv];
      for (let i = 0; i < boundParams.length; i++) {
        newEnv.unshift([boundParams[i]!, args[i]]);
      }

      // Get body S-expr
      const bodySExpr = (bodyEl instanceof SlMatrixElement)
        ? bodyEl.toSExpr()
        : null;

      // Return new closure waiting for remaining args
      const curried: ISlClosure = {
        type: 'closure',
        params: remainingParams,
        body: bodySExpr,
        bodyMount: bodyEl.getAttribute('mount') ?? undefined,
        lambdaMount: this._mount,  // Still points to this lambda
        env: newEnv,               // Has bound args in environment
      };

      return curried as unknown as SExpr;
    }

    // FULL APPLICATION: all args provided, evaluate body
    const newEnv: SlEnv = [...capturedEnv];
    for (let i = 0; i < params.length; i++) {
      newEnv.unshift([params[i]!, args[i]]);
    }

    return this.evalChild(bodyEl, newEnv as SExpr[]);
  }

  toSExpr(): SExpr {
    const bodyEl = this.body;
    const bodySExpr = (bodyEl instanceof SlMatrixElement)
      ? bodyEl.toSExpr()
      : null;

    return ['lambda', this.params, bodySExpr];
  }

  /**
   * Override connectedCallback to also bind the apply command.
   */
  connectedCallback(): void {
    super.connectedCallback();

    // TODO: Also bind 'apply' command via RequestReply.bind
    // For now, apply is called directly via the lambda element
  }
}

if (!customElements.get('sl-lambda')) {
  customElements.define('sl-lambda', SlLambda);
}

/**
 * Apply a closure to arguments.
 * This handles both local and remote lambda application.
 * Properly supports curried closures by passing the closure's remaining params.
 */
export async function applySlClosure(
  closure: ISlClosure,
  args: SExpr[],
  context?: { transport?: unknown }
): Promise<SExpr> {
  // If we have the lambda's mount, try to call it
  if (closure.lambdaMount) {
    // Try local first
    const localLambda = getElementByMount(closure.lambdaMount);
    if (localLambda instanceof SlLambda) {
      // Pass closure.params so curried closures work correctly
      return localLambda.apply(args, closure.env, closure.params);
    }

    // Fall back to transport if context is available
    if (context?.transport) {
      return RequestReply.execute(
        context as never,
        closure.lambdaMount,
        'apply',
        { args, env: closure.env, params: closure.params },
        { timeoutMs: 5000 }
      );
    }
  }

  // Last resort: evaluate body S-expression directly
  // (This path is less ideal but provides fallback)
  throw new Error('Cannot apply closure: no lambda mount or transport available');
}
