// src/framework/shadowlisp/primitives/SlFold.ts
//
// Vision 49: REAL fold primitive - NO JavaScript cheating
//
// <sl-fold fn="nest-in-border">
//   <sl-ref>init</sl-ref>
//   <sl-ref>cards</sl-ref>
// </sl-fold>
//
// fold applies fn(acc, item) for each item in list, threading the accumulator.
// This is REAL functional programming - the fold function is a ShadowLisp closure.

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

/**
 * SlFold - REAL fold/reduce primitive using ShadowLisp closures.
 *
 * @example
 * <sl-fold fn="nest-in-border">
 *   <sl-ref>init</sl-ref>      <!-- initial accumulator value -->
 *   <sl-ref>cards</sl-ref>     <!-- list to fold over -->
 * </sl-fold>
 *
 * The fn closure is applied as fn(acc, item) for each item.
 */
export class SlFold extends SlMatrixElement {
  /**
   * Get the fold function name to look up in environment.
   */
  get fnName(): string {
    return this.getAttribute('fn') ?? '';
  }

  /**
   * Evaluate: fold the list using REAL closure.
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const children = Array.from(this.children);
    if (children.length < 2) {
      this.warnKernel('[SlFold] Requires init and list children');
      return null;
    }

    // First child is initial value
    const init = await this.evalChild(children[0], env);

    // Second child is the list
    const list = await this.evalChild(children[1], env);
    if (!Array.isArray(list)) {
      this.warnKernel('[SlFold] Second child did not evaluate to array:', list);
      return init;
    }

    // Look up the fold function from environment
    const fnName = this.fnName;
    if (!fnName) {
      this.warnKernel('[SlFold] No fn attribute specified');
      return init;
    }

    // Find fn in environment
    let fn: ISlClosure | null = null;
    for (const [name, value] of env as [string, SExpr][]) {
      if (name === fnName) {
        if (isClosure(value)) {
          fn = value;
        }
        break;
      }
    }

    if (!fn) {
      this.warnKernel(`[SlFold] Function '${fnName}' not found in environment`);
      return init;
    }

    this.infoKernel(`[SlFold] Folding with REAL function '${fnName}':`, fn);

    // Fold using REAL closure application
    let acc = init;
    for (let i = 0; i < list.length; i++) {
      const item = list[i];
      // Apply fold function: fn(acc, item) - THIS IS REAL, NOT JS!
      acc = await applySlClosure(fn, [acc, item]);
      this.infoKernel(`[SlFold] fn(acc, item[${i}]) =>`, acc);
    }

    this.infoKernel(`[SlFold] Fold complete, result:`, acc);
    return acc;
  }

  toSExpr(): SExpr {
    const children = Array.from(this.children);
    return ['fold', this.fnName,
      children[0] instanceof SlMatrixElement ? children[0].toSExpr() : null,
      children[1] instanceof SlMatrixElement ? children[1].toSExpr() : null
    ];
  }
}

/**
 * SlFoldIndexed - fold with index passed to function.
 *
 * @example
 * <sl-fold-indexed fn="alternating-layout">
 *   <sl-ref>init</sl-ref>
 *   <sl-ref>cards</sl-ref>
 * </sl-fold-indexed>
 *
 * The fn closure is applied as fn(acc, item, index) for each item.
 */
export class SlFoldIndexed extends SlMatrixElement {
  get fnName(): string {
    return this.getAttribute('fn') ?? '';
  }

  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const children = Array.from(this.children);
    if (children.length < 2) {
      this.warnKernel('[SlFoldIndexed] Requires init and list children');
      return null;
    }

    const init = await this.evalChild(children[0], env);
    const list = await this.evalChild(children[1], env);

    if (!Array.isArray(list)) {
      this.warnKernel('[SlFoldIndexed] Second child did not evaluate to array:', list);
      return init;
    }

    const fnName = this.fnName;
    if (!fnName) {
      this.warnKernel('[SlFoldIndexed] No fn attribute specified');
      return init;
    }

    let fn: ISlClosure | null = null;
    for (const [name, value] of env as [string, SExpr][]) {
      if (name === fnName) {
        if (isClosure(value)) {
          fn = value;
        }
        break;
      }
    }

    if (!fn) {
      this.warnKernel(`[SlFoldIndexed] Function '${fnName}' not found in environment`);
      return init;
    }

    this.infoKernel(`[SlFoldIndexed] Folding with REAL function '${fnName}':`, fn);

    let acc = init;
    for (let i = 0; i < list.length; i++) {
      const item = list[i];
      // Apply fold function: fn(acc, item, index) - THIS IS REAL, NOT JS!
      acc = await applySlClosure(fn, [acc, item, i]);
      this.infoKernel(`[SlFoldIndexed] fn(acc, item[${i}], ${i}) =>`, acc);
    }

    return acc;
  }

  toSExpr(): SExpr {
    const children = Array.from(this.children);
    return ['fold-indexed', this.fnName,
      children[0] instanceof SlMatrixElement ? children[0].toSExpr() : null,
      children[1] instanceof SlMatrixElement ? children[1].toSExpr() : null
    ];
  }
}

// Register
if (!customElements.get('sl-fold')) {
  customElements.define('sl-fold', SlFold);
}
if (!customElements.get('sl-fold-indexed')) {
  customElements.define('sl-fold-indexed', SlFoldIndexed);
}
