// src/core/MockRuntime.ts

import type { IMatrixContext } from '../engine/core/IMatrixContext';
import type { IMatrixRuntime } from '../engine/core/IMatrixRuntime';
import type { IComponentDescriptor } from '../engine/core/IComponentDescriptor';
import type { IChildDescriptor } from '../engine/core/IChildDescriptor';
import type { IExternalAdapterOptions } from './composite/IExternalAdapterOptions';
import { MatrixActor } from './MatrixActor';
import { CheatDetector } from './CheatDetector';
import type { IMailbox, IActorMessage } from '../engine/mailbox/IMailbox';

/**
 * Component tracking info (matches MatrixRuntime structure).
 */
interface ComponentInfo {
  id: string;
  component: MatrixActor;
  mount: string;
}

/**
 * Simple mock mailbox for testing.
 */
class MockMailbox implements IMailbox {
  readonly address: string;
  private _queue: IActorMessage[] = [];
  private _claimed = false;
  private _closed = false;

  constructor(address: string) {
    this.address = address;
  }

  get depth(): number { return this._queue.length; }
  get closed(): boolean { return this._closed; }
  get claimed(): boolean { return this._claimed; }

  claim(): void {
    if (this._closed) throw new Error('Mailbox is closed');
    if (this._claimed) throw new Error('Mailbox already claimed');
    this._claimed = true;
  }

  send(message: IActorMessage): void {
    if (this._closed) return;
    this._queue.push(message);
  }

  async receive(): Promise<IActorMessage> {
    if (!this._claimed) throw new Error('Mailbox not claimed');
    if (this._closed) throw new Error('Mailbox closed');
    // Simple blocking behavior for mock
    while (this._queue.length === 0 && !this._closed) {
      await new Promise(resolve => setTimeout(resolve, 10));
    }
    if (this._closed) throw new Error('Mailbox closed');
    return this._queue.shift()!;
  }

  [Symbol.asyncIterator](): AsyncIterableIterator<IActorMessage> {
    const self = this;
    return {
      async next(): Promise<IteratorResult<IActorMessage>> {
        if (self._closed) return { done: true, value: undefined };
        try {
          const value = await self.receive();
          return { done: false, value };
        } catch {
          return { done: true, value: undefined };
        }
      },
      [Symbol.asyncIterator]() { return this; },
    };
  }

  close(): void {
    this._closed = true;
    this._queue = [];
  }
}

/**
 * MockRuntime - Minimal runtime for testing.
 *
 * Implements IMatrixRuntime with minimal functionality for unit tests
 * that don't need full runtime features.
 *
 * Uses same internal structure as MatrixRuntime (_components Map)
 * so MatrixActor can access tracked components.
 *
 * Usage in tests:
 * ```typescript
 * const runtime = new MockRuntime();
 * const context = MatrixContext.create('test', transport, runtime);
 * ```
 */
export class MockRuntime implements IMatrixRuntime {
  readonly type: 'headless' | 'browser' = 'headless';
  readonly supportsDom: boolean = false;

  // Track created components - uses _components to match MatrixRuntime
  readonly _components = new Map<string, ComponentInfo>();

  // Track mailboxes for actor communication
  private readonly _mailboxes = new Map<string, MockMailbox>();

  async createChild(
    parentContext: IMatrixContext,
    id: string,
    descriptor: IComponentDescriptor
  ): Promise<string> {
    const { componentClass } = descriptor;
    // NOTE: props are NOT directly assigned to component!
    // Props flow through lifecycle.childAdded event → shell._initialProps → handler
    // This maintains the architecture where all data flows through transport.

    // Create the component
    const component = new componentClass() as MatrixActor;

    // Derive context
    const childContext = parentContext.derive(id);

    // Initialize
    await component.initialize(childContext, id);

    // Track with same structure as MatrixRuntime
    const info: ComponentInfo = {
      id,
      component,
      mount: childContext.mount,
    };
    this._components.set(childContext.mount, info);

    return childContext.mount;
  }

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

  /**
   * Create an exclusive mailbox for a component (IMatrixRuntime interface).
   */
  createMailbox(mount: string): IMailbox {
    const address = `${mount}.$inbox`;
    if (this._mailboxes.has(address)) {
      return this._mailboxes.get(address)!;
    }
    const mailbox = new MockMailbox(address);
    this._mailboxes.set(address, mailbox);
    return mailbox;
  }

  /**
   * Get a component by mount path.
   * @deprecated This is a CHEAT - actors should use transport for communication.
   * Use RequestReply.execute() or sendTo() instead of getting direct references.
   * This method exists only for framework/testing infrastructure.
   */
  getComponent<T extends MatrixActor>(mount: string): T | undefined {
    // Report cheat if enabled
    CheatDetector.report('P1', `MockRuntime.getComponent accessing component directly: ${mount}`);
    return this._components.get(mount)?.component as T | undefined;
  }

  /**
   * Check if a component exists by mount path.
   * This is the PROPER way to check existence without getting a reference.
   */
  hasComponent(mount: string): boolean {
    return this._components.has(mount);
  }

  /**
   * Remove and dispose a component.
   */
  async remove(mount: string): Promise<void> {
    const info = this._components.get(mount);
    if (!info) return;

    try {
      info.component.dispose();
    } catch (err) {
      // eslint-disable-next-line no-console -- mock runtime cleanup failures need direct test diagnostics
      console.error(`[MockRuntime] Error disposing component:`, err);
    }

    this._components.delete(mount);
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // EXTERNAL ADAPTER PATTERN (No-op for mock)
  // Mock runtime doesn't support external visual adapters.
  // See: Vision/33-EXTERNAL-ADAPTER-PATTERN.md
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Register an external adapter (no-op for mock).
   *
   * Mock runtime doesn't support DOM or visual adapters.
   * Returns undefined to indicate no adapter was created.
   */
  registerExternalAdapter(
    _id: string,
    _ComponentClass: new () => MatrixActor,
    _options?: IExternalAdapterOptions
  ): string | undefined {
    return undefined;
  }

  /**
   * Unregister an external adapter (no-op for mock).
   */
  unregisterExternalAdapter(_tag: string): void {
    // No-op: mock runtime doesn't support external adapters
  }

  /**
   * Check if an external adapter is registered (always false for mock).
   */
  hasExternalAdapter(_tag: string): boolean {
    return false;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // SECURITY POLICY
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Check if dynamic component compilation is allowed.
   *
   * MockRuntime is for testing, so always allows dynamic compilation.
   *
   * @returns true (always for mock/testing)
   */
  isDynamicCompilationAllowed(): boolean {
    return true;
  }

  // P1: _getChildComponentInternal DELETED
  // Use transport-based communication instead

  /**
   * Clear all tracked components.
   */
  clear(): void {
    this._components.clear();
  }
}
