// src/runtime/MatrixRuntime.ts
// Unified runtime with pluggable DOM strategy.
// FP-23: ONE runtime with pluggable IDomStrategy.

import { MatrixContext } from '../engine/core/MatrixContext';
import type { IMatrixContext } from '../engine/core/IMatrixContext';
import type { IComponentDescriptor } from '../engine/core/IComponentDescriptor';
import type { IMatrixRuntime, ICreateOrGetResult } from '../engine/core/IMatrixRuntime';
import type { IChildDescriptor } from '../engine/core/IChildDescriptor';
import type { MatrixActor } from '../core/MatrixActor';
import { RootActor } from '../core/RootActor';
import { MatrixActorHtmlElement } from '../framework/MatrixActorHtmlElement';
import type { IExternalAdapterOptions } from '../core/composite/IExternalAdapterOptions';
import {
  COMPONENT_RUNTIME_METADATA,
  type ComponentSourceKind,
  type ComponentVersionSource,
  type IComponentRuntimeMetadata,
} from '../core/composite/IComponentRuntimeMetadata';
// TODO: MatrixActorMixin — not yet implemented
// import { MatrixActorMixin } from '../framework/mixin';
import { HeadlessHost } from './HeadlessHost';
import { ShellElementRegistry } from './ComponentRegistry';
import { ConsoleLogger, NullLogger } from './ConsoleLogger';
import type { ITransportAdapter } from '../engine/remoting/ITransportAdapter';
import { MailboxFactory } from '../engine/mailbox/MailboxFactory';
import { TopicRouter } from '../engine/messaging/TopicRouter';
import type { MxEnvelope } from '../engine/messaging/MxEnvelope';
import type { IMailbox } from '../engine/mailbox/IMailbox';
import type { IRuntimeConfig } from './IRuntimeConfig';
import type { IDomStrategy } from './IDomStrategy';
import type { IRuntimeHost } from './IRuntimeHost';
import type { IRuntimeStats } from './IRuntimeStats';
import type { IRuntimeEvent, RuntimeEventListener } from './IRuntimeEvent';
import type { IRuntimeLogger } from './IRuntimeLogger';
import type { ITopicClaimRegistry } from '../core/security/ITopicClaimRegistry';
import { TopicClaimRegistry } from '../core/security/TopicClaimRegistry';
import { SubscriptionBag } from '../engine/messaging/SubscriptionBag';
import { InMemoryBroker } from '../transport/InMemoryBroker';
import { InMemoryTransport } from '../transport/InMemoryTransport';
import { RECORD_STORE_PROVIDER_KEY } from '../core/IRecordStoreProvider';

/**
 * Interface for shell/element duck-typing in mount/unmount.
 */
interface ShellElementMethods {
  mount?: (context: IMatrixContext) => Promise<void>;
  _receiveContext?: (context: IMatrixContext) => void;
  setContext?: (context: IMatrixContext) => void;
  ready?: Promise<void>;
  context?: IMatrixContext;
  getAttribute?: (name: string) => string | null;
  setAttribute?: (name: string, value: string) => void;
  id?: string;
}

type ContextIntrospection = IMatrixContext & {
  _services?: Map<unknown, unknown>;
  realm?: string;
};

type ExternalHTMLElementConstructor = (new (...args: unknown[]) => HTMLElement) & Record<string, unknown>;

type CognitiveActor = MatrixActor & {
  getCognitiveState?: () => Record<string, unknown>;
};

/**
 * Tracked component info.
 */
interface ComponentInfo {
  id: string;
  component: MatrixActor;
  mount: string;
  metadata?: IComponentRuntimeMetadata;
}

/**
 * MatrixRuntime — the unified runtime for Matrix actors.
 *
 * Supports both headless (no DOM) and browser (with IDomStrategy) modes.
 *
 * Headless mode: `new MatrixRuntime({ transport })`
 * Browser mode:  `new MatrixRuntime({ transport, domStrategy: new BrowserDomStrategy() })`
 */
export class MatrixRuntime implements IMatrixRuntime {
  // ─── Core infrastructure ───────────────────────────────────────────
  private readonly logger: IRuntimeLogger;
  private readonly rootContext: IMatrixContext;
  private readonly mailboxFactory: MailboxFactory;
  private readonly _components = new Map<string, ComponentInfo>();
  private readonly _subs = new SubscriptionBag();
  private readonly eventListeners: RuntimeEventListener[] = [];
  private readonly startTime: number;

  // ─── DOM support (optional) ────────────────────────────────────────
  private readonly domStrategy?: IDomStrategy;
  /** @deprecated Legacy host — kept during migration, will be removed */
  private readonly host?: IRuntimeHost;
  private readonly registry?: ShellElementRegistry;

  // ─── Security config ───────────────────────────────────────────────
  private readonly _allowDynamicCompilation: boolean;
  private readonly _enableTopicClaiming: boolean;
  private readonly _topicClaimRegistry: ITopicClaimRegistry;

  // ─── Root-level supervision (runtime as init/PID 0) ────────────────
  /** Blueprints for supervised root actors — class + props for restart */
  private readonly _rootBlueprints = new Map<string, { componentClass: new () => MatrixActor; props?: Record<string, unknown> }>();
  /** Restart timestamps per mount — mirrors SupervisorBehavior._tooManyRestarts */
  private readonly _rootRestartTracking = new Map<string, number[]>();
  /** Unsub functions for $exit monitors */
  private readonly _rootExitUnsubs = new Map<string, () => void>();
  /** Supervision policy for root actors */
  private readonly _rootSupervisionPolicy = { maxRestarts: 5, windowMs: 10000 };

  // ─── State ─────────────────────────────────────────────────────────
  private running = true;
  private shuttingDown = false;
  private mountCounter = 0;
  private _rootJoinUnsub?: () => void;
  /** Tracks registered external adapters */
  private _adapters = new Set<string>();
  /** Root actor — runtime's public face in the actor tree (mount: "") */
  private _rootActor?: RootActor;

  constructor(config: IRuntimeConfig = {}) {
    this.logger = config.logging
      ? new ConsoleLogger(true, '[MatrixRuntime]')
      : new NullLogger();

    // Security config
    this._allowDynamicCompilation = config.allowDynamicCompilation ?? true;
    this._enableTopicClaiming = config.enableTopicClaiming ?? false;
    this._topicClaimRegistry = config.topicClaimRegistry ?? new TopicClaimRegistry();

    // DOM strategy (optional — null = headless)
    this.domStrategy = config.domStrategy;

    // Always provide a headless host-backed registry so component registration
    // works in both DOM and non-DOM runtime modes.
    this.host = config.host ?? new HeadlessHost();
    this.registry = new ShellElementRegistry(this.host.customElements, this.logger);

    // Transport: use provided or create InMemoryTransport
    let transport: ITransportAdapter;
    if (config.transport) {
      transport = config.transport;
    } else {
      const broker = new InMemoryBroker();
      transport = new InMemoryTransport(broker, { name: 'matrix-runtime' });
    }
    this.rootContext = MatrixContext.create('', transport, this, undefined, undefined, config.securityRealm);

    // Register optional record-store provider on the root context. PackageRootActor
    // resolves it via cascade at bootstrap to obtain a per-package store, so core
    // never reaches into a sibling implementation package.
    if (config.recordStoreProvider) {
      this.rootContext.setService(RECORD_STORE_PROVIDER_KEY, config.recordStoreProvider);
    }

    // Initialize mailbox factory
    this.mailboxFactory = new MailboxFactory(transport, {
      defaultMailboxOptions: {
        backpressure: { highWaterMark: 1000, onFull: 'drop-oldest' },
      },
    });

    // Root $join/$join_ack handler — fallback until RootActor takes over.
    // Once RootActor is initialized, it handles $join via MatrixActor.on$join()
    // which also registers children in ChildManager for introspection.
    // This fallback ensures $join_ack is sent even during the async init window.
    const rootInbox = TopicRouter.inbox('');
    this._rootJoinUnsub = this.rootContext.transport.subscribe(
      rootInbox,
      (rawPayload: unknown) => {
        const envelope = rawPayload as MxEnvelope;
        if (!envelope || envelope.op !== '$join') return;
        // If RootActor is initialized, it handles $join — skip fallback
        if (this._rootActor?.isInitialized) return;
        const payload = envelope.payload as { id: string; mount: string; componentClass?: string };
        const childInbox = TopicRouter.inbox(payload.mount);
        const ackEnvelope: MxEnvelope = {
          op: '$join_ack',
          payload: { status: 'ok', supervisor: '' },
        };
        this.rootContext.transport.publish(childInbox, ackEnvelope);
        this.logger.debug(`Admitted root child (fallback): ${payload.id} at ${payload.mount}`);
      }
    );

    this.startTime = Date.now();

    // ── Root actor — runtime-as-actor at mount "" ──
    // Initialized asynchronously via _initRootActor() called from start/create.
    // Cannot await in constructor, so we defer to first use.
    this._initRootActorSync();

    this.logger.info('Runtime initialized');
    this.emit({ type: 'runtime:started', timestamp: this.startTime });
  }

  /**
   * Initialize the root actor synchronously (wire lambdas, defer initialize() to async path).
   * The root actor subscribes to $inbox at "" and handles $introspect/$ping.
   */
  private _initRootActorSync(): void {
    const rootActor = new RootActor();
    this._rootActor = rootActor;

    // Wire snapshot lambdas so root actor can report runtime state
    rootActor._componentSnapshot = () => {
      const result: Array<{ id: string; mount: string; metadata?: IComponentRuntimeMetadata }> = [];
      for (const [, info] of this._components) {
        result.push({ id: info.id, mount: info.mount, metadata: info.metadata });
      }
      return result;
    };

    rootActor._serviceSnapshot = () => {
      // Return well-known service endpoints from rootContext
      const services: Record<string, string> = {};
      try {
        const ctx = this.rootContext as ContextIntrospection;
        if (ctx._services instanceof Map) {
          for (const [key, _val] of ctx._services) {
            if (typeof key === 'string') services[key] = key;
          }
        }
      } catch { /* service introspection best-effort */ }
      return services;
    };

    rootActor._runtimeMeta = () => ({
      realm: (this.rootContext as ContextIntrospection).realm ?? 'unknown',
      type: this.type,
      version: '3.4.0',
      running: this.running,
    });

    rootActor._registeredSnapshot = () => ({
      componentTags: this.registry?.listRegisteredTags() ?? [],
      supervisedRoots: Array.from(this._rootBlueprints.keys()).sort((left, right) => left.localeCompare(right)),
    });
    rootActor._startComponent = async (mount: string) => {
      return await this.startRoot(mount);
    };
    rootActor._stopComponent = async (mount: string) => {
      return await this.stopRoot(mount);
    };
    rootActor._reloadComponent = async (mount: string) => {
      await this.reloadRoot(mount);
    };
    rootActor._removeComponent = async (mount: string) => {
      await this.remove(mount);
    };
    rootActor._restartComponent = async (mount: string) => {
      await this.restartRoot(mount);
    };

    // Initialize root actor with root context (async, fire-and-forget from constructor)
    // This subscribes it to $inbox at "" for $introspect/$ping
    rootActor.initialize(this.rootContext, '').catch(err => {
      // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
      console.error('[MatrixRuntime] Failed to initialize root actor:', err);
    });
  }

  // ═════════════════════════════════════════════════════════════════════
  // IMatrixRuntime interface — type & capabilities
  // ═════════════════════════════════════════════════════════════════════

  get type(): 'headless' | 'browser' {
    return this.domStrategy ? 'browser' : 'headless';
  }

  get supportsDom(): boolean {
    return !!this.domStrategy;
  }

  // ═════════════════════════════════════════════════════════════════════
  // Component creation
  // ═════════════════════════════════════════════════════════════════════

  /**
   * Create a child component (UI Control Inversion).
   * Supports 5 instantiation modes: create_new, reuse_existing, singleton, replace, create_unique.
   */
  async createChild(
    parentContext: IMatrixContext,
    id: string,
    descriptor: IComponentDescriptor
  ): Promise<string> {
    const result = await this._createChildInternal(parentContext, id, descriptor);
    return result.mount;
  }

  /**
   * Create or get existing child (Vision 21 Pillar 6: createOrGet semantics).
   */
  async createOrGetChild(
    parentContext: IMatrixContext,
    id: string,
    descriptor: IComponentDescriptor
  ): Promise<ICreateOrGetResult> {
    return this._createChildInternal(parentContext, id, {
      ...descriptor,
      instantiation: 'reuse_existing',
    });
  }

  /**
   * Convenience method — create a component directly at root level.
   */
  async create<T extends MatrixActor>(
    ComponentClass: new () => T,
    id?: string,
    props?: Record<string, unknown>
  ): Promise<T> {
    this.ensureRunning();
    const componentId = id ?? `component-${++this.mountCounter}`;

    if (this._components.has(componentId)) {
      throw new Error(`Component '${componentId}' already exists`);
    }

    const component = new ComponentClass();
    if (props) {
      this._assignProps(component, props);
    }

    const context = this.rootContext.derive(componentId);
    const metadata = this._resolveRuntimeMetadata(ComponentClass, props, 'runtime-direct');
    this._applyRuntimeMetadata(context, metadata);
    this._components.set(componentId, { id: componentId, component, mount: context.mount, metadata });

    if (!this._registerTopicClaim(context.mount, undefined)) {
      this._components.delete(componentId);
      throw new Error(`Failed to register root topic claim for '${context.mount}'`);
    }

    await component.initialize(context, componentId);
    this.logger.debug(`Created component: ${componentId} at ${context.mount}`);
    return component;
  }

  /**
   * Create a supervised root-level actor.
   * The runtime stores a blueprint (class + props) and monitors the actor's
   * $exit signal. On crash, restarts from blueprint if within restart budget.
   * Follows OTP: runtime is init/PID 0, supervision is orthogonal to naming.
   */
  async createSupervised<T extends MatrixActor>(
    ComponentClass: new () => T,
    id: string,
    props?: Record<string, unknown>
  ): Promise<T> {
    // Store blueprint before creation (needed for restart)
    this._rootBlueprints.set(id, {
      componentClass: ComponentClass as unknown as new () => MatrixActor,
      props,
    });

    const component = await this.create(ComponentClass, id, props);

    // Monitor $exit signal for supervision
    this._monitorRootActor(id);

    return component;
  }

  /**
   * Subscribe to a root actor's $exit topic for supervision.
   */
  private _monitorRootActor(mount: string): void {
    // Clean up any prior monitor (e.g., from a restart)
    this._rootExitUnsubs.get(mount)?.();

    const exitTopic = `${mount}.$exit`;
    const unsub = this.rootContext.transport.subscribe(exitTopic, (payload) => {
      const signal = payload as { type: string; from: string; reason: string; error?: string };
      if (signal?.type === 'exit' && signal?.reason === 'error') {
        this._handleRootActorExit(mount, signal).catch(err => {
          // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
          console.error(`[MatrixRuntime] Error handling root actor exit:`, err);
        });
      }
    });
    this._rootExitUnsubs.set(mount, unsub);
  }

  /**
   * Handle a root actor crash. Check restart budget, then recreate from blueprint.
   * Mirrors SupervisorBehavior._restartChild + _tooManyRestarts.
   */
  private async _handleRootActorExit(
    mount: string,
    signal: { reason: string; error?: string }
  ): Promise<void> {
    const blueprint = this._rootBlueprints.get(mount);
    if (!blueprint) {
      // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
      console.warn(`[MatrixRuntime] No blueprint for crashed root actor: ${mount}`);
      return;
    }

    // Check restart budget
    const now = Date.now();
    let timestamps = this._rootRestartTracking.get(mount) ?? [];
    timestamps = timestamps.filter(ts => now - ts < this._rootSupervisionPolicy.windowMs);
    timestamps.push(now);
    this._rootRestartTracking.set(mount, timestamps);

    if (timestamps.length > this._rootSupervisionPolicy.maxRestarts) {
      // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
      console.error(
        `[MatrixRuntime] Root actor ${mount} exceeded max restarts ` +
        `(${this._rootSupervisionPolicy.maxRestarts} in ${this._rootSupervisionPolicy.windowMs}ms). ` +
        `Giving up. Last error: ${signal.error ?? signal.reason}`
      );
      this._rootBlueprints.delete(mount);
      this._rootExitUnsubs.get(mount)?.();
      this._rootExitUnsubs.delete(mount);
      return;
    }

    // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
    console.log(
      `[MatrixRuntime] Restarting root actor ${mount} ` +
      `(restart ${timestamps.length}/${this._rootSupervisionPolicy.maxRestarts}, ` +
      `reason: ${signal.error ?? signal.reason})`
    );

    // 1. Remove the crashed actor
    await this.remove(mount);

    // 2. Recreate from blueprint
    try {
      const component = await this.create(
        blueprint.componentClass,
        mount,
        blueprint.props,
      );

      // 3. Re-monitor
      this._monitorRootActor(mount);

      this.logger.info(`Restarted root actor: ${mount}`);
    } catch (err) {
      // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
      console.error(`[MatrixRuntime] Failed to restart root actor ${mount}:`, err);
    }
  }

  /**
   * Internal createChild with full instantiation mode support.
   */
  private async _createChildInternal(
    parentContext: IMatrixContext,
    id: string,
    descriptor: IComponentDescriptor
  ): Promise<{ mount: string; created: boolean; reused: boolean }> {
    this.ensureRunning();

    const { componentClass } = descriptor;
    const instantiation = descriptor.instantiation ?? 'create_new';

    const validModes = ['create_new', 'reuse_existing', 'singleton', 'replace', 'create_unique'];
    if (!validModes.includes(instantiation)) {
      throw new Error(`Unknown instantiation mode: ${instantiation}. Valid: ${validModes.join(', ')}`);
    }

    // Derive expected mount path (empty-mount guard)
    const expectedMount = parentContext.mount ? `${parentContext.mount}.${id}` : id;

    // Check topic claiming authority
    if (!this._canCreateChild(parentContext.mount, expectedMount)) {
      throw new Error(
        `Topic claiming violation: Parent '${parentContext.mount}' has no authority to create child at '${expectedMount}'.`
      );
    }

    // Check for existing component
    const existingInfo = this._components.get(expectedMount);

    if (existingInfo) {
      switch (instantiation) {
        case 'reuse_existing':
          this.logger.debug(`Reusing existing component at ${expectedMount}`);
          return { mount: expectedMount, created: false, reused: true };

        case 'singleton':
          throw new Error(`Singleton component already exists at ${expectedMount}`);

        case 'replace':
          this.logger.debug(`Replacing existing component at ${expectedMount}`);
          await this.remove(expectedMount);
          break;

        case 'create_new': {
          this.logger.debug(`Creating additional instance at ${expectedMount} (create_new)`);
          const uniqueId = `${id}.${this._generateUniqueId()}`;
          return this._createNewComponent(parentContext, uniqueId, componentClass, descriptor.props);
        }

        case 'create_unique':
          break;

        default:
          throw new Error(`Unknown instantiation mode: ${instantiation}`);
      }
    }

    if (instantiation === 'create_unique') {
      const uniqueId = `${id}.${this._generateUniqueId()}`;
      return this._createNewComponent(parentContext, uniqueId, componentClass, descriptor.props);
    }

    return this._createNewComponent(parentContext, id, componentClass, descriptor.props);
  }

  private async _createNewComponent(
    parentContext: IMatrixContext,
    id: string,
    componentClass: new () => unknown,
    props?: Record<string, unknown>
  ): Promise<{ mount: string; created: boolean; reused: boolean }> {
    const component = new componentClass() as MatrixActor;

    const childContext = parentContext.derive(id);
    const metadata = this._resolveRuntimeMetadata(componentClass, props, 'runtime-child', parentContext);
    this._applyRuntimeMetadata(childContext, metadata);

    // MatrixActorHtmlElement (custom elements) don't have initialize() —
    // they're initialized when added to DOM via _receiveContext.
    // The parent shell creates the DOM element in _handleChildAdded.
    if (typeof component.initialize === 'function') {
      if (props) {
        this._assignProps(component, props);
      }

      await component.initialize(childContext, id);

      this._components.set(childContext.mount, { id, component, mount: childContext.mount, metadata });

      if (!this._registerTopicClaim(childContext.mount, parentContext.mount)) {
        this._components.delete(childContext.mount);
        try { component.dispose(); } catch { /* ignore */ }
        throw new Error(`Failed to register topic claim for '${childContext.mount}'`);
      }

    }

    this.logger.debug(`Created child: ${id} at ${childContext.mount}`);
    return { mount: childContext.mount, created: true, reused: false };
  }

  private _generateUniqueId(): string {
    return Math.random().toString(36).substring(2, 10);
  }

  private _applyRuntimeMetadata(context: IMatrixContext, metadata: IComponentRuntimeMetadata): void {
    (context as MatrixContext).setService(COMPONENT_RUNTIME_METADATA, metadata);
  }

  private _resolveRuntimeMetadata(
    componentClass: new () => unknown,
    props: Record<string, unknown> | undefined,
    sourceKind: ComponentSourceKind,
    parentContext?: IMatrixContext,
  ): IComponentRuntimeMetadata {
    const classRecord = componentClass as unknown as Record<string, unknown>;
    const staticMetadata = this._asRuntimeMetadata(classRecord.runtimeMetadata);
    const propMetadata = this._asRuntimeMetadata(props?.componentMetadata);
    const inheritedMetadata = parentContext?.getService<IComponentRuntimeMetadata>(COMPONENT_RUNTIME_METADATA);
    const inheritedVersionSource = inheritedMetadata?.versionSource ?? 'undeclared';

    const version = propMetadata?.version ?? staticMetadata?.version ?? inheritedMetadata?.version;
    const resolvedVersionSource: ComponentVersionSource =
      propMetadata?.versionSource
      ?? staticMetadata?.versionSource
      ?? (version ? inheritedVersionSource : 'undeclared');
    const resolvedSourceKind =
      propMetadata?.sourceKind
      ?? staticMetadata?.sourceKind
      ?? inheritedMetadata?.sourceKind
      ?? sourceKind;

    return {
      ...(inheritedMetadata ?? {}),
      ...(staticMetadata ?? {}),
      ...(propMetadata ?? {}),
      sourceKind: resolvedSourceKind,
      versionSource: resolvedVersionSource,
    };
  }

  private _asRuntimeMetadata(value: unknown): IComponentRuntimeMetadata | undefined {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
      return undefined;
    }
    return value as IComponentRuntimeMetadata;
  }

  private _assignProps(component: MatrixActor, props: Record<string, unknown>): void {
    for (const [key, value] of Object.entries(props)) {
      const descriptor = this._getPropertyDescriptor(component, key);
      if (descriptor && descriptor.get && !descriptor.set) {
        (component as unknown as { _props?: Record<string, unknown> })._props =
          (component as unknown as { _props?: Record<string, unknown> })._props || {};
        (component as unknown as { _props: Record<string, unknown> })._props[key] = value;
      } else {
        (component as Record<string, unknown>)[key] = value;
      }
    }
  }

  private _getPropertyDescriptor(obj: object, key: string): PropertyDescriptor | undefined {
    let current: object | null = obj;
    while (current) {
      const descriptor = Object.getOwnPropertyDescriptor(current, key);
      if (descriptor) return descriptor;
      const next = Object.getPrototypeOf(current) as object | null;
      current = next;
    }
    return undefined;
  }

  // ═════════════════════════════════════════════════════════════════════
  // Mailbox (Carl Hewitt's Actor Model)
  // ═════════════════════════════════════════════════════════════════════

  createMailbox(mount: string): IMailbox {
    this.ensureRunning();
    const mailboxAddress = `${mount}.$inbox`;

    if (this.mailboxFactory.has(mailboxAddress)) {
      const existing = this.mailboxFactory.get(mailboxAddress)!;
      // If existing mailbox is closed (e.g., from supervisor restart), recreate
      if (existing.closed) {
        this.logger.debug(`Existing mailbox for ${mount} is closed, recreating`);
        this.mailboxFactory.remove(mailboxAddress);
      } else {
        this.logger.debug(`Mailbox already exists for ${mount}, returning existing`);
        return existing;
      }
    }

    const mailbox = this.mailboxFactory.create(mailboxAddress);
    this.logger.debug(`Created mailbox: ${mailboxAddress}`);
    return mailbox;
  }

  // ═════════════════════════════════════════════════════════════════════
  // Component removal
  // ═════════════════════════════════════════════════════════════════════

  async remove(mount: string): Promise<void> {
    const info = this._components.get(mount);
    if (!info) {
      this.logger.debug(`Component '${mount}' not found for removal`);
      return;
    }

    this._releaseTopicClaim(info.mount, 'component_removed');

    try {
      info.component.dispose();
    } catch (err) {
      this.logger.error(`Error disposing component '${mount}':`, err as Error);
    }

    this._components.delete(mount);
    this.logger.debug(`Removed component: ${mount}`);
  }

  async startRoot(mount: string): Promise<boolean> {
    const blueprint = this._rootBlueprints.get(mount);
    if (!blueprint) {
      throw new Error(`Component '${mount}' is not registered as a supervised root`);
    }

    if (this._components.has(mount)) {
      this.logger.debug(`Supervised root '${mount}' is already running`);
      return false;
    }

    await this.create(
      blueprint.componentClass,
      mount,
      blueprint.props,
    );

    this._monitorRootActor(mount);
    this.logger.debug(`Started supervised root: ${mount}`);
    return true;
  }

  async stopRoot(mount: string): Promise<boolean> {
    if (!this._rootBlueprints.has(mount)) {
      throw new Error(`Component '${mount}' is not registered as a supervised root`);
    }

    if (!this._components.has(mount)) {
      this.logger.debug(`Supervised root '${mount}' is already stopped`);
      return false;
    }

    await this.remove(mount);
    this.logger.debug(`Stopped supervised root: ${mount}`);
    return true;
  }

  async reloadRoot(mount: string): Promise<void> {
    await this.restartRoot(mount);
  }

  async restartRoot(mount: string): Promise<void> {
    const blueprint = this._rootBlueprints.get(mount);
    if (!blueprint) {
      throw new Error(`Component '${mount}' is not registered as a supervised root`);
    }

    if (this._components.has(mount)) {
      await this.remove(mount);
    }

    await this.create(
      blueprint.componentClass,
      mount,
      blueprint.props,
    );

    this._monitorRootActor(mount);
    this.logger.debug(`Restarted supervised root: ${mount}`);
  }

  unregisterRoot(mount: string): void {
    this._rootBlueprints.delete(mount);
    this._rootRestartTracking.delete(mount);
    this._rootExitUnsubs.get(mount)?.();
    this._rootExitUnsubs.delete(mount);
  }

  // ═════════════════════════════════════════════════════════════════════
  // Component queries
  // ═════════════════════════════════════════════════════════════════════

  hasComponent(mount: string): boolean {
    return this._components.has(mount);
  }

  /**
   * @deprecated Use transport-based communication instead.
   */
  getComponent<T extends MatrixActor>(id: string): T | undefined {
    return this._components.get(id)?.component as T | undefined;
  }

  getAllComponents(): MatrixActor[] {
    return Array.from(this._components.values()).map(info => info.component);
  }

  async waitForReady(id: string, timeoutMs: number = 5000): Promise<void> {
    const info = this._components.get(id);
    if (!info) throw new Error(`Component '${id}' not found`);
    let timer: ReturnType<typeof setTimeout>;
    const timeoutPromise = new Promise<never>((_, reject) => {
      timer = setTimeout(() => reject(new Error(`Timeout waiting for ${id} to be ready`)), timeoutMs);
    });
    try {
      await Promise.race([info.component.ready, timeoutPromise]);
    } finally {
      clearTimeout(timer!);
    }
  }

  // ═════════════════════════════════════════════════════════════════════
  // Direct transport convenience methods
  // ═════════════════════════════════════════════════════════════════════

  subscribe(
    topic: string,
    handler: (payload: unknown, meta: { topic: string; ts: number }) => void
  ): () => void {
    const off = this.rootContext.transport.subscribe(topic, handler);
    this._subs.add(off);
    return off;
  }

  publish(topic: string, payload?: unknown): void {
    this.rootContext.transport.publish(topic, payload);
  }

  sendTo(mount: string, command: string, payload?: unknown): void {
    const inboxTopic = TopicRouter.inbox(mount);
    const envelope: MxEnvelope = { op: command, payload: payload ?? {} };
    this.rootContext.transport.publish(inboxTopic, envelope);
  }

  /**
   * Register a well-known service on the root context.
   * All child contexts inherit services from their parent chain,
   * so any actor in this runtime can discover the service via context.getService().
   */
  registerService<T>(key: string | symbol, service: T): void {
    (this.rootContext as MatrixContext).setService(key, service);
  }

  onEvent(
    mount: string,
    event: string,
    handler: (payload: unknown) => void
  ): () => void {
    const eventsTopic = TopicRouter.events(mount);
    return this.rootContext.transport.subscribe(eventsTopic, (payload: unknown) => {
      const envelope = payload as MxEnvelope;
      if (envelope?.op === event) {
        handler(envelope.payload);
      }
    });
  }

  // ═════════════════════════════════════════════════════════════════════
  // Template parsing
  // ═════════════════════════════════════════════════════════════════════

  async parseTemplate(_html: string): Promise<IChildDescriptor[]> {
    return [];
  }

  // ═════════════════════════════════════════════════════════════════════
  // DOM operations (delegate to domStrategy if present)
  // ═════════════════════════════════════════════════════════════════════

  registerComponent(tag: string, constructor: CustomElementConstructor): void {
    this.ensureRunning();
    if (this.registry) {
      const errors = this.registry.validate(tag, constructor);
      if (errors.length) {
        throw new Error(`Component validation failed: ${errors.join(', ')}`);
      }
      this.registry.register(tag, constructor);
    } else if (this.domStrategy) {
      this.domStrategy.registerElement(tag, constructor);
    }
    this.emit({ type: 'component:registered', tag, timestamp: Date.now() });
  }

  registerComponents(components: Record<string, CustomElementConstructor>): void {
    for (const [tag, ctor] of Object.entries(components)) {
      this.registerComponent(tag, ctor);
    }
  }

  async createElement<T extends HTMLElement = HTMLElement>(
    tag: string,
    attributes?: Record<string, string>
  ): Promise<T> {
    this.ensureRunning();

    if (this.domStrategy) {
      return this.domStrategy.createElement(tag, attributes) as unknown as T;
    }

    if (!this.registry?.isRegistered(tag)) {
      throw new Error(`Component '${tag}' is not registered`);
    }

    const ctor = this.registry.getConstructor(tag);
    let element: T;
    if (ctor) {
      element = new ctor() as unknown as T;
    } else {
      element = this.host!.createElement(tag) as unknown as T;
    }

    if (attributes) {
      for (const [name, value] of Object.entries(attributes)) {
        if (typeof (element as unknown as ShellElementMethods).setAttribute === 'function') {
          element.setAttribute(name, value);
        } else {
          (element as unknown as Record<string, string>)[name] = value;
        }
      }
    }

    this.logger.debug(`Created element: ${tag}`, attributes);
    return element;
  }

  async mount(element: HTMLElement, parentContext?: IMatrixContext): Promise<void> {
    this.ensureRunning();

    const context = parentContext ?? this.createChildContext(this.rootContext, element);
    const tag = element.localName;

    try {
      const el = element as unknown as ShellElementMethods;

      const isMatrixElement = typeof el.mount === 'function' && typeof el._receiveContext === 'function';
      const isFabricElement = typeof el.setContext === 'function';

      if (isMatrixElement) {
        this.host?.document.body.appendChild(element);
        await (el.mount as (ctx: IMatrixContext) => Promise<void>)(context);
        await el.ready;
      } else if (isFabricElement) {
        (el.setContext as (ctx: IMatrixContext) => void)(context);
        this.host?.document.body.appendChild(element);
        if (el.ready && typeof el.ready.then === 'function') {
          await el.ready;
        }
      } else {
        this.host?.document.body.appendChild(element);
      }

      const mountPath = context.mount;
      this.registry?.trackInstance(tag, element, { context: { mount: mountPath } }, mountPath);

      this.logger.info(`Mounted component: ${tag} at ${mountPath}`);
      this.emit({ type: 'component:mounted', tag, mountPath, timestamp: Date.now() });
    } catch (error) {
      const err = error as Error;
      this.logger.error(`Failed to mount component: ${tag}`, err);
      this.emit({ type: 'error', message: `Failed to mount: ${tag}`, error: err, timestamp: Date.now() });
      throw error;
    }
  }

  async unmount(element: HTMLElement): Promise<void> {
    this.ensureRunning();

    const tag = element.localName;
    const el = element as unknown as ShellElementMethods;
    const mountPath = el.context?.mount ?? 'unknown';

    try {
      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }

      this.registry?.untrackInstance(element);
      this.logger.info(`Unmounted component: ${tag} from ${mountPath}`);
      this.emit({ type: 'component:unmounted', tag, mountPath, timestamp: Date.now() });
    } catch (error) {
      this.logger.error(`Failed to unmount component: ${tag}`, error as Error);
      throw error;
    }
  }

  // ═════════════════════════════════════════════════════════════════════
  // Lifecycle
  // ═════════════════════════════════════════════════════════════════════

  get isRunning(): boolean {
    return this.running;
  }

  get isShuttingDown(): boolean {
    return this.shuttingDown;
  }

  async shutdown(): Promise<void> {
    if (!this.running) {
      this.logger.warn('Runtime already stopped');
      return;
    }

    this.logger.info('Shutting down runtime...');
    this.shuttingDown = true;

    try {
      // Unmount DOM components if registry is active
      if (this.registry) {
        const instances = this.registry.getAllInstances();
        for (const { element } of instances) {
          try { await this.unmount(element); } catch (error) {
            this.logger.error('Error unmounting during shutdown', error as Error);
          }
        }
        this.registry.clearInstances();
      }

      // Dispose all headless-tracked components
      for (const [mount] of this._components) {
        try { await this.remove(mount); } catch (err) {
          this.logger.error('Error removing component during shutdown', err as Error);
        }
      }

      this.mailboxFactory.closeAll();
      this._subs.dispose();

      // Clean up root supervision monitors
      for (const unsub of this._rootExitUnsubs.values()) {
        try { unsub(); } catch { /* ignore */ }
      }
      this._rootExitUnsubs.clear();
      this._rootBlueprints.clear();
      this._rootRestartTracking.clear();

      if (this._rootJoinUnsub) {
        try { this._rootJoinUnsub(); } catch { }
        this._rootJoinUnsub = undefined;
      }

      this.host?.dispose();
      this.running = false;

      const shutdownTime = Date.now();
      this.logger.info(`Runtime stopped after ${shutdownTime - this.startTime}ms`);
      this.emit({ type: 'runtime:stopped', timestamp: shutdownTime });
    } catch (error) {
      this.logger.error('Error during shutdown', error as Error);
      throw error;
    } finally {
      this.shuttingDown = false;
    }
  }

  // ═════════════════════════════════════════════════════════════════════
  // Accessors
  // ═════════════════════════════════════════════════════════════════════

  getRootContext(): IMatrixContext {
    return this.rootContext;
  }

  /** Get the root actor (runtime-as-actor at mount ""). */
  getRootActor(): RootActor | undefined {
    return this._rootActor;
  }

  getHost(): IRuntimeHost | undefined {
    return this.host;
  }

  getDomStrategy(): IDomStrategy | undefined {
    return this.domStrategy;
  }

  get transport(): ITransportAdapter {
    return this.rootContext.transport;
  }

  get rootMount(): string {
    return this.rootContext.mount;
  }

  getStats(): IRuntimeStats {
    const regStats = this.registry?.getStats();
    return {
      registeredComponents: regStats?.registeredComponents ?? 0,
      activeComponents: (regStats?.activeInstances ?? 0) + this._components.size,
      totalComponentsCreated: regStats?.totalInstancesCreated ?? this._components.size,
      uptime: Date.now() - this.startTime,
    };
  }

  getActiveComponents() {
    return this.registry?.getAllInstances() ?? [];
  }

  getComponentsByTag(tag: string) {
    return this.registry?.getInstances(tag) ?? [];
  }

  async whenComponentDefined(tag: string): Promise<void> {
    await this.registry?.whenDefined(tag);
  }

  // ═════════════════════════════════════════════════════════════════════
  // Security policy
  // ═════════════════════════════════════════════════════════════════════

  isDynamicCompilationAllowed(): boolean {
    return this._allowDynamicCompilation;
  }

  // ═════════════════════════════════════════════════════════════════════
  // Topic claiming (Vision 27 §13.5)
  // ═════════════════════════════════════════════════════════════════════

  isTopicClaimingEnabled(): boolean {
    return this._enableTopicClaiming;
  }

  getTopicClaimRegistry(): ITopicClaimRegistry {
    return this._topicClaimRegistry;
  }

  private _registerTopicClaim(mount: string, parentMount?: string): boolean {
    if (!this._enableTopicClaiming) return true;
    const result = this._topicClaimRegistry.claim(mount, mount, { delegatedBy: parentMount });
    if (result.success) {
      this.logger.debug(`Claimed topic prefix '${mount}'`);
      return true;
    }
    this.logger.debug(`Failed to claim topic prefix '${mount}': ${result.error}`);
    return false;
  }

  private _releaseTopicClaim(mount: string, reason = 'component_removed'): void {
    if (!this._enableTopicClaiming) return;
    const count = this._topicClaimRegistry.revokeAll(mount, reason);
    if (count > 0) {
      this.logger.debug(`Released ${count} topic claim(s) for ${mount} (${reason})`);
    }
  }

  private _canCreateChild(parentMount: string, childMount: string): boolean {
    if (!this._enableTopicClaiming) return true;
    return this._topicClaimRegistry.canCreate(childMount, parentMount);
  }

  // ═════════════════════════════════════════════════════════════════════
  // External Adapter Pattern
  // ═════════════════════════════════════════════════════════════════════

  registerExternalAdapter(
    id: string,
    componentClass: new () => unknown,
    options?: IExternalAdapterOptions
  ): string | undefined {
    if (typeof customElements === 'undefined') return undefined;

    const tag = options?.tag || id;
    if (customElements.get(tag)) {
      this._adapters.add(tag);
      return tag;
    }

    const template = this._buildAdapterTemplate(options?.template, options?.styles);
    this._registerBasicAdapter(tag, template);

    this._adapters.add(tag);
    this.logger.debug(`Registered external adapter: <${tag}>`);
    return tag;
  }

  /**
   * Register a foreign web component class as a Matrix actor (M1: FP-1.3).
   *
   * Applies MatrixActorMixin to the foreign class, copies static metadata,
   * and defines a custom element. The resulting element has both the foreign
   * class's behavior AND the Matrix protocol (`.mx` facade).
   *
   * @param tagName - Custom element tag (e.g., 'mx-sl-button')
   * @param ForeignClass - Any HTMLElement subclass (e.g., SlButton)
   * @param config - Matrix metadata: accepts, emits, description
   * @returns The tag name, or undefined if custom elements unavailable
   *
   * @example
   * ```typescript
   * runtime.registerExternalComponent('mx-sl-button', SlButton, {
   *   accepts: { 'button.click': {} },
   *   emits: { 'button.clicked': {} },
   *   description: 'Shoelace button with Matrix protocol',
   * });
   * ```
   */
  registerExternalComponent(
    tagName: string,
    ForeignClass: new (...args: unknown[]) => HTMLElement,
    config?: {
      accepts?: Record<string, unknown>;
      emits?: Record<string, unknown>;
      description?: string;
    },
  ): string | undefined {
    if (typeof customElements === 'undefined') return undefined;
    if (customElements.get(tagName)) {
      this._adapters.add(tagName);
      return tagName;
    }

    // Apply mixin to foreign class (TODO: MatrixActorMixin not yet implemented)
    const MixedClass = ForeignClass as ExternalHTMLElementConstructor;

    // Copy static metadata
    const mc = MixedClass as Record<string, unknown>;
    if (config?.accepts) mc.accepts = config.accepts;
    if (config?.emits) mc.emits = config.emits;
    if (config?.description) mc.description = config.description;

    customElements.define(tagName, MixedClass);
    this._adapters.add(tagName);
    this.logger.info(`Registered external component: <${tagName}> (mixin-wrapped)`);
    return tagName;
  }

  unregisterExternalAdapter(id: string): void {
    this._adapters.delete(id);
  }

  hasExternalAdapter(id: string): boolean {
    return this._adapters.has(id) || (typeof customElements !== 'undefined' && !!customElements.get(id));
  }

  // ═════════════════════════════════════════════════════════════════════
  // Events
  // ═════════════════════════════════════════════════════════════════════

  on(listener: RuntimeEventListener): () => void {
    this.eventListeners.push(listener);
    return () => {
      const index = this.eventListeners.indexOf(listener);
      if (index >= 0) this.eventListeners.splice(index, 1);
    };
  }

  private emit(event: IRuntimeEvent): void {
    for (const listener of this.eventListeners) {
      try { listener(event); } catch (error) {
        // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
        console.error('[MatrixRuntime] Event listener error:', error);
      }
    }
  }

  // ═════════════════════════════════════════════════════════════════════
  // Private helpers
  // ═════════════════════════════════════════════════════════════════════

  private createChildContext(parentContext: IMatrixContext, element: HTMLElement): IMatrixContext {
    const el = element as unknown as ShellElementMethods;
    const explicitMount = typeof el.getAttribute === 'function'
      ? el.getAttribute('mount')
      : null;

    if (explicitMount) {
      const childRegistry = parentContext.registry?.createChild();
      return MatrixContext.fromOptions({
        mount: explicitMount,
        transport: this.rootContext.transport,
        parent: parentContext,
        runtime: this,
        ...(childRegistry ? { registry: childRegistry } : {}),
        ...(parentContext.lispEnv ? { lispEnv: parentContext.lispEnv } : {}),
        ...(parentContext.securityRealm ? { securityRealm: parentContext.securityRealm } : {}),
      });
    }

    const candidate = [explicitMount, element.getAttribute('name'), element.id]
      .map(v => (typeof v === 'string' && v.trim().length > 0 ? v : null))
      .find((v): v is string => v !== null);

    const baseSegment = candidate ?? `${element.localName}-${++this.mountCounter}`;
    return parentContext.derive(baseSegment);
  }

  private ensureRunning(): void {
    if (!this.running) throw new Error('Runtime is not running');
  }

  private _buildAdapterTemplate(template?: string, styles?: string): string {
    if (!template && !styles) return '<slot></slot>';
    let result = '';
    if (styles) result += `<style>${styles}</style>`;
    if (template) result += template;
    return result;
  }

  private _registerBasicAdapter(
    tag: string,
    template: string
  ): void {
    // Capture stateKeys from the schema so getCognitiveState can expose them.
    // The keys are set by setComponent() once the actor is wired.
    const adapterTemplate = template;

    class DynamicElement extends MatrixActorHtmlElement {
      /** The backing schema actor (DynamicComponent from SchemaEvaluator). */
      private _schemaComponent: MatrixActor | null = null;
      /** State keys declared in the schema — auto-exposed via getCognitiveState. */
      private _stateKeys: string[] = [];

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

      /**
       * Override connectedCallback: skip parent-search auto-pull.
       * Schema components manage their own context via setComponent().
       * The parent's _getDirectChildShells() still finds us by instanceof.
       */
      override connectedCallback(): void {
        this._connected = true;
        // Render template immediately
        if (adapterTemplate && this.shadowRoot) {
          this.shadowRoot.innerHTML = adapterTemplate;
        }
      }

      /** Schema components don't have a separate actor class — the actor is set externally. */
      protected getActorClass(): (new () => MatrixActor) | null { return null; }

      /** No-op: template is rendered in connectedCallback. */
      protected async _renderTemplate(): Promise<void> {}

      /**
       * Wire the schema actor into this shell.
       * Called by SchemaEvaluator/ChatMessageList after the actor is created.
       * Pre-sets context + actor so the parent's _receiveContext skips this element.
       */
      setComponent(comp: MatrixActor) {
        this._schemaComponent = comp;

        // Steal context from the initialized actor so oracle snapshots use the right mount
        const actorContext = comp.context;
        if (actorContext) {
          this._contextReceived = true;
          this._context = actorContext;
          this._actor = comp;
        }

        // Capture state keys for cognitive state (from the actor's getCognitiveState)
        const cognitiveActor = comp as CognitiveActor;
        if (typeof cognitiveActor.getCognitiveState === 'function') {
          this._stateKeys = Object.keys(cognitiveActor.getCognitiveState());
        }

        // Copy static accepts/emits from actor to shell for $introspect + oracle schemas
        const actorCtor = comp.constructor as ExternalHTMLElementConstructor;
        const shellCtor = this.constructor as ExternalHTMLElementConstructor;
        if (actorCtor.accepts) shellCtor.accepts = actorCtor.accepts;
        if (actorCtor.emits) shellCtor.emits = actorCtor.emits;

        this._bindHandlers();
        this._updateBindings();
        // Wire framework ops ($style, $tokens, snapshot.tree.oracle) on the shell
        this._wireShellAccepts();
        // Wire reactive state → DOM updates
        comp._shellUpdateCallback = () => this._updateBindings();
        // Give handlers DOM access via this.shell
        comp._shellRoot = this.shadowRoot;
      }

      /**
       * Auto-expose all schema.state keys as cognitive state.
       * This makes define_component'd components VISIBLE in the oracle HEAP snapshot.
       */
      override getCognitiveState(): Record<string, unknown> {
        if (!this._schemaComponent) return {};
        const state: Record<string, unknown> = {};
        const comp = this._schemaComponent as unknown as Record<string, unknown>;
        for (const key of this._stateKeys) {
          if (key in comp) state[key] = comp[key];
        }
        return state;
      }

      private _bindHandlers() {
        if (!this._schemaComponent || !this.shadowRoot) return;
        for (const el of Array.from(this.shadowRoot.querySelectorAll<Element>('[data-action]'))) {
          const action = el.getAttribute('data-action');
          if (action) {
            const handlerName = `on${action.charAt(0).toUpperCase()}${action.slice(1)}`;
            const isInput = el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT';

            const fire = () => {
              const comp = this._schemaComponent as Record<string, unknown> | null;
              if (!comp) return;
              const handler = comp[handlerName];
              if (typeof handler === 'function') {
                const inputEl = el as HTMLInputElement;
                const val = isInput ? inputEl.value : undefined;
                const payload = val !== undefined ? { value: val } : {};
                const meta = val !== undefined ? { inputValue: val } : {};
                (handler as (p: unknown, m: unknown) => void).call(comp, payload, meta);
                this._updateBindings();
              }
            };

            if (isInput) {
              el.addEventListener('change', fire);
              el.addEventListener('keydown', (e: Event) => {
                if ((e as KeyboardEvent).key === 'Enter') fire();
              });
            } else {
              el.addEventListener('click', fire);
            }
          }
        }
      }

      private _updateBindings() {
        if (!this._schemaComponent || !this.shadowRoot) return;
        for (const el of Array.from(this.shadowRoot.querySelectorAll<Element>('[data-bind]'))) {
          const prop = el.getAttribute('data-bind');
          if (prop && prop in this._schemaComponent) {
            const val = String((this._schemaComponent as Record<string, unknown>)[prop]);
            // Input/textarea/select elements need .value, not .textContent
            if ('value' in el && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
              (el as HTMLInputElement).value = val;
            } else {
              el.textContent = val;
            }
          }
        }
      }
    }

    // Set static accepts/emits so $introspect and oracle snapshots work
    // (these get populated when SchemaEvaluator calls setComponent)
    customElements.define(tag, DynamicElement);
    this.logger.info(`Registered dynamic element: <${tag}> (extends MatrixActorHtmlElement)`);
  }
}
