// src/framework/shadowlisp/SlMatrixElement.ts
//
// Vision 49: Base class for ShadowLisp elements that ARE Matrix components
//
// Each <sl-*> element:
// 1. Has a mount attribute
// 2. Subscribes to {mount}.accepts.eval on connectedCallback
// 3. Can be called via RequestReply
// 4. Evaluates children by calling their mounts

import type { SExpr } from '../../lisp/SExpr';
import type { IMatrixContext } from '../../engine/core/IMatrixContext';
import type { ITransportAdapter } from '../../engine/remoting/ITransportAdapter';
import { RequestReply } from '../../engine/remoting/RequestReply';
import type { ISlEvalContext, ISlEvaluable } from './primitives/ISlEvaluable';

const SlMatrixElementBase = HTMLElement;

/**
 * Global registry of all SlMatrixElements by mount.
 * This enables local evaluation without transport roundtrip.
 */
const elementRegistry = new Map<string, SlMatrixElement>();

/**
 * Get closest runtime ancestor (sl-runtime or sl-runtime-meta).
 */
function getClosestRuntime(el: HTMLElement): HTMLElement | null {
  return el.closest('sl-runtime, sl-runtime-meta');
}

/**
 * Get transport from closest runtime ancestor.
 */
function getTransportFromAncestor(el: HTMLElement): ITransportAdapter | null {
  const runtime = getClosestRuntime(el) as HTMLElement & { context?: IMatrixContext };
  return runtime?.context?.transport ?? null;
}

/**
 * Get context from closest runtime ancestor.
 */
function getContextFromAncestor(el: HTMLElement): IMatrixContext | null {
  const runtime = getClosestRuntime(el) as HTMLElement & { context?: IMatrixContext };
  return runtime?.context ?? null;
}

/**
 * Generate a unique mount path for an element based on its position.
 */
let mountCounter = 0;
function generateMount(el: HTMLElement, basePath: string): string {
  const tag = el.tagName.toLowerCase().replace('sl-', '');
  return `${basePath}.${tag}-${++mountCounter}`;
}

/**
 * SlMatrixElement - Base class for ShadowLisp web components that ARE Matrix components.
 *
 * Every element that extends this:
 * - Has a mount path (auto-generated or explicit)
 * - Subscribes to {mount}.accepts.eval
 * - Can be called via Matrix MCP
 * - Returns results via {mount}.emits.result
 *
 * Implements ISlEvaluable for compatibility with SlRuntimeMeta.
 */
export abstract class SlMatrixElement extends SlMatrixElementBase implements ISlEvaluable {
  protected _mount: string = '';
  protected _unsub: (() => void) | null = null;
  protected _context: IMatrixContext | null = null;

  static get observedAttributes(): string[] {
    return ['mount'];
  }

  /**
   * Get the mount path for this element.
   */
  get mount(): string {
    return this._mount;
  }

  protected debugKernel(message: string, ...args: unknown[]): void {
    // eslint-disable-next-line no-console -- kernel ShadowLisp element diagnostics have no actor context
    console.debug(message, ...args);
  }

  protected infoKernel(message: string, ...args: unknown[]): void {
    // eslint-disable-next-line no-console -- kernel ShadowLisp element diagnostics have no actor context
    console.info(message, ...args);
  }

  protected warnKernel(message: string, ...args: unknown[]): void {
    // eslint-disable-next-line no-console -- kernel ShadowLisp element diagnostics have no actor context
    console.warn(message, ...args);
  }

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

  /**
   * Subclasses implement this to perform evaluation.
   */
  abstract evalSelf(env: SExpr[]): Promise<SExpr>;

  /**
   * Convert to S-expression representation.
   */
  abstract toSExpr(): SExpr;

  /**
   * ISlEvaluable implementation - bridges to evalSelf.
   * This allows SlRuntimeMeta to call us via the standard interface.
   */
  async slEval(ctx: ISlEvalContext): Promise<SExpr> {
    // Cast SlEnv to SExpr[] for transport compatibility
    // (closures in env are handled specially by the runtime)
    return this.evalSelf(ctx.env as unknown as SExpr[]);
  }

  connectedCallback(): void {
    // Get or generate mount
    const explicitMount = this.getAttribute('mount');
    if (explicitMount) {
      this._mount = explicitMount;
    } else {
      // Generate mount from runtime's mount + element position
      const runtime = getClosestRuntime(this);
      const basePath = runtime?.getAttribute('mount') ?? 'shadowlisp';
      this._mount = generateMount(this, basePath);
      this.setAttribute('mount', this._mount);
    }

    // Register in global registry for local calls
    elementRegistry.set(this._mount, this);

    // Get context and subscribe to transport
    this._context = getContextFromAncestor(this);
    if (this._context?.transport) {
      this._subscribeToTransport();
    }

    // Dispatch event so parent knows we're ready
    this.dispatchEvent(new CustomEvent('sl-element-mounted', {
      bubbles: true,
      detail: { mount: this._mount, element: this }
    }));
  }

  disconnectedCallback(): void {
    // Unsubscribe from transport
    if (this._unsub) {
      this._unsub();
      this._unsub = null;
    }

    // Remove from registry
    elementRegistry.delete(this._mount);

    // Dispatch removal event
    this.dispatchEvent(new CustomEvent('sl-element-unmounted', {
      bubbles: true,
      detail: { mount: this._mount }
    }));
  }

  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
    if (name === 'mount' && newValue && newValue !== oldValue) {
      // Re-register with new mount
      if (oldValue) {
        elementRegistry.delete(oldValue);
      }
      this._mount = newValue;
      elementRegistry.set(this._mount, this);

      // Re-subscribe if connected
      if (this._unsub) {
        this._unsub();
        this._subscribeToTransport();
      }
    }
  }

  /**
   * Subscribe to eval requests via transport.
   */
  private _subscribeToTransport(): void {
    if (!this._context?.transport) return;

    this._unsub = RequestReply.bind(
      this._context,
      this._mount,
      'eval',
      async (params: { env?: SExpr[] }) => {
        const env = params?.env ?? [];
        const result = await this.evalSelf(env);
        return result;
      }
    );

    this.infoKernel(`[${this.tagName}] Subscribed at ${this._mount}`);
  }

  /**
   * Evaluate a child element by calling its mount.
   * Uses local registry if available, falls back to transport.
   */
  protected async evalChild(child: Element, env: SExpr[]): Promise<SExpr> {
    // If child has a mount, call it
    const childMount = child.getAttribute('mount');

    if (childMount) {
      // Try local first
      const localElement = elementRegistry.get(childMount);
      if (localElement) {
        return localElement.evalSelf(env);
      }

      // Fall back to transport
      if (this._context) {
        return RequestReply.execute(
          this._context,
          childMount,
          'eval',
          { env },
          { timeoutMs: 5000 }
        );
      }
    }

    // No mount - try to evaluate directly if it's an SlMatrixElement
    if (child instanceof SlMatrixElement) {
      return child.evalSelf(env);
    }

    // Text node or unknown
    return child.textContent?.trim() ?? null;
  }

  /**
   * Get all child elements that are evaluable.
   */
  protected getEvaluableChildren(): Element[] {
    return Array.from(this.children).filter(
      c => c instanceof SlMatrixElement || c.hasAttribute('mount')
    );
  }
}

/**
 * Get an element by its mount path.
 */
export function getElementByMount(mount: string): SlMatrixElement | undefined {
  return elementRegistry.get(mount);
}

/**
 * List all registered element mounts.
 */
export function listRegisteredMounts(): string[] {
  return Array.from(elementRegistry.keys());
}
