// src/framework/shadowlisp/primitives/SlOn.ts
//
// Vision 49: Event handler element - DOM events trigger Lisp
//
// Robin Milner (Pi-Calculus, Turing Award 1991):
//   "Interaction is computation. Events are messages."
//
// <sl-on event="click" target="#button">
//   <sl-set var="count">
//     <sl-add><sl-ref>count</sl-ref><sl-num>1</sl-num></sl-add>
//   </sl-set>
// </sl-on>
//
// When the button is clicked, the Lisp expression evaluates.

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

type RuntimeMetaElement = Element & {
  env?: SlEnv;
};

/**
 * SlOn - Connects DOM events to Lisp evaluation.
 *
 * When the event fires, the child expression evaluates.
 *
 * @example
 * <sl-on event="click" target="#increment">
 *   <sl-set var="count">
 *     <sl-add><sl-ref>count</sl-ref><sl-num>1</sl-num></sl-add>
 *   </sl-set>
 * </sl-on>
 */
export class SlOn extends SlMatrixElement {
  private _targetEl: HTMLElement | null = null;
  private _boundHandler: ((e: Event) => void) | null = null;
  private _env: SlEnv = [];

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

  /**
   * Get the event type to listen for.
   */
  get eventType(): string {
    return this.getAttribute('event') ?? 'click';
  }

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

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

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

  disconnectedCallback(): void {
    super.disconnectedCallback();
    this._detachListener();
  }

  private _attachListener(): void {
    if (!this.targetSelector) return;

    this._targetEl = document.querySelector(this.targetSelector);
    if (!this._targetEl) {
      this.warnKernel(`[sl-on] Target not found: ${this.targetSelector}`);
      return;
    }

    this._boundHandler = async (e: Event) => {
      this.infoKernel(`[sl-on] Event '${this.eventType}' on ${this.targetSelector}`);
      await this._handleEvent(e);
    };

    this._targetEl.addEventListener(this.eventType, this._boundHandler);
    this.infoKernel(`[sl-on] Listening for '${this.eventType}' on ${this.targetSelector}`);
  }

  private _detachListener(): void {
    if (this._targetEl && this._boundHandler) {
      this._targetEl.removeEventListener(this.eventType, this._boundHandler);
    }
    this._targetEl = null;
    this._boundHandler = null;
  }

  /**
   * Handle the event - evaluate the child expression.
   */
  private async _handleEvent(_e: Event): Promise<void> {
    const expr = this.expression;
    if (!expr) return;

    try {
      // Get the current environment from the closest runtime
      const runtime = this.closest('sl-runtime-meta') as RuntimeMetaElement | null;
      const env = runtime?.env ?? this._env;

      // Evaluate the expression — SlEnv is structurally compatible with SExpr[]
      const envAsSExpr = env as unknown as SExpr[];
      const result = await this.evalChild(expr, envAsSExpr);

      // Dispatch event with result
      this.dispatchEvent(new CustomEvent('sl-handled', {
        bubbles: true,
        detail: { event: this.eventType, result }
      }));

      // Trigger re-render of any sl-render elements
      document.querySelectorAll('sl-render').forEach(async (el) => {
        if (el instanceof SlMatrixElement) {
          await el.evalSelf(envAsSExpr);
        }
      });

      // Trigger re-bind of any sl-bind elements
      document.querySelectorAll('sl-bind').forEach(async (el) => {
        if (el instanceof SlMatrixElement) {
          await el.evalSelf(envAsSExpr);
        }
      });

    } catch (err) {
      this.errorKernel(`[sl-on] Error handling ${this.eventType}:`, err);
    }
  }

  /**
   * Evaluate - just stores the environment for later use.
   */
  async evalSelf(env: SExpr[]): Promise<SExpr> {
    this._env = env as SlEnv;
    return null;
  }

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

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