// src/framework/shadowlisp/primitives/SlRender.ts
//
// Vision 49: Render element - THE BRIDGE from Lisp to visible DOM
//
// Alan Kay (Smalltalk creator, Turing Award 2003):
//   "The best way to predict the future is to invent it.
//    SlRender makes Lisp expressions VISIBLE."
//
// <sl-render target="#display">
//   <sl-add><sl-num>1</sl-num><sl-num>2</sl-num></sl-add>
// </sl-render>
//
// This evaluates the child expression and renders the result
// to the target element. When values change, the display updates.

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

type ClosureSExpr = {
  type: 'closure';
  params?: string[];
};

/**
 * SlRender - Renders Lisp expression results to visible DOM.
 *
 * THE BRIDGE: Takes invisible Lisp computation → visible UI.
 *
 * @example
 * <sl-render target="#counter">
 *   <sl-ref>count</sl-ref>
 * </sl-render>
 * <div id="counter"></div>
 * <!-- Will show the value of 'count' -->
 *
 * @example
 * <sl-render target="#result" format="json">
 *   <sl-apply><sl-ref>get-data</sl-ref></sl-apply>
 * </sl-render>
 */
export class SlRender extends SlMatrixElement {
  private _targetEl: HTMLElement | null = null;
  private _lastValue: SExpr = null;

  static get observedAttributes(): string[] {
    return [...super.observedAttributes, 'target', 'format'];
  }

  /**
   * Get the target selector.
   */
  get target(): string {
    return this.getAttribute('target') ?? '';
  }

  /**
   * Get the format (text, html, json).
   */
  get format(): 'text' | 'html' | 'json' {
    const f = this.getAttribute('format');
    if (f === 'html' || f === 'json') return f;
    return 'text';
  }

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

  connectedCallback(): void {
    super.connectedCallback();
    this._findTarget();
  }

  private _findTarget(): void {
    if (this.target) {
      this._targetEl = document.querySelector(this.target);
      if (!this._targetEl) {
        this.warnKernel(`[sl-render] Target not found: ${this.target}`);
      }
    }
  }

  /**
   * Evaluate the child expression and render to target.
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    const expr = this.expression;
    if (!expr) {
      return null;
    }

    // Evaluate the expression
    const value = await this.evalChild(expr, env);
    this._lastValue = value;

    // Render to target
    this._render(value);

    return value;
  }

  /**
   * Render a value to the target element.
   */
  private _render(value: SExpr): void {
    if (!this._targetEl) {
      this._findTarget();
    }

    if (!this._targetEl) {
      return;
    }

    const formatted = this._format(value);

    if (this.format === 'html') {
      this._targetEl.innerHTML = formatted;
    } else {
      this._targetEl.textContent = formatted;
    }

    // Dispatch event for reactivity
    this.dispatchEvent(new CustomEvent('sl-rendered', {
      bubbles: true,
      detail: { value, formatted }
    }));
  }

  /**
   * Format a value for display.
   */
  private _format(value: SExpr): string {
    if (value === null || value === undefined) {
      return '';
    }

    if (this.format === 'json') {
      try {
        return JSON.stringify(value, null, 2);
      } catch {
        return String(value);
      }
    }

    if (typeof value === 'object') {
      // Check if it's a list (array)
      if (Array.isArray(value)) {
        return value.map(v => this._format(v)).join(' ');
      }
      // Check if it's a closure
      const closure = value as Partial<ClosureSExpr>;
      if (closure.type === 'closure') {
        return `[closure: (${closure.params?.join(' ') ?? ''})]`;
      }
      return JSON.stringify(value);
    }

    return String(value);
  }

  /**
   * Re-render with the last value (for external triggers).
   */
  async refresh(env?: SExpr[]): Promise<void> {
    if (env) {
      await this.evalSelf(env);
    } else if (this._lastValue !== null) {
      this._render(this._lastValue);
    }
  }

  /**
   * Update the rendered value directly (for MCP control).
   */
  setValue(value: SExpr): void {
    this._lastValue = value;
    this._render(value);
  }

  toSExpr(): SExpr {
    const expr = this.expression;
    const exprSExpr = (expr instanceof SlMatrixElement)
      ? expr.toSExpr()
      : null;
    return ['render', this.target, exprSExpr];
  }
}

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