// src/framework/shadowlisp/SlModule.ts
//
// Vision 48: ShadowLisp Module Element
//
// Guy Steele (Scheme co-designer):
//   "Shadow DOM IS lexical scope. Variables defined inside a module
//    are private. Slots allow controlled parameter passing - just like lambda."
//
// Mark Miller (E language, Capability Security):
//   "Module boundaries are authority boundaries. Shadow DOM isolation
//    provides the same encapsulation as object-capability modules."
//
// <sl-module> creates a Shadow DOM boundary = lexical scope

import { Environment, SlRuntime } from './SlRuntime';
import { SlForm } from './SlForm';
import type { SExpr } from '../../lisp/SExpr';

const SlModuleElementBase = HTMLElement;

/**
 * SlModule - Shadow DOM scoped module element.
 *
 * Creates a lexical scope boundary using Shadow DOM.
 * Variables defined inside are private to the module.
 * Slots allow controlled composition.
 *
 * @example
 * <sl-module name="counter-module">
 *   <!-- Private state -->
 *   <sl-form op="define">
 *     <sl-atom kind="sym">count</sl-atom>
 *     <sl-atom kind="num">0</sl-atom>
 *   </sl-form>
 *
 *   <!-- Exposed via slot -->
 *   <slot name="display"></slot>
 * </sl-module>
 */
export class SlModule extends SlModuleElementBase {
  static observedAttributes = ['name'];

  private _shadowRoot: ShadowRoot;
  private _env?: Environment;
  private _parentRuntime?: SlRuntime;
  private _initialized = false;

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

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

  /**
   * Get the module name.
   */
  get name(): string {
    return this.getAttribute('name') ?? 'anonymous';
  }

  /**
   * Set the module name.
   */
  set name(value: string) {
    this.setAttribute('name', value);
  }

  /**
   * Get the module's environment.
   */
  get env(): Environment | undefined {
    return this._env;
  }

  /**
   * Get exports - bindings exposed to parent.
   */
  getExports(): Record<string, unknown> {
    if (!this._env) return {};

    // Only export bindings defined in this module (not inherited)
    return this._env.toRecord();
  }

  /**
   * Import a binding from another module.
   */
  import(name: string, value: unknown): void {
    if (!this._env) {
      throw new Error('Module not initialized');
    }
    this._env.define(name, value);
  }

  /**
   * Evaluate an S-expression in this module's environment.
   */
  async eval(sexp: SExpr): Promise<unknown> {
    if (!this._env || !this._parentRuntime) {
      throw new Error('Module not initialized');
    }
    // Delegate to parent runtime's evaluator with this module's env
    // For now, use the runtime's eval with our env
    // (In full implementation, we'd have a proper eval that uses this._env)
    return this._parentRuntime.eval(sexp);
  }

  /**
   * Initialize the module.
   */
  private _initialize(): void {
    if (this._initialized) return;

    // Find parent runtime
    this._parentRuntime = this.closest('sl-runtime') as SlRuntime | null ?? undefined;

    // Create child environment (lexical scope)
    const parentEnv = this._parentRuntime?.env ?? new Environment();
    this._env = parentEnv.extend();

    // Define module-specific bindings
    this._env.define('__module', this);
    this._env.define('__module-name', this.name);

    // Move original content to shadow DOM
    const originalContent = Array.from(this.childNodes);
    for (const node of originalContent) {
      this._shadowRoot.appendChild(node);
    }

    // Add default slot for passed content
    const slot = document.createElement('slot');
    this._shadowRoot.appendChild(slot);

    // Add styles
    const style = document.createElement('style');
    style.textContent = `
      :host {
        display: block;
        border: 1px dashed #9c27b0;
        border-radius: 4px;
        padding: 8px;
        margin: 4px 0;
      }
      :host::before {
        content: attr(name);
        display: block;
        font-size: 10px;
        color: #9c27b0;
        margin-bottom: 4px;
      }
    `;
    this._shadowRoot.insertBefore(style, this._shadowRoot.firstChild);

    this._initialized = true;

    // Evaluate forms in shadow DOM
    this._evaluateModuleForms();
  }

  /**
   * Evaluate all sl-form elements in this module's shadow DOM.
   */
  private async _evaluateModuleForms(): Promise<void> {
    if (!this._env) return;

    for (const child of Array.from(this._shadowRoot.children)) {
      if (child instanceof SlForm || child.tagName === 'SL-FORM') {
        const form = child as SlForm;
        try {
          const sexp = form.toSExpr();
          await this.eval(sexp);
        } catch (err) {
          this._errorKernel(`[SlModule:${this.name}] Error evaluating form:`, err, form);
        }
      }
    }
  }

  connectedCallback(): void {
    // Defer initialization to allow DOM to settle
    setTimeout(() => this._initialize(), 0);
  }

  disconnectedCallback(): void {
    // Cleanup
    this._env = undefined;
    this._parentRuntime = undefined;
    this._initialized = false;
  }

  attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void {
    if (name === 'name') {
      if (this._env) {
        this._env.define('__module-name', this.name);
      }
      this.dispatchEvent(new CustomEvent('sl-module-renamed', {
        bubbles: true,
        detail: { name: this.name }
      }));
    }
  }
}

// Define the custom element
if (!customElements.get('sl-module')) {
  customElements.define('sl-module', SlModule);
}
