// src/framework/shadowlisp/primitives/SlDefine.ts
//
// Vision 49: Define element - bind a value to a name - IS a Matrix component
//
// <sl-define name="x" mount="app.define-1">
//   <value>
// </sl-define>
//
// As a Matrix component:
//   mount: app.define-1
//   accepts: eval
//   Evaluates child and binds result to name in environment

import type { SExpr } from '../../../lisp/SExpr';
import { SlMatrixElement } from '../SlMatrixElement';
import type { SlEnv } from '../SlBootstrap';

/**
 * SlDefine - Definition element that IS a Matrix component.
 *
 * Binds a value to a name in the current environment.
 * Returns the value that was bound.
 *
 * Note: The environment mutation happens at the runtime level.
 * This element returns the value; the runtime handles binding.
 *
 * @example
 * <sl-define name="x">
 *   <sl-num>42</sl-num>
 * </sl-define>
 *
 * @example
 * <sl-define name="double">
 *   <sl-lambda params="x">
 *     <sl-mul><sl-ref>x</sl-ref><sl-num>2</sl-num></sl-mul>
 *   </sl-lambda>
 * </sl-define>
 */
export class SlDefine extends SlMatrixElement {
  /**
   * Get the name being defined.
   */
  get definedName(): string {
    return this.getAttribute('name') ?? '';
  }

  /**
   * Set the name being defined.
   */
  set definedName(s: string) {
    this.setAttribute('name', s);
  }

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

  /**
   * Define evaluates the value and binds it to the name.
   * Called via Matrix transport when someone sends eval command.
   *
   * Note: This mutates the env array that was passed in.
   * The caller (runtime) owns the environment.
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const name = this.definedName;
    if (!name) {
      throw new Error('sl-define requires a name attribute');
    }

    const valueEl = this.valueElement;
    if (!valueEl) {
      // Define with no value = define as null
      (env as SlEnv).unshift([name, null]);
      return null;
    }

    // Evaluate the value by calling the child's mount
    const value = await this.evalChild(valueEl, env);

    // Bind to environment (prepend to simulate lexical scope)
    (env as SlEnv).unshift([name, value]);

    return value;
  }

  toSExpr(): SExpr {
    const valueEl = this.valueElement;
    const valueSExpr = (valueEl instanceof SlMatrixElement)
      ? valueEl.toSExpr()
      : null;

    return ['define', this.definedName, valueSExpr];
  }
}

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