// src/framework/shadowlisp/primitives/SlMap.ts
//
// Vision 49: Map function over list - THE WORKHORSE OF FUNCTIONAL LAYOUT
//
// <sl-map fn="render-card" list="items"/>
// => applies render-card to each item in items
//
// This is CRITICAL for layout:
//   <sl-map fn="card-wrapper" list="content-items"/>
// Maps a wrapper function over a list of content, creating wrapped elements!

import type { SExpr } from '../../../lisp/SExpr';
import { SlMatrixElement, getElementByMount } from '../SlMatrixElement';

type LambdaMountElement = Element & {
  apply?: (args: SExpr[], env: SExpr[]) => Promise<SExpr> | SExpr;
};

/**
 * SlMap - Map a function over a list.
 *
 * This is how you render lists of things:
 * <sl-map fn="card" list="items"/>
 *
 * Where "card" is a layout function and "items" is a list.
 *
 * @example
 * <sl-define name="items">
 *   <sl-list>
 *     <sl-str>Alice</sl-str>
 *     <sl-str>Bob</sl-str>
 *   </sl-list>
 * </sl-define>
 *
 * <sl-map fn="make-greeting" list="items"/>
 * => ["Hello Alice", "Hello Bob"]
 */
export class SlMap extends SlMatrixElement {
  /**
   * Get the function name to apply.
   */
  get fnName(): string {
    return this.getAttribute('fn') ?? '';
  }

  /**
   * Get the list variable name.
   */
  get listName(): string {
    return this.getAttribute('list') ?? '';
  }

  /**
   * Evaluate: apply function to each element of list.
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    // Lookup function in environment
    const fnValue = this._lookupEnv(this.fnName, env);
    if (!fnValue) {
      throw new Error(`sl-map: function '${this.fnName}' not found`);
    }

    // Lookup list in environment
    const listValue = this._lookupEnv(this.listName, env);
    if (!Array.isArray(listValue)) {
      throw new Error(`sl-map: '${this.listName}' is not a list`);
    }

    // Map function over list
    const results: SExpr[] = [];

    for (const item of listValue) {
      const result = await this._applyFn(fnValue, [item], env);
      results.push(result);
    }

    return results;
  }

  /**
   * Lookup a name in the environment.
   */
  private _lookupEnv(name: string, env: SExpr[]): SExpr | undefined {
    // Environment is a list of [name, value] pairs (or objects)
    for (const binding of env) {
      if (Array.isArray(binding) && binding[0] === name) {
        return binding[1];
      }
      if (typeof binding === 'object' && binding !== null && !Array.isArray(binding)) {
        const obj = binding as Record<string, SExpr>;
        if (name in obj) {
          return obj[name];
        }
      }
    }
    return undefined;
  }

  /**
   * Apply a function to arguments.
   */
  private async _applyFn(fn: SExpr, args: SExpr[], env: SExpr[]): Promise<SExpr> {
    // If fn is a closure (from SlLambda), call it via its lambda mount
    if (typeof fn === 'object' && fn !== null && (fn as unknown as { type?: string }).type === 'closure') {
      const closure = fn as unknown as { type: string; params: string[]; lambdaMount?: string; env: SExpr[] };

      // If closure has a lambda mount, use it to apply the lambda
      if (closure.lambdaMount) {
        const lambdaEl = getElementByMount(closure.lambdaMount) as LambdaMountElement | null;
        if (typeof lambdaEl?.apply === 'function') {
          return lambdaEl.apply(args, closure.env);
        }
      }

      // Fallback: extend environment with parameter bindings and eval inline
      const extendedEnv: SExpr[] = [...closure.env];
      closure.params.forEach((param, i) => {
        extendedEnv.unshift([param, args[i]]);
      });

      // Try to evaluate body if it's available (shouldn't normally reach here)
      throw new Error(`sl-map: closure without lambdaMount not supported`);
    }

    // If fn is a mount path, call it
    if (typeof fn === 'string' && fn.startsWith('')) {
      const el = getElementByMount(fn);
      if (el) {
        return el.evalSelf([...args, ...env]);
      }
    }

    // If fn is a JS function
    if (typeof fn === 'function') {
      return (fn as Function)(...args);
    }

    throw new Error(`sl-map: cannot apply ${typeof fn}`);
  }

  toSExpr(): SExpr {
    return ['map', this.fnName, this.listName];
  }
}

if (!customElements.get('sl-map')) {
  customElements.define('sl-map', SlMap);
}
