// src/framework/shadowlisp/primitives/SlApply.ts
//
// Vision 49: Apply element - function application - IS a Matrix component
//
// <sl-apply>
//   <function>
//   <arg1>
//   <arg2>
//   ...
// </sl-apply>
//
// Evaluates function, evaluates args, applies function to args.
// Supports partial application - if fewer args than params, returns curried closure.

import type { SExpr } from '../../../lisp/SExpr';
import type { ISlClosure, SlEnv } from '../SlBootstrap';
import { SlMatrixElement, getElementByMount } from '../SlMatrixElement';
import { SlLambda, applySlClosure } from './SlLambda';

/**
 * Type guard for closure.
 */
function isClosure(x: unknown): x is ISlClosure {
  return (
    typeof x === 'object' &&
    x !== null &&
    (x as ISlClosure).type === 'closure'
  );
}

/**
 * SlApply - Function application element that IS a Matrix component.
 *
 * First child is the function, remaining children are arguments.
 * Supports currying - if you provide fewer args than the function needs,
 * you get back a curried closure.
 *
 * @example
 * <!-- Full application: (add-triple 10 20 5) => 35 -->
 * <sl-apply>
 *   <sl-ref>add-triple</sl-ref>
 *   <sl-num>10</sl-num>
 *   <sl-num>20</sl-num>
 *   <sl-num>5</sl-num>
 * </sl-apply>
 *
 * @example
 * <!-- Partial application: (add-triple 10) => curried closure -->
 * <sl-apply>
 *   <sl-ref>add-triple</sl-ref>
 *   <sl-num>10</sl-num>
 * </sl-apply>
 */
export class SlApply extends SlMatrixElement {
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const children = Array.from(this.children);

    if (children.length === 0) {
      throw new Error('sl-apply requires at least a function');
    }

    // First child is the function
    const fnChild = children[0]!;
    const fn = await this.evalChild(fnChild, env);

    // Remaining children are arguments
    const argChildren = children.slice(1);
    const args: SExpr[] = [];
    for (const argChild of argChildren) {
      const argValue = await this.evalChild(argChild, env);
      args.push(argValue);
    }

    // Apply the function
    if (isClosure(fn)) {
      return applySlClosure(fn, args);
    }

    // If it's a built-in function (number, string, etc.), can't apply
    throw new Error(`sl-apply: cannot apply ${typeof fn} - expected closure`);
  }

  toSExpr(): SExpr {
    const children = Array.from(this.children);
    const parts: SExpr[] = ['apply'];

    for (const child of children) {
      if (child instanceof SlMatrixElement) {
        parts.push(child.toSExpr());
      }
    }

    return parts;
  }
}

if (!customElements.get('sl-apply')) {
  customElements.define('sl-apply', SlApply);
}
