// src/framework/shadowlisp/SlRuntimeMeta.ts
//
// Vision 49: Metacircular ShadowLisp Runtime
//
// Alan Kay (Smalltalk creator, Turing Award 2003):
//   "The best software feels like it's alive. You don't restart it - you reshape it.
//    SlRuntimeMeta brings this to the browser."
//
// This is the new runtime that uses the metacircular evaluator.
// The DOM IS the computer.

import type { SExpr } from '../../lisp/SExpr';
import type { SlEnv, SlRuntimeValue } from './SlBootstrap';
import type { IMatrixContext } from '../../engine/core/IMatrixContext';
import { SlMetaEval } from './SlMetaEval';
import { isSlEvaluable, type ISlEvalContext } from './primitives/ISlEvaluable';

// Import all primitives to register them
import './primitives';

const SlRuntimeMetaElementBase = HTMLElement;

/**
 * SlRuntimeMeta - Metacircular ShadowLisp Runtime
 *
 * The top-level container for ShadowLisp programs.
 * Contains a metacircular evaluator and manages the global environment.
 *
 * Now supports Matrix transport! Call mount(context) to enable messaging:
 *
 * @example
 * // With transport (Claude can call these elements!)
 * const context = await createIsolatedContext('shadowlisp');
 * const app = document.getElementById('app');
 * await app.mount(context);
 * await app.run();
 *
 * @example
 * // Without transport (local only)
 * <sl-runtime-meta>
 *   <sl-define name="x"><sl-num>42</sl-num></sl-define>
 *   <sl-add><sl-ref>x</sl-ref><sl-num>1</sl-num></sl-add>
 * </sl-runtime-meta>
 */
export class SlRuntimeMeta extends SlRuntimeMetaElementBase {
  private _eval: SlMetaEval | null = null;
  private _results: SExpr[] = [];
  private _autoRun = true;
  private _context: IMatrixContext | null = null;

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  private _infoKernel(message: string, ...args: unknown[]): void {
    // eslint-disable-next-line no-console -- kernel metacircular runtime diagnostics have no actor context
    console.log(message, ...args);
  }

  private _errorKernel(message: string, ...args: unknown[]): void {
    // eslint-disable-next-line no-console -- kernel metacircular runtime diagnostics have no actor context
    console.error(message, ...args);
  }

  static get observedAttributes(): string[] {
    return ['auto-run'];
  }

  /**
   * Get auto-run setting.
   */
  get autoRun(): boolean {
    const attr = this.getAttribute('auto-run');
    return attr !== 'false';
  }

  /**
   * Set auto-run setting.
   */
  set autoRun(value: boolean) {
    this.setAttribute('auto-run', String(value));
  }

  /**
   * Get the evaluator element.
   */
  get evaluator(): SlMetaEval | null {
    return this._eval;
  }

  /**
   * Get the current environment.
   */
  get env(): SlEnv {
    return this._eval?.env ?? [];
  }

  /**
   * Get the Matrix context (for transport access).
   * SlMatrixElement children look for this via getContextFromAncestor().
   */
  get context(): IMatrixContext | null {
    return this._context;
  }

  /**
   * Get the results of the last evaluation.
   */
  get results(): SExpr[] {
    return this._results;
  }

  connectedCallback(): void {
    // Create internal evaluator
    this._eval = document.createElement('sl-meta-eval') as SlMetaEval;
    this.shadowRoot!.appendChild(this._eval);

    // Add slot for light DOM content
    const slot = document.createElement('slot');
    this.shadowRoot!.appendChild(slot);

    // Auto-run if enabled
    if (this.autoRun) {
      // Use requestAnimationFrame to ensure all children are parsed
      requestAnimationFrame(() => {
        this.run().catch(err => {
          this._errorKernel('[SlRuntimeMeta] Error:', err);
        });
      });
    }
  }

  attributeChangedCallback(name: string, _old: string, _new: string): void {
    if (name === 'auto-run') {
      this._autoRun = this.autoRun;
    }
  }

  /**
   * Mount the runtime with a Matrix context.
   * This enables transport for all child elements.
   *
   * Carl Hewitt (Actor Model):
   *   "An actor is identified by its address. With transport,
   *    ShadowLisp elements become addressable actors."
   *
   * @param context - Matrix context with transport
   */
  async mount(context: IMatrixContext): Promise<void> {
    this._context = context;

    // Update mount attribute if context has a different mount
    const contextMount = context.mount;
    if (contextMount && contextMount !== this.getAttribute('mount')) {
      this.setAttribute('mount', contextMount);
    }

    this._infoKernel(`[SlRuntimeMeta] Mounted with transport at ${contextMount}`);

    // Re-initialize children if already connected
    // This triggers their connectedCallback again to pick up the transport
    if (this.isConnected) {
      // Force children to re-subscribe with transport
      for (const child of this.children) {
        if (child instanceof HTMLElement && 'connectedCallback' in child) {
          // Temporarily remove and re-add to trigger connectedCallback
          const parent = child.parentNode;
          const nextSibling = child.nextSibling;
          parent?.removeChild(child);
          if (nextSibling) {
            parent?.insertBefore(child, nextSibling);
          } else {
            parent?.appendChild(child);
          }
        }
      }
    }
  }

  /**
   * Evaluate an expression.
   *
   * @param expr - S-expression or DOM element to evaluate
   * @param env - Optional environment override
   */
  async eval(expr: SExpr | HTMLElement, env?: SlEnv): Promise<SExpr> {
    if (!this._eval) {
      throw new Error('SlRuntimeMeta not initialized');
    }
    return this._eval.evaluate(expr, env);
  }

  /**
   * Run all child elements and return the results.
   */
  async run(): Promise<SExpr[]> {
    if (!this._eval) {
      throw new Error('SlRuntimeMeta not initialized');
    }

    this._results = [];
    const ctx = this._createContext();

    for (const child of this.children) {
      if (child instanceof HTMLElement && isSlEvaluable(child)) {
        try {
          const result = await child.slEval(ctx);
          this._results.push(result);
        } catch (err) {
          this._errorKernel(`[SlRuntimeMeta] Error evaluating:`, child, err);
          this._results.push(null);
        }
      }
    }

    // Dispatch event with results
    this.dispatchEvent(new CustomEvent('sl-evaluated', {
      bubbles: true,
      detail: { results: this._results }
    }));

    return this._results;
  }

  /**
   * Re-run evaluation (useful after DOM modification).
   */
  async rerun(): Promise<SExpr[]> {
    return this.run();
  }

  /**
   * Define a variable in the global environment.
   */
  define(name: string, value: SExpr): void {
    if (this._eval) {
      this._eval.env.unshift([name, value]);
    }
  }

  /**
   * Look up a variable.
   */
  lookup(name: string): SlRuntimeValue {
    for (const [n, v] of this.env) {
      if (n === name) return v;
    }
    throw new Error(`Undefined: ${name}`);
  }

  /**
   * Create evaluation context.
   */
  private _createContext(): ISlEvalContext {
    return {
      env: this._eval!.env,
      runtime: this,
      evaluate: (expr, env) => this.eval(expr, env),
    };
  }
}

// Register the custom element
if (!customElements.get('sl-runtime-meta')) {
  customElements.define('sl-runtime-meta', SlRuntimeMeta);
}
